Player/Character class .... help

someone can tell me if there something of right?

teoretically is more “call” and less “polling” , the function of character pass the call to the state directly.
this call can cuse the change of state (note the change of state is completely managed from the state itself)

it work, anyway are 300 to move a cube :confused: how is possible ?:o
is normal ? anyway with polling is long too … the problem of the length is when there these damn states :ba:

can someone give me the right diection …


from mathutils import Vector
import bge




""" state -> animation
Waiting             -> waiting
WalkingForward      -> walking_forward
WalkingBackward     -> walking_backward
Jumping             -> jumping
Falling             -> falling
"""


"""state transitions
Waiting
    Falling
    Jumping
    WalkingForward
    WalkingBackward
WalkingForward
    Jumping
    Falling
    Waiting
WalkingBackward
    Jumping
    Falling
    Waiting
Jumping
    Falling
    Waiting
Falling
    Waiting
"""




class State:
    def __init__(self, char):
        self.char = char
        self.start()
    def start(self):        pass
    def forward(self):      pass
    def backward(self):     pass
    def left(self):         pass
    def right(self):        pass
    def jump(self):         pass
    def update(self):       pass
    def attack(self):       pass
    
    
    
class Waiting(State):
    
    def start(self):
        self.char.animator.play("wait")
        
    def forward(self):
        self.char.state = WalkingForward(self.char)
        
    def backward(self):
        self.char.state = WalkingBackward(self.char)
        
    def left(self):
        self.char.box.applyRotation((0,0,0.1),1)
        
    def right(self):
        self.char.box.applyRotation((0,0,-0.1),1)
        
    def jump(self):
        self.char.state = Jumping(self.char)
        
    def update(self):
        if not self.char.on_ground:
            self.char.state = Falling(self.char)








class WalkingForward(State):
    
    def start(self):
        self.char.animator.play("walk_forward")
        self.forward()
        
    def forward(self):
        self.char.box.localLinearVelocity.xy = 0, 2
        
    def backward(self):
        self.char.state = Waiting(self.char)
        
    def left(self):
        self.char.box.applyRotation((0,0,0.1),1)
        
    def right(self):
        self.char.box.applyRotation((0,0,-0.1),1)
        
    def jump(self):
        self.char.state = Jumping(self.char)
        
    def update(self):
        if not self.char.on_ground:
            self.char.state = Falling(self.char)
        else:
            if self.char.box.localLinearVelocity.y < 1.0:
                self.char.state = Waiting(self.char)
            
        




class WalkingBackward(State):
    
    def start(self):
        self.char.animator.play("walk_backward")
        self.backward()
        
    def forward(self):
        self.char.state = Waiting(self.char)
        
    def backward(self):
        self.char.box.localLinearVelocity.xy = 0, -2
        
    def left(self):
        self.char.box.applyRotation((0,0,0.1),1)
        
    def right(self):
        self.char.box.applyRotation((0,0,-0.1),1)
        
    def jump(self):
        self.char.state = Jumping(self.char)
        
    def update(self):
        if not self.char.on_ground:
            self.char.state = Falling(self.char)
        else:
            if self.char.box.localLinearVelocity.y > 0.5:
                self.char.state = Waiting(self.char)






class Jumping(State):
    
    def start(self):
        self.char.animator.play("jumping")
        self.char.box.worldLinearVelocity.z = 5
        
    def update(self):
        if self.char.on_ground:
            self.char.state = Waiting(self.char)
        elif self.char.box.localLinearVelocity.z < 0.0:
            self.char.state = Falling(self.char)
            
            
            
class Falling(State):
    
    def start(self):
        self.char.animator.play("falling")
        
    def update(self):
        if self.char.on_ground or self.char.box.localLinearVelocity.z > 0.0:
            self.char.state = Waiting(self.char)
            
    
    
    
    
class Animator:
    def play(self, name=""):
        pass






class Character:
    def __init__(self, box, armature=None):
        
        self.box = box
        self.animator = Animator()
        self.state = Waiting(self)
        
    @property
    def on_ground(self):
        return self.box.rayCast(self.box.worldPosition + Vector((0, 0, -1.1)), self.box)[0] != None
        
    def forward(self):
        self.state.forward()
        
    def backward(self):     
        self.state.backward()
        
    def left(self):         
        self.state.left()
        
    def right(self):        
        self.state.right()
        
    def jump(self):         
        self.state.jump()
        
    def attack(self):       
        self.state.attack()
        
    def update(self):       
        self.state.update()
    
    
    
    
    
class Player(Character):
    pass
    
    
    
    
    
    
    
    


def init(cont):
    box = cont.owner


    box["player"] = p = Player(box)


    
def update(cont):
    own = cont.owner
    if not "player" in own:
        init(cont)
        
    p = own["player"]
    p.update()
    own["state_name"] = p.state.__class__.__name__
    
    kev = bge.logic.keyboard.events
    w,s,a,d,space = [kev[getattr(bge.events, k.upper()+"KEY")] in [1,2] for k in ["w","s","a","d","space"]]
    
    
    if w:
        p.forward()
    if s:
        p.backward()
    if a:
        p.left()
    if d:
        p.right()
    if space:
        p.jump()



it should work with always true, and a cube dynamic , -> [module.update]

:frowning:



class State:
    def __init__(self, char):
        self.char = char
        self.start()
    def start(self):
        self.char.animator.play("wait")

    def forward(self):      
        self.char.state = WalkingForward(self.char)
    def backward(self):     
        ...
    def left(self):    
        ...
    def right(self):        
        ...
    def jump(self):         
        ...
    def update(self):       
        ...
    def attack(self):       
        ...
    
    
    
class Waiting(State):
    
    def attack(self):
        pass
        
    def update(self):
        if not self.char.on_ground:
            self.char.state = Falling(self.char)








class WalkingForward(State):
    
    def attack(self):
        pass

    def update(self):
        if not self.char.on_ground:
            self.char.state = Falling(self.char)
        else:
            if self.char.box.localLinearVelocity.y < 1.0:
                self.char.state = Waiting(self.char)
            
        

...


so, make a bit less “abstract” the base class State ?

to note that the length is not a real problem, the point is -> how much is reusable ?
i want make a code that is usable also for NPCs

to me seem wrong :slight_smile:

“300” what? “sausages?” [my physics teacher]

I guess you need some more organization/structure.


"""state transitions
Waiting
    Falling
    Jumping
    WalkingForward
    WalkingBackward
WalkingForward
    Jumping
    Falling
    Waiting
WalkingBackward
    Jumping
    Falling
    Waiting
Jumping
    Falling
    Waiting
Falling
    Waiting
"""

These is the description of your transitions?
Unfortunately I can’t see it is ever read.

These is just documentation of the transitions?
Ah, ok. But why not using it to create your FSM graph.
As it is now you need to write it twice once in the fsm and again in this documentation.
As the documentation is much easier to read and edit … I suggest to use the documentation as input for the FSM.

All you need is:

  • a generic FSM
  • a builder that creates the transitions for the FSM

You feed the builder with the FSM and transition description. The builder will configure the the FSM which be be able to perform the transitions.


builder.setFSM(myFsm)
builder.addTransitionDescriptions("""
Waiting
    Falling
    Jumping
    WalkingForward
    WalkingBackward
WalkingForward
    Jumping
    Falling
    Waiting
WalkingBackward
    Jumping
    Falling
    Waiting
Jumping
    Falling
    Waiting
Falling
    Waiting
""")
builder.createTransitions()

Yes, you can do that in several different ways.


builder.createTransitions(myFsm, """
Waiting
    Falling
    Jumping
    WalkingForward
    WalkingBackward
WalkingForward
    Jumping
    Falling
    Waiting
WalkingBackward
    Jumping
    Falling
    Waiting
Jumping
    Falling
    Waiting
Falling
    Waiting
""")

Yes, I suggest to do a similar thing to create states. You can have a separate state list, or you extract the state names from the transitions (if the names is all you need).



class State:
    def __init__(self, char):
        self.char = char
        self.start()
    def start(self):        pass
    def forward(self):      pass
    def backward(self):     pass
    def left(self):         pass
    def right(self):        pass
    def jump(self):         pass
    def update(self):       pass
    def attack(self):       pass
 

Are you sure that a state should perform these operations?
As far as I know a state does other things:


class State():
    def execute(self):
        transition = self.selectPossibleTransition()
        if transition:
            transition.transit()
        else:
            actions.execute()

    def selectPossibleTransition(self):
       ...

