您的位置:首页 > 其它

Finite State Machine

2014-12-16 23:23 363 查看

Contents

[hide]

1Description

2Components

3C#-FSMSystem.cs

4Example

Description

ThisisaDeterministicFiniteStateMachineframeworkbasedonchapter3.1ofGameProgrammingGems1byEricDybsend.Thereaaretwoclassesandtwoenums.IncludetheminyourprojectandfollowtheexplanationstogettheFSMworkingproperly.There'salsoacompleteexamplescriptattheendofthispage.

Components

Transitionenum:Thisenumcontainsthelabelstothetransitionsthatcanbefiredbythesystem.Don'tchangethefirstlabel,NullTransition,astheFSMSystemclassusesit.

StateIDenum:ThisistheIDofthestatesthegamemayhave.YoucouldusereferencestotherealStates'classesbutusingenumsmakesthesystemlesssusceptibletohavecodehavingaccesstoobjectsitisnotsupposedto.Allthestates'idsshouldbeplacedhere.Don'tchangethefirstlabel,NullStateID,astheFSMSystemclassusesit.

FSMStateclass:ThisclasshasaDictionarywithpairs(Transition-StateID)indicatingwhichnewstateS2theFSMshouldgotowhenatransitionTisfiredandthecurrentstateisS1.Ithasmethodstoaddanddeletepairs(Transition-StateID),amethodtocheckwhichstatetogotoifatransitionispassedtoit.Twomethodsareusedintheexamplegiventocheckwhichtransitionshouldbefired(Reason())andwhichaction(s)(Act())theGameObjectthathastheFSMStateattachedshoulddo.Youdon'thavetousethisschema,butsomekindoftransition-actioncodemustbeusedinyourgame.

FSMSystem:ThisistheFiniteStateMachineclassthateachNPCorGameObjectinyourgamemusthaveinordertousetheframework.ItstorestheNPC'sStatesinaList,hasmethodstoaddanddeleteastateandamethodtochangethecurrentstatebasedonatransitionpassedtoit(PerformTransition()).Youcancallthismethodanywherewithinyourcode,asinacollisiontest,orwithinUpdate()orFixedUpdate().

C#-FSMSystem.cs

usingSystem;
usingSystem.Collections;
usingSystem.Collections.Generic;
usingUnityEngine;

/**
AFiniteStateMachineSystembasedonChapter3.1ofGameProgrammingGems1byEricDybsand

WrittenbyRobertoCezarBianchini,July2010

Howtouse:
1.PlacethelabelsforthetransitionsandthestatesoftheFiniteStateSystem
inthecorrespondingenums.

2.Writenewclass(es)inheritingfromFSMStateandfilleachonewithpairs(transition-state).
ThesepairsrepresentthestateS2theFSMSystemshouldbeifwhilebeingonstateS1,a
transitionTisfiredandstateS1hasatransitionfromittoS2.RememberthisisaDeterministicFSM.
Youcan'thaveonetransitionleadingtotwodifferentstates.

MethodReasonisusedtodeterminewhichtransitionshouldbefired.
Youcanwritethecodetofiretransitionsinanotherplace,andleavethismethodemptyifyou
feelit'smoreappropriatetoyourproject.

MethodActhasthecodetoperformtheactionstheNPCissupposeddoifit'sonthisstate.
Youcanwritethecodefortheactionsinanotherplace,andleavethismethodemptyifyou
feelit'smoreappropriatetoyourproject.

3.CreateaninstanceofFSMSystemclassandaddthestatestoit.

4.CallReasonandAct(orwhichevermethodsyouhaveforfiringtransitionsandmakingtheNPCs
behaveinyourgame)fromyourUpdateorFixedUpdatemethods.

AsynchronoustransitionsfromUnityEngine,likeOnTriggerEnter,SendMessage,canalsobeused,
justcalltheMethodPerformTransitionfromyourFSMSysteminstancewiththecorrectTransition
whentheeventoccurs.

THESOFTWAREISPROVIDED"ASIS",WITHOUTWARRANTYOFANYKIND,EXPRESSORIMPLIED,
INCLUDINGBUTNOTLIMITEDTOTHEWARRANTIESOFMERCHANTABILITY,FITNESSFORAPARTICULARPURPOSE
ANDNON-INFRINGEMENT.INNOEVENTSHALLTHEAUTHORSORCOPYRIGHTHOLDERSBELIABLEFORANYCLAIM,
DAMAGESOROTHERLIABILITY,WHETHERINANACTIONOFCONTRACT,TORTOROTHERWISE,ARISINGFROM,
OUTOFORINCONNECTIONWITHTHESOFTWAREORTHEUSEOROTHERDEALINGSINTHESOFTWARE.
*/

