I want to centre the camera on the object and then, if player clicks on button (which will be added later) or outside of the pop-up which will be triggered by clicking on object (camera movement is just a first step).
My idea was to store the very first position of the camera and transform it back again if mouse is clicking outside of any clickable objects, but looks like it's not working.
void Update()
{
Vector3 camoriginposition;
if (Input.GetMouseButtonDown(0))
{
Camera cam = Camera.main;
camoriginposition = Camera.main.transform.position;
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit))
{
Rigidbody rb;
if (rb = hit.transform.GetComponent<Rigidbody>())
{
Vector3 obj = rb.transform.position - new Vector3(0, -3, 2);
cam.transform.position = obj;
Debug.Log("curr cam pos" + cam.transform.position);
}
else
{
cam.transform.position = camoriginposition;
Debug.Log("It triggers!");
}
}
}
}
I receive the Debug.Log output, but looks like transform.position cannot be processed.
Do you have any ideas how to fix it?
I'm not sure what behaviour you are meaning by "not working", but maybe It's because you are declaring camoriginposition on every Update(), which would be definitely not the desired behaviour. Whenever you tries to store your cam's origin position to local variable, it will be gone when the Update() scope ends, and will be initialized again with new Update() call.
You have to seperate camoriginposition from Update(), like:
private Vector3 camoriginposition;
void Update()
{
if (Input.GetMouseButtonDown(0))
{
...
}
And, it seems like there are another problem. You are storing camoriginposition on every mouse click, but with your description, It seems like it should be stored when the ray actually hits the gameobject.
You should move camoriginposition = Camera.main.transform.position;
into
if (rb = hit.transform.GetComponent<Rigidbody>()) block.
Related
I have a teleport orb in my game that you can hover over and click e to use to teleport to, however when I hold my e button down and hover over the orb it teleports me anyways, I want to make it so you have to hover over the orb and click e to teleport not so you can hold e and then hover over it. Any ideas on how to do this?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class IsLookingAtTeleport : MonoBehaviour
{
public AudioSource teleportsound;
public Rigidbody rb;
void Start()
{
}
void FixedUpdate()
{
// Bit shift the index of the layer (8) to get a bit mask
int layerMask = 1 << 8;
// This would cast rays only against colliders in layer 8.
// But instead we want to collide against everything except layer 8. The ~ operator does this, it
//inverts a bitmask.
layerMask = ~layerMask;
RaycastHit hit;
// Does the ray intersect any objects excluding the player layer
if (Input.GetKey("e"))
{
if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out hit, Mathf.Infinity, layerMask))
{
Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * hit.distance, Color.yellow);
Debug.Log("Did Hit");
if (hit.transform.tag == "TeleportOrb")
{
Debug.Log("Hit Teleportable Orb");
teleportsound.Play();
rb.transform.position = hit.transform.position;
Destroy(hit.transform.gameObject); // destroy the object hit
}
}
else
{
Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * 1000, Color.white);
Debug.Log("Did not Hit");
}
}
}
}
Input.GetKey is fired every frame while the key stays pressed.
What you rather want to use is Input.GetKeyDown which is true only in the one frame when the button is actually pressed down.
Further note that instead of the stringed version I would always go for the ones using KeyCode as parameter.
And then make sure you do your user Input in Update not in FixedUpdate. While the later might work in some cases for getting continous inputs such as GetKey you might miss single event inputs.
And finally whenever a Rigibody is invoved you do not want to set anything through the Transform component.
// Rather configure the desired layers you want to hit here
// via the Inspector in Unity!
// actually way better than just including everything rather only
// select these layers that actually should be considered as teleport targets
// This way you wouldn't even need to check for Tags!
// just rather place all your teleport objects on a Teleport layer
// and only use a raycast on that layer
// see https://docs.unity3d.com/Manual/Layers.html
[serializeField] private LayerMask hitLayers;
private void Update()
{
if (Input.GetKeyDown(KeyCode.E))
{
// store the ray once
var ray = new Ray(rigibody.position, rigidbody.rotation * Vector3.forward);
if (Physics.Raycast(ray , out var hit, Mathf.Infinity, hitLayers))
{
Debug.DrawRay(ray.origin, ray.direction * hit.distance, Color.yellow);
Debug.Log("Did Hit");
// Rather use CompareTag instead of string based ==
if (hit.gameObject.CompareTag("TeleportOrb"))
{
Debug.Log("Hit Teleportable Orb");
teleportsound.Play();
rb.position = hit.transform.position;
// destroy the object hit
Destroy(hit.gameObject);
}
}
else
{
Debug.DrawRay(ray.origin, ray.direction * 1000, Color.white);
Debug.Log("Did not Hit");
}
}
}
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
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.
I have a simple inventory system with Canvas set to screen space - Camera.
For some reason when setting icons to vector3.zero makes them go somewhere else.
here is an example:
first image is on start, everything works fine.
second image is apple being dragged, as you can see position is correct
once dropped as can be seen the apple goes to an unknown place.
This is the code for endDrag:
public void OnEndDrag(PointerEventData eventData)
{
if (item.category != ITEM_CATEGORY.Voxel)
{
icon.transform.position = Vector3.zero;
}
else
{
cube.transform.position = Vector3.zero;
}
}
nothing unique.
here is drag event:
public void OnDrag(PointerEventData eventData)
{
if (item.category != ITEM_CATEGORY.Voxel)
{
Vector3 screenPoint = Input.mousePosition;
screenPoint.z = 0.13f; //distance of the plane from the camera
icon.transform.position = Camera.main.ScreenToWorldPoint(screenPoint);
}
else
{
Vector3 screenPoint = Input.mousePosition;
screenPoint.z = 0.13f; //distance of the plane from the camera
cube.transform.position = Camera.main.ScreenToWorldPoint(screenPoint);
}
}
What you are seeing in the inspector of the RectTransform is the object's local position. But in the code you are manipulating the object's world position. When you set the object's world position to (0, 0, 0), it is unlikely that its local position will also be (0, 0, 0). What your code is doing is literally moving the object to the origin of the game world.
It's not Vector3.zero that is bugged (that doesn't make any sense), it's your code. In your OnEndDrag function, try resetting the object's local position instead of its world position like this:
cube.transform.localPosition = Vector3.zero;
Idem for the icon of course.
I wrote a script that allows the user to teleport between two spots by looking at designated objects. Specifically, it uses a ray to check whether one of the two objects is in the center of the screen. It works with the standard FPSController (placed under Main Camera) and mouse controls. When I enable VR support, the detection stops working (not just for the center point, but for the whole display). What should I add to my script to make it work with Oculus the same way it works with the mouse?
This code works by detecting objects by name and then teleporting to assigned objects by tag:
using UnityEngine;
using System.Collections;
using UnityEngine.VR;
public class lookTeleport : MonoBehaviour
{
public GameObject destination;
public Transform target;
RaycastHit hit;
Ray ray;
void Start ()
{
Cursor.lockState = CursorLockMode.Locked;
}
void Update ()
{
ray = Camera.main.ScreenPointToRay(Input.mousePosition); //mouse control
if (Physics.Raycast(ray, out hit))
{
if (hit.collider.gameObject.name == "teleporterA")
{
//Debug.Log("teleporter A detected");
destination = GameObject.FindWithTag("teleportY");
transform.position = destination.transform.position;
}
if (hit.collider.gameObject.name == "teleporterB")
{
//Debug.Log("teleporter B detected");
destination = GameObject.FindWithTag("teleportX");
transform.position = destination.transform.position;
}
}
}
}
I think I will have to look into replacing Input.MousePosition with the VR equivalent.
You should replace ray = Camera.main.ScreenPointToRay(Input.mousePosition);
with ray = Camera.main.ScreenPointToRay(new Vector2(Screen.height / 2, Screen.width / 2)); Since you are in VR, and not using a mouse, the Input.mousePosition can't be used as a screen-point to cast the ray from, instead you could use the middle of the screen that the user sees (like crosshairs)