The state checks if a transition is possible. If so it triggers the transit.
Otherwise it executes the state action (if any).

All states do that. The difference between the states, is:

  • the name
  • the transitions
  • the state actions
    As all three attributes are data, you do not even need a subclass of “State”. All you do is configure your states with different attributes.

Variations:
You might want to subclass State to implement a different transition selection strategy. E.g. if multiple transitions are possible, one state chooses a random transition, another version one chooses the first one, a third version chooses by priority …

You might want to subclass State to run the state action before the transition.


class Character:
    def __init__(self, box, armature=None):
        
        self.box = box
        self.animator = Animator()
        self.state = Waiting(self)
        
    @property
    def on_ground(self):
        return self.box.rayCast(self.box.worldPosition + Vector((0, 0, -1.1)), self.box)[0] != None
        
    def forward(self):
        self.state.forward()
        
    def backward(self):     
        self.state.backward()
        
    def left(self):         
        self.state.left()
        
    def right(self):        
        self.state.right()
        
    def jump(self):         
        self.state.jump()
        
    def attack(self):       
        self.state.attack()
        
    def update(self):       
        self.state.update()
    

You managed to dynamically bind a state to an event. Unfortunately you hard-coded the events. Imagine the amount of work to add another event (or two or three). Imagine the amount of work when adding another state (or one, two, twenty). None of that is unlikely.

I recommend to use a dynamic event handling implementation.

Look at the FSM. When do you need events?
You need them to trigger transitions. Nothing more, nothing less.
There is a direct relation between event and transition. When the event occurs, the transition gets enabled. If the event does not occur the transition is not enabled.

Lets see what an event is:

An event is an condition at a certain time. E.g. a mesh collides with another, a timer runs out, an animation finished, a property reaches a limit …

As you do not want to poll for a condition you let something else do the polling for you (e.g. a sensor, a callback …). This way check the current condition when you get notified something changed. The remaining time you do nothing.

Therefore you need to extend your transition description from post#5 by adding events:


"""
Waiting
    no ground: Falling
    jump: Jumping
    forward: WalkingForward
    backward: WalkingBackward
WalkingForward
    jump: Jumping
    no ground: Falling
    not forward: Waiting
WalkingBackward
    jump: Jumping
    noGround: Falling
    not backward: Waiting
Jumping
    no ground: Falling
    ground: Waiting
Falling
    ground: Waiting
"""

By adding events you tell what transition happens when.
You see you can perform logical operations on events (not, and, or …).

Each event provides you with either a positive or negative result:


class Event():
   def isEnabled():
       ...

As each event has to be treated differently you will need several different events:
Logic operation events:


class AND():
   __init__(self, events):
       self.events = events
   def isEnabled(self):
      for event in self.events:
         if not event.isEnabled():
            return False
      return True


class Not():
   def __init__(self, event):
      self.event = event
   def isEnabled(self):
      return not self.isEnabled()


sensing events:


def SensorEvent():
   def __init__(self, sensor):
      self.sensor = sensor # attention: this will fail if the owning object gets deleted
   def isEnabled(self):
      return self.sensor.positive

measuring events:


def EqualsEvent():
   def __init__(self, gameObject, property, limit):
      self.limit = limit
      self.gameObject = gameObject
      self.property = property

   def isEnabled(self):
      value = gameObject[property]
      return value == limit


You create a set of events. Then you configure your transitions. As the events do not have a relation to the transition you can reuse an event at multiple transitions.

I suggest you enhance your transition builder to create events too (an event builder can do the same thing).

What do you got?
A very limited set of state types, one kind of transitions, a lot of combine-able event types. Most of these types can be encapsulated into one or more separate modules.

To get your specific FSM, you create a description and let it build by the builder.

All your Character is doing is to notify the FSM that an event happened, and ensure the state actions get executed.


fsm.update()

I forgot … you need some actions too (playing an animation, moving forward). I suggest you create them as separate entity as well.



class State:
    def __init__(self, char):
        self.char = char
        self.start()

    def forward(self):
        self.char.fowardAction()
    ...



class NPCShooter(BaseCharacter):
    def fowardAction(self):
        self.char.box.localLinearVelocity.xy = 0, 7
    ...

class NPCShooter(BaseCharacter):
    def fowardAction(self):
        self.char.box.localLinearVelocity.xy = 0, 2
        
    ...

@MarcoIT

The way I did it is to not care if the state belongs to a PC or an NPC.
Input is translated into messages and then received by the FSM.

In each state if a message is received it can trigger a transition.

class State(object):
    def __init__(self, input):    
        self.transition_state = None  
        self.input = input
                        
    def transition(self):
        return self.transition_state
    
    def update(self):
        if self.input == "death":
            self.transition_state = Dying
        
class IdleState(State):
    
    def __init__(self,input):
        super().__init__(input)
        
        pass
    
    def update(self):
        
        super().update()
                
        if self.input == "jumping":
            self.transition_state = Jumping
            
        if self.input == "left":
            self.transition_state = WalkingLeft  

And then the character processes this:

def set_state(self,cls):
        self.state.end()
        self.state = cls(self)
        self.state_name = cls.__name__

cls = self.state.transition()

if cls:
    self.set_state(cls)   
                 
self.state.update()

You can use only messages, or you can pass the character and pick up booleans from them, such as character.on_ground or character.dead.

Outside of the state machine there is a method for turning both player keyboard input or AI logic in to input messages for the FSM. As far as the FSM is concerned it doesn’t matter who sends the input, because it all conforms to a single standard.

With this arrangement you can easily add new states, just add a method of transition within an existing state and then add the subclass for the new state, inheriting most of the code for an existing state that is similar, or creating a new subclass of State().

For example I have a ClimbingState(State) which is not used directly, but acts only as a parent of the Vaulting(ClimbingState) and LadderClimbing(ClimbingState) states since all of these states share a lot of code. The code skips ClimbingState(State) when transitioning and goes directly to one of the subclasses.

Actually you helped me a lot with this setup… :slight_smile: I can see why you would want to avoid a lot of if/else arguments, but it just seems more flexible that way, unless you write a dictionary based interpreter…

thanks to both , but i continue to see a mountain of code infinite.
interssing the use of super() , but i see ever code over code structures to manage other structure , and overall “too many self” (who is self ?)

seem code “empty” that not know well what do.
and more code i write more increase my confusion.

so i’m returned for a moment to stuff that i know better (basically , simply dictionary)
and i noticed that one possible solution can be in a certain sense invert the order partially .
from… “if state is X then if …”
to since this event is happened ,is probable that should change state , we can check…

while the normal “if state is X then if …”
come AFTER.

this is just a “sketch” , not tested : (almost pseudo code but should be understandable)
note the definition “def change_state()” that is the core of the fsm








STATE_TRANSITIONS=		{
"move":				{"underattack", "death"},
"underattack":		{"move", "death"},
"attack":			{"move", "underattack", "death"},
"death":			{},
						}


class StateChanged(Exception)
	pass


def change_state(own, name):
	cs = own["state"]
	if name in STATE_TRANSITIONS[cs]:
		own["state"] = name
		own["state_timer"] = 0.0
		raise StateChanged()
		




def fsm(own, sn, st, health, dmg):
	if health<0:
		change_state(own, "death")
	if dmg: 	
		change_state(own, "underattack")
		
		
	################################### end first step
	
	if sn == "underattack" and st > 2.0:
		change_state(own, "move")
	if sn == "attack" and ts > 1.0:
		change_state(own, "move")
		
	################################### end second step


	if sn == "move":
		if .......imput???
		if .......action?
		if .......task?
		
			
def update(own):
	update_stuff_for_damage_health_on_ground_etc()
	
	sn, st, health, dmg = own["state_name"], own["state_timer"], own["health"], own["damage"]
	try:
		fsm(own , sn, st, health, dmg)
	except StateChanged:
		pass
	

I’m coming back to my character class soon, during BGMC16 I had problems with too much of the AI behavior being separate from the states. I wanted to keep the number of states small, so I excluded most of the AI decision making code from the states and instead put it in the character class. This caused a lot of problems, including with smooth animation, since the states required updating too often.

A character shouldn’t have to exit walking state to get a route to the next valid enemy. Walking should continue smoothly until you stop walking either because you’ve reached your destination or because you decide to stop walking.

