• Advertisement
Sign in to follow this  

Designing a Stat+Effect system for an RPG.

This topic is 559 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

This is my first official activity on gamedev.net so I apologize if my 'forum etiquette' is a little rusty - advice/critique is welcomed in this area. I posted a question on stack overflow earlier but I think really I would much rather discuss this than have a single answer to the question. Feel free to read the posted question to get a better sense of what I'm talking about though - I'll leave it up there so also feel free to answer/comment.

 

(MODERATOR EDIT: StackExchange question copied inline below:)

 

What I would like some help with is this: How would I design/structure a creature stat and effect system so that it is elegant and flexible in that I can create effects that can be carried out and applied in several ways.

  • -15 to HP - Standard effect that desecrates a stat.
  • +25% Max Health to HP - Effect that increases a stat by a percentage of another stat.
  • 3pts Fire damage every 2 seconds for 15 seconds (Total 16 damage.) - An effect which decreases a stat intermittently until it has been doing so for a duration of time specified. (Damage types - fire, cold, poison - is also something I've had trouble with if anyone's feeling particularly generous.)
  • -2 to movement speed until removed - An effect which has a constant effect on a stat until the effect is removed; for example until the player has found a "Cure Illness" potion or removes a piece of armor which possesses this effect.

Obviously I'm not just looking for the ability to create the effects described above, but the ability to specify a number of variables to create any effect with the combination of the traits of the effects described above.

Basically I have no idea weather I need a single effect class or multiple effect classes. I don't know whether I need a class for a single entity's stat which may or may not have caps (HP vs something like Luck), or to store the stats in an Enum or simply an array of integers. Should stats have base and effected values? I think what I'm looking for is what CreatureStat/CreatureEffect classes I should be making and what variables/properties the CreatureEffect class(es) should have.

 

(END MODERATOR EDIT)

 

Basically I won't go over it all again but something I didn't mention in my post is that one of the big issues I'm having is creating this system so that everything feels neat and efficient. Sure I could (probably) code a solution up which would do the job but at the same time look horrific an preform awfully. I don't just want to implement the system, I want to implement the system well.

 

Also something else I didn't mention was having the ability to create effects like this: "+10% damage against undead". This effect would add a 10% bonus to the creature's attack when the attack is dealt to an undead enemy. It might be worth mentioning that nothing is set in stone with my game's design right now. Enemy types, enemy variants, damage types, stats and abilities, these are all things that aren't solid concepts yet. I'm really waiting to get this system in place to move forward with the design of the game.

 

For the record I am programming in C# using XNA, not that XNA is much relevance. Thank you for any feedback or future discussion.  :)

Edited by Kylotan
Copied the actual question into the post

Share this post


Link to post
Share on other sites
Advertisement

One way to do it would be to have a class with all the variables in it and then to apply effects as needed:

class Player :
PlayerHP  = 100
PlayerMP  = 100
PlayerATK = 10
PlayerDEF = 10
 
# Then here we will have effects.
#+x% damage against undead
def BuffVsUndead(Self, Target, Abilitylevel):
 
if Target.type == "Undead" :
  Bonus = (self.PlayerATK/100)* (Abilitylevel*10) # (10/100 = 0.1) * (1*10) = 1
  
return self.PlayerATK + Bonus #10+1 for 10% damage against undead
 
# Now when calculating the damage dealt
 
ZombieHP = ZombieHP -Player.BuffVsUndead(Zombie,1) #-11
 
#This is only a python code example it won't work, I didn't even check the syntax.

The advantage of doing it this way is that you don't change the attack value and if the player ability level is 0 it will add zero to the attack.

The same thing can be done with boosts for the player.

 

For now it's checking to see if the target is undead, however later you can allow the type of enemy to decide what buffs will be tested.

class Player :
PlayerHP  = 100
PlayerMP  = 100
PlayerATK = 10
PlayerDEF = 10
 
#+x% DefenceBoost
def BoostDef(Self, Boostlevel):
 
Bonus = (self.PlayerHP/100)* (Boostlevel*10) # (10/100 = 0.1) * (1*10) = 1
  
return self.PlayerDEF + Bonus #10+1 for 10% Defence boost
 
# When taking a hit
#Damage
if EnemyATK > Player.BoostDef(1)):
  PlayerHP= PlayerHP -( EnemyATK - Player.BoostDef(1)) #100- (20-11) 
else:
PlayerHP = PlayerHP-1
 
#This is only a python code example it won't work, I didn't even check the syntax.
You just use the function to do calculations instead of the variables.
Edited by Scouting Ninja

Share this post


Link to post
Share on other sites

I see. I've never touched Python so it looks a bit fuzzy to me but I think I see the way you're doing it. What I've tried doing in the past is having a class for a stat:

