The previous post got a funny comment in Reddit: “Yawn. Hang on, inheritance, singletons? Bletch”. So I thought… “I may be getting rusty… let’s give those coroutines a try”. At first, coroutines seemed like a sure way of leaving behind all my (humble) OOP practices. I though they were some kind of goto statement, probably because I had never given them a try. Ignorance is bold.
So here’s the same Miner from our previous post (yes, the one from Programming Game AI by Examples), going back and forth from the mine to the bank. But this time, each state is implemented using a coroutine. Thanks to the yield statement, coroutines can simulate our three state stages: Enter, Execute and Exit. Even better, we may easily add pauses wherever we need. If the coroutine is used to implement an entity’s AI, it can be updated every x milliseconds (depending, for example, on the entity’s reflexes), instead of every frame.
The trick lies in the FSM coroutine:
IEnumerator FSM() { // Execute the current coroutine (state) while (true) yield return StartCoroutine(state.ToString()); }
It constantly calls and waits for the execution of the current state (which is another coroutine). If the executed state changes the current state, the next iteration will launch it properly. I guess some code should be added to change states in external events, stopping any running coroutines.
So here we go! I’ve removed some variables, but it’s mostly the same behaviour:
File: CoMiner.cs using UnityEngine; using System.Collections; public class CoMiner : MonoBehaviour { public enum State { EnterMineAndDigForNuggets, EnterBankAndDepositGold } public State state; public void Awake() { state = State.EnterMineAndDigForNuggets; // Start the Finite State Machine StartCoroutine(FSM()); } IEnumerator FSM() { // Execute the current coroutine (state) while (true) yield return StartCoroutine(state.ToString()); } IEnumerator EnterMineAndDigForNuggets() { /* This part works as the Enter function of the previous post (it's optional) */ print("Entering the mine..."); yield return null; /* Now we enter in the Execute part of a state which will be usually inside a while - yield block */ bool dig = true; int digged = 0; while (dig) { print("Digging... " + (digged++) + " " + Time.time); if (digged == 2) dig = false; yield return new WaitForSeconds(1); } /* And finally do something before leaving the state (optional too) and starting a new one */ print ("Exiting the mine..."); state = State.EnterBankAndDepositGold; } IEnumerator EnterBankAndDepositGold() { //Enter print ("Entering the bank..."); yield return null; //Execute bool queing = true; float t = Time.time; while (queing) { print ("waiting..."); if (Time.time - t > 5) queing = false; yield return new WaitForSeconds(1); } //Exit print ("Leaving the bank a little bit richer..."); state = State.EnterMineAndDigForNuggets; } }
This way, each class contains it’s own FSM-like structure, though a simple one. No need for multiple files for each state, which can be cumbersome if a project grows (and they always do). And its really easy to follow and understand, once you understand how coroutines work.
It is a simpler solution, but pretty useful indeed! And of course, it can be combined with good OOP practices, not exactly as shown before 🙂
Great stuff, but any chance you can give a more working example like you did in the last post finite state machine post – I’m not sure I understand how this all comes together… I assume I can’t just drop CoMiner.cs in as is and expect it to work. Do I need to modify FSMState.cs or FinateStateMachine.cs?
Thanks Walter. Well, yes, it should work right as is. It doesn’t need FSMState.cs nor FiniteStateMachine.cs anymore, because all states are contained in CoMiner’s methods (no inheritance, no singletons… although it would look much better with a Miner interface, or something similar).
Just create an empty GameObject, attach CoMiner and run. When it awakes, it starts the FSM coroutine, which launches the appropriate state (EnterMineAndDigForNuggets). When EnterMineAndDigForNuggets is called, it simply prints “Entering the mine…” and returns (yield). The next time it’s called, in FSM’s while loop, the execution continues after the yield statement, so it enters in EnterMineAndDigForNugget’s while loop.
Try it! : )
Excellent – I personally (now that I’ve played around with both) like the singleton/abstract class as presented in the last post a bit more due to its being more reusable. I had some fun with your other class implementing the “Thirst” and “Fatigue” portion by adding “GoToHome” and “GoToBar” classes. Currently if you didn’t change your logic, the above could get messy pretty quickly with the additional states without some form of oo wrapper.
In any case, this was incredibly helpful and I appreciate you putting it out there!
I also prefer the ‘old OOP’ way, and I guess things could be more organized if CoMiner implemented some Miner Interface, forcing the class to define a function for each pre-defined state. It may not be useful for the miner, but it would be for a ‘monster’ class or something like that, more generic. Nevertheless, I don’t really understand what’s the problem with inheritance (when properly used), and I guess it would be quite useful in this case. That Reddit user never explained why he didn’t like it 🙁
It’s true, however, that coroutines allow an easy control of AI updates.
I’m adding some steering behaviors to this model (the one with coroutines), but I’m having some problems when dealing with physics. They must be handled in a LateUpdate function while the FSM runs its coroutines to deal with AI decisions. The LateUpdate function thus becomes some kind of low level ‘motor skill’ handler, but that’s a different problem : )
So, after much playing around I hit a snag using the OOP/Template version –
I was “trying” to incorporate the best of both worlds – A templated class hierarchy that was still inheriting from MonoBehavior so I could use Coroutines (simply for the delays) and hit a brick wall… If I didn’t include MonoBehavior as FiniteStateMachines.cs base class i would get this error:
Assets/Scripts/FiniteStateMachine.cs(40,17): error CS0103: The name `StartCoroutine’ does not exist in the current context
If I included it I would get this warning which in effect (if I tried to call a coroutine would crap out)
You are trying to create a MonoBehaviour using the ‘new’ keyword. This is not allowed. MonoBehaviours can only be added using AddComponent(). Alternatively, your script can inherit from ScriptableObject or no base class at all
UnityEngine.MonoBehaviour:.ctor()
FiniteStateMachine`1:.ctor()
Miner:Awake() (at Assets/Scripts/Miner/Miner.cs:72)
– That related to this line in Miner.cs
FSM = new FiniteStateMachine();
Anyway to get the best of both worlds? I can send you what I have so far, but I would really much prefer to keep the segmented class structure (with the Enter / Execute / Exit form).
—–
Added:
FSMState.cs – abstract public IEnumerator Test(T entity);
EnterMindAndDigForNuggets.cs –
public override IEnumerator Test (Miner m) {
Debug.Log(“Test”);
yield return new WaitForSeconds(1);
}
FiniteStateMachine.cs – StartCoroutine (CurrentState.Test(Owner));
Pretty basic stuff, but I can’t figure how to use AddComponent to compensate for the “FSM = new FiniteStateMachine();” or even if that will work when i did figure it out.
If you figure a way to combine them both, please post it as I will be eager to see how it would test drive.
Um… I’ll have a look at that as soon as possible, but I’m sure they can be combined!
I’m not sure if this solution is what you were looking for. I’ve changed two things. First, in Miner.cs, I removed the Update method. You won’t need it anymore unless you have to update physics. Then add FSM.StartRunning(); in the last line of the Awake method.
Open FiniteStateMachine.cs and change the imports and class definition to
using UnityEngine; : MonoBehaviour {
using System.Collections;
public class FiniteStateMachine
Add this method, which launches the coroutine when called:
Remove the Update method, and add this one instead:
And that’s all! Now the FSM updates once every second, or once every frame if you return null.
As I said, I’m not sure if that’s what you were looking for, neither if it’s a proper solution (what if ChangeState is called while UpdateState is running? I guess you should stop the coroutine, change the state and restart the coroutine) But you may find it useful!
I’m looking for a way to have more than one state at a time.
The main issue is that a code like this cause the current state to be stucked in the while loop and does not give control back to a central scheduler.
while (somethingIsTrue)
{
// do something
yield return new WaitForFixedUpdate();
}
What I would like to have is a way to execute two states during the same frame, without the state to know each other.
Yes, you are right! It’s not so straightforward as I thought. Well, you still have this solution (http://www.playmedusa.com/blog/2010/12/10/programming/a-finite-state-machine-in-c-for-unity3d/). If you find a workaround the coroutine problem, please share! I’m not working with them at the moment but it would be nice knowing how to solve that 😉
If someone stumbles across these posts – maybe this will come in useful –
In your posts above you say : “I guess some code should be added to change states in external events, stopping any running coroutines.”
This may help –
http://www.youtube.com/watch?v=lo80BOomaU4
“In this video I show one way to implement a Finite State Machine (FSM) in Unity3d. It uses coroutines and Prime[31]’s excellent coroutine manager to implement concurrency and interruptibility, two important features for FSMs.
I think the main benefit of my implementation is that long running tasks can be used in a ‘fire and forget’ manner: when they return with success we can kill all other tasks currently being performed using the coroutine manager. “
I’m in the process of testing different solutions to the state machine problem.
This one may fit the considerations raised in the comments above – also by Prime31
https://github.com/prime31/StateKit
“Lightweight and simple to use state machine implementing the “states as objects” system. The idea is to create a state machine for some target object (could be a player, NPC, overall game, menu system, etc) that allows you to separate all the states into separate classes while retaining access to the original target object.”
Enum.ToString uses reflection, i wouldn’t recommend doing it repeatedly in your main game processing loop. Do it once for each state and cache the results in a dictionary or something.
Well, that ToString is used just when the state changes, which shouldn’t happen too often!