///<summary>
///PlacethelabelsfortheTransitionsinthisenum.
///Don'tchangethefirstlabel,NullTransitionasFSMSystemclassusesit.
///</summary>
publicenumTransition
{
NullTransition=0,//Usethistransitiontorepresentanon-existingtransitioninyoursystem
}

///<summary>
///PlacethelabelsfortheStatesinthisenum.
///Don'tchangethefirstlabel,NullTransitionasFSMSystemclassusesit.
///</summary>
publicenumStateID
{
NullStateID=0,//UsethisIDtorepresentanon-existingStateinyoursystem
}

///<summary>
///ThisclassrepresentstheStatesintheFiniteStateSystem.
///EachstatehasaDictionarywithpairs(transition-state)showing
///whichstatetheFSMshouldbeifatransitionisfiredwhilethisstate
///isthecurrentstate.
///MethodReasonisusedtodeterminewhichtransitionshouldbefired.
///MethodActhasthecodetoperformtheactionstheNPCissupposeddoifit'sonthisstate.
///</summary>
publicabstractclassFSMState
{
protectedDictionary<Transition,StateID>map=newDictionary<Transition,StateID>();
protectedStateIDstateID;
publicStateIDID{get{returnstateID;}}

publicvoidAddTransition(Transitiontrans,StateIDid)
{
//Checkifanyoneoftheargsisinvalid
if(trans==Transition.NullTransition)
{
Debug.LogError("FSMStateERROR:NullTransitionisnotallowedforarealtransition");
return;
}

if(id==StateID.NullStateID)
{
Debug.LogError("FSMStateERROR:NullStateIDisnotallowedforarealID");
return;
}

//SincethisisaDeterministicFSM,
//checkifthecurrenttransitionwasalreadyinsidethemap
if(map.ContainsKey(trans))
{
Debug.LogError("FSMStateERROR:State"+stateID.ToString()+"alreadyhastransition"+trans.ToString()+
"Impossibletoassigntoanotherstate");
return;
}

map.Add(trans,id);
}

///<summary>
///Thismethoddeletesapairtransition-statefromthisstate'smap.
///Ifthetransitionwasnotinsidethestate'smap,anERRORmessageisprinted.
///</summary>
publicvoidDeleteTransition(Transitiontrans)
{
//CheckforNullTransition
if(trans==Transition.NullTransition)
{
Debug.LogError("FSMStateERROR:NullTransitionisnotallowed");
return;
}

//Checkifthepairisinsidethemapbeforedeleting
if(map.ContainsKey(trans))
{
map.Remove(trans);
return;
}
Debug.LogError("FSMStateERROR:Transition"+trans.ToString()+"passedto"+stateID.ToString()+
"wasnotonthestate'stransitionlist");
}

///<summary>
///ThismethodreturnsthenewstatetheFSMshouldbeif
///thisstatereceivesatransitionand
///</summary>
publicStateIDGetOutputState(Transitiontrans)
{
//Checkifthemaphasthistransition
if(map.ContainsKey(trans))
{
returnmap[trans];
}
returnStateID.NullStateID;
}

///<summary>
///ThismethodisusedtosetuptheStateconditionbeforeenteringit.
///ItiscalledautomaticallybytheFSMSystemclassbeforeassigningit
///tothecurrentstate.
///</summary>
publicvirtualvoidDoBeforeEntering(){}

///<summary>
///Thismethodisusedtomakeanythingnecessary,asresetingvariables
///beforetheFSMSystemchangestoanotherone.Itiscalledautomatically
///bytheFSMSystembeforechangingtoanewstate.
///</summary>
publicvirtualvoidDoBeforeLeaving(){}

///<summary>
///Thismethoddecidesifthestateshouldtransitiontoanotheronitslist
///NPCisareferencetotheobjectthatiscontrolledbythisclass
///</summary>
publicabstractvoidReason(GameObjectplayer,GameObjectnpc);

///<summary>
///ThismethodcontrolsthebehavioroftheNPCinthegameWorld.
///Everyaction,movementorcommunicationtheNPCdoesshouldbeplacedhere
///NPCisareferencetotheobjectthatiscontrolledbythisclass
///</summary>
publicabstractvoidAct(GameObjectplayer,GameObjectnpc);

}//classFSMState

///<summary>
///FSMSystemclassrepresentstheFiniteStateMachineclass.
///IthasaListwiththeStatestheNPChasandmethodstoadd,
///deleteastate,andtochangethecurrentstatetheMachineison.
///</summary>
publicclassFSMSystem
{
privateList<FSMState>states;

//TheonlywayonecanchangethestateoftheFSMisbyperformingatransition
//Don'tchangetheCurrentStatedirectly
privateStateIDcurrentStateID;
publicStateIDCurrentStateID{get{returncurrentStateID;}}
privateFSMStatecurrentState;
publicFSMStateCurrentState{get{returncurrentState;}}

publicFSMSystem()
{
states=newList<FSMState>();
}

///<summary>
///ThismethodplacesnewstatesinsidetheFSM,
///orprintsanERRORmessageifthestatewasalreadyinsidetheList.
///Firststateaddedisalsotheinitialstate.
///</summary>
publicvoidAddState(FSMStates)
{
//CheckforNullreferencebeforedeleting
if(s==null)
{
Debug.LogError("FSMERROR:Nullreferenceisnotallowed");
}

//FirstStateinsertedisalsotheInitialstate,
//thestatethemachineisinwhenthesimulationbegins
if(states.Count==0)
{
states.Add(s);
currentState=s;
currentStateID=s.ID;
return;
}

//AddthestatetotheListifit'snotinsideit
foreach(FSMStatestateinstates)
{
if(state.ID==s.ID)
{
Debug.LogError("FSMERROR:Impossibletoaddstate"+s.ID.ToString()+
"becausestatehasalreadybeenadded");
return;
}
}
states.Add(s);
}

///<summary>
///ThismethoddeleteastatefromtheFSMListifitexists,
///orprintsanERRORmessageifthestatewasnotontheList.
///</summary>
publicvoidDeleteState(StateIDid)
{
//CheckforNullStatebeforedeleting
if(id==StateID.NullStateID)
{
Debug.LogError("FSMERROR:NullStateIDisnotallowedforarealstate");
return;
}

//SearchtheListanddeletethestateifit'sinsideit
foreach(FSMStatestateinstates)
{
if(state.ID==id)
{
states.Remove(state);
return;
}
}
Debug.LogError("FSMERROR:Impossibletodeletestate"+id.ToString()+
".Itwasnotonthelistofstates");
}

///<summary>
///ThismethodtriestochangethestatetheFSMisinbasedon
///thecurrentstateandthetransitionpassed.Ifcurrentstate
///doesn'thaveatargetstateforthetransitionpassed,
///anERRORmessageisprinted.
///</summary>
publicvoidPerformTransition(Transitiontrans)
{
//CheckforNullTransitionbeforechangingthecurrentstate
if(trans==Transition.NullTransition)
{
Debug.LogError("FSMERROR:NullTransitionisnotallowedforarealtransition");
return;
}

//CheckifthecurrentStatehasthetransitionpassedasargument
StateIDid=currentState.GetOutputState(trans);
if(id==StateID.NullStateID)
{
Debug.LogError("FSMERROR:State"+currentStateID.ToString()+"doesnothaveatargetstate"+
"fortransition"+trans.ToString());
return;
}

//UpdatethecurrentStateIDandcurrentState
currentStateID=id;
foreach(FSMStatestateinstates)
{
if(state.ID==currentStateID)
{
//Dothepostprocessingofthestatebeforesettingthenewone
currentState.DoBeforeLeaving();

currentState=state;

//Resetthestatetoitsdesiredconditionbeforeitcanreasonoract
currentState.DoBeforeEntering();
break;
}
}

}//PerformTransition()

}//classFSMSystem


Example

