3
\$\begingroup\$

In Witcher 3, there are various potions that the character can consume. There seems to be potions that have various effects during their effect time:

  • Restores a certain number of health points per time
  • Instantly restores a certain number of health points
  • Increases the defense points
  • If one delivers a critical blow, the damage will be amplified
  • If one is attached by a vampire and it draws blood, the vampire will become poisoned
  • Increases the maximum weight capacity by 20 pounds.
  • Lets you see at night
  • Improves sight under water and extends the time that the character can hold his breath

Then there are various properties of the equipment that also have various effects (extra 5% experience points, extra attack points, ...) and runes one can put into the equipment.

I can imagine how one could program each of them separately, I do not see how one could program this in a non-intrusive and extensible fashion. It seems the potions must have access to virtually every aspect of the game.

Or is it the other way around? In the attack code, there are hooks for certain potions. In the diving code, there is hook for the one relevant potion.

How does one implement this kind of thing?

\$\endgroup\$
  • \$\begingroup\$ Do you have anything in your game which does any of these things and is not a potion? \$\endgroup\$ – Philipp May 27 '17 at 19:05
  • \$\begingroup\$ Most of those seem like character aspects in which case you could just have members of some character object like attack multiplier or health and then just change them when a potion is used. Although unlikely, when you have things that need to change outside of the character object, I'd go for some sort of event based system. So some objects can subscribe their functions to the potion using event. \$\endgroup\$ – Andrew Wilson May 28 '17 at 7:26
  • \$\begingroup\$ @Philipp: Certain enemies can poision you when they hit you. There are shrines that can upgrade certain skills for a time. There are skills that also have things like “restore health during the day out of battle, restore health at night during battles”. There is a modification for an armor that automatically casts a spell when you enter a battle. So there is a lot of things that change things. \$\endgroup\$ – Martin Ueding May 28 '17 at 7:58
  • \$\begingroup\$ See "What's a way to implement a flexible buff/debuff system?" Also, Aurélie Le Chevalier gave a great GDC talk this year called "Modify Everything! Data-Driven Gameplay Effects on For Honor" which may be worth a look, especially if you have a Vault membership to access the video. \$\endgroup\$ – DMGregory May 28 '17 at 14:10
4
\$\begingroup\$

When you have more than just potions which causes effects like that, it can be useful to put another layer of abstraction between the potions and the game mechanics in form of a temporary status modifier system.

This system keeps track of what status modifiers are currently active, how long they are still active, and what aspect of the character they affect. Other game systems should be able to query this system as a black box to get its current sum for any character stat. For example, when the character has drunk a +10 attack potion, has a -20 attack curse and a +15 attack weapon equipped, getTemporaryStatusModifier(ATTACK) returns 5.

You would have to call that function in any code which operates on data which can be subject to temporary status effects. Yes, that means you might have to add not just obvious keys like ATTACK, HEALTH_REGENERATION or CRIT_DAMAGE but also obscure stuff like VAMPIRE_POISON_FEEDBACK or NIGHT_POSTPROCESSING_GAMMA_ENHANCEMENT.

Instant effects, like the Instantly restores a certain number of health points potion, can be implemented as status effects with a duration of a single game-tick.


A different approach is to implement status effects with functions. This system makes new status-effect creating game mechanics more complicated, but leads to cleaner code at those places which use this system.

Temporary status effects don't have a simply numerical effect. They are instead objects with one or more callback-functions which get called at specific places in the code. This is again abstracted behind a TemporaryStatusModifier system.

For example, when you recalculate the stats of the player-character, your core game-mechanics call the TemporaryStatusModifier system to run the PlayerStats-object through the calculateStats callback-functions of all currently active temporary status effects which have such a function. For a simple +attack effect, this function would look like that:

void calculateStats(PlayerStat& stats) {
    stats.attack += 10;
}

These functions can be as complex as you want. So you can hide the code for status effects with complex and situational mechanics away at where they are defined and keep your core game-mechanics code relatively clean. For example, your "poison vampires" effect could implement the callback function which calculates damage on the player-character like this:

void calculateDamageOnPlayer(Player& player, Enemy& enemy, Attack& attack, int& damage) {
    if (enemy.monsterType == VAMPIRE && attack.hasTag(DRAW_BLOOD)) {
         StatusModifier poison;
         // ...build the status effect object...
         enemy.addStatusModifier(poison);
    }
}

All the effects mentioned in the question could be implemented by having just four places in the core-code which make a call to the status modifier system:

  • At the beginning of each tick when recalculating all player stats
  • When the player receives damage
  • When the player does damage
  • Before rendering when the engine determines the current parameters for the post-processing stack.
\$\endgroup\$
  • \$\begingroup\$ Thank you, this gives me some idea how all this logic could be organized in a sensible way. \$\endgroup\$ – Martin Ueding May 28 '17 at 11:21

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.