Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 14 Next capture
2021 2022 2023
2 captures
12 Jun 22 - 14 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
0
Question by michaellux · Mar 11, 2019 at 06:38 PM · statestate machinepatternstatesabstract

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: 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: Error
It is because I use keyword "new".

How it is possible to solve this problem?

2019-03-11-22-30-21.png (7.8 kB)
Comment
Add comment · Show 2
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Ymrasu · Mar 11, 2019 at 06:47 PM 0
Share

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.

avatar image michaellux Ymrasu · Mar 11, 2019 at 08:00 PM 0
Share

Thank you for help, Ymrasu.

I created the class "Game$$anonymous$$anager" and empty GameObject and attached to the class "GameState".

GameState class in Inspector window

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:

Error

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?

1 Reply

· Add your reply
  • Sort: 
avatar image
1
Best Answer

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.

Comment
Add comment · Show 3 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image michaellux · Mar 11, 2019 at 08:31 PM 0
Share

"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.

avatar image Bunny83 michaellux · Mar 12, 2019 at 08:35 AM 0
Share

I've edited my answer

avatar image michaellux Bunny83 · Mar 12, 2019 at 09:01 AM 0
Share

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

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

101 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Interface state structure with different type of enemies 0 Answers

Easier way of creating transitions in the animator? 1 Answer

Finite State Machine 1 Answer

how to make multiple transition from one state? 1 Answer

How do I make the transition between two states in the "Animator" happen instantly? 3 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges