Search code examples
c#unity-game-enginelogicstate-machine

How to write a state machine so that I don't have to duplicate states with 99% similar code?


I am making an city management game, where workers are completely controlled by state machines. I have an "issue" where I find my self re-writing a lot of states that are 99% similar. Usually the only difference in some of the states is which state it should go to when it is completed.

For example, the below state is used for delivering resources, this is something all professions do but I have a write a specific one for each profession, since they all lead to different states on complete.

public class BakerDeliverGrainState : BaseWorkerState, IState
{
    public void Enter()
    {
        //Go to storage
        SetDestination(storage.position);
    }

    public void Execute()
    {
        if (unit.ReachedDestination())
        {
            //Reached destination, state completed
            storage.Store(unit.TakeResource());
            unit.stateMachine.ChangeState(new BakerWorkState(unit)); //This is the line that seperates the states. For example a blacksmith will go to "BlacksmithWorkState", and so on.
        }
    }

    public void Exit()
    {
        
    }
}

Is there some way I can pass which state to change to in a constructor? Or some other way I can adjust my design that will not require me to re-write so much code.


Solution

  • How about passing an anonymous function into the constructor which will create the new state?

    public class BaseWorkerState
    {
       protected Func<Unit, IState> createCompletedState;
      
       // Use this constructor for states where you don't need to customize the
       // next state
       public BaseWorkerState()
       {
       }
    
       // Use this constructor for states where you do need to customize the 
       // next state
       public BaseWorkerState(Func<Unit, IState> createCompletedState)
       {
          this.createCompletedState = createCompletedState;
       }
    }
    
    public class BakerDeliverGrainState : BaseWorkerState, IState
    {
       public BakerDeliverGrainState(Func<Unit, IState> createCompletedState) 
          : base(createCompletedState)
       {
       }
    
       public void Execute()
       {
          if (unit.ReachedDestination())
          {
             // Reached destination, state completed
             storage.Store(unit.TakeResource());
             unit.stateMachine.ChangeState(this.createCompletedState(unit));
           }
       }
    }
    

    Then when you create a state for each unit type, you can pass in a function that will create the appropriate "on complete" state (I'm guessing at the architecture of your Unit class).

    // When the baker is done delivering grain, he'll go into this state
    public class BakerIdleState: IState
    {
       public BakerIdleState(Unit unit) : base(unit)
       {
       }  
    }
    
    public class Baker : Unit
    {
       public void DeliverGrain()
       {
          var newState = new BakerDeliverGrainState(unit => 
          {
             return new BakerIdleState(unit);
          }); 
          this.stateMachine.ChangeState(newState);
       }
    }
    

    Of course, if the child state (BakerIdleState) also required customization, then you'd have to do the same thing to set that state's next state and this would grow more complicated.