Code archives/3D Graphics - Misc/Finite State Machine (FSM)
This code has been declared by its author to be Public Domain code.
Download source code
| This is just a simple example of a Finite State Machine. It is primitive but is meant to be easy to understand. |
; ************************************************************************************************** ; Finite State Machine tutorial by Kevin Lee Legge (kevin8084@gmail.com) ; ************************************************************************************************** ; ; ************************************** DEFINITION ************************************************ ; ; A Finite State Machine (FSM) is an artificial intelligence method where the AI is in a series ; of "states". For game playing some of the most common states are: IDLE, HUNGRY, SEARCHING ; ATTACKING, SUPPORTING, RUNNING, DYING. ; There are two stages in the FSM - the "transistion" stage and the "behavior" stage. ; The transistion stage is where the AI's state is checked to see if the AI is in transistion ; from one state to another. That is, if the AI is in the IDLE state and is being attacked, then ; its state will be changing to ATTACKING or RUNNING or, perhaps, DYING. ; The behavior stage is where the action is. This part of the code instructs the AI on ; what to do according to what state it's currently in. For example, if the AI is currently in the ; SEARCHING state, then the behavior stage calls whatever searching functions are set up in the ; code. ; There are currently two schools of thought regarding FSM. The first regards the Behavior stage as ; completely adequate by itself, without bothering with any transistions. The second regards ; the transistion AND behavior stages as part of a whole. ; I, personally, subscribe to the idea that the FSM must have both transistion and behavior stages. ; ; ************************************************************************************************** Graphics3D 800,600 SetBuffer BackBuffer() SeedRnd MilliSecs() ; ************************************************************************************************** ; CONSTANTS ; ************************************************************************************************** Const IDLE = 0 Const SEARCHING = 1 Const ATTACKING = 2 Const DYING = 3 Const PLAYER_TYPE = 1 Const ENEMY_TYPE = 2 Const CUBE_TYPE = 3 Const GROUND_TYPE = 4 ; ************************************************************************************************** ; TYPES ; ************************************************************************************************** Type enemy Field entity ; the AI's mesh Field state ; what state the AI is currently in Field life# ; how much life the AI has Field range# ; the AI's search range Field bad.player ; use to store handle of AI's enemy (ie., the player) Field x#,y#,z# ; the AI's location Field vx#,vy#,vz# ; the AI's velocity End Type Type player Field entity Field x#,y#,z# End Type ; ************************************************************************************************** ; GLOBAL VARIABLES ; ************************************************************************************************** Global age ; use to age the enemy so that it transistions into its DYING state Global alpha# = 1 Global eState$ ; holds enemy's state for printing purposes ; Create the ground Global plane = CreatePlane() EntityType plane,GROUND_TYPE EntityColor plane,85,85,85 PositionEntity plane,0,0,0 ; Create the enemy Global enemy.enemy = New enemy enemy\entity = CreateSphere() EntityRadius enemy\entity,1 EntityType enemy\entity,ENEMY_TYPE EntityShininess enemy\entity,1 enemy\x# = Rnd(-200,200) enemy\y# = 2 enemy\z# = Rnd(-200,200) enemy\state = IDLE enemy\life# = 100.0 enemy\range# = 50.0 age = MilliSecs() PositionEntity enemy\entity,enemy\x#,enemy\y#,enemy\z# ; Create the player Global player.player = New player player\entity = CreateCamera() EntityRadius player\entity,1 EntityType player\entity,PLAYER_TYPE player\x#=0 player\y#=2 player\z#=0 PositionEntity player\entity,player\x#,player\y#,player\z# ; Create the light Global light = CreateLight() PositionEntity light,100,100,100 RotateEntity light,90,0,0 ; Populate the world with some cubes For c=1 To 25 cube=CreateCube() EntityColor cube,0,87,0 EntityType cube,CUBE_TYPE PositionEntity cube,Rand(-200,200),1,Rand(-200,200) Next HidePointer Collisions ENEMY_TYPE,CUBE_TYPE,2,2 Collisions ENEMY_TYPE,GROUND_TYPE,2,2 Collisions PLAYER_TYPE,GROUND_TYPE,2,21 Collisions PLAYER_TYPE,CUBE_TYPE,2,2 MoveMouse GraphicsWidth()/2,GraphicsHeight()/2 While Not KeyHit(1) If KeyDown(200) Then MoveEntity player\entity,0,0,.3 If KeyDown(208) Then MoveEntity player\entity,0,0,-.3 If KeyDown(203) Then TurnEntity player\entity,0,1,0 If KeyDown(205) Then TurnEntity player\entity,0,-1,0 TranslateEntity player\entity,0,-1,0 ; give player some gravity If enemy <> Null Then TranslateEntity enemy\entity,0,-1,0 ; same with the enemy mxs#=-MouseXSpeed()*.25 mys#=MouseYSpeed()*.25 MoveMouse GraphicsWidth()/2,GraphicsHeight()/2 TurnEntity player\entity,mys#,mxs#,0 RotateEntity player\entity,EntityPitch#(player\entity),EntityYaw#(player\entity),0 If EntityPitch#(player\entity)>80 Then RotateEntity player\entity,80,0,0 If EntityPitch#(player\entity)<-80 Then RotateEntity player\entity,-80,0,0 FSM_Transistion() ; check to see if there are any state transistions FSM_Behavior() ; now do the code, depending on the state the AI is in UpdateWorld RenderWorld Text 0,0,"I'm "+eState$ Flip Delay 10 Wend FreeEntity player\entity Delete player End ; ************************************************************************************************** ; FUNCTIONS ; ************************************************************************************************** Function FSM_Transistion() ; this function determines whether or not the AI is in transistion from one state to another For this.enemy = Each enemy ; iterate through each enemy AI Select this\state Case IDLE eState$="Idle" EntityColor this\entity,0,255,0 p.player = getEnemyDistance(this) ; check to see if player is in sensory range If p <> Null Then ; we see the player! this\state = ATTACKING ; we transistion to the attack state this\bad = p ; make sure that we store player internally ElseIf MilliSecs() > age+50000 Then ; are we about to die of old age (50 seconds)? this\state = DYING ; yes? Then transistion to dying state age = MilliSecs() ; not needed, really, but what the heck :) End If Case SEARCHING EntityColor this\entity,2550,0,255 p.player = getEnemyDistance(this) ; again, check to see if player is nearby If p <> Null Then ; yes? this\state = ATTACKING ; then transistion to attack state this\bad = p ElseIf MilliSecs() > age+50000 Then ; are we dying of old age? this\state = DYING ; yep...then we transistion age = MilliSecs() End If Case ATTACKING EntityColor this\entity,255,0,0 p.player = getEnemyDistance(this) ; even though we are in attacking state, we need to ; make sure that player is still in range If p <> Null Then ; yes? this\state = ATTACKING ; Houston, we have liftoff...we attack! this\bad = p ElseIf MilliSecs() > age+50000 Then ; else, are we dying? this\state = DYING ; if so, then die already! age = MilliSecs() Else this\state = SEARCHING ; else let's go to the searching state this\bad = Null ; no bad player here, so make it null End If Case DYING ; we are dying EntityColor this\entity,45,45,45 End Select Next End Function Function FSM_Behavior() ; this function reroutes according to which state the AI is in For this.enemy = Each enemy ; iterate through the enemy Select this\state Case IDLE this\state = SEARCHING ; idle is so boring...let's search, instead Case SEARCHING patrol(this) ; do our patrol Case ATTACKING attack(this) ; attack! Case DYING die(this) ; well, it's been real alpha#=alpha# - .001 ; this is to fade out the enemy End Select Next End Function Function patrol(e.enemy) ; put patrol code here eState$ = "patrolling" ; let's have the enemy move around a little bit MoveEntity e\entity,0,0,.2 If Rand(255)=17 Then RotateEntity e\entity,0,EntityYaw#(e\entity)+5,0 End Function Function attack(e.enemy) ; put attack code here eState$ = "attacking" ; just to show how to implement the attack state ; this is NOT my normal attack function! :) PointEntity e\entity,player\entity MoveEntity e\entity,0,0,.2 End Function Function die(e.enemy) ; we are dying now eState$ = "dying" EntityAlpha e\entity,alpha# If alpha# <= 0 Then FreeEntity e\entity Delete e eState$="Nothing...no more enemies left" End If End Function Function getEnemyDistance.player(e.enemy) ; function to determine if player is in AI's sensory range For p.player = Each player ; iterate through all the players If p <> Null Then ; hey, do we have a player? If EntityDistance#(e\entity,p\entity)<=e\range# Then ; yep...is he/she in range? Return p ; yes? Then return the player End If End If Next End Function |