unity-game-enginemeshprocedural-generationuser-interaction

How to enable selection of a procedural mesh in Unity with the mouse pointer


My Unity app so far allows the user to draw box-shaped meshes by clicking and dragging in the 3D scene.

Now I'd like to enable the user to click on one of these created meshes and interact with it.

I'm taking my first baby steps with the interactivity. I'd like to show (log) some basic info of the box, like the area of its base, when the user clicks on it. And when the box has been clicked on, I'd like its material color to change, showing that it has been selected.

Is the interaction with procedurally created meshes any different than interaction with regular game objects in Unity? Not sure where to start, since I'm new to game object interaction too. So, any help is appreciated.


Solution

  • From the documentation:

    OnMouseDown is called when the user has pressed the mouse button while over the Collider.

    This event is sent to all scripts of the GameObject with Collider. Scripts of the parent or child objects do not receive this event.

    So two key points there, one is that your GameObject needs a collider to trigger the OnMouseDown message, and two is that it will only fire OnMouseDown for scripts attached to the GameObject that has the collider.

    After you make the mesh, add a mesh collider and set the custom mesh as the collider's mesh. Up to you to figure out how you want to receive and handle the OnMouseDown event though, I don't know enough about your project architecture to really make a thoughtful contribution there.

    :EDIT:

    Okay, so if you want to make a mesh and get OnMouseDown events from it, then you need to have a script on that mesh (with a collider also on that mesh). The problem might be that you want to have functionality from the script that created the mesh, so what can you do? Well, you can make a forwarding function! Consider something like:

    public class MouseDownForwarder : MonoBehaviour
    {
        private System.Action callback;
    
        public void ForwardTo(System.Action callback)
        {
            this.callback = callback;
        }
    
        public void OnMouseDown()
        {
            callback?.Invoke();
        }
    }
    

    So this is a script, it's a MonoBehaviour so you attach it to a GameObject, and if callback isn't null then you invoke it whenever that script gets an OnMouseDown event. Then, whenever you make a mesh:

     public class MeshMakingScript
    {
        public void MakeMesh()
        {
            GameObject meshGO = new GameObject("your procedural thing");
            // Set the mesh, collider, etc.
            var forwarder = meshGO.AddComponent<MouseDownForwarder>();
            forwarder.ForwardTo(this.MouseDownReceiver);
        }
    
        public void MouseDownReceiver()
        {
            // Your functionality goes here.
        }
    }
    

    Now this is pretty handy - you make your mesh, attach a collider, then you attach this forwarding function. You pass your own MouseDownReceiver, which is just a System.Action (which in turn is a function that returns void) and then whenever someone clicks on your procedural thing the MouseDownForwarder gets that event and invokes the callback you give it.

    Now maybe you want to know which gameObject you instantiated is calling home, in which case you just add a template argument to your System.Action and have the forwarder pass a reference to itself:

    public class MouseDownForwarder : MonoBehaviour
    {
        private System.Action<GameObject> callback;
    
        public void ForwardTo(System.Action<GameObject> callback)
        {
            this.callback = callback;
        }
    
        public void OnMouseDown()
        {
            callback?.Invoke(this.gameObject);
        }
    }
    

    Now whenever the MouseDownForwarder fires it hands the gameObject it's attached to off to whoever gave it the callback. You would receive it like:

    public class MeshMakingScript
    {
        public void MakeMesh()
        {
            GameObject meshGO = new GameObject("your procedural thing");
            // Set the mesh, collider, etc.
            var forwarder = meshGO.AddComponent<MouseDownForwarder>();
            forwarder.ForwardTo(this.MouseDownReceiver);
        }
    
        public void MouseDownReceiver(GameObject proceduralGameObject)
        {
            // Your functionality goes here.
        }
    }
    

    You could make the callback in the MouseDownForwarder be an event, and multiple people can get notifications, etc., but this is how you bridge that connection :)