I think I’m going to have a two state system:

  1. AI thinking states (Get target, Get route, Advance, Stand still, Retreat, etc…)
  2. Character Movement and animation states (Walk, Run, Idle, Die, Dead etc…)

I don’t think it’s a bad idea to have different states for different things. I used it before with a shooting/reloading state which was semi independent of the moving state. There was some overlap of course, I had a list of non-shooting states [dead,falling,taking_damage], and if the movement state was one of those, you couldn’t initiate shooting. Simple enough to do an if current movement state not in invalid list.

Keeping types of states separate also cuts down on messy code.

This is by convention the current class instance (the object). In Java this is called “this”, but can be skipped if it can’t be confused with a local variable.

In Python you have to explicitly define it in the method’s argument list. It is always the first argument. You do not need to call it “self”. This is the typical use name in Python. You have to use the full qualified name due to the implicit variable declaration of Python:


class SampleClass():
  def __init__(self):
     localVariable = 1
     self.field = localVariable
     localFunctionOfThisModule()
     self.methodOfSampleClass()
     self.methodWithOtherArguments()

   def methodOfMyClass(self):
      return "self = me = instance of SampleClass = " + str(self)

   def methodWithOtherArguments(me):
      return "me = instance of SampleClass = " + str(me)

have 2 separate things (fsm) sound extremely good , but for some reason seem not doable.
since the dependences for example between a “body manager” and a “brain manager” are continues.

anyway after some other experiment seem that the dictionary only is not viable.

the more doable solution is use -> super().transition() (i made some modification)
is not easy but seem the only way to reuse the code and mantain the code almost redable.

anyway , i want make a game where there different NPCS warriors.(i abandoned the player for now :D)
ALL turn around to 9 animations, these:

“”" WarriorNpc animations

waiting (loop layer 0)
walking (loop layer 0)
attack_head (once layer 1)
attack_body (once layer 1)
underattack_head_front (once layer 1)
underattack_head_back (once layer 1)
underattack_body_front (once layer 1)
underattack_body_back (once layer 1)
ko (loop layer 0)
“”"

i have already a fsm that work with strings but is really hugly and overall in the state “untouchable”,
so i need to an alternative

this is the pseudo code that start to have some form.

note -> “Underattack_ things”
sound too bad , but the alternative is make “actions” nested inside the class , extremely dirty, since the transition should define not only the new state but also the new action of the new state , too intrusive … or not?


        




class State:
    def __init__(self, owner):
        self.name = self.__class__.__name__
        self.owner = owner
        self.transition = None
        self.action = None
        self.start()
    
    def start(self):
        pass
        
    def replace(self, cls):
        if not self.transition:
            self.transition = cls
            
    def check_transition(self):
        pass
        
    def run(self):
        if self.action:
            self.action() #anything clear what should be this :D
            
        
        
        
        


        
        
        
class Alive(State):
    def check_transition(self):
        if self.owner["health"] <= 0.0:
            self.replace(Death)
            
class NotAttacked(Alive):   # <- this sound bad!
    def check_transition(self):
        super().check_transition()
        if self.owner["damaged"]:
            dmz = self.owner["damage_zone"]
            D =     {
                    "head_front":     UnderAttackHeadFront,
                    "head_back":     UnderAttackHeadBack,
                    "body_front":     UnderAttackBodyFront,
                    "body_back":     UnderAttackBodyBack,
                    }
            self.replace(D[dmz])
            
################################


















class Ko(State):
    animation = "ko"
    def start(self):
        self.owner["animator"].play("ko")
        
            
class Wander(NotAttacked):
    def check_transition(self):
        super().check_transition()
        if self.owner["enemy_position"] != None:
            self.replace(Alert)
            
class WanderWait(Wander):
    def check_transition(self):
        super().check_transition()
        
            
class Alert(NotAttacked):
    def check_transition(self):
        super().check_transition()
        if self.owner["alert"] <= 0.0:
            self.replace(Wander)


class AlertFollow(Alert):
    def check_transition(self):
        super().check_transition()
        if not self.owner["enemy_position"]:
            self.replace(AlertWait)
        else:
            d, vec = self.owner.getVectTo(self.owner["enemy_position"])
            if 0.0 < d < 1.0:
                angle = vec.angle(self.owner.worldOrientation.col[1])
                if angle < 0.2:
                    cls = random.choice([AttackHead, AttackBody])
                    self.replace(cls)
            
            
