This is a snippet from the code used to control an enemy. If you have any expertise in computer science concepts, you can probably recognize this as implementing a finite state machine (FSM). Lots of repetitive code. This is why enemies and NPCs in video games often use domain-specific langauges.
I did it the hard way.
def setup_state(self,*args,**kwargs):
if self.state == "looking":
self.to_feet()
self.role = acting.HoldStill(self)
elif self.state == "returning":
self.to_feet()
self.role = acting.MoveToPoint(self,*self.path.get_pointvec(0.0))
elif self.state == "patrolling":
self.to_path(self.path,0.0)
self.role = acting.Pace(self)
elif self.state == "charging":
self.to_feet()
self.role = acting.Chase(self,game.lance,speed=10.0,nearthresh=0.5)
elif self.state == "swatting":
self.to_feet()
self.role = acting.MeleeAttack(self,self.swat_choreo,"goblin_swat",duration=0.667)
elif self.state == "recoiling":
self.to_feet()
self.role = acting.Recoil(self,*args,**kwargs)
elif self.state == "dying":
self.to_feet()
self.role = acting.Shift(self,self.supine_choreo,0.5)
self.weakness.targetable = False
else:
raise ValueError("unknown state %s" % self.state)
def check_transitions(self):
strikedir = self.strike
self.strike = None
if self.state == "looking":
if self.life <= 0:
self.transition("dying")
elif strikedir is not None:
self.transition("recoiling",strikedir)
elif self.timer >= 0.4 and self.hero_is_swattable():
self.transition("swatting")
elif self.timer >= 0.4 and self.hero_is_visible():
self.transition("charging")
elif self.timer > 2.0:
self.transition("returning")
elif self.state == "returning":
if self.life <= 0:
self.transition("dying")
elif strikedir is not None:
self.transition("recoiling",strikedir)
elif self.hero_is_swattable():
self.transition("swatting")
elif self.hero_is_visible():
self.transition("charging")
elif self.role.completed:
self.transition("patrolling")
elif self.state == "patrolling":
if self.life <= 0:
self.transition("dying")
elif strikedir is not None:
self.transition("recoiling",strikedir)
elif self.hero_is_swattable():
self.transition("swatting")
elif self.hero_is_visible():
self.transition("charging")
elif self.state == "charging":
if self.life <= 0:
self.transition("dying")
elif game.register["lance.life"] <= 0:
self.transition("returning")
elif strikedir is not None:
self.transition("recoiling",strikedir)
elif self.hero_is_swattable():
self.transition("swatting")
elif self.state == "swatting":
if self.life <= 0:
self.transition("dying")
elif strikedir is not None:
self.transition("recoiling",strikedir)
elif self.role.completed:
self.transition("looking")
elif self.state == "recoiling":
if self.life <= 0:
self.transition("dying")
elif strikedir is not None:
self.transition("recoiling",strikedir)
elif self.role.completed:
self.transition("looking")
elif self.state == "dying":
if self.timer > 5.0:
game.scene.remove_object(self)
Edit: June 28, 2018: As I reread this I realized that it’s not actually a finite state machine: one of the states (“recoil”) is parameterized with a 3-D real-valued vector, which means it’s an infinite number of states. Though techincally speaking, since real numbers are discretized, it still has a finite number of states, but it’s not really an FSM in spirit.


