Advertisement

[C#] Timed sequence of events for an object

Started by September 16, 2010 04:45 PM
4 comments, last by Spa8nky 14 years, 2 months ago
I'm trying to work out a good way of implementing a sequence of events for an object's AI. I thought I would start out with a simple enum that describes all possible AI states:

    public enum AIState : byte    {        Moving,        Waiting    }


Then the current AI state would then be evaluated in the Update() method:

            switch (state)            {                case AIState.Moving:                    {                        // Move the object                        Position = Body.Position;                        Rotation = Body.Rotation;                        Body.Velocity = direction * speed;                        break;                    }                case AIState.Waiting:                    {                        // Don't move                        break;                    }            }


How would it be best to go from here and set up a timed sequence of AI behavioural changes?

Should I have a dictionary of AI behaviours with a period of time for each one, or would you guys suggest adopting a different approach?
The problem with this structure is that in a FSM, the code for the transition to the next state is held in the state itself. That is, the only way for StateA to switch to StateB is if StateA says so. The only exception to this is if it is switched from outside the state machine itself -- either in the same class or by a "set" call from another class (or a messaging system). If you just want StateA to be active for n seconds, then you just keep doing StateA until the internal code in StateA says it is finished.

One way around this is to have each update increment a timer (either by +1 or by the amount of elapsed time). Then, have a separate function as part of your action loop that checks to see if enough time has elapsed to switch to the next state. You can define what the "next state" is in a variety of ways for whatever suits your needs. For example, a list structure would work. You could even create a list of a struct with the struct containing values for both what the next state is and how long it will be active.

For the record, at this point you are almost doing something similar to a behavior tree in that you have an external controller that is directing traffic to the target states. It is NOT a behavior tree in the true sense, but I point it out just to show how you have lifted (at least some of) the transition logic out of the states themselves.

Dave Mark - President and Lead Designer of Intrinsic Algorithm LLC
Professional consultant on game AI, mathematical modeling, simulation modeling
Co-founder and 10 year advisor of the GDC AI Summit
Author of the book, Behavioral Mathematics for Game AI
Blogs I write:
IA News - What's happening at IA | IA on AI - AI news and notes | Post-Play'em - Observations on AI of games I play

"Reducing the world to mathematical equations!"

Advertisement
Following your advice I've created a simple struct that holds the AI behaviour and the period in which it takes place:

    public struct BulletState    {        public AIState state;           // AI state for bullet        public float period;            // Period to remain in that state for        public BulletState(AIState state, float period)        {            this.state = state;            this.period = period;        }    }


A timer class evaluates each period:

    class PeriodTimer    {        float currentTime;        float period;        /// <summary>        /// Returns true after a period of time has passed        /// </summary>        /// <param name="period">Period of time (ms)</param>        public PeriodTimer(float period)        {            this.period = period;            currentTime = 0.0f;        }        public void Update(GameTime gameTime)        {            currentTime += (float)gameTime.ElapsedGameTime.TotalMilliseconds;        }        public bool HasPeriodElapsed        {            get { return currentTime >= period; }        }        public float Period        {            get { return period; }            set            {                period = value;                currentTime = 0.0f;            }        }    }


Now I set up the AI as follows:

        BulletState[] states = new BulletState[4];        int stateIndex = -1;        PeriodTimer stateTimer = new PeriodTimer(0);...            states[0] = new BulletState(AIState.Moving, 1000);            states[1] = new BulletState(AIState.Waiting, 1000);            states[2] = new BulletState(AIState.Moving, 1000);            states[3] = new BulletState(AIState.Waiting, 1000);


and then my Update() method now looks like the following:

        public override void Update(GameTime gameTime)        {            // Update the timer            stateTimer.Update(gameTime);            // Check to see if the period for the current state has elapsed            if (stateTimer.HasPeriodElapsed)            {                stateIndex++;                // What should I do if the final state has expired?                if (stateIndex >= states.Length)                {                    stateIndex = Math.Min(stateIndex, states.Length - 1);                }                else                {                    stateTimer.Period = states[stateIndex].period;                }            }            switch (states[stateIndex].state)            {                case AIState.Moving:                    {                        // Move the object                        Position = Body.Position;                        Rotation = Body.Rotation;                        Body.Velocity = direction * speed;                        break;                    }                case AIState.Waiting:                    {                        // Don't move                        Body.Velocity = Vector2.Zero;                        break;                    }            }        }


Are there any changes you would suggest to this structure?

What would you do in the case of the final state reaching the end of its period?
Just wondering, but what is the drawback of having state transitions to the next state within the state itself?

state:searching
- look for targets
- if target found, state = attacking

state:attacking
- attack target
- if target dead, state = searching

^ Something like this seems pretty common.
This is the common drawback of FSMs. As the number of states increases, the transition mesh increases exponentially. Imagine 10 states that could transition to 10 others (including staying put). Imagine 20 states that can transition to 20 others. What if you want one state to happen immediately in special circumstances? Now you have to touch all your states to make sure that circumstance is handled. Things get unwieldy rather quickly.

Dave Mark - President and Lead Designer of Intrinsic Algorithm LLC
Professional consultant on game AI, mathematical modeling, simulation modeling
Co-founder and 10 year advisor of the GDC AI Summit
Author of the book, Behavioral Mathematics for Game AI
Blogs I write:
IA News - What's happening at IA | IA on AI - AI news and notes | Post-Play'em - Observations on AI of games I play

"Reducing the world to mathematical equations!"

Doesn't my method avoid this happening though? I thought it to be a sensible implementation, but I would like to take on board any criticisms you might have.

This topic is closed to new replies.

Advertisement