class AlertFollow(Alert):
    
        
            
class UnderAttack(Alive):
    def check_transition(self):
        super().check_transition()
        if self.owner["state_tic"] > 120:
            self.replace(Alert)
            
            
class UnderAttackHeadFront:
    def start(self):
        self.owner["animation"] = "under_attack_head_front"
            
class UnderAttackHeadBack:
    def start(self):
        self.owner["animation"] = "under_attack_head_back"
            
class UnderAttackBodyFront:
    def start(self):
        self.owner["animation"] = "under_attack_body_front"
            
class UnderAttackBodyBack:
    def start(self):
        self.owner["animation"] = "under_attack_body_back"
            
            
            
            
            
            
            
class Attack(NotAttacked):
    def check_transition(self):
        super().check_transition()
        if self.owner["state_tic"] > 60:
            self.replace(Alert)
            
            
            
            
            
def init(own):
    own["health"] = 1.0
    own["alert_value"] = 0.0
    own["enemy_position"] = None
    own["damage_zone"] = None
    own["state_tic"] = 0
    own["state_instance"] = Wander(own)
    own["state_name"] = own["state_instance"].name






def update(own):
    own["state_tic"] += 1
    generate_events(own)
    
    own["state"].check_transition()
    if own["state"].transition:
        own["state"] = own["state"].transition(own)
    #else:
        #own["state"].run()





divide actions, locomotion, and animation.

you can attack while walking, etc.

I have a FSM, but its based on sensors+ a generated list of keypresses.

I am sure you could do a better job then me using the same ideas.

maybe even have the sensor data in the same list as the keypresses?

for enemy AI, have the direction of motion and the sensor data determine what animations to play?
some states block others until they are finished? (like jump blocks walk?)

this way a steering actuator can move the enemy actor or?

http://www.pasteall.org/59280/python

here is my current state machine for chaos emergency (it handles actions and locomotion)

Here’s my opinion on the subject.

State systems are just a tool. They’re not perfect, and sometimes you will have to adapt them to support new features.

Fundamentally, states are used to encapsulate distinct behaviours / responses of an entity to input stimuli. Is walking forward distinct from walking backwards? Perhaps, but to the same extent that attacking is distinct from walking? No, and so you choose to draw the line.
I would recommend avoiding getting to deep into the “perfect” fsm. When designing a state machine, the following points must be considered.

An FSM is composed of states and transitions. A transition involves two states, and such must know about both states. If you perform these transitions inside the state, then you’re undermining the ability to re-use states. Abstracting the stimuli is the first step in enabling the same logic to be reused between NPC and Player characters.

Here’s an example setup for a simple target-follower and state-driven animations:
agent (1).blend (106 KB)

I will explain a little of its function:

Each state is basically an enter / exit state. AIState also adds an update feature; I didn’t add this to the default base class for State, because I’m leaving it up to the state-machine user to decided whether it needs an update - it’s not the behaviour of the state machine. You could also add a “handle_event” method to the state, and then you would have to update the code for the state machine as well as the states; it’s an unnecessary dependency.

The state machine essentially just handles the exit/entry of each state. It doesn’t use strings, because you already know which state you want, just store a reference to it rather than making things more complex.

The transitions of the state machine are namedtuples, which allows you to access data members like a tuple (x, y, z) or a class instance (.x, .y, .z). It’s just syntactic sugar.

The FSM looks complicated because there are a few helper functions. In reality, you only need to worry about:

  • add_state
  • remove_state
  • create_and_add_transitions_from_table

There are many ways to add transitions. The base add_transition method just stores the transition according to the state it transitions from. The create_and_add_transition creates a transition object first. It’s just quicker than creating the Transition object and then adding it (to type, only). The create_and_add_transitions_from_table method (long naming, my own choice) is designed to take a transition table (rows of: condition, from_state, to_state) and call create_and_add_transition for each row. It’s just cleaner to use.

You can remove transitions if you wish, but that may not be often required. Perhaps removing the transition to the follow state (for the NPC) if it is broken, for example (e.g no wheels on a robot).

