- Home /
Solved
Creating a Branching Combo System using a FSM
I have traditionally wrote combos for battle systems using a series of compound if-statements and switch cases, but that tends to build up and become a mess at times.
As of my most recent implementation, I have three attack buttons: Soft Medium Heavy
Now, the attacks are stored in individual classes that contain attack info such as: Attack strength Speed Knockback Force Launch Force Animation
In Update, I have a switch case that increments as I press an attack key (each press is it's own attack). For each case in the switch, it calls the function that's in it, and the function replaces the global variables with the matching names that are in the class (i.e Attack.attackStr = attackStr)
Now I was thinking of using a finite state machine.
I basically want it so that when i hit the standard attack say, 3 times, and hit the medium button next (the fourth blow), it knows to continue the combo using that button or finish the combo off, etc (while checking to see if the player is in the air or holding the "x button".
Is there an easier way to make a branching combo system using minimal compound statements? Thanks in advance!
Answer by whydoidoit · May 13, 2012 at 11:50 PM
Perhaps you should put your button handling in the attack classes (though I may have mistaken what you are after). This would create an FSM as each press of the button would effectively change the state to the next attack.
To do that you would pass the handling of player input to a function in the current attack class (or a NoAttack if this is the first move).
The function you called would return the next attack so that you are ready the next time the player presses a button. If there was no user input you just return the current state.
Pseudo code:
public class Attack
{
public virtual Attack HandleUserInput() { return this; }
public bool performImmediately;
}
public class NoAttack : Attack
{
public override Attack HandleUserInput()
{
switch(...) //Some kind of player input step
{
case ...:
return attackMove1; //Move through the combo if 'x' pressed
case ...:
return attackMove2; //Another state if 'y' pressed
}
return this;
}
}
public NoAttack noAttack = new NoAttack();
public AttackMove1 attackMove1 = new AttackMove1();
...
public Attack currentAttack = noAttack;
...
float attackStartTime = 0f;
void Update()
{
currentAttack = currentAttack.HandleUserInput();
if(currentAttack.performImmediately)
{
Dosomething(currentAttack);
currentAttack = noAttack;
attackStartTime = 0f;
}
if(currentAttack != noAttack && attackStartTime == 0)
attackStartTime = Time.time;
//You would probably combine this with the block above
//split here for clarity
if(currentAttack != noAttack && Time.time - attackStartTime > someValue)
{
Dosomething(currentAttack);
currentAttack = noAttack;
attackStartTime = 0f;
}
}
Where attackMove1, attackMove2 etc are instances of classes that handle that stage of the combo and are also inherited from Attack.
Presumably at some point an attack would have performImmediately set and that's what you would do, alternatively an Update function might wait for a time out and then implement the attack that was currently selected. In either case you would return the variable containing the current attack to an instance of NoAttack after selecting the action you desire.
This is nice because all of your logic is neatly encapsulated in an class that can easily be swapped around, unlike complicated switch and if combinations.
You would call the HandleUserInput of currentAttack in your Update function.
While this provides you a basic state machine - it doesn't do a bunch of "State Machine" like functions such as handling state entry and exit - but I think it might be enough for what you are after.
I think he's looking for a solution that will be easily maintainable... This one looks like it might end up code spaghetti.
Really? I guess it's down to what you like. I like encapsulated classes that are easily documented and separated. He already has a class for each of the attacks. I'd rather that than a 100 line embedded switch statement. It's all down to style I suppose :)
That was actually my first suggestion as well, but apparently its all the same functionality with just different values. If the attacks were as complicated as $$anonymous$$H though, I'd personally separate them into files. That way you aren't searching through one file for different attacks.