Design Pattern for User Input Context?

Hi All,

This isn’t specifically a Unity question, rather something regarding general game development practice. I have been exploring various approaches to encapsulating user input actions/mappings dependant upon their context. I’ve been wondering if this is something that others concern themselves with, and if so what design patterns others adopt when trying to handle user input. What I currently have is a central Input Handler which has a reference to a “context” object representing the current context. These “contexts” follow something similar to the State Pattern, with each “context” listening for/reacting to user input, handling their own interactions. As these “context” objects are solely responsible for actioning user input, and only context is active at any given time, a change in context means a change in control scheme/input handling. The context changes are controlled via returning a new context object to the Input Handler given some trigger even, this context is then set to the current context, and the last is destroyed. At the project level, input mapping is deliberately generic with names such as “Main Interaction” rather than “Fire” for example, as the exact interaction will depend on the context. Following this pattern allows me to encapsulate the actions contextually, however it somehow feels wrong. I am unable to see a reasonable way to make use of one of the biggest bonuses of using the the State Pattern, entry and exit actions. Hence I have come to the conclusion that something better must exist, I’m just not sure what it is. To be clear, when saying context I’m referring to changes in control scheme within the game. For example, one context may be for general gameplay, maybe something similar to a top down strategy game, another context could be the menu, another could be some kind of mini-game all of which have distinct interactions for the same input.

Sorry for the rant and thanks for any help, I appreciate it.

Can you elaborate on this? I don’t know what you mean. What problem are you trying to solve that’s not already covered by the solution that you’ve proposed?

Are you aware of the new Unity Input System that is based on action maps? Unity Blog your context switches could be reflected by a switch of an action map.
I usually go with an event driven approach where multiple components handle input from one source. These components can subscribe/unsubscribe to input events and that defines the effect of the input. For instance, the menu controller will register to the mouse click event and handle it appropriately while the gun controller unsubscribed from that event when the menu was opened. That way the input system does not have to actively know about all the game mechanics and trigger them actively. Neither does it care about the context. Might be I misunderstood your approach, though.

Thank you both for the responses. I had a feeling I hadn’t explained myself particularly well.

What I’ve got so far does solve the issue I was trying to address, however the fact that I cant leverage some of the key elements of the pattern I’ve adopted just made it feel like a poor fit. It may not be, I primarily wanted to see how other people address context switching of control scheme.

I am aware of the new input system, however I wasn’t sure when this would be officially released and I’m a little hesitant to adopt something beforehand. It does seem to address exactly what I was talking about, and vastly more. Maybe I should just focus elsewhere and transition to the new system after release. The subscriber type approach had occurred to me, however I wasn’t sure if it was considered good practice or not (not sure why it wouldn’t be). I’m quite new to input handling so I’m more just trying to gauge what people’s opinions are. If you don’t mind me asking, have you found your approach easy to use/extend? Can you think of any drawbacks etc? It seems to me to be a great solution to the problem provided you’re careful when managing event un/subscription.

Sorry I’ll try to be a little more clear. As the context change is currently driven by FixedUpdate I’m having trouble identifying a good way of having entry and exit actions. The only ways I can think of doing this either result in coupling of states in some way, or require a check for state change between ticks. This may be a totally irrelevant concern as I can’t even think of a use for entry or exit actions regarding input contexts. Which makes me think all the more that my approach could be more elegant.

Are you talking about something like an event?
https://docs.unity3d.com/ScriptReference/Events.UnityEvent.html

Why is your state transition handled in FixedUpdate?

That’s because my Input Handler is listening for input on FixedUpdate. My states/contexts have a handleInput() method, which is called by the Input Handler. Depending on if/when required the Input Manager is returned a new context after processing keystrokes. My logic at the time was, lets say you press esc while in the gameplay context, that context would return a new menu context to the handler, this context would then handle input differently. If pressing esc, the handler would be told to revert to the previous context.

After looking at UnityEvent, this looks like a tidy approach. Thanks for introducing me (I’m obviously quite new to Unity), this answers some other totally unrelated questions I had :wink:

Edit: I’m unsure of how else I would manage a state transition when input needs to be polled frequently.

