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

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

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;
}

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":
The yellow ray goes from camera to asteroid hit point
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

Related

Unity3D - Raycast not hitting correct position

I'm trying to make a gun by shooting a ray from the centre of the camera which the gun is a child of, I also have a particle effect where the impact happens only, the impact happens in the wrong position.
I did Debug.DrawLine and saw that the origin of the ray was happening 0.8 units above the center of the camera, and it sometimes hits directly on the camera
Here is the gun script
using UnityEngine;
public class GunShoot : MonoBehaviour
{
public float gunDamage = 1f;
public float fireRate = .1f; // wait x seconds to fire again
public float weaponRange = 100f;
public float hitForce = 100f;
public Camera playerCam;
//public ParticleSystem muzzleFlash;
public GameObject impactEffect;
private float nextFire; // Holds the time for which the gun is able to fire again
void Update()
{
Shoot();
}
public void Shoot()
{
if (Input.GetButtonDown("Fire1") && Time.time > nextFire)
{
nextFire = Time.time + fireRate;
RaycastHit hit;
if (Physics.Raycast(playerCam.transform.position, playerCam.transform.forward, out hit, weaponRange)) // Center of the camera, directly forward, hat it hits and that infomation, the range of the weapon
{
Target target = hit.transform.GetComponent<Target>();
if (target != null)
{
target.TakeDamage(gunDamage);
}
GameObject impactGameObject = Instantiate(impactEffect, hit.point, Quaternion.LookRotation(hit.normal));
Destroy(impactGameObject, .15f);
}
}
}
}
I have been using a tutorial by Brackleys https://www.youtube.com/watch?v=THnivyG0Mvo but i followed it perfcetly (execpt chaning some variable names) and i cannot see where it is going wrong.
I cant just reduce the high of the initial ray as it sometimes maybe 1/10 times shoots from the right position
As you can see it clearly isn't shooting from the middle where the crosshair is
Any help would be great thanks!
Consider using Camera.ScreenPointToRay.
If you're always shooting from the center of the screen you could use Camera.ViewportPointToRay
Ray ray = Camera.main.ViewportPointToRay(new Vector2(0.5f, 0.5f));
if (Physics.Raycast(ray, out RaycastHit hit, weaponRange)) // handle hit

How do I drag objects on the screen using `OnMouseDrag`?