Here'sanexamplethatimplementstheaboveframework.TheGameObjectwiththisscriptfollowsapathofwaypointsandstartschasingatargetifitcomeswithinacertaindistancefromit.AttachthisclasstoyourNPC.Besidestheframeworktransitionandstateidenums,youalsohavetosetupareferencetothewaypointsandtothetarget(player).IusedFixedUpdate()intheMonoBehaviourbecausetheNPCreasoningschemadoesn'tneedtobecalledeveryframe,butyoucanchangethatanduseUpdate().

usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
usingUnityEngine;

[RequireComponent(typeof(Rigidbody))]
publicclassNPCControl:MonoBehaviour
{
publicGameObjectplayer;
publicTransform[]path;
privateFSMSystemfsm;

publicvoidSetTransition(Transitiont){fsm.PerformTransition(t);}

publicvoidStart()
{
MakeFSM();
}

publicvoidFixedUpdate()
{
fsm.CurrentState.Reason(player,gameObject);
fsm.CurrentState.Act(player,gameObject);
}

//TheNPChastwostates:FollowPathandChasePlayer
//Ifit'sonthefirststateandSawPlayertransitionisfired,itchangestoChasePlayer
//Ifit'sonChasePlayerStateandLostPlayertransitionisfired,itreturnstoFollowPath
privatevoidMakeFSM()
{
FollowPathStatefollow=newFollowPathState(path);
follow.AddTransition(Transition.SawPlayer,StateID.ChasingPlayer);

ChasePlayerStatechase=newChasePlayerState();
chase.AddTransition(Transition.LostPlayer,StateID.FollowingPath);

fsm=newFSMSystem();
fsm.AddState(follow);
fsm.AddState(chase);
}
}

publicclassFollowPathState:FSMState
{
privateintcurrentWayPoint;
privateTransform[]waypoints;

publicFollowPathState(Transform[]wp)
{
waypoints=wp;
currentWayPoint=0;
stateID=StateID.FollowingPath;
}

publicoverridevoidReason(GameObjectplayer,GameObjectnpc)
{
//IfthePlayerpasseslessthan15metersawayinfrontoftheNPC
RaycastHithit;
if(Physics.Raycast(npc.transform.position,npc.transform.forward,outhit,15F))
{
if(hit.transform.gameObject.tag=="Player")
npc.GetComponent<NPCControl>().SetTransition(Transition.SawPlayer);
}
}

publicoverridevoidAct(GameObjectplayer,GameObjectnpc)
{
//Followthepathofwaypoints
//Findthedirectionofthecurrentwaypoint
Vector3vel=npc.rigidbody.velocity;
Vector3moveDir=waypoints[currentWayPoint].position-npc.transform.position;

if(moveDir.magnitude<1)
{
currentWayPoint++;
if(currentWayPoint>=waypoints.Length)
{
currentWayPoint=0;
}
}
else
{
vel=moveDir.normalized*10;

//Rotatetowardsthewaypoint
npc.transform.rotation=Quaternion.Slerp(npc.transform.rotation,Quaternion.LookRotation(moveDir),5*Time.deltaTime);
npc.transform.eulerAngles=newVector3(0,npc.transform.eulerAngles.y,0);

}

//ApplytheVelocity
npc.rigidbody.velocity=vel;
}

}//FollowPathState

publicclassChasePlayerState:FSMState
{
publicChasePlayerState()
{
stateID=StateID.ChasingPlayer;
}

publicoverridevoidReason(GameObjectplayer,GameObjectnpc)
{
//Iftheplayerhasgone30metersawayfromtheNPC,fireLostPlayertransition
if(Vector3.Distance(npc.transform.position,player.transform.position)>=30)
npc.GetComponent<NPCControl>().SetTransition(Transition.LostPlayer);
}

publicoverridevoidAct(GameObjectplayer,GameObjectnpc)
{
//Followthepathofwaypoints
//Findthedirectionoftheplayer
Vector3vel=npc.rigidbody.velocity;
Vector3moveDir=player.transform.position-npc.transform.position;

//Rotatetowardsthewaypoint
npc.transform.rotation=Quaternion.Slerp(npc.transform.rotation,
Quaternion.LookRotation(moveDir),
5*Time.deltaTime);
npc.transform.eulerAngles=newVector3(0,npc.transform.eulerAngles.y,0);

vel=moveDir.normalized*10;

//ApplythenewVelocity
npc.rigidbody.velocity=vel;
}

}//ChasePlayerState



                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: