0
\$\begingroup\$

I have a bird entity that needs different update rules when it's moving or dying (among potentially other states).

I want to avoid making my Update method a clutter of if-else branches like this:

void Update() {
   if(state == movingState) {
       // Do moving logic.
   else if(state == dyingState) {
       // Do dying logic.
   else if...

Instead, I thought I could make a base class for all my bird behaviours, then derive classes for each state with their own update functions, and use the appropriate derived version based on the current state.

public class BirdBaseModel : MonoBehaviour
{
    void Update()
    {
        // ...
    }
}

public class BirdMoving : BirdBaseModel
{
    void Update()
    {
        // ...
    }
}

public class BirdDying : BirdBaseModel
{
    void Update()
    {
        // ...
    }
}    

My BirdBaseModel is attached to the bird game object. Based on game states, I want to dynamically reference it in the GameController class.

BirdBaseModel b;

if(State == 1)
{
    b = new BirdMoving();
}
else if(State == 2)
{
    b = new BirdDying();
}

Based on the above logic, I want to call the Update function of that particular class. How do I invoke the Update of the subclass?

\$\endgroup\$
  • \$\begingroup\$ Did you try this model and it didn't work or are you asking about "best practices"? You seem to be asking "is it possible?" to which the answer is almost always a vague mess. I think the most reasonable answer would be "Try it and see if it works then come back with a more specific question if you have problems." Asking about whether or not approaches are possible or what the "best approach" to something is will almost always devolve into answers that aren't much more than personal preferences. \$\endgroup\$ – Honeybunch May 11 '17 at 22:46
  • \$\begingroup\$ @Honeybunch I don't want to implement the trivial approach where I place nest if-else statements in Update function to check if it has reached that state, so I thought of separating it into classes, but not sure how to invoke the functions of sub class \$\endgroup\$ – MrRobot9 May 11 '17 at 22:51
  • \$\begingroup\$ @Gnemlock but base.Update calls super class, right? \$\endgroup\$ – MrRobot9 May 11 '17 at 23:46
  • \$\begingroup\$ @Gnemlock I think the problem of setting up a multi-state behaviour in Unity is a legitimate on-topic question here (and something I run into in my own work fairly often). In this case, I think it's just obscured in an xy problem fashion, by focusing on an attempted solution syntax rather than the feature or need it's serving. \$\endgroup\$ – DMGregory May 12 '17 at 1:00
  • \$\begingroup\$ @DMGregory One problem, when I change from Play state to dead state and back to play state, everything from that play state gets called twice for the coroutine solution. \$\endgroup\$ – MrRobot9 May 19 '17 at 0:27
4
\$\begingroup\$

If you just want different behaviours for different states, but without complicated switching logic in your Update loop, I'd recommend using Coroutines. That way you keep the related behaviour in one class/instance, rather than juggling a collection of them every time you want a different state.

A coroutine with a yield return null loop executes once each frame very similar to the Update message, and by switching between multiple coroutines you get your state change behaviour.

public class MultiStateBehaviour : MonoBehaviour {

    // Set up externally-visible state labels we can edit in the Inspector.
    [System.Serializable]
    public enum State { A, B }

    Coroutine _activeState;

    public void ChangeState(State destinationState) {       

       // Optionally, you can check if you're already in destinationState
       // and early-out rather than restarting the state.

       IEnumerator state;
       switch(destinationState) {
           case A : state = StateA(); break;
           case B : state = StateB(); break;
       }

       // Abort the currently-running state.
       if(_activeState != null)
           StopCoroutine(_activeState);

       _activeState = StartCoroutine(state);
   }

   IEnumerator StateA() {
       // Do State A setup stuff here.

       while(true) {
           // Do State A per-frame stuff here.

           // Resume again next frame.
           yield return null;            
       }
   }

   IEnumerator StateB() {
       // Do State B setup stuff here.

       while(true) {
           // Do State B per-frame stuff here.

           // Resume again next frame.
           yield return null;            
       }
   }
}

This gives you a lot of power to have local data tracked in the current state, or complicated control flow within a state that changes from frame to frame.

If you only need another method to run though, you can do this even more simply with delegates:

public class MultiStateBehaviour : MonoBehaviour {

    // Define the method signature all of our update functions should have.
    delegate void UpdateBehaviour();

    // Store a delegate representing our current desired behaviour.
    UpdateBehaviour _currentUpdateBehaviour;

    // Here I just initialize to a particular state. 
    // You can adapt the same kind of state switch logic as the example above.
    void Start () {
        _currentUpdateBehaviour = StateA;
    }

    // In Update, run whatever our current update behaviour is.
    void Update () {
        _currentUpdateBehaviour();
    }

    // Define a couple of update behaviours we might want to use:
    void StateA()
    {
        // Do State A per-frame stuff here.
    }

    void StateB()
    {
        // Do State B per-frame stuff here.
    }
}

That said, if you really need subclasses for each state, you can do this using virtual methods like so:

public class BaseClass : MonoBehaviour {
    protected virtual void Update() {
        // Do base class stuff here.

        // Or, if you don't ever construct the base class at all,
        // use the "abstract" keyword instead of "virtual"
        // and mark the class itself as "abstract" too.
    }
}

public class DerivedClass : BaseClass {

    protected override void Update() {
        // Do derived class stuff here.
    }
}

Now any code calling DerivedClass.Update() will get the derived class's version, even if they know only that it's some flavour of BaseClass. That includes the Unity game loop.

You can't just force an instance of BaseClass to use the DerivedClass's method though. You'd have to remove/disable the BaseClass component on the object and replace it with AddComponent<DerivedClass>, or enable a DerivedClass instance that was already on the object. As you can see, switching states this way is a much more heavyweight operation than the previous two options, so I wouldn't really recommend it unless the states need to be radically different from each other.

\$\endgroup\$
  • \$\begingroup\$ In the first example (Coroutines), how is the ChangeState method called? \$\endgroup\$ – MrRobot9 May 12 '17 at 1:58
  • \$\begingroup\$ I guess StartCoroutines("ChangeState"); in Start method \$\endgroup\$ – MrRobot9 May 12 '17 at 2:01
  • 1
    \$\begingroup\$ You can call it from basically anywhere you want. ;) \$\endgroup\$ – DMGregory May 12 '17 at 2:49
  • \$\begingroup\$ One problem, when I change from Play state to dead state and back to play state, everything from that play state gets called twice. \$\endgroup\$ – MrRobot9 May 19 '17 at 0:19
  • \$\begingroup\$ Oops, code example above was missing an assignment to _activeState. Edited to fix. Does this address the issue you've observed? \$\endgroup\$ – DMGregory May 19 '17 at 0:28
2
\$\begingroup\$

Simply put, you simply override the base methods. The problem I see is not that you can not call the specific Update; it is that you can not call Update, at all:

class ParentClass : MonoBehaviour
{
    void Update()
    {
        // ...
    }
}

In this fashion, Update will naturally be called during the game logic, as it is a MonoBehaviour. However, it is also a private method. You can not access it from outside the class.

Let's assume we are using a non-broadcasted method; that is, a method that does not get called unless we call it.

class ParentClass : MonoBehaviour
{
    public ParentClass[] variations;

    void Start()
    {
        for(int i = 0; i < variations.Length; i++)
        {
            variations[i].Call();
        }
    }

    protected virtual void Call()
    {
        Debug.Log("I am a Parent");
    }
}

Note the two distinct differences:

  • I use a protected method. This is the same as a private method, except you can access it from other children of the parent. If you wish to access this method from outside the class (for example, from a controlling class that is not a child), you need to make the method public.
  • I use a virtual method. This tells the class that I might override it in a child class.

You will also notice that I have a public array of ParentClass types, and a Start method. This is to demonstrate the way in which I store all of my variations, and output the Call method.

When I create children, I simply override the original method. You can still use base to reference the parent and its internal methods, including the one we are overriding.

class ChildOne : ParentClass
{
    protected override void Call()
    {
        Debug.Log("I am the first Child");
    }
}

class ChildTwo : ParentClass
{
    protected override void Call()
    {
        Debug.Log("I am the second Child");
        base.Call();
    }
}

This gives you the following debug output:

I am a Parent
I am the first Child
I am the second Child
I am a Parent

For situations like Update, where you have a broadcasted method, behaviour will stay the same. As long as it is an override of a virtual method (and it will throw errors, if this is not the case), the Update method of the specific child will be called, automatically.

\$\endgroup\$
1
\$\begingroup\$

Another option might be something like this:

public class BirdBehaviour : MonoBehaviour
{
    private BirdMovingState movingState;
    private BirdDyingState dyingState;

    private BirdState currentState;


    private void Awake()
    {
        // Initialize states.
        this.movingState = new BirdMovingState();
        this.dyingState = new BirdDyingState();

        // Set the current state.
        this.currentState = this.movingState;
    }

    private void Update()
    {
        // Invoke update method of the current state.
        this.currentState.Update();
    }
}


public abstract class BirdState
{
    public virtual void Update()
    {
    }
}


public class BirdMovingState : BirdState
{
    public override void Update()
    {
        Debug.Log("Moving...");
    }
}


public class BirdDyingState : BirdState
{
    public override void Update()
    {
        Debug.Log("Dying... :'(");
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.