Tried doing 2D and 3D. I want the player to click and drag objects to stack them. I tried copying and pasting code from a video and it still didn't work.
UPDATE! Now it kinda works but it teleports the object far off the screen. I want the player to be able to smoothly click and drag the object.
The code as of now:
using UnityEngine;
using System.Collections;
public class DragObject : MonoBehaviour
{
private float deltax, deltay;
private void OnMouseDown()
{
deltax = 1f;//Camera.main.ScreenToWorldPoint(Input.mousePosition).x - transform.position.x;
deltay = 1f; //Camera.main.ScreenToWorldPoint(Input.mousePosition).y - transform.position.y;
}
private void OnMouseDrag()
{
Vector2 currentTouchPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
transform.position = new Vector2((Input.mousePosition).x - deltax, (Input.mousePosition.y) - deltay);
}
}
It's not clear at all what exact problem is for you, you obviously need to give more info and describe your issue better. But I'll try to help as I can.
To get mouse position you need to use:
Input.mousePosition
If you need mouse position in 2d coordinates you can use this:
Vector2 currentTouchPos = mainCamera.ScreenToWorldPoint(Input.mousePosition);
This method works regardless of your camera type or how the camera moves between frames
First, when the object is first clicked, get the plane at the object's position perpendicular to the direction of the camera:
Plane dragPlane = new Plane(Camera.main.transform.forward, transform.position);
Then, get the ray coming out of the camera from where the mouse is:
Ray camRay = Camera.main.ScreenPointToRay(Input.mousePosition);
Find out where that ray intersects using Plane.Raycast:
float enter = 0.0f;
if (dragPlane.Raycast(camRay, out enter))
{
Vector3 fingerPosition = camRay.GetPoint(enter);
This gives you a position in world space, which is where you can consider the finger to currently be at. Just put the object there:
transform.position = fingerPosition;
Altogether it might look like this:
void OnMouseDrag()
{
Plane dragPlane = new Plane(Camera.main.transform.forward, transform.position);
Ray camRay = Camera.main.ScreenPointToRay(Input.mousePosition);
float enter = 0.0f;
if (dragPlane.Raycast(camRay, out enter))
{
Vector3 fingerPosition = camRay.GetPoint(enter);
transform.position = fingerPosition;
}
}
No OnMouseDown necessary.
An alternative is to keep track of how fingerPosition changes over time, and Translate the transform based on that. Keeping as much math as possible in world units makes it easy:
Vector3 prevFingerPosition = Vector3.zero;
// Same code as before but we use it in different places so it goes in its own method.
// If the Raycast fails, return a decent guess.
private Vector3 GetMouseOnPositionInWorldSpace()
{
Plane dragPlane = new Plane(Camera.main.transform.forward, transform.position);
Ray camRay = Camera.main.ScreenPointToRay(Input.mousePosition);
float enter = 0.0f;
if (dragPlane.Raycast(camRay, out enter))
{
return camRay.GetPoint(enter);
}
return prevFingerPosition;
}
void OnMouseDown()
{
prevFingerPosition = GetMouseOnPositionInWorldSpace();
}
void OnMouseDrag()
{
Vector3 fingerPosition = GetMouseOnPositionInWorldSpace();
transform.Translate(fingerPosition-prevFingerPosition);
prevFingerPosition = fingerPosition;
}
There is an issue behind using OnMouseDrag to handle dragging objects around is that if the cursor moves too quickly, it can move off the object in the span of a single frame, and it will "drop" the object.
If that's not desirable, you might want to set a bool to be true in OnMouseDown that remembers that the object was clicked and a move the logic out of OnMouseDrag and into Update where it can do its thing if that bool is true. And when the mouse is released, set that bool back to false.

How can I launch the ball based on the position of the touch on the screen?

So I am trying to make a game where you are rquired to set the angle and speed of a ball that has to bounce on specific platforms in order to get to the destination.
Now, how do I find the direction from where the finger touches, to the ball, in order to make it move "away from the finger".
I have tried to use the subtraction of vectors in order to get the direction but It does not work since the vectors are relative to the world origin...it always gives me a wrong direction...
How do I solve this problem, I need a direction vector relative to touch and player(the ball), not to world, so I can launch the ball.
You will see that in the next picture I am simulating a touch with the mouse arrow(let say the mouse arrow is the finger of the player.I want to launch the ball based on the distance and position of the finger relative to the ball. It works well in the code BUT ONLY if the ball is placed on the origin of the scene, so i think it's a mathematical problem with vectors that I don't know how to resolve...
Below is the code that I have by now. It is attached to the ball's gameobject:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour {
[SerializeField]
Camera playerCamera;
Rigidbody rb;
Vector3 touchPostion;
private void Awake()
{
rb = GetComponent<Rigidbody>();
}
private void FixedUpdate()
{
if (Input.GetMouseButton(0))
{
LounchPlayer();
}
}
void LounchPlayer()
{
Vector2 mousePos = Input.mousePosition;
touchPostion = (transform.position - playerCamera.ScreenToWorldPoint(
new Vector3(mousePos.x,
mousePos.y,
playerCamera.transform.position.z))).normalized;
rb.AddForce(touchPostion.normalized, ForceMode.Impulse);
}
}
When finding your touch position, the z component of ScreenToWorldPoint's parameter should be an appropriate distance along the camera's forward vector, definitely not the camera's world z position. playerCamera.nearClipPlane is appropriate here, but really any small constant (such as 0) would suffice.
Also, there is no need to normalize the launch direction twice.
Vector2 mousePos = Input.mousePosition;
touchPostion = (transform.position - playerCamera.ScreenToWorldPoint(
new Vector3(
mousePos.x,
mousePos.y,
playerCamera.nearClipPlane))
).normalized; // This isn't a position vector; it's a direction vector.
// The var name "touchPosition" is misleading and should be changed.
float launchMagnitude = 1f; // change this to adjust the power of the launch
rb.AddForce(touchPostion * launchMagnitude , ForceMode.Impulse);

In Unity3d, my Raycast is always pointing to the (0,0,0) world coordinates

I'm developping a car engine script and I want to use raycast to avoid obstacles. The problem is when I call raycast it points to the (0,0,0) world coordinates though I mentioned the direction to be forward from my object.
public float sensorLength = 10f;
public float frontSensorPosition = 3.65f; // distance from the center of
//the car to its front
public float frontSideSensorPosition = 1.1f;
private void FixedUpdate () {
Sensors();
ApplySteer();
Drive();
CheckWayPointDistance();
}
private void Sensors()
{
RaycastHit hit;
Vector3 sensorStartPos = transform.position;
sensorStartPos.z += frontSensorPosition;
Vector3 fwd = transform.TransformDirection(Vector3.forward);
if (Physics.Raycast(sensorStartPos, fwd, out hit, sensorLength))
{
}
Debug.DrawLine(sensorStartPos, hit.point, Color.green);
}
The output is this:
https://i.imgsafe.org/00/0038d11730.png
Your codes, seem fine, except you are trying to draw line from start pos to the zero point of world space, why is that alway 0,0,0 ? because you have not hit something yet, and if the raycast didn't hit anything the hit.point will stay 0,0,0
The good way to Debug the line is by checking 'are we hit something?'
here's the full example
private void Sensors()
{
RaycastHit hit;
Vector3 sensorStartPos = transform.position;
sensorStartPos.z += frontSensorPosition;
Vector3 fwd = transform.TransformDirection(Vector3.forward);
if (Physics.Raycast(sensorStartPos, fwd, out hit, sensorLength))
{
//if it a surface, then Draw Red line to the hit point
Debug.DrawLine(sensorStartPos, hit.point, Color.red);
} else
{
//If don't hit, then draw Green line to the direction we are sensing,
//Note hit.point will remain 0,0,0 at this point, because we don't hit anything
//So you cannot use hit.point
Debug.DrawLine(sensorStartPos, sensorStartPos + (fwd * sensorLength), Color.green);
}
}
It will draw green line if it's not hit, but we don't use hit.point because it's still zero.
And it will draw red line when hit
Please tell me if this work, or tell if it doesn't

how to make world space UI crosshair line up with screen space crosshair?

So I'm trying to make the gun on the turret of my tank point towards the center of the screen just like in World of tanks where you have a crosshair for where the gun is pointing and the crosshair for the center of the screen.
The problem is that the gun crosshair which is a UI image in world space parented to the gun, doesn't exactly line up with the center of the screen which is the big crosshair in the image [enter image description here][1].
edit: so this works but how can I change it to the x axis?
public class CenterCursor : MonoBehaviour
{
// speed is the rate at which the object will rotate
public float speed;
void FixedUpdate()
{
// Generate a plane that intersects the transform's position with an upwards normal.
Plane playerPlane = new Plane(Vector3.up, transform.position);
// Generate a ray from the cursor position
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
// Determine the point where the cursor ray intersects the plane.
// This will be the point that the object must look towards to be looking at the mouse.
// Raycasting to a Plane object only gives us a distance, so we'll have to take the distance,
// then find the point along that ray that meets that distance. This will be the point
// to look at.
float hitdist = 0.0f;
// If the ray is parallel to the plane, Raycast will return false.
if (playerPlane.Raycast(ray, out hitdist))
{
// Get the point along the ray that hits the calculated distance.
Vector3 targetPoint = ray.GetPoint(hitdist);
// Determine the target rotation. This is the rotation if the transform looks at the target point.
Quaternion targetRotation = Quaternion.LookRotation( targetPoint - transform.position);
// Smoothly rotate towards the target point.
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, speed * Time.deltaTime);
}
}
You can also try putting your turret object as a child of camera object...

Categories