JeyesElite
JeyesElite

Reputation: 73

Unity3D - Using Raycasting to detect Game Objects. Top-down view

I'm making a top down space resource-gathering game, with 3D models.

I'm trying to cast a ray towards my mouse position, to detect collisions with the objects in my game, for my little spaceship to know which direction to head to find resources. The resources are in the form of asteroids the player has to break to gather.

In the code, I use the "center" vector to find the middle of the screen/player position, as the camera follows the player around. All movement happens along the X,Y axis. center.Z is set to 15 simply to counteract the -15Z position of the main camera in worldspace. So far, the code "works" to the extent that it can detect a hit, but the ray doesn't travel in the direction of the mouse. I have to hold it way off for it to hit, and it's difficult to replicate, to the point where it almost seems random. Might the ray not be able to make sense of the mouse position?

In case I've worded myself poorly, this search ability is not meant to break the asteroid, simply locate it. The breaking ability is a separate script.

Code:

public class AbilitySearch : MonoBehaviour
{
Vector3 center;
Vector3 mousePos;
Vector3 direction;
private float range = 100f;

void Update()
{
    center = Camera.main.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 15.0f));
    mousePos = Input.mousePosition;
    direction = mousePos - transform.position;

    if (Input.GetMouseButton(1))
    {
        Search();
    }
}

void Search()
{
    RaycastHit hit;
    if (Physics.Raycast(center, direction, out hit, range))
    {
        Debug.Log("You hit " + hit.transform.name);
    }
}
}

Thanks in advance

Upvotes: 0

Views: 3690

Answers (2)

Luc
Luc

Reputation: 186

When using Input.mousePosition the position is a pixel coordinate on your screen, so if your mouse was in the bottom left corner of your screen it would be 0,0. This causes the direction to be inaccurate. When what you need is to use the game space coordinates, which is done by using Camera.ScreenToWorldPoint(Input.mousePosition), as an example.

This solution allows you to click on a gameObject (provided it has a collider attached), move the current gameObject towards the clicked object, as well as gather an array of possible gameObjects (RaycastHit[s]) in the direction of the clicked one.

Vector3 destination;
RaycastHit[] possibleTargets;
bool isTravelling;
float searchDistance = 5f;

private void Update()
{
    //If right mouse button has been pressed down
    if (Input.GetMouseButtonDown(1))
    {
        RaycastHit hit;
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        if (Physics.Raycast(ray, out hit)) //If ray collides with something
        {
            Search(hit);
        }
    }
    //If going to destination
    if (isTravelling)
    {
        float step = 2f * Time.deltaTime;
        transform.position = Vector3.MoveTowards(transform.position, destination, step);
        //If approx arrived
        if (Vector3.Distance(transform.position, destination) < 0.1f)
        {
            isTravelling = false; //Stop moving to destination
            Debug.Log("You've arrived");
        }
    }
}

private void Search(RaycastHit _hit)
{
    //Normalise directional vectors, so (if needed) they can be multipled as expected
    Vector3 direction = (_hit.point - transform.position).Normalized();
    RaycastHit secondHit;
    //Grab objects in direction of clicked object (including the one clicked)
    possibleTargets = Physics.RaycastAll(transform.position, direction, searchDistance);
    Debug.Log($"There are {possibleTargets.Length} possible targets ahead");
    Debug.Log($"You hit {_hit.transform.name}");
    //Set destination, and set to move
    destination = _hit.point;
    isTravelling = true;
}

Upvotes: 2

antoson
antoson

Reputation: 38

You used the ViewportToWorldPoint method, which expects normalized viewport coordinates in range 0 to 1, but you supplied what seems to me as world coordinates of your camera as its parameter.

You only need to cast a ray from camera to mouse pointer world position (see first line of code in method FindAsteroid) to check for collision with asteroid. The returned RaycastHit provides you with information about the collision - hit position, gameobject, collider - which you can use for other game logic, e.g. shooting a projectile from spaceship to asteroid hit point.

I edited your class and included a screenshot from my simple scene below, which shows the two different "rays":

  1. The yellow ray goes from camera to asteroid hit point
  2. The magenta ray goes from spaceship position to asteroid hit point.

I would also recommend filtering the raycast to affect only specific colliders - see LayerMask (section Casting Rays Selectively)

public class AbilitySearch : MonoBehaviour
{
    private float  range = 100f;
    private Camera mainCam;

    private void Awake()
    {
        // TIP: Save reference to main camera - avoid internal FindGameObjectWithTag call
        mainCam = Camera.main;
    }

    private void Update()
    {
        if (Input.GetMouseButton(1))
        {
            if (FindAsteroid(out var asteroidHit))
            {
                // Draw a line from spaceship to asteroid hit position
                Debug.DrawLine(transform.position, asteroidHit.point, Color.magenta, Time.deltaTime);

                Debug.Log($"You hit {asteroidHit.transform.name}");
            }
        }
    }

    private bool FindAsteroid(out RaycastHit hit)
    {
        // Create a ray going from camera position to mouse position in world space
        var ray = mainCam.ScreenPointToRay(Input.mousePosition);

        if (Physics.Raycast(ray, out hit, range))
        {
            // Draw a line from camera to world mouse position
            Debug.DrawLine(ray.origin, hit.point, Color.yellow, Time.deltaTime);

            // An asteroid was found
            return true;
        }

        // An asteroid was NOT found
        return false;
    }
}

Sample spaceship scene

Upvotes: 1

Related Questions