How is this better than simple if statements?

  • Animation logic is separate from control logic
  • States are reusable, with possibly different transitions, reducing bugs and amount of code to manage.
  • Forces cleaner code to be written to maintain appropriate state separation, which is good code practice (usually)

If I were to use this in a project (which I am - check here for updates), then I would also edit the walk / run / idle states to accept the animation data as initialisation arguments.

On a final note, don’t worry if you don’t think that a certain design pattern is a good choice for your game. People often say this, but it’s for good reason; there’s never a perfect solution, and if the code works and is very unlikely to be changed (or you’ve already written it in a previous project and you’re rushing to finish a BGMC / tech demo) then it’s fine to leave it in an ugly state. You may regret it later on, but it’s fine to do. Especially for smaller projects, where the architecture of the game isn’t required to be rigidly defined, you can afford to make those mistakes. Over time, you will recognise when something is required / better written in a different manner. Don’t try and force code to use a certain pattern because it’s cool / looks good. (You don’t have to create new states for each movement direction ;)!).

thanks , “actions” is not very clear whar are “tecnically” (are by chance command with duration of one frame only, equivalent of a function … as “fire and forget”?)

animations are (teoretically) already separated (ie i write all necessary on one string own[“animation”], that should be readed from the armature …the armature MUST play the according animation <if arm.parent[“animation”] != “”>)

some animation must be explicitely writtten , cannot the armature know if and when the box attack, and what kind of attack precisely is (unfortunately this , yes is a dependence … i just try to limitate it)

the logic of player is too different from NPC , (Alert, Wander are “states” useful(i guess :D) for NPC, it has any sense for the player , as alert value, and some timer)

your scrip seem that excecute the output of some FSM (placed somewhere else) where the output are [“Move”] and [“Act”] ?

so lack the mess part -> transitions , changes of state ?
where are evaluated the events? and what is the state current?

i have underastand the “main design” ?
so separate better the FSM itself ? (the different operation?)
i had thinked to a cycle similar to:
gameloop to gameobject ->
gameobject to fsm (concrete to abstract) ->
fsm internal -> (abstract)
fsm to gameobject(abstract) ->
gameobject excecution (abstract to concrete … your script)
-> gameloop

oh… not say nothing … but …



-         <b>for</b> Actions <b>in</b> own['ActuList']:


for action in [container of actions]
for actuator in [container of actuators] :wink:

Thanks, i apprecciate much the “blend tutorial working”
is rare … typically (as i made :o ) are readable only “piece of code” more or less without sense
but seem require a python level a bit too high.

what i have noticed is that if the FSM manage ONLY the transition , become much more clean .
you confirm this .
are the action concrete inside the state (ie-> own.alignAxisTo(something)) that make the fsm immediately dirty.

how make the transition separated from the state is anything clear anyway. (more over sound as need tons of “self” :smiley: i can manage at max 1 or 2 “self” at the same time :smiley: )

well what i mean for reusability was between one state and another state … (of the same FSM of the same object of the same module … concept very relative of reusability )

what you think about the use of super() to reuse piece of code ?(overall for transition)
to me seem that work well anyway , better of copy and paste, but i noticed some little bug , that not understand from where it come .with all these subclasses…

this is the last version (was working until at the previous version, then now still bloked in “WanderWait”)


import random






""" state branches structure


TransitableToKo
    TransitableToUnderAttack
        TransitableToAlert
            BaseWander
                WanderWait
                WanderMove
        BaseAlert
            AlertWait
            AlertMove
        BaseUnderAttack
            UnderAttackHeadFront
            UnderAttackHeadBack
            UnderAttackBodyFront
            UnderAttackBodyBack
        BaseAttack
            AttackHead
            AttackBody
Ko      
"""


### the state that start with Base as suffix are not directly used




class BaseState:
    def __init__(self, owner):
        self.owner = owner
        self.name = self.__class__.__name__
    
    def start(self):
        pass
        
    def check_transition(self):
        pass
        
    def replace(self, cls):
        if not self.owner["state_next"]:
            self.owner["state_next"] = cls
            
            


class TransitableToKo(BaseState):
    def check_transition(self):
        super().check_transition()
        if self.owner["health"] &lt;= 0.0:
            self.replace(Ko)
            
class TransitableToUnderAttack(TransitableToKo):
    def check_transition(self):
        super().check_transition()
        if self.owner["damage_zone"]:
            dmz = self.owner["damage_zone"]
            d=  {
            "head_front":   UnderAttackHeadFront,
            "head_back":    UnderAttackHeadFront,
            "body_front":   UnderAttackHeadFront,
            "body_back":    UnderAttackHeadFront,
                }
            self.replace(d[dmz])


class TransitableToAlert(TransitableToUnderAttack):
    def check_transition(self):
        super().check_transition()
        if self.owner["alert_value"] &gt; 0.0:
            self.replace(AlertWait)
            
class BaseUnderAttack(TransitableToUnderAttack):
    def start(self):
        self.owner["state_tic_max"] = 120
    def check_transition(self):
        super().check_transition()
        if self.owner["state_tic"] &gt; self.owner["state_tic_max"]:
            self.replace(AlertWait)
            
class BaseAttack(TransitableToUnderAttack):
    def start(self):
        self.owner["state_tic_max"] = 60
        self.owner["stamina"] -= 0.3
        
    def check_transition(self):
        super().check_transition()
        if self.owner["state_tic"] &gt; self.owner["state_tic_max"]:
            self.replace(AlertWait)
        
class AttackHead(BaseAttack):   pass
class AttackBody(BaseAttack):   pass


class UnderAttackHeadFront(BaseUnderAttack): pass
class UnderAttackHeadBack(BaseUnderAttack):  pass
class UnderAttackBodyFront(BaseUnderAttack): pass
class UnderAttackBodyBack(BaseUnderAttack):  pass


            
class BaseWander(TransitableToAlert):
    def start(self):
        self.owner["state_tic_max"] = random.randint(20,50)
    def check_transition(self):
        super().check_transition()
        if self.owner["state_tic"] &gt; 50:#self.owner["state_tic_max"]:
            self.replace(random.choice([WanderWait, WanderMove]))
            
class WanderWait(BaseWander):   pass
class WanderMove(BaseWander):   pass


class BaseAlert(TransitableToUnderAttack):
    def check_transition(self):
        super().check_transition()
        if self.owner["alert_value"] &lt;=0:
            self.replace(random.choice([WanderWait, WanderMove]))
            
class AlertWait(BaseAlert):
    def check_transition(self):
        super().check_transition()
        if self.owner["enemy_position"]:
            self.replace(AlertMove)
            
class AlertMove(BaseAlert):
    def check_transition(self):
        super().check_transition()
        if not self.owner["enemy_position"]:
            self.replace(AlertMove)
        elif self.owner["enemy_attackable"] and self.owner["stamina"] &gt; 0.1:
            self.replace(random.choice([AttackHead, AttackBody]))
            
            
class Ko(BaseState):
    def check_transition(self):
        if self.owner["health"] &gt; 0.3:
            self.replace(AlertWait)
            




    

Some people say it’s not good to use super if you have multiple lines of inheritance. But I haven’t found any situation where it would cause trouble yet.

I think that you’re over thinking this, and over separating.

When adding reusability to code makes it less reusable, you’re trying to “add similarities” which do not exist.
super() is a tool, similar to using ParentCls.some_method(self, …) to call a parent method. The only time you would do this is if the method has been over-ridden in your own class. Therefore, it’s used to extend parent method behaviour.

The main reason for using an FSM is that the statesare re-usable, because the transitions are separate from the states. When you hard-code transitions into the states, you make it more difficult to re-use those actions. If you can, trigger transitions from the object’s state (as I do in my example). Where you cannot easily do this without writing horrible code because the transition depends upon what the state “did”, create generic callbacks that the state can call when an event happens , but can be over-ridden outside the state. Or, create condition methods that return a transition trigger, e.g State.is_action_complete.

Furthermore, you can nest state machines where the state might have complex tasks to perform, e.g


State Patrol
{
    FSM fsm = FSM
        [
            State FindRandomPoint,
            State GOTORandomPoint,
            State FollowEnemy,
        ]


}

State Attack
{
    FSM fsm = FSM [...]
}

Transitions
[
(agent.is_near_enemy, from_state=Patrol, to_state=Attack),
(agent.has_no_enemy, from_state=Attack, to_state=Patrol)
]


This way, we can still transition from states without exiting the main parent state. This is more organised.