Player/Character class .... help

I always end up with a lot of states which are similar, like climbing. There are three states each with a different animation and timing but with the same basic mechanic, play animation then warp to where you just climbed to. I made these states subclasses of a main climbing state to avoid duplicating the code three or more times. The transition is still triggered from within the state.

yup the same for me .
ie, from the box point of wiew , no there any difference between attack_head and attack_body.
(also the damage caused is exactly the same)
this is purely a question of animation, but unfortunately influence also the code of box.(and not few)

PS: thanks for the tip super()! and your games :wink:

in this case,
FindRandomPoint is a subclass of Patrol or is another object?

or… are 2 fsm nested?

i try to clean my “fsm string working” (hugly) then i post it (basically are 2 fsms nested)

The Patrol state has an FSM within it, as the behaviour is different for the inner FSM and the outer FSM.
FindRandomPoint is a state within that inner FSM that just finds a random point on a navmesh.

I was worrying today about cyclic references in an FSM. I’ve been including a reference to the Character in the State, so the state can grab info from the character like health or worldPosition. But can’t this lead to the state not being cleared from memory when it transitions? Do I have to shut down states before transitioning? Remove references to other objects?

Or is there a better structure which doesn’t rely on cyclic references?

I don’t usually bother worrying about this, as there aren’t usually enough agents to cause memory issues.
Python can deal with circular references, they’re just slower for the GC. You could use a weakref proxy to mitigate this issue if you felt it pressing.

TL;DR: Don’t worry.

someone has already seen this MONSTER? :slight_smile:


class StateNested:
    def __init__(self, owner):
        self.name = self.__class__.__name__
        self.owner = owner
        self.child = None
        self.childs = {}
        self.tic = 0
    
    def start(self):
        pass
        
    def end(self):
        pass
        
    def add_child(self, instance):
        assert isinstance(instance, StateNested)
        self.childs[instance.name] = instance
        if len(self.childs) == 1:
            self.set_child(instance.name)
        
    def set_child(self, name):
        self.child = self.childs[name]
        self.child.start()
        
    def check_transition(self):
        pass
        
    def run(self):
        pass
        
    def update_all(self):
        print(self.name)#######
        self.run()
        if self.child:
            self.child.tic += 1
            new_child_name = self.child.check_transition()
            if new_child_name:
                self.child.end()
                self.reset_all_childs()
                self.set_child(new_child_name)
                return
            else:
                self.child.update_all()
                
    def reset_all_childs(self):
        if self.child:
            self.child.tic = 0
            self.child.reset_all_childs()
            

i guess agoose you should know since you have give me a link with something similar to read (BT_?) time ago.
but i not understand much at time.

anyway there something similar surely.
the part very cool is that the fsm is encapsulated inside a method of the state.

i already tested it with a cube and some states.
work absolutely perfect.

if there enought space, m(cont) is the box dynamic, brain(cont) is the empty children (of course always true a lot )

copy & paste with these 2 object + a plane,
use -> W S A D SPACE
and should work


from mathutils import Vector
import bge




class StateNested:
    def __init__(self, owner):
        self.name = self.__class__.__name__
        self.owner = owner
        self.child = None
        self.childs = {}
        self.tic = 0
    
    def start(self):
        pass
        
    def end(self):
        pass
        
    def add_child(self, instance):
        assert isinstance(instance, StateNested)
        self.childs[instance.name] = instance
        if len(self.childs) == 1:
            self.set_child(instance.name)
        
    def set_child(self, name):
        self.child = self.childs[name]
        self.child.start()
        
    def check_transition(self):
        pass
        
    def run(self):
        pass
        
    def update_all(self):
        print(self.name)#######
        self.run()
        if self.child:
            self.child.tic += 1
            new_child_name = self.child.check_transition()
            if new_child_name:
                self.child.end()
                self.reset_all_childs()
                self.set_child(new_child_name)
                return
            else:
                self.child.update_all()
                
    def reset_all_childs(self):
        if self.child:
            self.child.tic = 0
            self.child.reset_all_childs()
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            


#state broters LEVEL 1


class Alive(StateNested):
    def start(self):
        self.owner["animation"] = "waiting"
        
    def check_transition(self):
        if self.owner["health"] < 0.0: 
            return "Death"
    
    def run(self):
        if self.owner["health"] < 1.0:
            self.owner["health"] += 0.001
        
    
class Death(StateNested):
    def start(self):
        self.owner["animation"] = "death"
        
    def check_transition(self):
        pass


    def run(self):
        pass


#####################






#state broters LEVEL 2 (Alive)
class Move(StateNested):
    def start(self):
        self.owner["animation"] = "waiting"
        
