1
\$\begingroup\$

Games like Borderlands, Fortnite, Destiny all have some sort of mechanic where your skills are modifiable by the equipment you carry.

For example in Borderlands, you can equip a pistol with generic damage of 100. The shield you equip also increases the damage of pistols only by 10%. Equipping a class mod increase any gun damage by 25%. And equipping an artifact reduces pistol damage by -10 points (Not a percentage). Your skill tree adds another 5%. And your Guardian rank adds another 2.9% of damage over time of 10 seconds. Moreover, your skill tree gives you the ability to add 5% to your health of the gun damage inflicted on an enemy for 10 seconds.

As you can see the forumla for calculating just gun damage for a pistol has a lot of moving parts.

There can literally be hundreds of possible combinations.

Then you have shields, grenades, and other buffs that are also practically endless with their own individual customization. On top of possibly modifying the other items you have equipped. By increasing your guns reload speed or your magazine size etc...

Is every possible attribute an interface? There may be instances where your equipped shield gives Shotguns a 5% increase in reload speed, but you dont' have a shotgun equipped.

Is there some master abstract character class with every possible key/value attribute? Then when you pick a character like a "Gunner" or "Siren" you inherit from the master character class so you have a base stat to start working with?

I know that some bullets are actual objects that travel through space and not just a ray-cast. Is every bullet equipped with some struct that carries along a bunch of information relevant to the gun that shot it and what it should do?

For example an "acid bullet" might have a base damage, a damage over time, and then a spread chance. On top of stealing 5% of the health of the target to give back to the shooter for x amount of time.

I've beent trying to wrap my head around how to implement such a system. How is such a system implemented?

\$\endgroup\$
  • 1
    \$\begingroup\$ I'd start with the behaviour and functionality you want (and what would be common behaviour and functionality) and then figure the representation out from there. The attributes of a character can be single integers, a complex class structure with inheritance, interfaces, many methods and callbacks, or anything in between, depending on how you want to use them. Start simple and change the representation as your needs change or you figure out what you need - don't worry too much about starting with the wrong representation. \$\endgroup\$ – Bernhard Barker Nov 4 '19 at 22:14
  • 2
    \$\begingroup\$ Have you consulted past Q&A on this topic, including How to implement a flexible buff/debuff system, OOP implementation of buffs and stats, etc? More links to possible leads can be found in this Meta thread \$\endgroup\$ – DMGregory Nov 4 '19 at 23:05
1
\$\begingroup\$

I solved this problem in many different ways in the past. Unfortunately there is no silver bullet here. Every architecture has advantages and drawbacks. So I am going to describe several approaches here so you can choose which one is most appropriate for the game you want to create.

First of all, such mechanics usually requires that a modifiable character attribute (like maxHp, attackSpeed or even exotic attributes like perAttackLifeStealChance) is more than just a int or float member variable of your character. It usually requires that you store all the modifiable attributes in an array. That way you can generalize the code which calculates them. You don't use character.attackSpeed, you use the function character.GetAttribute(ATTACK_SPEED) with ATTACK_SPEED being a constant representing the position of the attack speed in the character attribute array.

This function might or might not cache its results. This is an optimization question which depends on how computationally expensive this function gets and how often it is called. But be weary of premature optimization here! You won't know yet if your attribute handling is going to become a bottleneck. So you should prioritize readability and maintainability over performance until you have profiled it with real gameplay and the data shows that you have to optimize.

OK, but how do we implement GetAttribute?

Calculate everything in one place

Have the GetAttribute function go through every single system in the game which could modify attributes and apply it to the return value. Character perks, equipment, area effects, temporary effects, you name it.

This has the advantage that it is pretty easy to find bugs. If an attribute comes out on some nonsensical value, you know exactly where you have to set your debugger breakpoint.

But on the other hand, it creates a very tight coupling between this function and everything which changes an attribute. When you have a lot of attribute modifying features, this function could turn out pretty gargantuan and with dependencies on a lot of different game systems.

Register and unregister modifiers

OK, so let's make this more generic. Make the stat calculation system unaware of which systems can cause stat modifiers. Any other system can call character.AddAttributeModifier to add an attribute modifier and then character.RemoveAttributeModifier to remove it again. The character.GetAttribute calculation function now only needs to go through all the modifiers and apply them. It doesn't need to concern itself with where that modifier comes from.

This has the advantage that the attribute calculation function becomes very simple. You can hide the complexity of exotic conditional modifiers in those systems where they belong. It might also have performance advantages, because the function no longer needs to look into modifiers from systems which currently don't modify anything. (But again: be weary of premature optimization here).

But the disadvantage is that this architecture is very prone to bugs resulting from systems not unregistering modifiers when they should be. Have you heard of exploits in games where repeating some unlikely sequence of actions allows the player to raise a character attribute to unlimited values? Instructions like "use temporary buff, quicksave, change area, quickload, die before the buff runs out, respawn, and suddenly the temporary buff is permanent and can be cast again and it stacks"? Whenever I read about something like that I suspect that they are using this architecture and have a system which forgets to unregister some temporary attribute modifier in some edge-case they didn't think about.

Apply modifiers every game tick

OK, so we need to make sure that something which isn't active anymore no longer affects the character attributes. How can we make sure of that? We could insist that each game system which modifies attributes modifies them again for every game tick. That modifier is then active for only the next game tick. If the system wants to maintain that modifier for longer, it has to keep posting it every tick.

This architecture could be realized by maintaining 3 values for every attribute:

  • base value
  • current value
  • modification value

Every system can increase or decrease the modification value for each stat. But no system is allowed to ever change the base or current value. They are only allowed to read those.

Then you would have a separate attribute recalculation system which would run every tick and for each attribute:

  1. reset current to base
  2. add modification to current
  3. set modification back to 0

This architecture relies on the premise that every system gets executed exactly once between two calls to the attribute recalculation system. But is this actually true in your architecture? There might be systems which would benefit from doing only occasional updates. So forcing them to run every tick just to maintain their modifiers might become restricting for your architecture. You also have that "spooky action at a distance" problem which comes with shared access to global values. If you suddenly end up with a ridiculous modification value in your attribute system update, you might have to go on a wild goose chase to find out which system set that value and why.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Wow. Thanks for the detailed answer. You gave me a lot to think about. I hadn't thought about just creating an attribute array to store the volatile values. Keeping a base stat per character to modify. \$\endgroup\$ – user-44651 Nov 9 '19 at 15:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.