Simple FSM Usage
A Panda3D FSM is implemented by defining a new Python class which inherits
from the class direct.fsm.FSM.FSM
(normally imported as simply
FSM), and defining the appropriate enter and exit methods on the class.
FSM states are represented by name strings, which should not contain spaces
or punctuation marks; by Panda3D convention, state names should begin with a
capital letter. An FSM is always in exactly one state a time; the name of the
current state in stored in fsm.state
. When it transitions from one state
to another, it first calls exitOldState()
, and then it calls
enterNewState()
, where OldState is the name of the previous state, and
NewState is the name of the state it is entering. While it is making this
transition, the FSM is not technically in either state, and fsm.state
will be None–but you can find both old and new state names in
fsm.oldState
and fsm.newState
, respectively.
To define a possible state for an FSM, you only need to define an
enterStateName()
and/or exitStateName()
method on your class, where
StateName is the name of the state you would like to define. The
enterStateName()
method should perform all the necessary action for
entering your new state, and the corresponding exitStateName()
method
should generally undo everything that was done in enterStateName()
, so
that the world is returned to a neutral state.
An FSM starts and finishes in the state named “Off”. When the FSM is created,
it is already in “Off”; and when you destroy it (by calling
fsm.cleanup()
), it automatically
transitions back to “Off”.
To request an FSM to transition explicitly to a new state, use the call
fsm.request('StateName')
, where
StateName is the state you would like it to transition to.
Arguments to enterStateName methods
Normally, both enterStateName()
and exitStateName()
take no arguments
(other than self). However, if your FSM requires some information before it
can transition to a particular state, you can define any arguments you like
to the enterStateName method for that state; these arguments should be passed
in to the request()
call, following the state name.
from direct.fsm.FSM import FSM
class AvatarFSM(FSM):
def enterWalk(self, speed, doorMask):
avatar.setPlayRate(speed, 'walk')
avatar.loop('walk')
footstepsSound.play()
enableDoorCollisions(doorMask)
def exitWalk(self):
avatar.stop()
footstepsSound.stop()
disableDoorCollisions()
myfsm = AvatarFSM('myAvatar')
myfsm.request('Walk', 1.0, BitMask32.bit(2))
Note that the exitStateName method must always take no arguments.
Allowed and disallowed state transitions
By default, every state transition request is allowed: the call
fsm.request('StateName')
will always succeed, and the the FSM will be
left in the new state. You may wish to make your FSM more robust by
disallowing certain transitions that you don’t want to happen.
For instance, consider the example FSM described previously, which had the following state diagram:
In this diagram, the arrows represent legal transitions. It is legal to transition from ‘Walk’ to ‘Walk2Swim’, but not from ‘Walk’ to ‘Swim2Walk’. If you were to request the FSM to enter state ‘Swim2Walk’ while it is currently in state ‘Walk’, that’s a bug; you might prefer to have the FSM throw an exception, so you can find this bug.
To enforce this, you can store self.defaultTransitions
in the FSM’s
__init__()
method. This should be a map of allowed transitions from each
state. That is, each key of the map is a state name; for that key, the value
is a list of allowed transitions from the indicated state. Any transition not
listed in defaultTransitions is considered invalid. For example:
class AvatarFSM(FSM):
def __init__(self):
FSM.__init__(self, 'myAvatar')
self.defaultTransitions = {
'Walk' : [ 'Walk2Swim' ],
'Walk2Swim' : [ 'Swim' ],
'Swim' : [ 'Swim2Walk', 'Drowning' ],
'Swim2Walk' : [ 'Walk' ],
'Drowning' : [ ],
}
If you do not assign anything to self.defaultTransitions()
, then all
transitions are legal. However, if you do assign a map like the above, then
requesting a transition that is not listed in the map will raise the
exception FSM.RequestDenied
.