Jump to content

  • Log In with Google      Sign In   
  • Create Account

We're offering banner ads on our site from just $5!

1. Details HERE. 2. GDNet+ Subscriptions HERE. 3. Ad upload HERE.


How to handle states in a fighting game?


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
23 replies to this topic

#1 tashaklikedi   Members   -  Reputation: 218

Like
2Likes
Like

Posted 18 February 2013 - 11:51 AM

Hi everyone, as a newbie programmer, I am trying to develop a 2D side scrolling Streets of Rage like fighting game with a lot of characters, moves, and animations. I started to learn programming 1 year ago, and I managed to have a game where I have two player controllable characters hit each other, jump to higher elevations in a streets of rage style map, etc...

 

However, since I am new to programming, and rushed enthusiastically into programming the game without any plan, my code got really messy. 

My problem is how to handle character states ( or moves ) , and their animation...

 

How I proceeded is: I have a huge switch function with states, another one with animations, another one with hitboxes for example:

 


//this is the base punch for the ninja character.
// this is the state switch function
case NINJA_BASEPUNCH:
		falsify(); // set everything to false (i.e. he cant jump while punch animation is on)
		bCanBasePunch = true; 
		enumAnimState = NINJA_AS_BASEPUNCH; //animation is set to base punch animation
		enumHitMessage = HIT_LIGHT_HI; //this will tell the enemy entity the damage type ( light ) and its position ( high )
		basePunch(); //punch function 
		break;

//once this is executed, the program will set the animation settings
//this is from animation switch function

case NINJA_AS_BASEPUNCH:
		sprite.w = 36;
		sprite.h = 36;
		iCurrentFrameCol = (bFaceRight ? 13:14);
		iCurrentFrameRow = 0;
		animation.maxFrames = 2;
		animation.setFrameRate(animation.getCurrentFrame() > 0 ? 100:60);
		break;
		}
//this is from the hitbox update function. 
case NINJA_AS_PUNCH:
		hitbox.w = groundPos.w;
		hitbox.h = 35;


 

All this is only for a simple basic punch. Now imagine how would it be if there was a big move list, big combos, stunts, etc... I need to get a better system for it, anyone who can think of a better implementation system?

 



Sponsor:

#2 MrDaaark   Members   -  Reputation: 3555

Like
6Likes
Like

Posted 18 February 2013 - 01:51 PM

This stuff should be data driven, and you really only need one attack state. The data for each attack will handle the rest. You should know which frame is the attacking frame, and have the hitbox for it on the part that is doing the attacking. Like a box around the fist on the frame where it would connect.

Edited by Daaark, 18 February 2013 - 01:51 PM.


#3 tashaklikedi   Members   -  Reputation: 218

Like
0Likes
Like

Posted 19 February 2013 - 04:15 AM

I don't know how to do that (I'll learn tho, I'm sure there is documentation about how its done), but each attack does not work the same way, especially input wise, some attack will combo with some, while they wont combo with others etc.. it sounds like a great solution tho..



#4 MrDaaark   Members   -  Reputation: 3555

Like
6Likes
Like

Posted 19 February 2013 - 06:36 AM

I'll use Streets of Rage 3 logic and the main character Axel as an example for everything below.

First, you'll need an input array that can hold up to whatever the maximum amount of inputs for a move is. In streets of rage, this is 3.

Every time the user presses a button, you check against previous inputs to see if matches any specific patterns. So Up,Up or Down,Down cause Axel to roll up or down. Forward, forward or back, back, cause him to dash. Forward,Forward,Attack causes the Bare Knuckle Uppercut.

So you need an array of inputs 3 entries long, and a SetState(STATE s, STATEINFO si) function.

STATE_IDLE
STATE_ROLLING
STATE_DASHING
STATE_JUMPING
STATE_ATTACK
STATE_HIT
STATE_KNOCKBACK
STATE_DAZED

Each state will have it's own input code and logic. The logic and input for STATE_DAZED will be very different than STATE_IDLE.

