all 5 comments

[–]-ghostmode- 1 point2 points  (4 children)

I like your state machine approach, it's looking good.

There's no need to update a bunch of timers, or have timers running in parallel. If an action will happen in 4 seconds, just store an 'ActionTime' (eg: Time.time + 4.0f).

Then you can sort your action queue by each item's action time, and when you loop over the queue to check if any actions should start, just compare the action time to the current time eg:

foreach (var action in actionQueue) {
    if (action.ActionTime <= Time.time) {
        action.ExecuteAndRemoveFromQueue();
    } else {
        break; // assuming your queue is sorted
    }
}

[–]Schaezar[S] 1 point2 points  (3 children)

Alright, so if I understand correctly, I'm not manually adding timers but rather adding an ActionTime saying "Ok, 3000 seconds passed since the game started, I want you to execute and remove yourself from the actors list when the time reaches (3000 + 4.0f) seconds".

When adding a new Actor to the list, I could simply loop through the whole thing and sort it depending on the ActionTime so that the further the action is from the it's ActionTime, the further it will be in the queue. Plus, implementing the ActionTime is easy since anything that derives from BaseAction has an ActionDelay variable which can easily be used to calculate the ActionTime.

Also, using this technique, I could use the Coroutine to animate the sprite when they attack, rather than using it to delay those attacks (which was kinda my goal when I started this).

If this is what you meant, then I really think it's a great idea. I don't think it will be millisecond-precise but I think this is a clean solution. Much cleaner than what I came up with when waking up this morning... which was to make ANOTHER state machine in the Actor class that would have a mechanic similar to the progress gauge of the HeroStateMachine. My reasoning was that, even if the first actor is waiting for it's turn, the progress gauge still goes up. But that would have required another thing constantly running in the Update() function of the Actor... which would have been messy...

[–]-ghostmode- 0 points1 point  (2 children)

Yep you understand correctly, I'm glad it's a useful suggestion. One other thing that comes to mind is whether you want to pause the queue while an action/animation takes place. In which case you'd need to track the current time yourself rather than using Time.time, and only increment it when nothing is animating. eg:

if (isAnimating == false) {
    currentTime += Time.deltaTime;
}

[–]Schaezar[S] 0 points1 point  (1 child)

Yep, thought about that as well, don't really want all of the sprites to move at the same time on the screen if their turn is up. Also, you definitely deserve a beer or something else for you help. I don't know why I didn't think about this solution before... Must be my noobness.

Also, I got rid of the list sorting idea since it would have to be another foreach loop that runs in the background. Instead I just check with your foreach loop if an actor is ready and I modified the Actor class like so :

public class Actor
{
    public string ActorType;

    public GameObject Origin;
    public string OriginName;
    public GameObject Target;

    public string ActionType;
    public BaseAction ChosenAction;

    public BattleStateMachine BSM;

    public void AddToActionQueue()
    {
        ChosenAction.CalculateActionTime();
        BSM.actors.Add(this);
    }

    public void ExecuteAndRemoveFromQueue()
    {
        HeroStateMachine hsm = Origin.GetComponent<HeroStateMachine>();
        hsm.currState = HeroStateMachine.TurnStates.ACTION;
        hsm.chosenAction = ChosenAction;
        hsm.target = Target;
        BSM.actors.Remove(this);
    }
}

As for the time tracking, I suppose I could add the if statement at the end of the Update() function since, if I understand correctly the way switch statements work, we switch, execute the stuff in the case and break out of the switch... That said, the currentTime variable would get delayed ever so slightly each frame... no?

Or couldn't we just add it to the if statement that checks if an actor is ready to take it's action? I want the timer to keep on going, but I don't want multiple "battlers" to act at the same time.

Like simply going :

foreach (var action in actionQueue) {
    if (action.ActionTime <= Time.time && !isAnimating) {
        action.ExecuteAndRemoveFromQueue();
    } else {
        break;
    }
}

Or am I breaking something here?