Controller enabler / disabler - A good solution?

I’ve been struggling to figure out an elegant solution to this problem, which is how to handle switching a controller’s state between ‘no-control’ and ‘full-control’, in an RPG-styled game. My problem is, there are many factors which can switch the controller state, and I want to avoid conflicting code.

This is my current solution below, although I’m not totally satisfied with it:

Some examples of when the controller will be turned off (in my game):

  • Transitioning between scenes
  • Playing animations
  • Interacting with an NPCs

Each of these scenarios can turn the controller on / off, yet they all happen independently.

An example of how these may conflict with each other, goes:
-Player begins a room transition (controller turns off), but then a separate animation plays and finishes before the room has fully transitioned. After the animation has complete, it would make a call to the controller to return to ‘full-control’.

Really, there should only be one entity at a time, which dictates the state of the controller.

Currently, I’m using a state-machine in a ControllerManager class. I’m also using an interface called IStateCaller, which stores an IStateCaller variable in ControllerManager, which is now the ‘registered called’.

Then, IStateCaller is applied to any object that is intended to switch the state of the controller. Whenever that object has completed it’s function, it then checks ControllerManager if this object is the ‘registered-caller’, and if it is, then it switches the controller’s state.

An example for transitioning between room, we go through a door script.
A snippet:…

public class ControllerManager : Monobehaviour
{
    private ControllerManagerState  state;
    private IStateCaller caller;
  
    private FullControl_CMState     fullControl_S;
    private NoControl_CMState       noControl_S;
    private Pause_CMState           pause_S;

    public void RegisterCaller(IStateCaller registerCaller)
    {
        if (caller != null) return;

        caller = registerCaller;
    }
  
    public void State(IStateCaller registeredCaller, ControllerManagerOptions nextState)
    {
        if (FailedToMatchCaller(registeredCaller)) return;
  
        state.LoadNextState(nextState);
    }
}
public class DoorOpen : Monobehaviour, IStateCaller
{
    public void OpenDoor()
    {
            // Here's where we set the StateCaller:
            ControllerManager.Instance.RegisterCaller(this);
            ControllerManager.Instance.State(this, ControllerManagerOptions.noControl);
            ControllerManager.Instance.PlayerAnim(AnimRegistery.openDoor, doorAngle);
    }
}

If anyone knows a better solution please let me know!

Something similar to reference counting is how I usually solve this sort of thing.

Add to the count when disabling control. Subtract when enabling. If the count reaches zero then you actually re-activate the control. You might also want a small utility function to completely clear the ref count and force control to return. That comes in handy from time-to-time.

You could also try a kind of list-based system where you add the control inhibitors to an internal list and then remove them when control from that source is no longer inhibited. Once the list is empty you know you can return control. Again, a handy method to clear the list and force control to return might also be desired.

Counting could be useful, although for my project it may complicate things. I’m trying to design it in a way, where only one IStateCaller is being used at a time. After it’s complete, it will either select a new caller, or end its call. So the process is completely linear.

How about an approach where controller ownership changes based on priority?

  public interface IControllerHolder
  {
      int Priority { get; }
      void UpdateControl();
  }

  public class Mediator
  {
      List<IControllerHolder> holders = new();

      public IControllerHolder? CurrentOwner { get; private set; }

      public void Add(IControllerHolder caller)
      {
          Debug.Assert(!holders.Contains(caller));

          holders.Add(caller);
          holders.Sort((a, b) => b.Priority - a.Priority);
          CurrentOwner = holders[0];
      }

      public void Remove(IControllerHolder caller)
      {
          holders.Remove(caller);
          if (CurrentOwner == caller)
              CurrentOwner = holders.Count > 0 ? holders[0] : null;
      }
  }
1 Like

Okay, it looks like you’re more interested in the entire game state. I was thinking on a more granular level like if you needed to inhibit the control of specific characters, one at a time. Sometimes even turning off specific parts like movement without affecting, say, the ability to look around.

I think a stack or priority list system suggest above would be a likely candidate. It would remove the need for the awkward registration that kind of goes against the grain of your typical FSM design. Your controllers can just send the signal as needed without worry of what state you’re currently in and the FSM shouldn’t accidentally restore control until the entire stack or list has emptied out.

1 Like

Yeah, you may be right about that, I’m probably overthinking it with the IStateCaller interface.
I may try something even more simple, removing the IStateCaller and just use the FSM to check if the conditions are met.
I can probably add a priority list later, if the complexity grows

Each situation should have it’s own set of input actions. Transition would enable the Skip action and nothing else should be able to affect anything.