Concerning the observer pattern for input, I really don’t think there are obvious drawbacks. And it is quite extendable. All other approaches I tried grew more complicated while the observer pattern only grows in size. Some things to note, though:

  • always unsubscribe (I usually subscribe/unsubscribe in OnEnable/OnDisable)
  • order. Handlers are called in order of subscription which you have little influence on. Do not depend on the order.
  • naming. Get your naming consistent. I always call my events “SomethingHappened” if that something has been handled, or “SomethingHappening” when the invoking script still has things to do. Handler methods are always called “On[EventName]”. Whatever notation you use, keep it consistent.
  • try/catch. Errors will bubble up to your event invocation and other subscribers do not get called either. This can lead to unexpected errors all over the place.
  • do not overuse. If you end up with long event chains, where one event triggers the next, it can be hard to debug.
  • delegates. I use delegates to name my event parameters. This is much less boilerplate than the EventArgs-class approach.
  • consider UniRX. I just started playing around with it and it takes some getting used to, but it is pretty powerful.

Here is a script that includes the patterns I use. So far, I am quite happy with those approaches:

public class TestClass : MonoBehaviour
    {
        // use a delegate to name event parameters
        public delegate void Handler(TestClass sender, int someInt);

        public event Handler SomethingIsHappening;

        // with UniRx, use a Subject and expose it as IObservable
        // I use tuples to allow for multiple named parameters and often add the sender
        private readonly Subject<(TestClass sender, int someInt)> _somethingHappened = new Subject<(TestClass, int)>();
        public IObservable<(TestClass sender, int someInt)> SomethingHappened => _somethingHappened;

        // when there are no parameters, might as well just use Action
        public event Action MouseButtonClicked;

        public void DoSomething(int someInt)
        {
            SomethingIsHappening?.Invoke(this, someInt);
            // actually do something
            _somethingHappened.OnNext((this, someInt));
        }

        private void Update()
        {
            if(Input.GetMouseButtonDown(0)) MouseButtonClicked?.Invoke();
        }
    }

    public class TestSubscriber : MonoBehaviour
    {
        [SerializeField] private TestClass _testClass;

        // this is used to unsubscribe from all UniRx events
        private CompositeDisposable _eventHandlers;

        private void OnEnable()
        {
            _eventHandlers = new CompositeDisposable();
            _testClass.SomethingIsHappening += OnSomethingIsHappening;
            _testClass.SomethingHappened.Subscribe(OnSomethingHappened).AddTo(_eventHandlers);
            _testClass.MouseButtonClicked += OnMouseButtonClicked;
        }

        private void OnDisable()
        {
            _eventHandlers.Dispose(); // unsubscribe from all UniRx events
            _testClass.SomethingIsHappening -= OnSomethingIsHappening;
            _testClass.MouseButtonClicked -= OnMouseButtonClicked;
        }

        private void OnSomethingIsHappening(TestClass sender, int someInt)
        {
            Debug.Log($"Something is happening with {sender} and {someInt}");
        }
      
        private void OnSomethingHappened((TestClass sender, int someInt) args)
        {
            Debug.Log($"Something happened with {args.sender} and {args.someInt}");
        }

        private void OnMouseButtonClicked()
        {
            Debug.Log("Mouse button clicked");
        }
    }
2 Likes

You probably wouldn’t want do that. By default the fixed time step is 0.02 which is most-likely less than your frame rate. Potentially, FixedUpdate may also run multiple times per frame to catch-up. People who have implement input-polling in FixedUpdate often complain that it feels unresponsive.

As far as I know, FixedUpdate is mostly just there to allow the physics engine to have a fixed number of “steps” over time, which is necessary for physics calculations. The common rule-of-thumb is to do only physics updates in FixedUpdate and everything else in Update, or some such event in Unity’s regular frame-rate cycle.

2 Likes

Perfect, thanks for the help :). That’ll explain why input responses are occasionally inconsistent. Sometimes it’s hyper responsive, but very occasionally it seems as if a button press (not holds etc) are missed or double triggered for a frame. You’ve been very helpful.

I think i’ll just hold off implementing anything too advanced, rather I’ll likely adopt the new Input System after it’s officially released. It seems like a far more complete and tidy solution than anything I’d implement on my own. For now i’ll place my focus elsewhere. However, you’ve given me a couple of nuggets of information which will help my journey. So, thank you very much.