- Home /
Need help with a State Machines where each state is its own Script...
Hey guys,
New Unity Dev here, trying to make my code at work cleaner and well more modular and faster by having the ability to have many people work on the project at once instead of 1 person.
A little background info.
I am doing Tutorials that are broke up into Lessons, In each lesson there are different 'States' In these states is where we do scaleform things and some simple unity Camera work and AI stuff.
Right now we have a Psuedo state machine in C# where its a HUGE switch statement, Instead each Case of the statement we have the normal stuff for a state machine ie:
if(!Init)
{
//Init Stuff
}
else if(Exiting)
{
//Turn off and clean stuff up
}
else
{
//Your Update loop, make stuff happen here.
}
Problem with this is, if the Lesson has 10000 different 'States' thats one MASSIVE switch statement and only 1 person can be working on that Lesson at a time. If I have a State machine where every 'State' is its own script, then i can split the work load between the employees and increase productivity and speed at which we develop these lessons.
I know I need 2 main scripts, the StateMachine it self, and then the BaseState that all other states are derived from.
My question is how would I go about this, when I switch states, I need the state machine to be able to know which states he has for that lesson IE: Lesson1_State1 to Lesson1_State20, and I also need the state machine to be able to call each of the seperate scripts functions. And in the StateMachine I need it to be able to just do like CurrentState.OnEnter() and CurrentState.Update(), etc etc...
Is there any issues with this, or How can I connect the Enum or the 'CurrentState' with a seperate script file?
I don't quite get it. If you want to have multiple workers on the same file, versioning systems like svn or mercurial should do the trick.
If you want each state to have its own script, you can make vital variables of the main class public (I don't recall c# having friend classes) and make a new script for each state. Then, on each state, you just call the specific script's "doStuff()" method. $$anonymous$$ight use inheritance to make life easier. One point to note: will each script be a $$anonymous$$onoBehaviour or a normal class? It's a tricky question =)
The problem with source control and things of that nature, is still double check outs cause issues. $$anonymous$$erging code from the same file together doesn't always go as planned.
I was thinking of the Friend class but C# like you mentioned does not have this.
One of my co workers suggesting using a Dictionary, and I think this might be the answer, since every state is derived from base state, I can use the string from scale form as the $$anonymous$$EY and then the Actual class / script be in the Dictionary.
so it would be something like
Changestate(string desiredState)
{
Dictionary[currstate].OnExit();
currState = desiredState;
Dictionary[currState].OnEnter();
}
this is all psuedo code as I am just trying to formulate how to do this before spending any time on actually implementing it.
I've written a pretty good answer. Just waiting for it to be approved by a moderator :)
@$$anonymous$$isaacs
The code you showed somewhat remembers me about the objects for states pattern (http://en.wikipedia.org/wiki/State_pattern). It can reduce the size of the swicth statement, but I don't know if it is worth the extra complexity.
Answer by Gruffy · Mar 08, 2014 at 10:23 PM
Okay, so this is how you would build the bare bones state machine... This wont fix your problem at hand, that s for the bin. Instead , take this and build your old needs into the way this is pipelined.
Firstly, you will need a good folder structure as we going to utilize namespaces (our own)
So, folder structure like this please.... Inside your Assets windows in Unity 3D make a folder and name it..."Code", inside the "Code" folder make a directory of other folders named "Interfaces" "Scripts" "States". at this point you are good to go. in your "Interfaces" folder create a new C# script, name it "IStateBase" (I for Interface). in that script place this code....
using System;
namespace Assets.Code.Interfaces
{
public interface IStateBase
{
//Interface implementable methods
//base declarations should be placed here to derive out to classes inheriting form IStateBase
void StateUpdate();
}
}
Save that. Now in your "Scripts" folder create a new C# script and name it "StateManager" or something like that, keeping it obvious is best. in there paste this code below...
using UnityEngine;
// inherit state classes
using Assets.Code.States;
//inherit interface class
using Assets.Code.Interfaces;
public class StateManager : MonoBehaviour
{
//declare a private state class to cache and work on inside script , taken from the namespace declared at the top
private IStateBase iActiveState; //activeState is reference to BeginState class
private static StateManager instanceRef;
// Use this for initialization
void Start ()
{
//instantiate activeState object for use
iActiveState = new BeginState(this);
//Debug.Log ("This is an object of type: " + activeState);
}
// Update is called once per frame
void Update ()
{
if(iActiveState != null)
{
iActiveState.StateUpdate();
}
}
//publically available method to execute that will return a new state when executed
public void SwitchState(IStateBase newState)
{
iActiveState = newState;
}
}
Save that!
Now, in your States folder, make some derived state classes, starting with this one. I named it BeginState, you can name it what you like but keeping these obvious works best really.
using UnityEngine;
using Assets.Code.Interfaces;
using Assets.Code;
namespace Assets.Code.States
{
public class BeginState : IStateBase
{
//declare a statemanger class
private StateManager stateManager;
private float futureTime = 0.0f;
private int countdown = 0;
private float screenDuration = 8.0f;
//private static GUIHelper guiHelper;
public BeginState(StateManager stateManagerRef) //constructor
{
//assign the argument delivered through stateManagerRef to the private instance of our StateManager class
stateManager = stateManagerRef;
//check we are in the start scene
if(Application.loadedLevelName != "Scene0")
{
Application.LoadLevel ("Scene0");
}
//assign futureTime the value derived from adding screenDuration to Unity`s Time.realtime variable
futureTime = screenDuration + Time.realtimeSinceStartup;
Debug.Log ("constructing BeginState");
}
//implementable methods declared in IStateBase and must be declared in any class using the IStateBase class
//this simply means anything in IStateBase must be carried over to the class implementing it.
//this is why it must be a base class that has derived IStates so we can deal with specific things set up form there
public void StateUpdate()
{
//cache the value of this exact moment in time
float asOfNow = Time.realtimeSinceStartup;
//countdown
countdown = (int)futureTime - (int)asOfNow;
//timing above here
//input conditional
if(Input.GetKeyUp (KeyCode.Space) || countdown <= 0)
{
SwitchOver();
}
//just test if countdown is running in console - non-important code
if(Input.GetKeyUp(KeyCode.F))
{
Debug.Log (countdown.ToString());
}
}
/// <summary>
/// this on GUI method could be implemented through the interface IStateBase, as long as an OnGUI methood in the statemanager class called the replacement method that covers GUI in the Interface class
/// </summary>
void OnGUI()
{
//GUI.BeginGroup(new Rect(guiHelper.FullSizeRect));
if(GUI.Button(new Rect(10, 10, 500, 100), " Assign a Game Scene to this button"))
{
SwitchOver ();
}
//show a countdown timer display
GUI.Box (new Rect(Screen.width / 2, 10, 150, 100), countdown.ToString());
//GUI.EndGroup();
}
/// <summary>
/// SwitchOver method made to change state level or scene. (could be another way to implement this? (IStateBase derived maybe or maybe not?))
/// </summary>
void SwitchOver()
{
//Application.LoadLevel ("Scene1");
//Debug.Log (Application.isLoadingLevel + Application.loadedLevelName);
stateManager.SwitchState (new PlayState(stateManager));
}
}
}
Save that!
In your "States" folder make another C# state class, i`m calling this PlayState as the class names are becoming important for state transactions in the code (as can be seen in the above SwitchOver method code)...anyway.
paste the below in to your PlayState class...
using UnityEngine;
using Assets.Code.Interfaces;
namespace Assets.Code.States
{
public class PlayState : IStateBase
{
//declare a statemanger class
private StateManager stateManager;
public PlayState(StateManager stateManagerRef) //constructor
{
//assign the argument delivered through stateManagerRef to the private instance of our StateManager class
stateManager = stateManagerRef;
Debug.Log ("constructing PlayState");
}
//implementable methods declared in IStateBase and must be declared in any class using the IStateBase class
//this simply means anything in IStateBase must be carried over to the class implementing it.
//this is why it must be a base class that has derived IStates that specifically deal with specific things
public void StateUpdate()
{
//if spacebar hit, switch state (same for all IStateBase state classes)
if(Input.GetKeyUp (KeyCode.Space))
{
stateManager.SwitchState (new WonState(stateManager));
}
}
}
}
Save That !
Here is another state, but by now, and with your own knowledge of staet machines , you shold be able to see that all im doing is what you did in your state machine, except I have taken the leg work out a single operation and given a set of classes some commonly implementable methods to handle the data (well one method in this demonstrative case to show that you can do this, I cant write the whole thing for you im afraid, I dont know what you are trying to make lol) Anyway, the other demo state class...
using UnityEngine;
using Assets.Code.Interfaces;
namespace Assets.Code.States
{
public class WonState : IStateBase
{
//declare a statemanger class
private StateManager stateManager;
public WonState(StateManager stateManagerRef) //constructor
{
//assign the argument delivered through stateManagerRef to the private instance of our StateManager class
stateManager = stateManagerRef;
Debug.Log ("constructing WonState");
}
//implementable methods declared in IStateBase and must be declared in any class using the IStateBase class
//this simply means anything in IStateBase must be carried over to the class implementing it.
//this is why it must be a base class that has derived IStates that specifically deal with specific things
public void StateUpdate()
{
if(Input.GetKeyUp (KeyCode.Space))
{
stateManager.SwitchState (new BeginState(stateManager));
}
}
void OnGUI()
{
if(GUI.Button(new Rect(Screen.width / 2, Screen.height / 2, 150, 100), "Press Me"))
{
//Debug.Log (Application.isLoadingLevel)
//Debug.Log (Application.loadedLevelName.ToString());
stateManager.SwitchState (new BeginState(stateManager));
}
}
}
}
Anyhoo, the bottomline to all this, is... If you do Not know how to implement a state machine like this, you will undoubtedly come up against issue that reflect its initial planning. These are not super complicated, but do require a slightly different approach to using Unity (You might notice that for the most we are actually running classes outside of MonoBehaviour, where StateManager is handling the pipeline to MonoBehaviour after the classes have executed)
So, I hope this has helped you in some way. Your other options are delegates and the way you have tried already but, if you can get your head round what i`ve laid out here (and im happy to keep conversing about it), you cant go far wrong and should easily see how you can transfer your current state system to this optimized and scalable solution.
take care bud and thanks for reading this long winded Answer.
Gruffy :)
P.S. to se this work you would need to make two scenes and names them "Scene0" and "Scene1" repspectively. You would also have an empty gameobject in "Scene0" with the "StateManager" script attached to it. Run in Editor when "Scene0" is the active scene in the Unity Editor.
I doubt the original poster reads this anymore. good answer none the less.
As for the usage of interfaces, I would rather inherit from an abstract base state and add interfaces to the inheriting classes. This makes it easier to share implementation between all the states.
Thanks Jamora, typically, I didnt look at the date of this guys post. Hopefully it might help others eh.
As to your method of implementation, yeah, I would agree wholeheartedly and this should indeed scale up to that quite easily i reckon. i just kept it to the single example use of the state machine (updating) as its nigh on impossible to see what it was they really wanted (as in I didn`t want to overkill them on state handling, just rather get them to see whats going on in the code, tippytoes n all that). Anyway, thanks loads for the acknowledgment. I really love it on here, you $$anonymous$$ods(Higher Ups/whatever you are graced with name-wise) are some top guys. I only hope I can keep trying to give back for what ive learnt on here through this forum. Take care and thanks for reading Jamora.
Gruffy
Answer by Jeremy Hindle · Feb 07, 2013 at 07:29 PM
There are two things you can do here.
Is learn how to use a version control system as was mentioned in the comments above. You have a choice of SVN, Mercurial, Git and even the Unity Asset server if you have purchased it. For scripts though I prefer using Git and Github. It's really awesome and worth learning.
Make the state manager set a public "currentGameState" variable which can then be used in other scripts to know what code to run. I recommend using an ENUM as well just to make it so that your state machine stays tidy and its easy to reference states all around the place.
simple example.
public enum GameState { State1, State2, State3};
public GameState currentGameState = GameState.State1;
Do a whatever logic you want to set that current game state however you like. I, in my games, tend to also use a switch statement.
switch (TestedSituationWhichChangesState)
{
case TestedSituation1:
currentGameState = GameState.State1;
Debug.Log("Setting Game State 1");
break;
case TestedSituation2:
currentGameState = GameState.State2;
Debug.Log("Setting Game State 2");
break;
case TestedSituation3:
currentGameState = GameState.State3;
Debug.Log("Setting Game State 3");
break;
}
You can obviously use whatever logic you want above. The important thing is that you set the public variable of currentGameState to whatever state you want it to be.
You then just test for the condition when you run the code you want to run in another script. Make sure you reference the script with the currentGameState variable in it and test it however you like. An "if" statement is the simplest example but you can obviously do it with anything.
private StateMachineScriptName stateMachine;
void OnAwake()
{
stateMachine = GameManager.FindGameObject("NameOfGameObjectWhichHasTheStateMachineScriptOnIt").GetComponent<StateMachineScriptName>();
}
if(stateMachine.currentGameState = StateMachineScriptName.GameState.State1)
{
//Do State1 Game Code
}
Does that help?
i believe what you are looking for is an interface based state machine (perfectly feasible in Unity 3D with C#) using one of these allows you to specify succinct tasks/methods that any deriving class would have to implement (these would be the common tasks you are seeing now at this stage of your state machine) your inherited pathway would be along the lines of A state$$anonymous$$anager class -> handling classes representing each state, which in turn are derived from an interface class that has the base methods required to operate any state transactions. I will be back in a mo, as these things take a little while to code out..
Your answer
Follow this Question
Related Questions
Multiple Cars not working 1 Answer
Distribute terrain in zones 3 Answers
All scripts fail? 1 Answer
Access script (not derived from mono) functions in another script 2 Answers
Illuminating a 3D object's edges OnMouseOver (script in c#)? 1 Answer