Inspiaaa / UnityHFSM

A simple yet powerful class-based hierarchical finite state machine for Unity
MIT License
1.13k stars 125 forks source link

How would you model a Weapon StateMachine having a "Idle" Movement and Cooldown in parallel? #41

Closed ManuelRauber closed 6 months ago

ManuelRauber commented 8 months ago

Hi!

I've been using UnityHFSM for a bit and I really like it.

Now, I've run into a scenario where I'm not sure how to model it correctly using UnityHFSM. Let me set up the scenario for it a bit:

I develop a little bullethell game, like Vampire Survivor etc.,. I have a player who can move around using his own state machine.

I have a Weapon that works independently of the player, so the player does not know about his weapon and the weapon does not know about the player (only his transform is relevant).

The weapon, when idling will follow and circle around the player. The weapon has a firerate of 1 shoot per second (I'll call it cooldown). When it shoots the circling should stop and it should attack the enemy.

Now, I have some issues how to model the "idle when circling and cooling down". I have some possibilities to model this scenario:

  1. Have one distinct StateMachine for Movement and Attack States. They don't know each other but both use the Weapon script where some fields are available in order to make transitions in their own states.
  2. Have on StateMachine with a nested StateMachine (this was my first try). However, since only one state can run at a given time, I would need to have the code for the cooldown in the circle state. That does not make much sense.
  3. Use the HybridStateMachine somehow. I'm not sure yet how to model it with the HSM, because either the cooldown or the movement runs in its OnBeforeLogic callback. It still feels like mixing up things that don't belong together.

Maybe I'm not seeing something that I could leverage in order to achieve this scenario.

Any idea will be appreciated. :-)

Inspiaaa commented 6 months ago

Hi @ManuelRauber, Thanks for using UnityHFSM!

If I understand your scenario correctly, you're having difficulties modelling the behaviour of the weapon. While in idle, the weapon should circle around the player. When the attack button is pressed (and sufficient time has passed since the last attack, which is determined by the weapon cooldown), it should shoot an enemy. After the attack, while in cooldown, the weapon should circle around the player again.

There are two ways that this could be implemented.

The first approach is to have three states: Idle, Shoot, Cooldown. The advantage of this is that you have a dedicated cooldown state which allows you to e.g. play animations / sounds / etc. The state machine would then look like this:

stateDiagram-v2
    [*] --> Idle
    Idle --> Shoot: Attack Btn Pressed
    Shoot --> Cooldown
    Cooldown --> Idle: Cooldown Passed

To make the weapon circle around the player during cooldown, you call the same movement function as you do in Idle in the OnLogic of the Cooldown state.

There are two ways how we can handle the Cooldown Passed condition from the above state diagram:

  1. We can use a TransitionAfter transition between the Cooldown and the Idle states. This transition only triggers after the 1s cooldown has elapsed. However, the problem with this approach is that the weapon may need some time to attack (e.g. stop moving, target enemy, play shoot animation, ...), essentially increasing the duration of the cooldown to 1s + time to attack.
  2. To mitigate this, we can keep track of when the player attacked in a variable. In the condition of the Cooldown to Idle transition we simply check whether enough time has passed. The advantage with this approach is that it is not affected by how long the attack state needs.

The second approach is to use only two states: Idle and Attack. The trick here is that the cooldown phase need not be a separate state, and can instead be modelled a different way. We can use the same idea from above (see point 2): use a variable to store when the player last attacked to essentially keep track of the cooldown in the background. This solves your circling-while-in-idle-and-cooldown problem by removing the cooldown state altogether.

stateDiagram-v2
    [*] --> Idle
    Idle --> Attack: Attack Btn Pressed <br> & Cooldown Passed
    Attack --> Idle

Which approach is better for you depends on your exact game and what your weapons should do when they shoot. As a small tip, remember that you can also use coroutines for the Attack / Shoot states if you have complex game logic / multiple steps that need to be run by using a CoState.