Mouse input management

My handling of mouse input has a scaring tendency to get messy and devolve in to a large mess of scattered spaghetti in assorted scripts and whenever a new case pops up it becomes more and more difficult to deal with. It’s fine most of the time when I do what amounts to prototyping or testing out small specific concepts but not when I want to tie things together in to resembling games.

I have a rough idea on how to proceed but looking for examples or designs to imitate as to avoid reinventing the wheel but I can’t seem to figure out a good search query netting the results I’m looking for. I only find the api references or questions that are on a too low level. Ie Input.GetMouseButtonDown(1) or how to cast a ray from mousepos and similar.

But what I’m looking for is how do I deal with multiple objects on the screen which say are selectable but then have different behaviours? Some might activate a UI element for upgrade alternatives. Some might be for RTS styled unit selection etc.

My current idea is to base things of a simple Interface with say LeftClick(); RightClick(). Then have all mouse inputs go through a Manager or Handler class for mouse input. The mouse input would then use a ray as normal to see if there is something you try to select and if it hits an object that has a script that implements either of these methods it would then call these functions and they would deal with what happens next.

I would also implement a callback so that it’s perhaps up to the current “holder” of input to say that it’s time for me to be released depending on its logic.

I think I’ll be able to come up with a design that mostly suits my needs but would love to get some input on how others avoid having this specific part devolve in to a whole bunch of “if/else” checking as well as if someone knows of any good examples, resources, blogposts discussing this subject.

Isn’t that more or less how OnMouseDown already works?

Not really, OnMouseDown is used to cast the ray but how do you manage this on a bigger scope? One way would be to have every clickable object run it’s function where OnMouseDown is called and cast what amounts to the same ray but if it doesn’t hit itself further logic won’t run but since code is going to get called over the place it can get messy.

If I have 10 rts-styled units running the same script I don’t want to trigger 10 raycasts in order to see if one of those where selected.

Or say that we have a GameObject that upon selection “unpacks”, lets call it #1 so there are three other Gameobjects spawned and orbiting the first as long as it’s selected. If the single input gets passed down to #1 from handler. The ball is in the court of #1 and the logic it runs says to throw another ray and if neither of those are hit it should destroy them and use the callback to the handler that it not longer holds input control.

Now if I want to change the behaviour I can edit the input behavior in #1 script. Perhaps this object allows for selecting another object if it’s the selected one. Instead of having to do an statement that checks if #1 is selected and ray hits #2 also select #2 I can just have #1 do a call to the function that does the raycast and handle it there. #2 could do the same to #3 and so on.

Basically it would create an input hierarchy where the decision to actually decide what to do lies within the leaf node. For some cases a right click might be to just collapse the entire hierarchy and other cases it would only mean to walk back up to the parent and handback control.

I think this should make for a relatively neat and flexible system without being overly complex but also allow for complex behaviours. at the same time. But as I said I haven’t really found anything on how to go about it.

The stuff I find is: how to select, and move a unit in an rts.
What i want would be closer to a case study that looks in to how different games handles their input flows and compares strength and weaknesses in the design of them.

I’ve mostly used the UI system, so I don’t have experience with OnMouseDown, but the documentation I linked claims that OnMouseDown only gets called on objects whose colliders were under the mouse when it was pressed. Is that incorrect? If not, why do you also need to cast a ray?

I can only offer you my current approach as example. Disclaimer: I am quite new to Unity and C and this is my first game. But I have lots of programming/computer science experience in general.

It’s a 2D, top down game. I have a central component that investigates the mouse click since I had some inconsistencies with OnMouseDown when two clickables were at the same location. (Literally the same location, for rendering the ambiguity is resolved with sorting layers but raycasts seem to ignore those.)
So, currently, I do a single raycast and investigate all hits. I might still change this later to only inspect the first hit… anyway, that does not matter too much for unit selection.

There is a loop over the raycast hits and if something is selectable we tell the central game state instance about it.

/// In my game, selectable enemies are Programs in a network, 
// they have a ProgramBehaviour script component
if (hit.collider.TryGetComponent<ProgramBehaviour>(out ProgramBehaviour program))
                {
                    Debug.Log("Clicked on program");
                    GameState.Get.SetSelectedProgram(program);
                }

GameState handles most higher-order coordinations like selection. It does not care how the selection was made, whether from a mouse click like above or a key press or something else. When a program is newly selected the game state will notify listeners that have registered themselves. Any script can be a listener by implementing the IProgramSelectionListener interface. For example, one of them is an info box that displays details about the selected thing.

public class GameState
{
    // The singleton instance for the game state. 
    // I'll probably remove the readonly modifier when I implement save/load
    private static readonly GameState state = new GameState();

    public static GameState Get => state;

    private ProgramBehaviour selectedProgram;
    // an accessor so that anyone can get the currently 
    // selected program from the game state
    public ProgramBehaviour SelectedProgram
    {
        get => selectedProgram;
    }

    private List<IProgramSelectionListener> programSelectionListeners = new List<IProgramSelectionListener>();

    public void AddProgramSelectionListener(IProgramSelectionListener listener)
    {
        programSelectionListeners.Add(listener);
    }


    public void SetSelectedProgram(ProgramBehaviour program)
    {
        if (selectedProgram == program)
            return;

        selectedProgram = program;
        foreach (IProgramSelectionListener listener in programSelectionListeners)
        {
            listener.ProgramSelected(program);
        }

        // Here I could also notify the selectedProgram that it was selected,
        // if that ever becomes necessary
    }

}

This is just the base, But everything else, like, e.g. clearing the selection, can be added quite neatly. Even multiselection would not need big changes although I don’t envision that I’ll need that for my game.

I hesitated a bit before implemening my own listener list thinking that I might be able to use the Unity event system somehow. But from what I gathered it would hardly have resulted in a neater structure. At best it would have looked very similar. So I stuck to what I knew already. :slight_smile:

I’d OnMouseDown once, do raycast once, and then GetComponent on whatever was clicked to tell it that it was clicked. I would not have each clickable object on its own test if it was clicked, as that doesn’t scale nearly as well.

I’d probably have a single component, something like Clickable.cs, which I would attach to anything clickable, with a method like WasJustClicked(), which all it does is call a delegate. The delegate can then be a method on any script specific to just this kind of object, so your script checking OnMouseDown doesn’t actually need to know anything about the objects you click on other than they have the Clickable component.

This is how I handle my mouse hits, I need my mouse handlers to do different things depending on what was hit, so I just subscribe to OnMouseHit and do a quick component comparison in the handling scripts.

My mouse input (mouseLeftClick) is raised from an input manager that raises input events, so I dont have to check inputs every frame in multiple places. My GameManager holds certain game info variables, such as if the game is in pause menu, in which case a raycast into the world space shouldnt be possible.

I think you might understand the concept:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

public class MouseSelectManager : MonoBehaviour
{
    public static UnityEventHit OnMouseHit;

    private void Awake()
    {
        OnMouseHit = new UnityEventHit();
    }

    private void Start()
    {
        InputManager.InputManager.MouseLeftClick.AddListener(MouseClick);
    }

    private void MouseClick()
    {
        if (GameManager.instance.inMenu) return;
        var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        if(Physics.Raycast(ray, out RaycastHit hit))
        {
            OnMouseHit.Invoke(hit);
        }
    }

    public class UnityEventHit : UnityEvent<RaycastHit>
    {
    }
}