Tell me more ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I'm wondering what would be a clever way of making a layered menu system, in terms of what data structures to use.

An example of what I mean: A unit has the ability to construct buildings, and to cast some spells. The selection of buildings and spells are accessed either through pressing a hotkey or clicking an icon in the user interface. What is the best way to achieve this in the code?

I'm thinking something like:

Menu base state: options are A and B. If the players clicks A: change state and display the suboptions of A, and so on. But how is this achieved in the actual code? Perhaps a dict() containing functions:

options = {
           'A' : self.change_state ('a', self.current_state),
           'B' : self.change_state ('b', self.current_state)
          }

def on_key_event(self, key):
     options[key]

def change_state(self, key, state):
     if state == state_1:
          if key == 'a':
              ...

And so on, this seems really tedious and if possible I want to avoid if / else loops and just define everything in the simplest, and shortest manner.

I'm interested in how you would do this, or if you've already made a similar system, how you solved it in your own code.

share|improve this question

1 Answer

Try using a common class/interface for each of your "states", this is a fairly common approach to state machines (and avoids the long chain of if/else).

You could use just a dictionary of functions, but more often than not you'll likely find you want some form of initialisation functions too, or notification of completion - so may as well go with a "classic"

Generally a state implementation will have at minimum enter(), exit() and update() methods, and the state machine will just contain a stack of states which it updates.

It can also be handy (as you mention) to have a dictionary lookup for them, otherwise you need to keep references or instantiate new states in order to switch. For a menu system I'd highly recommend having a dictionary of some sort as it becomes a lot easier to dynamically switch between menus in a data-driven fashion. (eg. switching to a menu based on metadata in the button rather than pre-coding everything)

Depending on your needs, you can either:

  • build your menu on top of these vanilla states (and have things like input checks happen in each respective update() method)
  • extend the types of "events" states can receive so that your central application can call states with things like userSelected(), mouseClicked() or similar.

I've whipped up a (very) simple example in Python here, but I'm not a Python programmer, so apologies if it burns your eyes.

The main purpose of this code is to demonstrate that the state machine itself can be a state, allowing you to nest them for complex behaviour (or just more flexible menus)

""" A simple base state for the state machine
"""
class BaseState:
    parent = None
    name = "state"
    isDone = False
    def __init__(self, name):
        self.name = name or self.name

    def enter(self):
        pass
    def update(self):
        pass
    def exit(self):
        pass


""" A simple State Machine
    Note that it is a sub-class of BaseState, this allows
    for nested complex states.

"""
class StateMachine(BaseState):
    def __init__(self, name):
        BaseState.__init__(self, name)
        self.stateDict = {}
        self.reset()

    def update(self):
        """ Update states
            note that it's sometimes beneficial to have an extra
            method on your state objects to allow them to forcibly
            seize control in certain scenarios, this simple model
            doesn't include such things
        """

        # no processing an empty stack often this will be used
        # to exit the menu or application (for example)
        if len(self.stateStack) <= 0:
            # here we're manually exiting the last state (if any)
            # since we are ensuring predictable behaviour by delaying
            # calls to enter() and exit() ie. not exiting a state
            # when it's done, but instead once we're ready for it to be
            self.exitState(self.lastState)
            self.isDone = True
            return

        currentState = self.stateStack[-1]

        if self.lastState != currentState:
            """ We've switched states, notify everyone involved """
            self.switchState( currentState )

        if currentState != None:
            currentState.update()            
            if currentState.isDone:
                self.popState()

    def reset(self):
        """ Reset the state machine, but leave its dictionary intact """
        self.stateStack = []        
        self.lastState = None
        self.isDone = False

    def switchState(self, newState):
        """ Handles exiting and entering states appropriately """
        self.exitState( self.lastState )
        self.enterState( newState )
        self.lastState = newState

    def enterState(self, stateObj):
        """ Helper function to safely enter the specified state object """
        if stateObj != None:
            stateObj.enter()

    def exitState(self, stateObj):
        """ Helper function to safely exit the specified state object """
        if stateObj != None:
            stateObj.exit()

    def addState(self, state):
        """ Add a state to the dictionary for later
            ! Doesn't push or enter !
        """
        state.parent = self
        self.stateDict[state.name] = state

    def pushState(self, stateName):
        """ Push a state onto the stack by name """
        if not self.stateDict.has_key(stateName):
            print "here you whinge that state:%s doesn't exist.." % stateName
            return        
        newState = self.stateDict[stateName]
        self.stateStack.append(newState)

        return newState

    def popState(self):
        currentState = self.stateStack.pop()


""" A little example state for demonstration
"""

class TestState(BaseState):
    def __init__(self, name):
        BaseState.__init__(self, name)

    def enter(self):
        print "entered " + self.name

    def update(self):
        print "updated " + self.name

        # just bail out of this state for testing
        self.isDone = True

    def exit(self):
        print "exiting " + self.name


if __name__ == '__main__':
    """ Test out the state machine """

    machine = StateMachine("Master Control Program")

    ## Ultra simple
    machine.addState( TestState("State-A") )
    machine.addState( TestState("State-B") )

    ## set up a simple stack
    machine.pushState("State-A")
    machine.pushState("State-B")

    ## test it out
    print("Running simple state machine...\n")
    while not machine.isDone:
        machine.update()



    ## Nested test
    machine.reset()

    nestedMachine = StateMachine("Nested-A")
    nestedMachine.addState( TestState("Sub-State-A") )
    nestedMachine.addState( TestState("Sub-State-B") )
    nestedMachine.addState( TestState("Sub-State-C") )

    ## Set up a simple nested stack
    nestedMachine.pushState("Sub-State-A");
    nestedMachine.pushState("Sub-State-B");
    nestedMachine.pushState("Sub-State-C");

    machine.addState( nestedMachine )

    ## set up another simple stack
    machine.pushState("State-A")
    machine.pushState("Nested-A")
    machine.pushState("State-B")

    print("Running simple nested state machine...\n")
    while not machine.isDone:
        machine.update()
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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