Hi all, I have been reading and watching online tutorials on how to create state machines and have come up with the following code.
I played around with some code which uses classes to create the states and have them as separate scripts but, with my limited understanding I had no idea of the inner workings. I still learning and it’s easy to get lost in code, I would rather have a machine that I can understand.
I have managed to split it up into two scripts (FSM, FSMActions) but, up to now have not figured out how to have separate states in files. However, by the length of the code so far (with just debug info) it’s going to get a little over crowded later on after adding more vars, functions etc.
What I’m looking for is advice on how to either split up the code into smaller chucks (if not too complicated) or ways to organise the current code.
Thanks in advance.
FSM.cs
using UnityEngine;
using System.Collections;
public class FSM : MonoBehaviour {
public enum State {
init,
idle,
setup,
task,
search,
move,
work
}
public State _state;
public enum Task {
enterTask,
executeTask,
exitTask
}
public Task _task;
public enum Searching {
enterSearch,
executeSearch,
exitSearch
}
public Searching _search;
public enum Moving {
enterMove,
executeMove,
exitMove
}
public Moving _move;
public enum Working {
enterWork,
executeWork,
exitWork
}
public Working _work;
void Start() {
StartCoroutine("FSMachine");
}
IEnumerator FSMachine() {
_state = State.init;
while(true) {
switch(_state) {
case State.init:
Init();
break;
case State.setup:
Setup();
break;
case State.idle:
yield return new WaitForSeconds(1f);
Idle();
break;
case State.task:
yield return new WaitForSeconds(1f);
InTask();
break;
case State.search:
yield return new WaitForSeconds(1f);
InSearch();
break;
case State.move:
yield return new WaitForSeconds(1f);
InMove();
break;
case State.work:
yield return new WaitForSeconds(1f);
InWork ();
break;
}
yield return 0;
}
}
public virtual void Init() {
}
public virtual void Setup() {
}
public virtual void Idle() {
}
public virtual void InTask() {
}
public virtual void EnterTask() {
}
public virtual void ExecuteTask() {
}
public virtual void ExitTask() {
}
public virtual void InSearch() {
}
public virtual void EnterSearch() {
}
public virtual void ExecuteSearch() {
}
public virtual void ExitSearch() {
}
public virtual void InMove() {
}
public virtual void EnterMove() {
}
public virtual void ExecuteMove() {
}
public virtual void ExitMove() {
}
public virtual void InWork() {
}
public virtual void EnterWork() {
}
public virtual void ExecuteWork() {
}
public virtual void ExitWork() {
}
}
FSMActions.cs
using UnityEngine;
using System.Collections;
public class FSMActions : FSM {
#region *** SETUP ***
public override void Init() {
Debug.Log ("- init");
_state = State.setup;
}
public override void Setup() {
Debug.Log ("- setup");
_state = State.idle;
}
#endregion
public override void Idle() {
Debug.Log ("- idle");
_state = FSM.State.task;
}
#region *** TASKS ***
public override void InTask() {
switch(_task) {
case Task.enterTask:
EnterTask ();
break;
case Task.executeTask:
ExecuteTask ();
break;
case Task.exitTask:
ExitTask ();
break;
}
}
public override void EnterTask() {
Debug.Log ("- task > enter");
_task = Task.executeTask;
}
public override void ExecuteTask() {
Debug.Log ("- task > execute");
_task = Task.exitTask;
}
public override void ExitTask() {
Debug.Log ("- task > end");
_task = Task.enterTask;
_state = State.search;
}
#endregion
#region *** SEARCH ***
public override void InSearch() {
switch(_search) {
case Searching.enterSearch:
EnterSearch();
break;
case Searching.executeSearch:
ExecuteSearch ();
break;
case Searching.exitSearch:
ExitSearch();
break;
}
}
public override void EnterSearch() {
Debug.Log ("- search > enter");
_search = Searching.executeSearch;
}
public override void ExecuteSearch() {
Debug.Log ("- search > execute");
_search = Searching.exitSearch;
}
public override void ExitSearch() {
Debug.Log ("- search > exit");
_search = Searching.enterSearch;
_state = State.move;
}
#endregion
#region *** MOVE ***
public override void InMove() {
switch(_move) {
case Moving.enterMove:
EnterMove();
break;
case Moving.executeMove:
ExecuteMove();
break;
case Moving.exitMove:
ExitMove();
break;
}
}
public override void EnterMove() {
Debug.Log ("- move > enter");
_move = Moving.executeMove;
}
public override void ExecuteMove() {
Debug.Log ("- move > execute");
_move = Moving.exitMove;
}
public override void ExitMove() {
Debug.Log ("- move > exit");
_move = Moving.enterMove;
_state = State.work;
}
#endregion
#region *** MOVE ***
public override void InWork() {
switch(_work) {
case Working.enterWork:
EnterWork();
break;
case Working.executeWork:
ExecuteWork();
break;
case Working.exitWork:
ExitWork();
break;
}
}
public override void EnterWork() {
Debug.Log ("- work > enter");
_work = Working.executeWork;
}
public override void ExecuteWork() {
Debug.Log ("- work > execute");
_work = Working.exitWork;
}
public override void ExitWork() {
Debug.Log ("- work > exit");
_work = Working.enterWork;
_state = State.idle;
}
#endregion
}
I needed MonoBehavior in FSM so I could use StartCoroutine() & I needed to access _state, _task, _search, _move & _work in FSMAction to find the current values.
Your FSM shouldn’t have any references to the types of things that the states do. I also don’t understand why you’re using an enum for states instead of doing each one as a separate class implementation. What you have isn’t drastically removed from a big switch statement against an enum in Update which is sloppy and definitely not an FSM.
And again - your inheritance structure makes no sense.
Thanks for your comments, even though they seem a little harsh
I was probably not clear in my original post, I’m very new to C#, Unity and have a very basic understanding of how classes & inheritance work. I know this code is very sloppy and I will most likely come across major problems later that’s why I asked for advice, I tend to learn a ton faster by getting stuck in and experimenting and at the moment I can follow this code even though the way both files/classes work together are very cloudy.
This was the only tutorial I could find about an state machine that (at my level) made sense to me
Maybe I should go back to reading more about C# and inheritance but, I’m a bit of a chancer I just can’t help myself, I need to experiment
I’m honestly not terribly enamored with BergZerg. While I appreciate the breadth of their work, the quality is not the greatest sometimes and there are things in this particular example that I flatly disagree with.
I would highly recommend grabbing a copy of Programming Game AI by Example by Mat Buckland. The code snippets are in C++ but the real value is in the concepts.
At a high level - an FSM should simply facilitate transitions between states. Using Buckland as a template, here’s a starting point that is similar to where we started.
public class Entity : MonoBehaviour
{
StateMachine stateMachine = new StateMachine();
void Start()
{
stateMachine.ChangeState(new IdleState(this));
}
void Update()
{
stateMachine.Execute();
}
}
public class StateMachine
{
State currentState;
public void ChangeState(State newState;)
{
if (currentState != null)
currentState.Exit();
currentState = newState;
curentState.Enter();
}
public void Execute()
{
if (currentState != null)
currentState.Execute();
}
}
public abstract class State
{
protected Entity owner;
public State(Entity owner)
{
this.owner = owner;
}
public virtual void Enter() { }
public virtual void Execute() { }
public virtual void Exit() { }
}
public class IdleState : State
{
public IdleState(Entity owner) : base(owner) { }
public override void Enter() { Debug.Log("Entering idle state"); }
public override void Execute() { Debug.Log("Idle"); }
public override void Exit() { Debug.Log("leaving idle state" ); }
}
Anything that inherits from State can be used to control an AI entity so you’re not locked into an arbitrary set of enums. And, the only MonoBehaviour used is the one representing the agent itself. Lastly, if there is a bug in how the AI is behaving then you know it has to be in whatever State class he’s executing so debugging and tweaking behavior becomes much easier.
Awesome thanks, although some of it looks new to me it’s pretty simple enough for me to study it.
I’m guessing all I would have to play with is the Entity.cs and add more states i.e. TaskState.cs, SearchState.cs?
Would I then create a switch for changing the states within Entity.cs like in my code?
Also how would I implement a pause in the Enter(), Execute(), Exit() functions?
You should be avoiding switches. If you end up with a big switch statement your first reaction should be “I think I did something wrong here”
Generally, an entity will change their state based on something that happens in the world or player input if they are player controlled. The first step is defining what those things are which should inform how you implement them.
Here is an advice from me, it may be not the best advice.
public enum State {
idle,
move,
punch
}
This is all you need. And this is your game code:
if (state==idle)
if (Vector3.Distance(enemy.transform.position, transform.position)<10) {
state=move;
destination=enemy.transform.position;
return;
}
if (state==move){
float milesPerFrame=speed/Vector3.Distance(destination, transform.position);
transform.position=Vector3.Lerp(transform.position, destination, milesPerFrame*Time.deltaTime);
if (Vector3.Distance(enemy.transform.position, transform.position)<2)
state=punch;
else
state=idle;
}
if (state==punch){
enemy.GetComponent<EnemyScript>().health-=10;
state=idle;
}
Your game is done. This is all the code StarCraft One had, this is all of that game’s code. And this is a lie. The point is, if you start making a game from state machine soon you will say “screw it I will go drink with my buddies”, and you should. The best way to make a first game is to make lazy cr#p that no one will want to play. And after this you will have nowhere lower to fall, so the only way is to improve. This way you will get feel for that personal improvement direction vector. Or just take your first game, call it slappy fish put on the Google Play Store and make money.
that’s kind of what I’m having problems understanding, I just can’t figure out how to do the same thing as I had in my original code in this code. How would I go about finding out which state it’s currently in from Entitiy.cs?
Starting Coroutine and Waiting for seconds is not something you should do for your AI. Coroutines are actually delicate things. Do your AI in FixedUpdate() function and create your own timer if you want to pause things:
I have a sprite which will search for areas around a circle for work then move to them when found. I have some text above them to display what they are currently acting upon and without a pause it’s far too quick and cannot be read. I need it to look like it takes time to figure things out.
I would do this in the state’s Execute Method by storing the time the state was started
public void Enter()
{
timeStarted = Time.time;
}
public void Execute()
{
if (Time.time - timeStarted < 1)
return;
}
What you’re suggesting becomes impossible to read or maintain when your AI becomes more complex - and it will become more complex. It’s also not easily extensible because you’re limited to the enums you’ve set out at the beginning. And it won’t perform very well if you have to run through a huge if-else or switch block every frame. Lastly - there is no chance for re-usability for states that are similar and could therefore share common code. For example - an AttackState can be based on a more basic MoveState and re-use MoveState code to find a path and move towards a target.
It’s easy to say “this is all you need” when you use incredibly simple use cases to illustrate your point.
thanks again but, I can’t get it working correctly and have no idea how to hand it back to Entity.cs once the state has finished. I just don’t know where to begin learning how this works, feeling a little demotivated should probably go back to basics and go over some C# tutorials before starting again.
Aw, I thought you were talking about my second post where I said not to use Coroutine, Waiting for seconds. First one doesn’t even try to present itself seriously enough to be takken as it is and would do no harm. Second one is a substantial one.
See, KelsoMRK, I can recognise beginner when I see one. And all this delegates shmelegates you guys are talking about is a complicated stuff. I consider myself smart and it intimidates me. The only wat to encourage new guys to code is by saying “This is too complicated, leave it”. And now he is feeling failure because you had faith in him. I am glad I had friends who took me for nothing. This is what every man needs: someone who would never support or rely on him so he could prove them wrong and rise above the pack.
I’ve been using state machines based on the simple and elegant solution from that book (and similar to the code you posted) since the XNA days. Having each state as its own class is a great way to encapsulate functionality. I use that style of FSM quite a lot in my current game for AI, menus/screens, and input states and it’s served me well.
I’m not sure what value there is in posting a “solution” in jest. Were you trying to make a humorous post? Or are you covering because you presented a bad idea and I called you out on it?
SideKick, pay good attention to KelsoMRK. His advice is right on. If things aren’t working, keep at it and it’ll all make sense soon.
One thing to think about as you are doing is that States can be used to represent Actions or Goals, and people tend to use them interchangeably, which can be confusing. Some games use States to represent Actions (i.e. commands that you execute over many frames, similar to co-routines). These would be things like MoveTo, PlayAnim, etc. Other games use States to represent Goals (i.e. the current objective of the AI). These would be things like KillPlayer, RunAway, DoNothing etc. If you can keep your states focused on one or the other, it’ll help make everything else clearer.