#state broters LEVEL 3(Move)
class OnGround(StateNested):
    def start(self):
        self.owner["animation"] = "waiting"
    
    def check_transition(self):
        if not self.owner["on_ground"]:
            return "OnAir"
        
        
class OnAir(StateNested):
    def start(self):
        self.owner["animation"] = "jumping"
    
    def check_transition(self):
        if self.owner["on_ground"]:
            return "OnGround"


#state broters LEVEL 4(OnGround)
class Waiting(StateNested):
    def start(self):
        self.owner["animation"] = "waiting"
    
    def check_transition(self):
        if self.owner.localLinearVelocity.y > 1.0:
            return "Walking"
    
    def run(self):
        own = self.owner
        inputs = self.owner["inputs"]


        if "left" in inputs:
            own.applyRotation((0, 0, 0.05), 1)
        if "right" in inputs:
            own.applyRotation((0, 0, -0.05), 1)
        if "forward" in inputs:
            own.localLinearVelocity.xy = 0,4
        if "jump" in inputs:
            own.applyForce((0,0,own.mass*50),0)
                
        
class Walking(StateNested):
    def start(self):
        self.owner["animation"] = "walking"
    
    def check_transition(self):
        if self.owner.localLinearVelocity.y < 0.5:
            return "Waiting"
        
    def run(self):
        Waiting.run(self)
        
#state broters LEVEL 4(OnAir)
class Jumping(StateNested):
    def start(self):
        self.owner["animation"] = "waiting"
    
    def check_transition(self):
        if self.owner.localLinearVelocity.z < -0.1:
            return "Falling"
        
        
class Falling(StateNested):
    def start(self):
        self.owner["animation"] = "jumping"
    
    def check_transition(self):
        if self.owner.localLinearVelocity.z > 0.1:
            return "Jumping"
        
        
        
class Root(StateNested):
    def run(self):
        own = self.owner
        own["on_ground"] = own.rayCast(own.worldTransform*Vector((0,0,-1.1)), own)[0] != None
        
        
        
        
        
        
        
        
def m(cont):
    own = cont.owner
    if not "init" in own:
        own["health"] = 1.0
        own["on_ground"] = False
        own["inputs"] = set()
        root_state = Root(own)
        
        alive = Alive(own)
        death = Death(own)
        root_state.add_child(alive)
        root_state.add_child(death)
        
        move = Move(own)
        alive.add_child(move)
        
        on_ground = OnGround(own)
        on_air = OnAir(own)
        move.add_child(on_ground)
        move.add_child(on_air)
        
        jumping = Jumping(own)
        falling = Falling(own)
        on_air.add_child(jumping)
        on_air.add_child(falling)
        waiting = Waiting(own)
        walking = Walking(own)
        on_ground.add_child(waiting)
        on_ground.add_child(walking)
        
        #root_state.set_child("Alive")
        own["monostate"] = root_state
        own["init"] = 1
        
    own["monostate"].update_all()
    
    
    
def brain(cont):#empty children of box
    own = cont.owner
    box = own.parent
    kev, ev = bge.logic.keyboard.events, bge.events
    outputs = set()
    if kev[ev.WKEY]: outputs.add("forward")
    if kev[ev.AKEY]: outputs.add("left")
    if kev[ev.DKEY]: outputs.add("right")
    if kev[ev.SPACEKEY]: outputs.add("jump")
    box["inputs"] = outputs
    
    
    
    

removed some bugs (the states children.children… was not ended/started correctly)
removed some method unnecessary nesting all operations in 2 main methods.


class StateNested:
    def __init__(self, owner):
        self.name = self.__class__.__name__
        self.owner = owner
        self.child = None
        self.childs = {}
        self._tic = 0
    
    @property
    def tic(self):
        return self._tic
        
    def check_transition(self):     # user method to override
        pass                        # return the name of the new "brother" or None
    
    def start(self):                # user method to override
        pass
    
    def run(self):                  # user method to override
        pass
    
    def end(self):                  # user method to override
        pass
    
    def add_child(self, instance):
        assert isinstance(instance, StateNested)
        self.childs[instance.name] = instance
        if not self.child:
            self.set_child(instance.name)
        
    def set_child(self, name):
        nc = self.childs[name]
        c = self.child
        while c:
            c.end()
            c = c.child
        c = nc
        while c:
            c._tic = 0
            c.start()
            c = c.child
        self.child = nc
        
    def update_all(self):
        self._tic += 1
        self.run()
        if self.child:
            ncn = self.child.check_transition()
            if ncn:
                self.set_child(ncn)
                return
            self.child.update_all()
            
            

“2 main methods” sounds funny :D.

I usually tend to define methods to be overwritten in the base class too (I’m a Java developer). With Python this is not necessary due to the dynamic structure (no compile time checks).

I do not really see why your state has children. Is it meant to define a transition? (transition = event + source state + target state)

Nested
I also do not understand why your state is “nested”? where is it nested?
Agoose77 wrote about a nested FSM. Which means an (inner) FSM is an “action” within another (outer) FSM of an higher abstraction level. The states of both FSM do not need to know about the states of the other.

E.g.
The outer FSM describes the behavior of a character. Lets assume the behavior

  • contains a state “sitting down on couch”,
  • the character is in the corridor,
  • the couch is in the living room,
  • there is a closed door between corridor and living room.

“sitting down on couch” is not trivial (not just move forward + track to). The character’s brain knows that is is peforming “sitting down on couch”, but does not know how to do that. It knows someone who can do this, so it delegates the task to…

… a nested FSM that knows how to “sit down on couch”. It describes how to go to the door, opening it, go to the couch and sit down.

Quite a lot of operations, isn’t it?
They do not run all at the same time, but in a sequence. Lets define the states and the main transition flow:
“walking to door” -> “opening door” -> “walking to couch” -> “sitting down on couch”

You see nothing in the inner FSM has anything to do with the outer FSM.
The outer FSM treats the inner FSM as state action and waits for feed back from the inner FSM (e.g. “I’m sitting on the couch” or simpler “done!”).
The inner FSM does not know it is called by another FSM and it should not care. It only knows it got input (command “go to couch”) and has to provide output (“done!”, “not possible”)

Both FSMs can have the same design (regarding FSM behavior), but they have different setup/configuration as they describe different object behavior.

Nested nested
Now imaging, you need another FSM that tells the character how to open the door.
Then you get an inner FSM of the inner FSM :P.
(and you need something that can make the character walk and something that can make the character sit)

Now not every action is a nested FSM ;).

“2 main methods” sounds funny :D.

i for main i mean matter / central

I usually tend to define methods to be overwritten in the base class too (I’m a Java developer). With Python this is not necessary due to the dynamic structure (no compile time checks)

yes but it make the code more readable (then teoretically some “lock” is possible to add i guess using double underscore this is useful more over to avoid stupid error, as poxision = Vector() , not give error , i just created a new attribute )

I do not really see why your state has children. Is it meant to define a transition? (transition = event + source state + target state)

heam , the children current (if exist) rapresent the current state in a certain sense , also if i imagine it as a sort of node of a navmesh dynamic where the current node is the old node of the last pathfinding.:smiley:

each state is a state(for the parent) and an fsm (for the children)
the parent ask to the children (the old one that was valid): you can run or you want be replaced ?
if the children say nothing(None) mean that the node is valid , so the parent pass the “main” to the children that do the same thing with its childrens.
otherwise, if must be changed return the name of the “brother” (that MUST be right, and ready to run) (recent change, not wait with transitions)

Nested
I also do not understand why your state is “nested”? where is it nested?
Agoose77 wrote about a nested FSM. Which means an (inner) FSM is an “action” within another (outer) FSM of an higher abstraction level. The states of both FSM do not need to know about the states of the other.

E.g.
The outer FSM describes the behavior of a character. Lets assume the behavior

  • contains a state “sitting down on couch”,
  • the character is in the corridor,
  • the couch is in the living room,
  • there is a closed door between corridor and living room.

“sitting down on couch” is not trivial (not just move forward + track to). The character’s brain knows that is is peforming “sitting down on couch”, but does not know how to do that. It knows someone who can do this, so it delegates the task to…

… a nested FSM that knows how to “sit down on couch”. It describes how to go to the door, opening it, go to the couch and sit down.

Quite a lot of operations, isn’t it?
They do not run all at the same time, but in a sequence. Lets define the states and the main transition flow:
“walking to door” -> “opening door” -> “walking to couch” -> “sitting down on couch”

You see nothing in the inner FSM has anything to do with the outer FSM.
The outer FSM treats the inner FSM as state action and waits for feed back from the inner FSM (e.g. “I’m sitting on the couch” or simpler “done!”).
The inner FSM does not know it is called by another FSM and it should not care. It only knows it got input (command “go to couch”) and has to provide output (“done!”, “not possible”)

Both FSMs can have the same design (regarding FSM behavior), but they have different setup/configuration as they describe different object behavior.

Nested nested
Now imaging, you need another FSM that tells the character how to open the door.
Then you get an inner FSM of the inner FSM :P.
(and you need something that can make the character walk and something that can make the character sit)

Now not every action is a nested FSM ;).

Now imaging, you need another FSM that tells the character how to open the door.

i guess the point is exactly this at the end, not all state has the same priority .
ie , “alive”, “death” , “open door” ?
there something of wrong
alive cannot still on the same level of “open door” right?

so, maybe worth , organize everiting in this way, new stuff -> new state, but ready to become an fsm is necessary (or maybe not , but can)

:wink:

this rapresent perfecly the structure, logic nested (not doable with an “fsm flat”)


        
"""
0   1   2   3   4
Root
    Alive
        Move
            OnGround
                Waiting
                Walking
            OnAir
                Jumping
                Falling
                
        Attack
        UnderAttack
        ## Ko 
    Death
        
"""




update , removed some bug changed some behaviour , more over , not more wait for the transitions.
each path can be recreated from scratch in no time from the root to the last children (much more powerful)
afaik now is perfect


class StateNested:
    def __init__(self, owner):
        self.name = self.__class__.__name__
        self.owner = owner
        self.child = None
        self.childs = {}
        self._tic = 0
        self._active = False
    
    @property
    def tic(self):
        return self._tic
    
    @property
    def active(self):
        return self._active
    
    @active.setter
    def active(self, bol):
        if bol:
            self._tic = 0
            self._active = True
            self.start()
            
        else:
            self._active = False
            self.end()
            if self.child:
                self.child.active = False
                
    def check_transition(self):     # user method to override
        pass                        # return the name of the new "brother" or None
    
    def start(self):                # user method to override
        pass
    
    def run(self):                  # user method to override
        pass
    
    def end(self):                  # user method to override
        pass
    
    def add_child(self, instance):
        assert isinstance(instance, StateNested)
        self.childs[instance.name] = instance
        if not self.child:
            self.set_child(instance.name)
        
    def set_child(self, name):
        nc = self.childs[name]
        if self.child:
            self.child.active = False
            
        self.child = nc
        self.child.active = True
        
    def update_all(self):
        self._tic += 1
        self.run()
        if self.child:
            ncn = self.child.check_transition()
            if ncn:
                self.set_child(ncn) 
            elif not self.child.active:
                self.child.active = True
            self.child.update_all() # continue to cycle 
            
            
            

this is how my state machines are, except I don’t use functions,

This is still a little over ambitious. Where two separate need to exist in parallel, like a state driven animation system and a logic system to move the character, two fsms (concurrent, not nested) should be used. When a state is not simple, a nested fsm should be used to separate it and make it more manageable

doh … i not sure if is the obvious result of “monster”
anyway to make it usable in some way i keeped only the method for manage the transitions interrnally and no other (otherwise is too prone to bug)
so start() , run() end() are now caller of registred “mirrors” that are class nested in the same way (with the same names)
but do other things more specific when called from the monster :smiley:

this is the “mirror executor” that manage the movement
then there the mirror animator etc :smiley:


####################################################################
####################################################################
            
class MoveManager(Mirror):
    class Alive(Mirror):
        class Move(Mirror):
            class OnGround(Mirror):
                def run(self):
                    own = self.owner
                    own.localLinearVelocity.xy *= 0.8 
                    inputs = own["inputs"]
                    if "left"    in inputs:        own.applyRotation((0, 0, 0.05), 1)                    
                    if "right"   in inputs:        own.applyRotation((0, 0, -0.05), 1)
                    if "jump"    in inputs:        own.applyForce((0, 0, own.mass*150), 1)
                    if "forward" in inputs:        own.localLinearVelocity.xy = 0, 2
                    if "back"    in inputs:        own.localLinearVelocity.xy = 0,-1


                    
                class Waiting(Mirror):      pass
                class Walking(Mirror):      pass
            
            class OnAir(Mirror):
                class Jumping(Mirror):      pass
                class Falling(Mirror):      pass
            
        class Attack(Mirror):
            class Head(Mirror):             pass
            class Body(Mirror):             pass
        
        class UnderAttack(Mirror):
            class HeadFront(Mirror):        pass
            class HeadBack(Mirror):         pass
            class BodyFront(Mirror):        pass
            class BodyBack(Mirror):         pass


    class Death(Mirror):   pass

but start to seem another trap…

Nested classes? This looks very strange.

I suggest to apply the structure of the FSM as configuration rather than class hierarchies.

Don’t define classes in a nested fashion. Then you can’t reuse them. Granted, an FSM doesn’t need to encourage re-usability - its sole purpose to to enforce a state-transition architecture. But, given that such a program could be implemented using many if statements and functions,

E.G


class FSM:
    
    def __init__(self):
        self.state = None
    
    def update(self):
        state = self.state
        
        if state == "running":
            if IS_NOT_RUNNING:
                self.state = "walking"
                
                self.on_transition_to_walking()
        
        elif state == "walking":
            if IS_RUNNING:
                self.state = "running"
                
                self.on_transition_to_running()
        
        

re-usability is always preferred.

As Monster suggests, design a general purpose FSM architecture as I provided above to provide the features of the FSM. Configure this FSM with your specific features.