For example, it is common to see games with game loop and states:

stateChanged(){
  switch(state){
    STATE.PLAYER_SELECT_CHARACTER:
      this.currentController=new PlayerSelectCharacterController();
      break;
    STATE.CHARACTER_MOVE:
      this.currentController=new CharacterMoveController();
      break;
  }
  this.replaceController(this.currentController);
}

class PlayerSelectCharacterController{
  .
  .
  .
  characterSelected(int charIndex){
    this.globalData.charIndex=charIndex;
    this.globalData.state=STATE.CHARACTER_MOVE;
    this.parentController.stateChanged();
  }
}

but my question is, why don't I init the next state at last states directly? I think it is better, especially when I need to pass parameters to next states:

class PlayerSelectCharacterController{
  characterSelected(int charIndex){
    this.parentController.replaceController(new CharacterMoveController(charIndex));
  }
}    

Lets focus on the obvious advantage first: in the first example, the controllers are mostly independent from each other. PlayerSelectCharacterController does not need to know about the class CharacterMoveController or vice versa. Each of these classes can be developed separately, tested separately, or if necessary, placed in different compilation units.

That is not possible in the second example: PlayerSelectCharacterController needs to know about CharacterMoveController, this is a hardcoded dependency.

With just two classes, this may not be worth to think about, but if your game has 20 or more states, you will easily get a huge dependency tree, where every controller depends on every other controller, maybe in some cyclic way.

What may be less obvious: in games, state transitions are often not that simple as in your question's example. There might be complex rules how to change from one state to another. For a certain kind of complexity, it may be desirable to separate the two concerns of

  • deciding about the new state, and
  • actually changing the state.

I would actually expect the logic you showed us in characterSelected to be happen somewhere outside the PlayerSelectCharacterController, maybe in the parentController (characterSelected maybe a function returning the new state, but the assignment to globalData should not happen directly in PlayerSelectCharacterController). That would also avoid the need to call parentController.stateChanged() from within PlayerSelectCharacterController, this could be done by the outer loop, which can prevent some potential problems with recursion as well as code duplication.

As a general strategy, try to build the individual controllers as separate building blocks, with no side effects to global data, and no direct dependencies to other controllers of the same kind. Otherwise you risk getting an unmaintainable "big ball of mud".

  • also, think about the call stack after you die a couple of times – Ewan Aug 10 at 7:39
  • @Ewan: yes, if stateChanged would directly call characterSelected, this could become a problem. In the given example, however, it is not inherently clear where characterSelected is called, and if there is a recursion involved. – Doc Brown Aug 10 at 8:16
  • @Ewan: State transitions in a state machine (which is essentially what we are doing here) are usually tail calls. Depending on the language, that should not be a problem, e.g. in ECMAScript or Scheme. Implementing state transitions as calls is really a very nice and straightforward way to model state transitions. The problem here is more the coupling than the tail calls. – Jörg W Mittag Aug 10 at 13:55

If you study the State Pattern, you can see that its valid either for the current state object to choose the next state or the context can do it also. Typically if you are in one state, the next state transition is determined by events in the current state.

IMHO its more OOP-like to make each state object responsible for making the next transition, rather than some outside body (the context). Also you can pass parameters or make injections right there as you suggest!

The consequence is that each state object has to be correctly programmed to do its bit in the overall state machine.

The consequence of doing the transitions externally is that some external code has to know the entire state machine (maybe good or bad) but also as you suggest, you have to marshal parameters and injections between states.

I definitely prefer giving this responsibility to the State objects as you suggest.

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.