0
\$\begingroup\$

What are good approaches for disabling user input during certain moments in a game?

In my case, I have a grid-based tactics game (kind of like Fire Emblem) where I periodically want to toggle the user's ability to interact with the board. (For instance, the player may click a unit and then move it to a certain square; while the character is physically walking to the square, I want to disable user input). What's a good strategy for doing this?

  1. Add a UI element that overlays the entire game, with a transparent image that blocks raycasts. Toggle this on and off as I want to disable user input.

  2. Add a "locked" boolean and toggle the boolean. Then, for every function that processes user input, check if the "locked" boolean is true or not. This requires a lot of long-term maintenance so I don't prefer it.

  3. ???

Also, I'm using "OnMouseEnter", "OnMouseExit", etc. Would things be easier if I transition to this event system model, with things like "IPointerHandler"?

I'm looking for a solution that eases long-term maintenance and maximizes flexibility down the road.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ The transparent UI element approach is likely to work only if your units/grid are themselves UI elements using the event system, but in that case a CanvasGroup would be a neater way to handle it. \$\endgroup\$
    – DMGregory
    Jan 13, 2021 at 17:41

2 Answers 2

2
\$\begingroup\$

As DMGregory noted, a Canvas Group is an easy way to control interactivity for a group of UI controls. Just assign the Canvas Group to the parent containing your UI controls, and update the interactable property:

canvasGroup.interactable = false;

As for user interaction with non-UI GameObjects, there are several approaches depending on how you process input:

  • Update() does not run if the MonoBehaviour is disabled. For example:
public class ClickableGameObject : MonoBehaviour {
    void Update() {
        if (Input.GetMouseButtonDown(0)) {
            //handle click here
        }
    }
}

clickableGO.enabled = false; //disables the Update() function
  • For other functions like OnMouseDown() or OnPointerClick(), you'll need a flag you can check to see if the input should be disabled. There are several ways to do this. Here are a few examples:
//This solution is clean but may require you to call `Interactable = true;` or 
//`Interactable = false;` on many objects depending on your game structure.
public class ClickableGameObject : MonoBehaviour {
    [SerializeField] private bool interactable = true;
    public bool Interactable { get => interactable; set => interactable = value; }

    void OnMouseDown() {
        if (!interactable) return;
        //handle click here
    }
}
//this solution uses a global static variable, which is generally bad practice,
//but can save a lot of effort in this case:
public static class UserInput {
    public static bool InputEnabled {get; set;} = true;
}

public class ClickableGameObject : MonoBehaviour {
    void OnMouseDown() {
        if (!UserInput.InputEnabled) return;
        //handle click here
    }
}
//this solution is a compromise between the above two solutions
public class UserInputSettings {
    public bool InputEnabled {get; set;} = true;
}

public class ClickableGameObject : MonoBehaviour {
    public UserInputSettings InputSettings { get; set; }
    
    void OnMouseDown() {
        if (!InputSettings.InputEnabled) return;
        //handle click here
    }
}

//create the settings instance before instantiating your GameObjects
UserInputSettings settings = new UserInputSettings();

//assign the settings to your GameObjects immediately after instantiating them
ClickableGameObject clickableGO1 = Instantiate(clickableGOPrefab);
clickableGO1.InputSettings = settings;
ClickableGameObject clickableGO2 = Instantiate(clickableGOPrefab);
clickableGO2.InputSettings = settings;

//affects all GameObjects we've assigned the settings to
settings.InputEnabled = false;
\$\endgroup\$
5
  • \$\begingroup\$ I'd be tempted to use a singleton pattern for the UserInputSettings so that it's easy to read the flag without injecting the dependency via script. Handy for objects that already exist in your scene or are added on scene load, rather than being instantiated by a script. \$\endgroup\$
    – DMGregory
    Jan 15, 2021 at 15:51
  • \$\begingroup\$ @DMGregory I thought about that but didn't want to make the answer more complicated. In this case, if we're just talking about one variable to disable or re-enable user input, a static global variable is the easiest possible solution, though there's the risk of forgetting to reset it after a scene change. \$\endgroup\$
    – Kevin
    Jan 15, 2021 at 18:15
  • \$\begingroup\$ @DMGregory I'd probably use a singleton as well, but after reading the note about it being considered bad practice, wouldn't a singleton be essentially the same a static variable? I mean it's basically a static property of a static variable vs a static variable. \$\endgroup\$ Jan 16, 2021 at 2:28
  • \$\begingroup\$ @BenjaminDangerJohnson A singleton is generally a class which only supports a single instance, accessed through a static field. Since the singleton has an instance, we can potentially reset it by nulling the instance. This can be helpful in some cases to avoid memory garbage or to ensure we don't end up with invalid state. For example, if the singleton was a MonoBehaviour and we left the gameplay scene to return to the main menu, the instance would be destroyed (unless we called DontDestroyOnLoad() on it, which is dangerous because we can end up with multiple instances of the singleton). \$\endgroup\$
    – Kevin
    Jan 18, 2021 at 19:47
  • \$\begingroup\$ @Kevin True enough but I was thinking about this case specifically where the user settings is just a struct wont be destroyed on scene load. The only benefit I can really think of in this particular case is avoiding namespace clutter. \$\endgroup\$ Jan 20, 2021 at 1:44
1
\$\begingroup\$

I created a similar game once where I solved this problem by introducing the concept of states.

The game had different states it could be in (select unit, select walk target, select attack target, wait for animation, wait for AI turn, and so on).

Each state was represented by a different gameObject. Switching states happended through a StateHandler class, which deactivated all of these objects except the one for the current state. Each of these gameObject had a separate script responsible for all input handling during that state. That way I could control precisely what input functionality was available in which state.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .