Search code examples
c#unity-game-engineinstantiationcursor-positionmouse-position

How do I instantiate a Unity prefab at the mouse's position in C#?


I'm trying to instantiate my ballPrefab on click at the cursor's position. I know that Input.mousePosition only takes the x and y coordinates. However, my camera looks in the direction of x (global) (as you can see here). So I changed x to 0. The problem is that the prefabs spawn at the same y and z position every time, no matter where I left-click.

This is my code.

[SerializeField]
private GameObject ballPrefab;

void Update()
{
    Vector3 mousePos = Input.mousePosition;
    mousePos = Camera.main.ScreenToWorldPoint(mousePos);
    mousePos.x = 0;

    if (Input.GetButtonDown("Fire1"))
    {
        Instantiate(ballPrefab, mousePos, ballPrefab.transform.rotation);
    }
}

I searched the internet for like an hour.

What I came across was Cursor.Position, which seems to be old (?), since System.Windows.Forms doesn't exist.

I also tried making the mouse position a new Point , but then I can't convert it to a Vector3.

I also thought of transform.localPosition since my camera is facing the "wrong" direction, but I can't apply transform if I use Input.mousePosition .

Finally, I read about raycasts, but I don't really understand them.


Solution

  • To understand the issue it's best to understand how ScreenToWorldPoint works. Close one of your eyes and look straight ahead and don't move your eye (like a 2D camera). Then extend one of your arms and take your pointer finger and place it on top of something in the distance of your view (like a mouse pointer) but don't move your eye or look directly at it. The ScreenToWorldPoint function works somewhat by taking note of where your finger is on the X and Y axis of your eye's view, but it will also need to know how far out you extended your arm and finger on the Z axis (depth) to make your finger align with what you are pointing at. Like drawing a line from your eye to your finger.

    The issue is that Input.mousePosition will return a Vector3 where the Z axis (depth) is 0. That means when you pass it to ScreenToWorldPoint it basically doesn't know how far out you extended your arm and finger. That is like taking your finger and placing it directly on top of your eyeball, but adding very little X or Y so the finger is still pointing at your object. You will get very small X and Y values, and the Z value will be whereever your eyeball (camera) is.

    The other issue is that you are manually setting the resulting X value to 0. So X will always be 0, and the Z will always be at the camera's Z position, and the Y value will be very small, all because no depth value was passed to the ScreenToWorldPoint function. This will result in the ballPrefab always ending up in roughly the same location in the world.

    So try to give it a depth reference like this:

    public float distanceFromCamera = 5f;
    public GameObject ballPrefab;
    
    void Update()
    {      
        if (Input.GetButtonDown("Fire1"))
        {
            Vector3 mousePos = Input.mousePosition;
            mousePos = Camera.main.ScreenToWorldPoint(
                           new Vector3(mousePos.x, mousePos.y, distanceFromCamera));
            Instantiate(ballPrefab, mousePos, ballPrefab.transform.rotation);
        }     
    }
    

    It seems you want the ball to always be on the X axis of the world, so another solution is to use the absolute value of the camera's X position (which is the distance the camera is from X = 0) as the depth value.

    mousePos = Camera.main.ScreenToWorldPoint(
                  new Vector3(mousePos.x, mousePos.y,
                    Mathf.Abs(Camera.main.transform.position.x)));
    

    This will allow you to move your camera in and out (in your case along the X axis) without having to adjust any distanceFromCamera variable.