public class CreatureStat
    {
        public float Base { get { return base; } set { base = value; }
        public float EffectedValue { get { return effectedValue; } set { effectedValue = value; }

        private CreatureStatID identifier;
        private string name;
        private float baseValue;
        private float effectedValue;

        public CreatureStat(CreatureStatID identifier, string name, float baseValue)
        {
            this.identifier = identifier;
            this.name = name;
            this.baseValue = baseValue;
            effectedValue = baseValue;
        }

        public void Refresh()
        {
            effectedValue = baseValue;
        }
    }

Not sure if you're familiar with C# but basically each stat will be dealing with 4 variables: An ID which comes from the enum CreatureStatID which could be dor example maxHP. It also has a specified name for printing to the screen and a base and effected value. The base value will be used in the effect calculations and the effected value will be the stat's value after effects are applied. For example +10% ATK will increase ATK by 10% of its base value. ATK's effectedValue will then be 11. 

To be honest I'm not massively happy with this class.

 

Does this class look reasonable or does anybody have a better suggestion of storing stats? Some will have cap stats like HP and MaxHP. Each of these is a stat which can be effected but HP can not go above MaxHP. I think it would be nice to have a way of checking this other than if statements inside the Creature class. Should I create a data structure containing all stats? I also need to keep in mind base and effected values to prevent issues occurring when reversing effects.

Share this post


Link to post
Share on other sites
Not sure if you're familiar with C#

C#, it was my first language Python is just a lot faster, my C# is a bit rusty but it would be some thing like this:

public class Player
 {
 int HP, MP = 100;
 int ATK, DEF = 10;
 
 int UndeadBonus(int AbilityLevel)
   { 
int Bonus = (PlayerATK/100)* (Abilitylevel*10);
return Bonus; 
   }
 
}
 
//Now when attacking:
 
int DamageResult = Enemy.HP - Player.UndeadBonus(1);

The important part here is that you don't actually change the values of the ATK and DEF variables, that way when the boost stops you don't have to struggle with variables. You do all addition and subtraction against a function that is fluid, and doesn't affect the variables that effects it; that way you prevent loops.

It also prevents the level up problem, where a player has a boost that gets added when leveling up.

// Now we do the boost as simple as possible
int Boost = Player.DEF +1;
Player.DEF = Player.DEF+ Boost;
 
//Now you level up:
int LevelUpStat = 1;
Player.DEF = Player.DEF+ LevelUpStat; // It should have been 11 instead it's 12 because the boost was still active when the player leveled up.

The code here is just a mock up, however it should show the basic idea.

You could have just made a branch to check that all boosts are stopped when leveling up, however it's better not to allow variables to loop on each other.

 

Does this class look reasonable or does anybody have a better suggestion of storing stats? Some will have cap stats like HP and MaxHP. Each of these is a stat which can be effected but HP can not go above MaxHP.

This is normal unless you want the players to have a infinite Hp bar.

 

The thing is you will have to values, the health bar limit and at what amount health is. With the code I showed above you could add a HP boost that increases the player's max HP or even there Hp(then if time runs out and there health is less than the boost they die).

 

Although I am also intrested in a way to do a HP with out checking the maxHP, I will think about it.

Should I create a data structure containing all stats? I also need to keep in mind base and effected values to prevent issues occurring when reversing effects.

It's normal to have a base class and then to make a sub class with each sub class having it's own stats:

 

You would have a base class of Enemy and it has the variable HP, because all enemies will need HP.

Now you also have two sub classes: Slime and Bat, both inherited HP from Enemy however Bat also has a new variable of Fly. You could also make the sub classes types like Flying and Undead, then make sub classes from Undead like zombie and vampire; this way you only need to do a check to see if they are a sub class of Undead.

 

 

The thing about code there are billions of ways of doing even the most simple thing, you should use what you feel works best

Edited by Scouting Ninja

Share this post


Link to post
Share on other sites

I covered a lot of this in my StackExchange answer and the concepts are the same even if the language is different. I'm not going to type it all out again but fundamentally here's what I do.

 

Data:

  • Characters contain a set of Attributes, indexed by type (eg. Strength, Intelligence, Health)
  • Attributes contain a current value and a max value.
  • Characters contain a list of CharacterEffects, which show the things the character is currently affected by
  • CharacterEffects contain a list of AttributeEffects, one for each attribute they modify, and a timer to say when they expire
  • AttributeEffects contain an AttributeType, which tells you which Attribute they modify, a modifier type (e.g. Add, Multiply, SetAbsolute), and a value

Methods:

  • To calculate a character's current level of a given attribute, do the following:
    • Get the base level from the character's attribute
    • Iterate through each character effect on the character
      • Iterate through each attribute effect on the character effect
        • Apply the effect's modifier and the effect's value to the previous value of the attribute, to get the new value
        • Clamp the value against minimum and maximum levels, if necessary
    • Return the final value, having allowed every effect to modify it

Note that this doesn't handle recurring events - I like to handle that via a different system, since the changes to attributes in that case are instantaneous and permanent. But it's fairly easy to extend CharacterEffects to contain some sort of period timer, which performs one-off attribute modifications when the timer expires, which can re-use AttributeEffect objects except applying their values directly to the base attribute instead of only using them in calculations.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement