Another re-write from my old website. Maybe eventually I’ll move onto new content =D; By the way, I can post these screenshots and talk about this work.
I wanted to start this post off in the right state of mind. This is a trap many would-be developers probably fall into on a daily basis: Taking on a task so far over their head that they can’t see the bottom of it. To phrase it another way – they don’t know what they don’t know. There’s nothing wrong with being in this position, of course. Everybody starts off like this. Any UE4 freelancer worth their salt could have cleared this up, but I’m going to share with you how I dealt with this complexity.
Here’s the pitch:
Every bit of gear can influence every stat, and the way each stat calculates this could be different. Also, some stats will depend on different stages of other stat calculations.
For those of you who are shouting something about gas, just hang on. I promise your stove is off. The idea for the segment is that we want to get the total base value of some stat from our data. Above, there is a call to “Compiler” for each stat, and each call to compiler is linked to all of the variables on the left. A start would be collapsing those to a graph, but that only band-aids the problem and you’d still have to update for each new gear “slot” you use.
Inside the “Compiler” function, we see the top of a very high stack of nodes that essentially all do the same thing. They’ll get a variable from the output based on the type of stat we want the output of, and then the result of all of that ends up being added together for the output.
Above you can see what one of these does up close. Essentially we’re just wrapping a break struct but we also update a mesh. This coupling is no good, especially with how much the client would want to increase the complexity of the calculations here. I think that’s quite enough going through what was there – the original post did go into more detail than this, but it won’t contribute to my point here.
One of the first things to do is to move away from the separate variable for each slot of gear. Instead, we’ll use an enum to identify each slot. One of the nice things about UE4 (BP in particular) is that you can get the number of entries in an enumeration – along with a iterating over them.
Here, we go ahead and reserve the amount of slots we’ve defined in our slots array. We’ll then loop through all defined slots and update the array element corresponding to them.
Note that this screenshot just here is erroneous in many ways: if you pre-allocate you won’t need to check if there’s an invalid index. Also, there’s a check to see if the value is the default, and if it is, then set the item to be default. The check itself is meaningless, here. Theoretically you can do anything you’d want to here.
Now, here’s what the new “compiler” looks like. We’re still manually calling a lot of GetBaseStatCombined functions when we don’t have to. But, we’re able to leave out a lot of variables that would have been cumbersome to update.
Instead, we can just loop through our equipped gear and just increment by our chosen variable. This part would have to be updated when BaseGearData changes, but ideally this post would be one of many improvements made to a project such as this. Note that I’ve said that there may be other ways these stats may be calculated – its as simple as duplicating this function in BP. In C++ I’d set this up to allow the math operation itself to be dynamic. Also, the Data Table is still involved, only now the specific data table is resolved behind the scenes and we only have to worry about the output.
What of setting gear in a slot?
Whatever checks need to be done to make sure the player can put this gear into this slot can occur as-normal, and then the array is updated to the new gear. This would be the point where the mesh should be updated.
Now, this little trick is pretty niche but the general idea is very basic – and I’ve seen countless spaghetti bowls caused by this sort of missing fundamental. In this case, it was recognizing a pattern that could be rolled into a loop and abstracted into an array (not always the same, mind).
I still have an urge to talk about gas.
Alright. Fine. Pretty sure it isn’t my stove either.
You’ve seen where this is going – this project also rolled their own stats system. I didn’t actually know about GAS (Gameplay Abilities System), but if I did I wouldn’t have wanted to use this anyway – you see, this client was very hands on which is another reason why I didn’t push for C++. GAS seems somewhat tangential to this post for those not in the know – but is far more suitable for handling stats (yes, even composite stats) than anything you could roll in BP. My take on rolling your own stats system in BP is upcoming (yes, its another re-write).