Sign in to follow this  
Antonym

Buffs/PowerUps

Recommended Posts

Stuff that alters the player stats one way or another, temporarily or untemporarily, stacking or not. What is a good method to go about doing them? Do I have to create a class for each or should I create a single one and stack all different attributes in it? Any info on the subject is appreciated. I use c++ in case it's relevant. Same for 'similar' objects? What do I do if I have several objects different in just one or two characteristics?

Share this post


Link to post
Share on other sites
If I were to do it I think I'd try something like


class cPowerUp : friend cPlayer
{
public:
cPowerUp();
~cPowerUp();
float mDuration; //Remaining duration of power up
void* mPowerUpFunction(cPlayer* player) = NULL; //Function pointer of power up.
//Probably also want to make the function pointed at a member of cPowerUp
//so you have access to variables.

void* mEndOfPowerUpFunction(cPlayer* player) = NULL; //Called when power up expires.
//Removes benefits of this power up.
};




The power ups could be tracked using either as part of the player or as part of a global object list.

Of course, this asumes the power ups are reasonably straightforward. If you employ a lot of complicated and similar power ups you may need to also use some sort of scripting system.

Share this post


Link to post
Share on other sites
Thanks for the reply.

Being a friend of the object it's affecting that means it has access to all its variables?

How can you know how big mDuration should be/how much you should subtract from it in each iteration to have the buff last a specific amount of time? Say 5/10/15 seconds?

Function pointers, that's new to me, how do you set/call them?

A scripting system?

Share this post


Link to post
Share on other sites
Quote:
Original post by Antonym
Being a friend of the object it's affecting that means it has access to all its variables?


Declaring a function or a class as a friend means that it can access the protected and private members of the class that does the declaration.

Quote:
How can you know how big mDuration should be/how much you should subtract from it in each iteration to have the buff last a specific amount of time? Say 5/10/15 seconds?

It entirely depends on how you want your power-up system to work. That is, of the urles of the game. Personally, I advocate not altering the base stat value, but completely recomputing the "adjusted" stat whenever a power-up is added, changed or removed.

Quote:
Function pointers, that's new to me, how do you set/call them?


int foo(int x)
{
return x;
}

int (*pfn)(int) = &foo; // The & is optional when dealing with non-member functions

int y = pfn(25);



With non-static member functions (more complex, don't worry about it)

class Foo
{
int y;
public:
Foo() : y(42) {}
int Bar(int x) { return x + y; }
};

int (Foo::*pmf)(int) = &Foo::Bar; // The & is not optional

Foo f;

int x = (f.*pmf)(666); // The parentheses are needed because of operator precedence



Quote:
A scripting system?

That's a very deep topic.

Share this post


Link to post
Share on other sites
Wait why would player need access to the PowerUp class, shouldnt it be the other way around? The way I had understood it was: I create a powerup class with all sorts of function members for all the different effects powerups could have and whenever I create an instance of it I set the function pointers to point to one of those function members. Each function member altering variables in player. Although now that I think about it I am much more confused.

Quote:
It entirely depends on how you want your power-up system to work. That is, of the urles of the game. Personally, I advocate not altering the base stat value, but completely recomputing the "adjusted" stat whenever a power-up is added, changed or removed.

Err I am not sure how that's related to my question regarding mDuration. Anyway are you saying I should have all my base variables and for each a variable of the/a 'modified' version? Why do you not advocate changing the base value? Wouldn't doing otherwise would just make things much more complicated, vastly increase the number of variables too?

Share this post


Link to post
Share on other sites
Quote:
Original post by Antonym
Wait why would player need access to the PowerUp class, shouldnt it be the other way around?

Probably, yes. And possibly not even that.

Quote:
The way I had understood it was: I create a powerup class with all sorts of function members for all the different effects powerups could have and whenever I create an instance of it I set the function pointers to point to one of those function members.

Since you're using C++, what you should do is create a base power-up class with virtual functions and have a derived class for each type of power-up. Let the compiler muck with the function pointers for you. If you have power-ups that combine multiple effects things get a bit more complicated (look at Diablo II items and spells, for example) at which point you need to decompose into basic effects and combine them (joy!).

Quote:
Err I am not sure how that's related to my question regarding mDuration.

I misread the question. The answer is still "it depends", but this time on how you schedule your game logic updates and on what you actually do with the power-up structure. His example was even less than bare bones. [smile]

Quote:
Anyway are you saying I should have all my base variables and for each a variable of the/a 'modified' version?

That's exactly it, yes.

Quote:
Why do you not advocate changing the base value? Wouldn't doing otherwise would just make things much more complicated, vastly increase the number of variables too?

Because depending on what you do with your power-ups, you can quite easily get some "drift" in and lose the initial value if you don't exactly undo the power up effects. One basic example would be "double your strength" combine with a "get +5 strength", assuming you don't just apply the "double your strength" power up as a fixed "get +N strength" where N is the value of the stat at the time you pick up the power-up.

Does that make it more complicated? Yes, obviously. Although you're the one who wants a power-up system without having told us exactly the set of power-ups you want to support. At least it's simpler than Winegums's suggestion of a scripting system. [smile] You did leave the question quite open-ended.

As for multiplying the number of variables... surely you're kidding. One single bitmap image or texture is probably going to consume more memory than the entire set of variables you use to describe your character. The only thing that costs you is some typing, and possibly not even that if you create a class that represents the type of your character's stats and includes the logic for updating and retrieving the current value of the stat.

The reason why I suggest having that extra variable is that you obviously wouldn't want to recompute the whole stat every time you need it. That's especially true when you deal with a combination of power-ups or of basic effects (with plenty of function calls, whether virtual functions or function pointers - which pretty much amount to the same thing underneath).

Caching, trading off space (memory) for time is one of the basic programming strategies.

Share this post


Link to post
Share on other sites
Yeah I am sorry I didn't explain things much I myself am still not sure what
I want. Let's see.. I have an object class which describes each object in the game, this one inherits to different objects.

Here's an example of all members of object, and of ship and projectile(Which inherit from object). Is it normal to have this amount of variables?

Object

SpriteInstance sprt_instance;
b2Body *body;
obj_type type;
float hp_max;
float hp;
float dmg;
float pts;
bool hit;
bool dead;
DWORD show_health;
DWORD last_show;
DWORD last_homing;
DWORD homing_duration;



Ship

DWORD last_shot;
DWORD cooldown;
float energy;
float energy_max;
float shield;
float shield_max;
bool shield_hit;
DWORD shield_show;
DWORD shield_last_show;
float damage;
std::vector<BuffData*> buffs;
bool homing;
bool AI;
DWORD atk_rate;
DWORD last_atk;
b2Vec2 target;
bool stay;
int bullets;
bool shoot;



Projectile

b2Vec2 target;
int id;
float power;
bool homing;




The PowerUps I am working with right now are:
Heal: Replenishes hit points.
Power: Increases your damage, increases impact strength of your ammo.
Homing: Makes your projectiles homing.
Shield: Gives you a shield that absorbs a certain amount of damage.
Score: Gives you score points.
I am thinking of adding others aswell.
To increase the amount of bullets you shot at a time. Reduce your cooldown(attack rate). Increase your energy(Amount of projectiles you can shot non-stop).
I want some PowerUps to be permanent, others not, others to just be an instant effect thing.

Yeah my concern had nothing to do with memory management, I know too little of that to begin with. It's just I am handling about 30 variables per major object and it already scares me a bit. Great idea, having a class per 'stat' would really help and crunch things.

Share this post


Link to post
Share on other sites
Quote:
Original post by Antonym
Here's an example of all members of object, and of ship and projectile(Which inherit from object). Is it normal to have this amount of variables?

I'm just wondering about why a projectile would need hp - unless you're going to be able to shoot them down. Anyway, your ship class takes about a hundred bytes of memory (I don't know how big your SpriteInstance is). Unless you are going to often make copies, or have tens of thousands of ships in play at the same time, you're probably going to be fine.

Quote:
Heal: Replenishes hit points.

No need to track the power up, just add the points back, unless you want them to be replenished over time.

Quote:
Power: Increases your damage, increases impact strength of your ammo.

If the power-ups do not stack, you would only need to keep track of when the last such power-up was picked up. If they do stack, you have two options, either keep track of when each was picked up to have them fade individually, or have a single such timer that is reset to its full duration each time you get a power up. It is a game design decision.

Quote:
Homing: Makes your projectiles homing.

A bit like for power-up increase.

[quote]Shield: Gives you a shield that absorbs a certain amount of damage./quote]
For that, you need a shield variable in the ship data structure, since you need to subtract damage taken from the shield first if it is present. Doing otherwise would require handling damage taken in different functions depending on whether you do have a shield power-up active or not. I don't think you want to go that way yet.

Quote:
Score: Gives you score points.

Just add the points to the score.

Quote:
I am thinking of adding others aswell.
To increase the amount of bullets you shot at a time. Reduce your cooldown(attack rate). Increase your energy(Amount of projectiles you can shot non-stop).
I want some PowerUps to be permanent, others not, others to just be an instant effect thing.

For permanent power-ups you can just add to the 'base' stat, though you may want to keep track of how many such power-ups you have picked up, if there is to be a limit to, e.g. the maximum health you ship may have.

Quote:
Yeah my concern had nothing to do with memory management, I know too little of that to begin with.

Time to learn. [smile]

Quote:
It's just I am handling about 30 variables per major object and it already scares me a bit.

I don't think you're yet at the point you need to worry about micro-managing your data structure, particularly since you're still learning.

Quote:
Great idea, having a class per 'stat' would really help and crunch things.


Well, with what you've shown us, I don't think that's necessary. Most your power ups can be handled either as individual adjustments (e.g. recovered hp, score) or simply as flags.

The only important thing is that when a power-up fades, you have to be 100% sure you're back to the correct state. That's particularly important with floating-point values: adding 1 and then subtracting 1 again does not necessarily return you to the exact same value as you had originally!

Share this post


Link to post
Share on other sites
Quote:
The only important thing is that when a power-up fades, you have to be 100% sure you're back to the correct state. That's particularly important with floating-point values: adding 1 and then subtracting 1 again does not necessarily return you to the exact same value as you had originally!


Oh now that's a problem. If I had a powerup that stacked I wouldnt be able to just set the mod version back to base if one of them faded(Since there could be others still affecting it). How do I deal with it then?

I thought about this I make a vector to keep track of this particular buff, whenever one fades I subtract it from the mod version, each time I check if there are still others on the stack if there aren't then I set it back to base.

Share this post


Link to post
Share on other sites
Sorry if my post caused confusion...I think it's created more questions than it's answered.

Quote:
Original post by Antonym
Oh now that's a problem. If I had a powerup that stacked I wouldnt be able to just set the mod version back to base if one of them faded(Since there could be others still affecting it). How do I deal with it then?


I don't think using arithmatic to subtract/add values will be beneficial when removing buffs. Perhaps it'd be best to simply recalculate the stat from scratch after one buff wears off (ie go back to its base value and reapply the whole buff stack).

Share this post


Link to post
Share on other sites
Hmm about float, then I think I should use int for most of my numerical variables then right? Such as timers and ammo, the ones that need to be precise.

Share this post


Link to post
Share on other sites
But they just said that adding and/or subtracting a float sometimes won't give you the same number back/the one you expect.

Share this post


Link to post
Share on other sites
Quote:
Original post by Antonym
Hmm about float, then I think I should use int for most of my numerical variables then right? Such as timers and ammo, the ones that need to be precise.


Quote:

But they just said that adding and/or subtracting a float sometimes won't give you the same number back/the one you expect.



I think you may be being misled by the word precise. Don't confuse it with the word accurate. Floating point numbers can represent decimals precisley, eg 3.14. An integer could only approximate it to 3. However, because of the way floating point numbers are stored, their accuracy cannot be relied on beyond (I think) the sixth decimal place. eg in 12345678.901, you can't garuntee that anything after 6 will be correct. However, for games this is usually sufficient.

Integer values, on the other hand, are not very precise but absolutley accurate. Provided you don't ask an integer to represent a value it can't (too big, or a decimal) it will represent it perfectly.

For keeping track of something that should stay as whole numbers, such as ammo or health, you should use integers. You don't need a decimal for ammo since you won't be using half bullets, and you don't need a float for health unless you're doing something very unconventional (and really, if you were going to use floating point health values it'd be easier to just shift everything up a few decimal places and have giant health values).

Share this post


Link to post
Share on other sites
Quote:
Original post by Antonym
Using floats for health is unconventional? Why? I suppose then damage too would have to be in ints.


Well concepts like health would've started in pen and paper games. If you represent a number as an integer you limit the precision, which makes storing it easier. Imagine this scenario...


Health: 100
*Goblin hits you for 3 damage
Health: 97
*Goblins mace of smiting reduces your health by 5%!
Health: 92.15
*Goblin hits you for 3 damage
Health: 89.15
*Goblins mace of smiting reduces your health by 5%!
Health: 84.6925
...


Pretty soon your health will be a long number. So you'll probably end up just shortening it to a few decimal places anyway. So why bother using a float?

It's far easier to say "all health is in integers. Round decimals down".

Share this post


Link to post
Share on other sites
1) I don't think powerups necessarily have to do anything. The powered-up thing can hold a container of power-up objects, and when you call a member function that calculates one of its current statistics, it looks through the container for powerups that affect the stat, and applies the necessary adjustments.

2)
Quote:
Is it normal to have this amount of variables?


SpriteInstance sprt_instance; // don't use words like 'Instance' in class or
// variable names. The class isn't an instance; the instances are. And you
// already know the instance is an instance, so you don't need to say it.
b2Body *body;
obj_type type; // consider using polymorphism instead of remembering a "type"
float hp_max; // make a struct to represent "X out of Y" type values and use
float hp; // it for these two
float dmg; // what is this?
float pts; // what is this?
bool hit; // calculate this on demand
bool dead; // calculate this on demand
DWORD show_health; // what is this?
DWORD last_show; // what is this?
DWORD last_homing; // put these last two into a struct probably
DWORD homing_duration;


You can imagine similar changes/questions for the other classes. :)

Quote:

Heal: Replenishes hit points.
Score: Gives you score points.


Okay, so those take effect instantly, and just once, and don't need to be held onto or timed out. You'll want to allow for that possibility too. :)

Quote:
It's just I am handling about 30 variables per major object and it already scares me a bit. Great idea, having a class per 'stat' would really help and crunch things.


Yep. Decomposing things hierarchically is a good way of managing that. :)

Share this post


Link to post
Share on other sites
Oh the reason it's called 'SpriteInstance' is because there is a 'Sprite' class that holds the texture along with its width and height, the spriteinstance class holds a pointer to a an instance of the Sprite class and also holds the frame width, height, current column and row along with other animation related functions. If there are better ways to handle this I'd love to hear.

The object_type variable is mostly check variable, collision related. For example I removed hp from the object class and passed it on to the ship class(since only ship uses it). I don't want any damage involved in a collision that doesn't involve the ship class(Otherwise I'd get an error).

the show_health variable is because I only want to display an object's health bar if it suffers damage.
I already added several classes.
cStat: has a base value and a modified value for a stat. This also has a vector to store buffs, each stat of the object is now iterated through on each game iteration to check if any buffs have expired, if so then the buff is removed from the vector and the stat is recomputed(set back to base and each buff's effect is added again to the mod value).
cBar: inherits from cStat. Has a baseMax and modMax members(To know max hit points/energy/shield).Has 2 spriteinstance members(To show the empty health bar and the full health bar). Also has a duration member to know wether to display the bar or not(if <= 0 don't display).
cBuff: a class with a generic 'amount' member and a virtual function called 'effect' that takes a cStat as a parameter. The effect function is defined on the different buff classes that inherit from cBuff.

pts is how many points the object is worth.

About the bool hit and bool dead. The first was to know wether to display the health bar or not(Problem seemingly solved above) and the dead was to not involve the object in several function calls. What did you meant with 'calculate this on demand'? Any suggestions to improve this are appreciated.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this