Any keypress in STATE_DAZED just goes towards decreasing the dazed timer.

STATE_HIT doesn't need to concern itself with input, because the player is just reacting to getting hit. The only important information would come from STATEINFO_HITHIGH, STATEINFO_HITMED, STATE_INFOHITLOW, STATEINFO_HITKNOCKBACK, etc to know which animation to play, and then when it's down, you go back to STATE_IDLE. I would probably actually make STATEINFO a struct and include a ton of information in there for any state.

Press the attack button while in STATE_IDLE, STATE_DASHING, or STATE_JUMPING will change state to STATE_ATTACK, and then you will play the relevant attack animation. Unless you have special situational rules, you shouldn't need to concern yourself with STATE_DASHINGATTACK, or STATE_JUMPINGATTACK.

You will also have an array of animation frame data which will be specified in a data file somewhere.

All your attack data will come from here while in STATE_ATTACK. You will need to know
*-the number of frames, and their coordinates on the sprite sheet
*-which frame(s) are the contact fames, and their hitboxes
*-which animation is next in the chain (0 for no combo ability (used on jump kick, dashing attack, and the last move in any combo)
*-frame delays, so each frame is played with the proper time delay

With this system, if the user presses attack button during the attack, or is idle but pressed attack within the allowable grace period, you automatically know if you can combo based on the current or last animation.

In Streets of Rage 3, attack 3 times causes something like left punch, right punch, uppercut.

The left punch is the basic attack. It's frame data would indicate that it combos into the right punch.
The right punch would frame data would indicate that it combos into the uppercut.
The uppercut frame data would indicate that it combos into nothing. So you return to STATE_IDLE.

It's all in the data set. Your code for the attack will read in the data and be able to handle every possible attack with it's general purpose code. Even if you want to want to have people tossing projectiles, you will have data that specifies this, and the contact frame is just the frame where you spawn a projectile (which should just need type, direction, owner data).

That's just an example off the top of my head. Your rules will be different, and it will get a bit more complex, but hopefully that helps you understand a way to get it done. Also, why not check out this thread I'm in that analyzes these types of games? http://www.gamedev.net/topic/637975-what-makes-a-good-beatem-up-game/

#5 tashaklikedi   Members   -  Reputation: 218

Like
2Likes
Like

Posted 19 February 2013 - 07:24 AM

Hey, that was a great reply and will be very helpful!! I think I grasped the logic, now I got to apply it to my system. thanks!

I'm also gonna look at that thread

 

EDIT: Wow that was a great post!! the game I'm working on is actually directly influenced by River City Ransom (with unique characters and moves, the RPG elements, free world, etc). I was thinking of a mario style map like Scott Pilgrim, which looks also great.

 

Well I am a noob programmer, and I have a LOT to learn... But its so fun ^^


Edited by tashaklikedi, 19 February 2013 - 07:54 AM.


#6 MrDaaark   Members   -  Reputation: 3555

Like
1Likes
Like

Posted 19 February 2013 - 08:28 AM

I forgot STATE_WALKING in the list above. That's a big oversight! smile.png

I put SetState() as a function because you shouldn't be setting all those random variables all over the place like that. Everything should be single responsibility, and as clean and simple as possible.
STATE StateCurrent;
STATE StatePrevious;


void SetState (STATE s, STATEINFO si)
{
	//buffer the current state into a previous one, just incase
	//you ever want to know what the character was doing right before now
	//you have that information easily available
	StatePrevious = StateCurrent;

	//now use a switch statement to setup the state s
	switch(s)
	{
		//character got hit
		case STATE_HIT:
		{
			StateCurrent = STATE_HIT;

			//use the info in si to figure out what to do
			//if the attack was high, medium, or low, set
			//the proper reaction animation to current
			//IMAGINARY CODE HERE


			//use other info in si about how strong the attack
			//was or whatever to determine knockback distance
			//and animation speed to simulate weight and impact
			//IMAGINARY CODE HERE			
				

			break;
		}

	}

}


#7 tashaklikedi   Members   -  Reputation: 218

Like
0Likes
Like

Posted 19 February 2013 - 08:59 AM

I already have a function like this but I assigned a state to each hit type ^^ 

 

but again there will be a lot of hit styles ( different animation for strong or light hits, a lot of death animations given the weapon / move)... Should I do it also with data?



#8 tashaklikedi   Members   -  Reputation: 218

Like
0Likes
Like

Posted 19 February 2013 - 09:23 AM

Oh and by the way is it better to open and close the data file each time, or to open it once and close it when the program ends?



#9 MrDaaark   Members   -  Reputation: 3555

Like
0Likes
Like

Posted 19 February 2013 - 09:44 AM

Open the data file, put it into an array of animation frame data structures, and close it.

Use as few states as possible. It doesn't matter what type of attack is it. Figure out what an attack is, describe a general purpose attack with a set of variables, and then use the data you have to supply the information.

Your characters should have an update function. The game will loop through all the characters and run their update function every frame. The update should just be a switch statement and it will play out whatever the current animation is in the current state, and then take whatever action needs to be taken on special frames.

You only need a new state when the code will be entirely different, and cause different things to happen. Otherwise, you will just have duplicate in all your states with 1 thing that is different. And every time you make a change, you will have to change it everywhere, or risk introducing subtle bugs, or unintended behaviour.

This is why I suggested that data structure called StateInfo. You can use it as a catch all data structure to describe specifics when you change your states. You make a new one to pass to the function, and it just contains a bunch of switches you can toggle.

#10 tashaklikedi   Members   -  Reputation: 218

Like
0Likes
Like

Posted 19 February 2013 - 10:41 AM

And what about inputs?

Right now, my input function has control over states.

For example, when I press punch, it verifies the state I'm in, and if its allowed to do so, it transitions into a new state.

 

With your system I can think of a solution like this:

When I press an attack button (there are a punch, a kick, a grab, and an attack modifier button, and I plan on adding shoryuuken style special inputs), I change state to attack, And then logic in the attack case in the state function will deal with its type.

 

Am I getting it right?



#11 MrDaaark   Members   -  Reputation: 3555

Like
1Likes
Like

Posted 19 February 2013 - 02:36 PM

Inputs is a matter of opinion.

In my main update loop, I would only first check global inputs, like for pause or anything that affects the game as a whole, then I would do specific checks on a per state basis.

STATE_DAZED:
Check for ANY possible input, and use it to reduce the daze timer.
STATE_IDLE or STATE_WALKING
Check for anything and respond. Like movement keys, attack, jump, whatever.
STATE_DASHING:
Check for anything and respond, but the responses are different than above.
STATE_ATTACKING:
Player is busy is attacking
STATE_HIT:
No valid input in this state, player is reacting to being hit.
etc...

I find that cleaner and straight forward than other methods. But that is a matter of opinion and organization. I find checking input and then later trying to match it up to a correct state to be backwards thinking. Why do the logic up front only do find out later down the line that you aren't even in the correct context?

Question 2:

As for the attack stuff. I kept my description very high level. I'll try to answer it better

You have a character object, and he references a sprite sheet, such as: http://soronline.net/sprites/sor3/Axel(sor3).png

So now you have a list of animations for this character. An animation is a class that has some data that describes it, plus a list of frame structures...

PSEUDO CODE
class animation
{
	string Name;

	ANIMATION_TYPE Type;

	//with enums
	ANIMATION_TYPE_WALKING
	ANIMATION_TYPE_RUNNING
	ANIMATION_TYPE_ATTACKING
	etc...

	//a list of frames
	vector <Frame> Frames;

}


class Frame
{

	bool isContact;	//contact frame?
        someType delay; //delay until the next frame, in whatever measurement form you are using

	//coords of the frame on the sprite sheet
	int x,y;
	int height,width;

	//coords of the hitbox
	int hitbox_x,hitbox_y;
	int hitbox_width,hitbox_height;

	//coords of the contact hitbox, if applicable, on contact frame only
	int contact_x,contact_y;
	int contact_width,contact_height;
	
}
The logic in your update function will deal with the attack. The SetState function example was just code to handle state switching cleanly.

#12 tashaklikedi   Members   -  Reputation: 218

Like
1Likes
Like

Posted 19 February 2013 - 03:54 PM

I like the frame class idea^^ 

Thanks for the long replies and for your effort! I'll try to implement all these things (gonna take me a lot of time since I think I'm gonna reprogram the game from scratch) and let  you know if I succeed^^


