State

Intent: Let an object alter its behavior when its internal state changes by encoding states as explicit structures rather than scattered booleans.

Acron Mapping

Acron ships a production-ready FSM framework. Use it — do not hand-roll a state machine.

Pattern role

Acron equivalent

State machine

Class inheriting direct.fsm.FSM.FSM

State

Named string (e.g. 'Walk', 'Jump', 'Dead')

Enter action

def enterWalk(self): ...

Exit action

def exitWalk(self): ...

Transition

self.request('StateName')

Guard (allowed transitions)

defaultTransitions dict

How to Implement

  1. from direct.fsm.FSM import FSM

  2. Subclass FSM. Pass a name string to super().__init__('MyFSM').

  3. Define enterX() / exitX() pairs for each state X. enterX() starts tasks, plays animations, enables collisions. exitX() reverses them.

  4. Optionally restrict valid transitions: defaultTransitions = {'Idle': ['Walk', 'Jump'], 'Walk': ['Idle', 'Jump'], 'Jump': ['Idle']}.

  5. Trigger transitions from game logic with self.request('Walk'). Illegal transitions raise a clean exception.

  6. Call self.cleanup() on object destruction — automatically exits the current state and transitions back to 'Off'.

Code Sketch (Python)

from direct.fsm.FSM import FSM

class PlayerFSM(FSM):
    defaultTransitions = {
        'Idle': ['Walk', 'Jump'],
        'Walk': ['Idle', 'Jump'],
        'Jump': ['Idle'],
    }

    def __init__(self, actor):
        super().__init__('PlayerFSM')
        self.actor = actor

    def enterIdle(self):
        self.actor.loop('idle')

    def exitIdle(self):
        self.actor.stop()

    def enterWalk(self):
        self.actor.loop('walk')
        taskMgr.add(self._move_task, 'playerMove')

    def exitWalk(self):
        self.actor.stop()
        taskMgr.remove('playerMove')

    def _move_task(self, task):
        self.actor.setY(self.actor, 5 * globalClock.getDt())
        return task.cont

    def enterJump(self):
        self.actor.play('jump')
        taskMgr.doMethodLater(0.6, self._land, 'playerLand')

    def exitJump(self):
        taskMgr.remove('playerLand')

    def _land(self, task):
        self.request('Idle')
        return task.done

Pitfalls

  • Mismatched enter/exit: everything started in enterX() must be stopped in exitX(). Tasks not removed here leak and run in the wrong state.

  • Re-entering the same state: request('Walk') while already in 'Walk' is a no-op by default. If you need refresh behavior, call self.request('Off') first or handle it explicitly.

  • Accessing self.state during transition: it is None while exiting one state and entering the next. Use self.oldState / self.newState if you need to inspect mid-transition.