2

I am making a finite state machine for my game in unity:

using UnityEngine;
using System;
public class StateMachine : MonoBehaviour {

    private State currentState;

    public void ChangeState<T>() where T : State {
        if (currentState != null) {
            currentState.Exit ();
        }
        currentState = (T)Activator.CreateInstance (typeof(T), this.gameObject);
        currentState.Enter();
    }

    void Update() {
        if (currentState != null) {
            currentState.Execute ();
        }
    }

    void OnCollisionEnter2D (Collision2D col)
    {
        currentState.ResolveCollision (col);
    }
}

Abstract class for the state:

using UnityEngine;
public abstract class State  {
    protected GameObject parent;

    public State(GameObject parent) {
        this.parent = parent;
    }

    abstract public void Enter();
    abstract public void Execute();
    abstract public void Exit();
    abstract public void ResolveCollision(Collision2D col);
}

Short example of a running state:

using UnityEngine;
public class Running : State {

    public Running(GameObject parent) : base(parent) {}

    public override void Enter ()
    {
        return;
    }

    public override void Execute ()
    {
        if (Input.GetMouseButtonDown (0)) {
            parent.GetComponent<StateMachine> ().ChangeState<Jumping> ();
        }
    }

    public override void Exit ()
    {
        return;
    }

    public override void ResolveCollision (Collision2D col)
    {
        switch (col.gameObject.tag) {
        case "Obstacle":
            break;
        case "Pickupable":
            break;
        case "Enemy":
            break;
        }
        return;
    }
}

Jumping:

using UnityEngine;
public class Jumping : State {


    public override void Enter ()
    {
        return;
    }

    public override void Execute ()
    {
        return;
    }

    public override void Exit ()
    {
        return;
    }

    public override void ResolveCollision (Collision2D col)
    {
        switch (col.gameObject.tag) {
        case "Obstacle":
            break;
        case "Pickupable":
            break;
        case "Enemy":
            break;
        }
    }
}

The thought is that you attach the StateMachine to any gameobject and it would be easy to change state of that object.

Each state would need the parent object to operate on to for example change its position.

Each state would also need to be able to change the current state. Passing the parent gameobject (the gameobject the state machine is attached to) would allow the state to both access the statemachine and operate on the gameobject.

When changing a state i always know the gameobject to operate on (this is obviously the gameobject that the statemachine is attached to) so a requirement is that i do not manually pass in the gameobject each time i change the state.

To achieve this i thought of using (T)Activator.CreateInstance (typeof(T), this.gameObject);

When calling ChangeState<Running> (); in start method i get this:

MissingMethodException: Method not found: 'Default constructor not found...ctor() of Running'.

if i remove the argument this.gameobject (and remove the constructor in state and running) the game runs without errors. What is the problem and how can i solve it? (Still using the activator method) ... Any help is appretiated!

EDIT:

Just letting the user create an object of the class and assigning the gameobject later suits my needs but it is according to me not that elegant of a solution:

public void ChangeState(State state) {
    state.parent = this.gameObject;
    if (currentState != null) {
        currentState.Exit ();
    }
    //currentState = (T)Activator.CreateInstance (typeof(T), this.gameObject);
    currentState.Enter ();
}
8
  • 1
    I am not 100% certain those rules work in Unity, but in general: This code propably needs a Parameterless Constructor. C# only provides a parameterless default constructor if no other Constructor is given. If you add any custom Cosntructor, you also have to manually add a Parameterelss one (if you need one). Commented Apr 17, 2018 at 23:28
  • @Christopher Thanks for the input, i tried adding a parameterless constructor to both the state and running class but i get the same error. Commented Apr 17, 2018 at 23:36
  • Can you post your Jumping class? Commented Apr 17, 2018 at 23:43
  • @Programmer I never get to the point where i change the state to jumping. It is when i change to running that i get the error. Jumping only returns in each overriden method. Commented Apr 17, 2018 at 23:48
  • 1
    Why bother using reflection?, it is like hacking your own code and you still have to know how to create new state passing GameObject as parameter... later on, this could break easily... recommend refactor this and get of the reflection code. Commented Apr 18, 2018 at 1:21

1 Answer 1

2

Instead of using Activator.CreateInstance, you could also use reflection to instantiate your states. This has 2 benefits:

  • You can implement some logic to find the correct constructor based on parameter types (and you'll get more descriptive errors if you mess something up)
  • It performs a little better

Here's how you'd do that:

var ctor = typeof(T).GetConstructor(new []{typeof(GameObject)})
currentState = ctor.Invoke(new object[]{this.gameObject}) as T;

Or if you have a known & limited set of states, you could simply create a factory class that encapsulates the instantiation of different states. You can redirect to the correct private method based on the generic type argument, and within those private methods simply call the constructor directly. That performs even better.

Though, if you're going for clean, using a Dependency Injection framework (Autofac is one of my favorites) is usually the cleaner solution.

Sign up to request clarification or add additional context in comments.

2 Comments

Nice, your solution worked! I am not really out for performance but rather extendability. Nevertheless i appreciate you providing other alternatives as well, never heard of autofac :)
I experienced the same problem because IL2CPP is stripping that constructor out. I was hopeful when I saw your answer but it didn't work for me. Since the constructor is stripped by IL2CPP, GetConstructor returns null. :(

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.