Edited by tashaklikedi, 19 February 2013 - 04:08 PM.


#13 MrDaaark   Members   -  Reputation: 3555

Like
0Likes
Like

Posted 19 February 2013 - 06:23 PM

No problem. smile.png I love beat em ups. If you have any questions go ahead and ask, and add some insight into the other beat em up thread. smile.png

#14 bollµ   Members   -  Reputation: 354

Like
0Likes
Like

Posted 21 February 2013 - 01:37 AM

Actually, I have a question :)

 

What about the whole "state machines are bad and lead to huge switch cases" argument? This kind of structure seems prone to the exact same argument dosen't it? if you want to add more abilities / combos, won't they just become branches in your switch case?

 

So, is there a better way to structure it that won't evolve into a thousand lines of code? (this happened to me, not kidding) :D

 

Thanks! 


a WIP 2d game engine: https://code.google.com/p/modulusengine/

English is not my first language, so do feel free to correct me :)


#15 MrDaaark   Members   -  Reputation: 3555

Like
0Likes
Like

Posted 21 February 2013 - 09:46 AM

Bollµ, state machines are not bad, especially when they map perfectly to the problem you are solving. Saying state machines are bad makes about as much sense as saying that comments slow down code. smile.png

So, is there a better way to structure it that won't evolve into a thousand lines of code? (this happened to me, not kidding)

