- Home /
How to implement the "State" pattern in Unity using an abstract class?
I want to implement a pattern "State". For this purpose I created an abstract class "GameState" and empty Gameobject in the Hierarchy view with same name. But it became clear that it is impossible to attach a script with an abstract class. Whether it is possible to implement a pattern "State" in Unity, using an abstract class?
GameState.cs:
public abstract class GameState : MonoBehaviour
{
[SerializeField]
protected static GameObject gameManagerObject;
protected GameManager gameManager = gameManagerObject.GetComponent<GameManager>();
internal virtual void HandleButton(GameManager gameManager, PressedButton button)
{
ChangeState(gameManager, button);
}
protected abstract void ChangeState(GameManager gameManager, PressedButton button);
}
internal class StartState : GameState
{
internal StartState()
{
Debug.Log("Launch game");
}
protected override void ChangeState(GameManager gameManager, PressedButton button)
{
gameManager.State = new WorkState();
}
}
internal class WorkState : GameState
{
internal WorkState()
{
gameManager.DoSomething(false);
}
protected override void ChangeState(GameManager gameManager, PressedButton button)
{
gameManager.State = new PauseState();
}
}
internal class PauseState : GameState
{
internal PauseState()
{
Debug.Log("Pause");
gameManager.DoSomething(true);
}
protected override void ChangeState(GameManager gameManager, PressedButton button)
{
gameManager.State = new WorkState();
}
}
GameState.cs in inspector:
GameManager.cs:
public class GameManager : MonoBehaviour
{
internal GameState State { get; set; }
...
void Start()
{
...
State = new StartState();
}
public void FindOut(PressedButton button)
{
State.HandleButton(this, button);
}
...
}
At start the error appears:
It is because I use keyword "new".
How it is possible to solve this problem?
Your code will still work even when not attached to gameobjects. Continue like you were thinking! And as for getting it to 'run', you would only need to attach your state machine/manager script to an empty gameobject (named State$$anonymous$$anager or the like). So that you can run your states through the Update loop in that. The rest of your states do not need to monobehaviors nor be attached to work.
Thank you for help, Ymrasu.
I created the class "Game$$anonymous$$anager" and empty GameObject and attached to the class "GameState".
I specified the link to a component in the class "GameState".
public abstract class GameState : $$anonymous$$onoBehaviour
{
[SerializeField]
protected static GameObject game$$anonymous$$anagerObject;
protected Game$$anonymous$$anager game$$anonymous$$anager = game$$anonymous$$anagerObject.GetComponent<Game$$anonymous$$anager>();
...
}
At start the error appears:
It is because I use keyword "new".
public class Game$$anonymous$$anager : $$anonymous$$onoBehaviour
{
internal GameState State { get; set; }
...
void Start()
{
State = new StartState();
...
}
...
}
How it is possible to solve this problem?
Answer by Bunny83 · Mar 11, 2019 at 07:25 PM
edit
Ok since the question got edited and now includes concrete code i'll rewrite my answer:
There are some issues in your code. First of all your abstract GameState class must not be derived from MonoBehaviour. MonoBehaviours are components and can not be created with "new" but only with AddComponent. Since the state objects should be exchanged quite often and they don't need / use anything component related, just remove the : MonoBehaviour
from your GameState class.
The next 3 lines have countless issues / conceptional flaws:
[SerializeField]
protected static GameObject gameManagerObject;
protected GameManager gameManager = gameManagerObject.GetComponent<GameManager>();
First you attach the SerializeField attribute to a static field, this is pointless since static fields are never serialized since they do not belong to an instance of the class.
Next issue is that you essentially implement a singleton like pattern, but in a foreign class. This is really confusing. Even when the creation of the state class instances will be carried out on the main thread, it's generally bad practise to use GetComponent in a field initializer. For any MonoBehaviour derived classes this won't work since the actual constructor (and the field initializers) are executed on the internal loading thread. GetComponent can only be used on the main thread.
Finally it's pretty pointless to have a static singleton field which holds a gameobject reference, and than have every state using GetComponent and store that reference in an instance member variable. It would make much more sense to have a static GameManager variable inside the GameManager class which the states can access directly. Simply an ordinary singleton.
Next you shouldn't do actual "work" inside the constructor of a state. This has several issues. Since the constructor runs before the instance is returned, if the state decides to switch state again inside the constructor it won't work since the assignment of the state takes place when the constructor is finished. Any work should be done through the common interface that your State represents.
The interface of your GameState class seems weird. Of course we don't know what you have in mind and what exactly this framework should be used for. Though having an abstract method "ChangeState" inside a State is just strange. The ChangeState method should be part of the "moderator" / GameManager / StateContext since it's the one who actually holds the current state. Having classes messing around with fields of other classes is generally bad practise (See OOP: encapsulation). It makes it quite hard to find errors if any class can change the state variable from everywhere. A clear ChangeState method in the GameManager is one central place to change the current state.
As i said we don't really know what's the purpose of this state machine. Though the actual interface of your GameState class may need some more general callbacks. Common callbacks are like EnterState (maybe pass the old / current state as parameter), ExitState (maybe pass the new state as parameter) and some general purpose Tick / Work method where the actual work is handled. All of these callbacks would be called from the GameManager. The Enter / Exit callbacks are usually called from the ChangeState method to inform all involved states (the new one and the old one) that the state has changed.
One point of the state pattern is that the current state will decide if the state should be changed and which one should be the next state. In your current setup the ChangeState method always changes to one predefined state which would result in an almost linear code flow.
One final note: the internal access modifier is only relevant when you compile your code to a seperate assembly. When you just have your code inside a Unity project all your classes are compiled to the same assembly so for the scripts inside your project it acts like public. If you actually plan to compile it to a seperate assembly it would be a completely encapsulated system as it would be impossible to create new / more states outside that assembly. So concrete all your internal classes aren't visible to any code ouside your assembly. So you can not switch to those states from states defined outside your assembly. Since your "State" property is also internal, the state can not be changed from new states outside the assembly. Just think about your actual usecase and if and where it makes sense to use internal.
second edit
I just realised that you used the default references of the GameState class. This won't work at all. The default references are an editor only feature and only work for components (i.e. classes derived from MonoBehaviour). Since you create your class instances at runtime (even when you use AddComponent) those references won't be set automagically. The default references are just meant for seperate frameworks / packages. So when you attach a script at edit time it gets this reference set by default. Since your states shouldn't be components in the first place (and they aren't created at edit time) the default references are competely irrelevant to you.
"The State pattern generally consists of several classes. In your question you only mentioned one. So it's not clear where you're actually suck at the moment. Trying to attach / instantiate an abstract class is obviously just plain wrong. Since that's all we know about your problem we can't really help you to solve it."
You are right, Bunny83. I added the code of classes to a question.
Thank you so much for your help, Bunny83.
So nice of you to take the time to answer my question in such detail.
Your answer
![](https://koobas.hobune.stream/wayback/20220612193403im_/https://answers.unity.com/themes/thub/images/avi.jpg)