Buffs/PowerUps

Started by
17 comments, last by Antonym 15 years, 1 month ago
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?
Advertisement
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.
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?
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 functionsint 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 optionalFoo f;int x = (f.*pmf)(666);     // The parentheses are needed because of operator precedence


Quote:A scripting system?

That's a very deep topic.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
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?
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.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
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.
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.

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!
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
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.
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).

This topic is closed to new replies.

Advertisement