If you have a giant mess of a switch statement, it looks like you started the programming before the design was finalized. Design your program first (possibly on a big white board), and then figure out what code is needed to make that design work.

I've mentioned a few times in this topic not to introduce new states needlessly, and to let the data handle everything. Comboing isn't a state. An attack is an attack. Only one state is needed. The data in the animation / attack table will tell the inform the code what to do. The one section of code should be as general purpose as possible. It doesn't know or care anything about the attack. It just does whatever the data tells it.

Once you know what an attack is (informed by your design and ruleset), you can make a general purpose state that handles every possible attack cleanly.

If you press the attack button, and you are in a state where pressing the attack button will initialize an attack then you go through a checklist.

What is current state? Determine which animation to play.

AM I CURRENTLY IN THE AIR? -> SetActiveAnimation(ATTACK_JUMPATTACK);
AM I CURRENTLY DASHING? -> SetActiveAnimation(ATTACK_DASHINGATTACK);
AM I HOLDING A WEAPON? -> WHICH? -> SetActiveAnimation(ATTACK_SPECIFICWEAPONBATTACK);
AM I IDLE? ->
------------>DID I JUST ATTACK, AND, AM I WITHIN THE GRACE PERIOD FOR COMBOING?
--------------> AND DOES MY CURRENT ATTACK CHAIN INTO ANYTHING?
----------------->SetActiveAnimation(ATTACK_WHATEVER);

SetState(STATE_ATTACKING, whatever else);

So now you are in STATE_ATTACKING which has it's own update code path. It doesn't matter what attack is happening, it's all handled by the data.

WHAT FRAME AM I ON NOW? -> DID ENOUGH TIME PASS TO ADVANCE TO THE NEXT FRAME?->SetAnimationFrame(x);
---->FRAME ADVANCED? -> WAS THAT THE LAST FRAME? -> SetState(STATE_IDLE);
IS THIS A CONTACT FRAME? -> YES? -> (Whichever part of your program that does attack collision checks will be notified).

It gets a bit more complicated, but that's the gist of it. Your code is only concerned with the fact that it is playing an attack animation, and running the proper logic on contact frames. It doesn't know or care what attack is happening. It just takes in data, and processes it per the rules of the game.

#16 bollµ   Members   -  Reputation: 354

Like
1Likes
Like

Posted 22 February 2013 - 12:47 AM

Well, that does make sense :D

 

And yes, you're right. I'd considered each ability to be a state and then went on adding new states to it. I refactored it afterwards by storing the data in XML (what buffs, what frames etc) in quite a similar fashion as you described :)

 

Thanks!


a WIP 2d game engine: https://code.google.com/p/modulusengine/

English is not my first language, so do feel free to correct me :)


#17 MrDaaark   Members   -  Reputation: 3555

Like
0Likes
Like

Posted 22 February 2013 - 12:28 PM

Also, don't forget, those states are also very useful when doing your AI.
if (Target == null)
{
    Character *PotentialTarget = GetNextTarget();
    CharacterState = PotentialTarget->GetState();

    switch(CharacterState)
    {
        //..
    }

}
Then you switch through CharacterState and decide the course of action to take. When you do acquire a target you will have another switch based on he state that will decide the proper course of action.

Is the target idle? Move in for the attack.

Is the target attacking? Create distance, if no distance, possibly block, or use whatever attack can override theirs. Or do nothing if the attack is the opposite direction.

Is the target reacting to a hit? Don't attack just yet. Possibly play animation to taunt or cheer on the attacker.

Is target dazed? TAUNT or use big finishing attack.

You can mix this with personality type variables to make complex gameplay from just that set of simple states.

#18 tashaklikedi   Members   -  Reputation: 218

Like
0Likes
Like

Posted 25 February 2013 - 06:50 PM

Hello again, after all this discussion, I thought about how I can implement a combo system that could be unique for each character. I came up with something like this:

 

//PSEUDOCODE:


class attack:
{
	enum type   
	enum stance //the game will have a lot of stances, this includes running while grabbing a guy, or different weapon styles, figting styles etc..

	std::string name //each attack should have a name for the sake of clarity
	int ID           //to be inserted in a vector

	unsigned int animationID  //which animation will be played?
	
	std::vector<std::string> vCanComboTo //to which moves it can be comboed to?
	
	gameInput inputList[5]  /*what input will be querried to the move to be executed? 
this will be read from a buffer of enums, which will go like below.*/
}

//the gameInput struct covers all kinds of commands that can be input to the game.

struct gameInput:
{
	enum button
	enum direction
	bool bModifier
}

/*the button enum reads from a secondary buffer, that only records the 3 attack buttons
and combinations like "HCF" which means half circle forward 
( a street fighter style input)*/

enum button
{
	punch
	kick
	grab
	HCF
	HCB
	QCF
	QCB
}
 
 

what do you think?


Edited by tashaklikedi, 25 February 2013 - 06:56 PM.


#19 tashaklikedi   Members   -  Reputation: 218

Like
0Likes
Like

Posted 25 February 2013 - 06:57 PM

I forgot direction and type enums:

/* the direction enum reads from another secondary buffer which is relative to the direction the user's facing.
i.e. left = forward if the user is facing left*/
enum direction
{
    up
    down
    forward
    back
}
//this is simply to know what kind of attack it is enum type: { hit grab projectile etc... }


enum type:
{
	hit
	grab
	projectile
	etc...
}


#20 MrDaaark   Members   -  Reputation: 3555

Like
0Likes
Like

Posted 26 February 2013 - 02:42 PM

what do you think?

Looks like you got organized. Just as long as you knew what all the possible combinations were before you invented all those data structures, you should be on the right path.

IMO, the next step is probably to go borrow some streets of rage, or final fight sprite sheets, and to test it out. Just try to get someone walking around a level and responding to input.




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS