right now I'm working on a planet generation project in the Unity engine.
The problem I have happens when I want to place plants on the planet surface.
My approach is to cast rays from the center of the planet out towards the surface to get the positions of the plants (because the surface is no plain sphere, so I don't know the exact positions). But I don't get any hits.
So I tried several things to fix that. And what I found is quite strange for me.
When I create a Ray using "new Ray", I get nothing. But with "UnityEngine.Camera.main.ScreenPointToRay" I get some expected hits. So it cannot be an issue about the GameObject layers.
For me it looks like using "new Ray" is buggy or something.
Anyone here to explain this?
public void CreatePlants(List<PlantDefinition> plantSpeciesDefinitions)
{
foreach (PlantDefinition plantDefinition in plantSpeciesDefinitions)
{
directions = new Vector3[plantDefinition.maximumCount];
for (int i = 0; i < plantDefinition.maximumCount; i++)
{
Vector3 direction = new Vector3(Random.Range(-1f, 1f), Random.Range(-1f, 1f), Random.Range(-1f, 1f));
direction.Normalize();
directions[i] = direction;
Ray ray = new Ray(planetCenter, directions[i]); // does not give any hits
Ray ray2 = UnityEngine.Camera.main.ScreenPointToRay(Input.mousePosition); // does give hits
Physics.Raycast(ray2, out RaycastHit hit);
if(hit.collider != null)
Debug.LogWarning($"Object hit: {hit.collider.gameObject.name}");
}
}
}
By default raycasts in Unity do not hit the "inside" / backfaces of a Collider.
You start your ray inside a sphere and want to hit the surface from the inside.
You will have to enable Physics.queriesHitBackfaces either via code
private void Awake ()
{
Physics.queriesHitBackfaces = true;
}
or via the Physics Settings
Related
I'm using 2D colliers in my game, and I want to use a perspective camera also. I have a raycast working in orthographic that draws a line from the player to a clicked point. It, of course, breaks when I change the camera to perspective.
I've been trying to get it to work in 2D, but without success.
I'm trying to cast a ray from the player character in the direction of the mouse position on screen, and returning the first hit 2D collider.
This is what I have so far which doesn't work. I appreciate some help.
void RaycastToCollider()
{
Ray ray = new Ray();
ray.direction = Camera.main.ScreenToViewportPoint(Input.mousePosition) - transform.position;
ray.origin = transform.position;
RaycastHit2D hit = Physics2D.GetRayIntersection(ray, Mathf.Infinity);
if (hit.collider != null)
{
Debug.DrawLine(ray.origin, hit.point);
}
}
Why ScreenToViewportPoint? This only returns a pixel position (e.g. 356 x 847) into normalized viewport coordinates that move in range 0 to 1 (e.g. 0,07 * 0,26)
Since it is purely about the direction you want to get you could go the other way round via the screen.
Also not sure but I would simply use a Raycast instead of GetRayIntersection
void RaycastToCollider()
{
// already is in screen space anyway
var mouseScreenPos = Input.mousePosition;
var transformPos = transform.position;
var transformScreenPos = Camera.main.WorldToScreenPoint(transformPos);
var direction = (Vector2)mouseScreenPos - (Vector2)transformScreenPos;
var ray = new Ray(transformPos, direction);
var hit = Physics2D.Raycast(transformPos, direction, Mathf.Infinity);
if (hit.collider != null)
{
Debug.DrawLine(ray.origin, hit.point);
}
}
For Unity Game: shouldn't the cross-hair move accordingly as the player rig does to point at enemies? how can I find error with no syntax error messages?
I tried visiting Unity forums I found support about raycasting followed directions add some code to the crosshair.cs script I receive no syntax errors.
public class CrossHair: MonoBehaviour{
[SerializeField] private GameObject standardCross;
[SerializeField] private GameObject redCross;
float moveForce = 1.0f;
float rotateTorque = 1.0f;
float hoverHeight = 4.0f;
float hoverForce = 5.0f;
float hoverDamp = 0.5f;
Rigidbody rb;
private RaycastHit raycastHit;
void Start()
{
standardCross.gameObject.SetActive(true);
rb = GetComponent<Rigidbody>();
// Fairly high drag makes the object easier to control.
rb.drag = 0.5f;
rb.angularDrag = 0.5f;
}
void Update()
{
// Push/turn the object based on arrow key input.
rb.AddForce(Input.GetAxis("Vertical") * moveForce * transform.forward);
rb.AddTorque(Input.GetAxis("Horizontal") * rotateTorque * Vector3.up);
RaycastHit hit;
Ray downRay = new Ray(transform.position, -Vector3.up)
if (Physics.Raycast(downRay, out hit))
{
//The "error" in height is the difference between the desired height
// and the height measured by the raycast distance.
float hoverError = hoverHeight - hit.distance;
// Only apply a lifting force if the object is too low (ie, let
// gravity pull it downward if it is too high).
if (hoverError > 0)
{
// Subtract the damping from the lifting force and apply it to
// the rigidbody.
float upwardSpeed = rb.velocity.y;
float lift = hoverError * hoverForce - upwardSpeed * hoverDamp;
rb.AddForce(lift * Vector3.up);
}
}
Ray targettingRay = new Ray(transform.position, transform.forward)
if (Physics.Raycast(targettingRay, out raycastHit, 100))
{
if (raycastHit.transform.tag == "Enemies")
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
redCross.gameObject.SetActive(true);
standardCross.gameObject.SetActive(false);
}
}
else
{
redCross.gameObject.SetActive(false);
standardCross.gameObject.SetActive(true);
}
}
}
I except for the crosshair in my game to follow the player rig camera as the code explains. Any guidance is appreciated.
Syntax errors are those kind of errors which prevent your game/program from running at all. They are like grammar mistakes that need to be fixed before you can run your code.
A syntax error is your pc telling you:
I can't run this because I don't understand this. or I can't run this because this is not allowed.
One example would be int x = "Hello World". This in not allowed because I can not assign an string value to an integer (like that).
But your code not having any syntax errors does not mean it will do what you intended it for, it just means your code will run.
A good and easy way of debugging your code in Unity is to add Debug.Log("My Log Message"); statements to your code where you think it would be beneficial. These are going to be logged to your console output in Unity while you are in play mode. You can for example do something like this to constantly get your cross's position and rotation logged:
Debug.Log("Cross Position: " + standardCross.transform.position);
Debug.Log("Cross Rotation: " + standardCross.transform.rotation);
Just be sure to remove them once you are done with them because having Debug.Logs in your code takes as significant hit to your performance.
Another, more sophisticated way of debugging your code is through the usage of breakpoints in Visual Studio or whatever IDE/Editor you are using.
It basically comes down to declaring points where your program should pause execution for you to look at values in your program itself.
It's quite handy but goes above and beyond what I could tell you via this text, so please have a look at this Unity-specific use case: Debugging Unity games with Visual Studio
Now to your code:
First: There is a Vector3.down so you don't have to use -Vector3.up.
Second: Do you need a GameObject as crosshair? Why not just add a UI-Crosshair instead?
That way it always stays in the middle of your screen wherever you turn your camera.
Just add a new Image to your UI via GameObject -> UI -> Image, give it some kind of crosshair look in the inspector and lock it to the middle of the screen by left-clicking on the little crosshair in the top left of the Inspector while you have your Image selected and then Shift + Alt + Left click the middle option.
If you really want to use a separate gameObject as crosshair then maybe attatch it to your player object as a child. That way it will move and rotate with your player automatically and you do not have to do this via script.
Hope this helps!
I'm currently integrating a real-time battleships game I wrote with the Oculus Rift.
The raycasting worked fine when I was doing it from the regular camera, however I am now trying to raycast from the LeftEyeAnchor of the OVRCameraRig to the mouse with no success. This is the code's current state.
IEnumerator setupPlayer(){
int i = 0;
while (i < 3){
if(Input.GetKeyDown("s")){
RaycastHit hit;
Ray ray = oculusCamera.camera.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast (ray, out hit, 2000)) {
Debug.Log ("Yay");
Vector3 shipSpawn = new Vector3 (hit.point.x, hit.point.y+0.6f, hit.point.z);
if(validPlayerPosition(shipSpawn)){//call test function here
shipClone = Instantiate (ship, shipSpawn, Quaternion.identity) as GameObject;
i++;
}
}
}
yield return null;
}
}
I spammed the 's' key in an attempt to find somewhere it would hit but to no avail.
I then wrote a short script and attached it to the LeftEyeAnchor camera in an attempt to track its movements and that read as follows.
using UnityEngine;
using System.Collections;
public class mousetrack : MonoBehaviour {
public Camera oculusCamera;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
Ray ray = oculusCamera.camera.ScreenPointToRay(Input.mousePosition);
Debug.DrawRay(ray.origin, ray.direction * 100, Color.red);
}
}
Upon running and moving the headset it did nothing but project a line straight outwards along the z axis , although by removing the headset i could see the frustrum changing in the Unity scene yet the line remained stationary. I searched for issues like my own and came across this thread.
https://forums.oculus.com/viewtopic.php?t=2754
I attempted caliberMengsk's fix but that made no difference. I am left every runtime with multiple slightly varying instances of the error message:
Screen position out of view frustum (screen pos -1061.000000, 837.000000) (Camera rect 0 0 1036 502)
UnityEngine.Camera:ScreenPointToRay(Vector3)
mousetrack:Update() (at Assets/mousetrack.cs:13)
which points to this line:
Ray ray = oculusCamera.camera.ScreenPointToRay(Input.mousePosition);
Well I am at a loss as to what to do now. Any help on where I'm going wrong would be much appreciated.
So before I explain my problem. I will first tell what I am really doing.
I am working on a click to move/zoom camera script. There are 3 planes in front of my Main Camera. Now what I am doing is, creating a script which says " The camera will zoom on the plane which gets clicked. I made several attempts to come up with a working script but it didn't worked well. Every time I come across new bugs , errors and what not. :|
I got frustrated and deleted the buggy script. Now I want to start from scratch. I am doing it in C#
Since I am not a professional, Can anyone explain me in detail to get it done?
I am confused how to deal with the planes I placed. I want to know what is missing in my script.
Here is a screenshot of how I placed those planes.
Edit. - I managed to work on it. Now I need advice, how to target the planes I placed in front of the camera.
using UnityEngine;
using System.Collections;
public class CameraZoom : MonoBehaviour
{
public int zoomLevelSelected = 0;
public float[] ZoomLevels = new float[] { 60, 40, 20 };
void Update()
{
int zoomChange = 0;
if (Input.GetMouseButtonDown(0)) { zoomChange = +1; } // back
else if (Input.GetMouseButtonDown(1)) { zoomChange = -1; } // forward
if (zoomChange != 0)
{
zoomLevelSelected = Mathf.Clamp(zoomLevelSelected + zoomChange, 0, ZoomLevels.Length - 1);
camera.fieldOfView = ZoomLevels[zoomLevelSelected];
}
}
}
Heck with it, here is one way to create a click zoom. The gist is that you create a ray from your camera in to the scene through the mouse cursor. When that ray intersects an object create a second ray from the point of intersection back out along the intersecting face's normal.
void Update () {
if(Input.GetMouseButtonDown(0)){
// get ray from camera in to scene at the mouse position
Ray ray = Camera.mainCamera.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
// hardcoded "zoom" distance.
float zoomDist = 15.0f;
// Raycast from camera to mouse cursor, if object hit, zoom.
if (Physics.Raycast(ray,out hit,Mathf.Infinity)){
// Create a second ray from the hit object back out, zoom the camera along this ray.
Ray r = new Ray(hit.point,hit.normal);
Camera.mainCamera.transform.position = r.GetPoint(zoomDist);
}
}
}
Things to keep in mind:
Physics.Raycast, as written, will return true for any GameObject with a collider. Use layers if you only want to zoom when selecting specific GameObjects.
The camera won't directly center on the GameObject you click. I use the exact point of intersection to create the position where the camera will zoom to.
zoomDist is the distance away from the object.
This code only works with perspective cameras, if you use orthographic you'll need to modify the size value of the camera to zoom.
The problem with your script is that your var int zoomChange is getting set to zero every frame so move that variable to class level.
using UnityEngine;
using System.Collections;
public class CameraZoom : MonoBehaviour
{
public int zoomLevelSelected = 0;
public float[] ZoomLevels = new float[] { 60, 40, 20 };
int zoomChange = 0; //<<<<<<<<<<<<<
void Update()
{
if (Input.GetMouseButtonDown(0)) { zoomChange = +1; } // back
else if (Input.GetMouseButtonDown(1)) { zoomChange = -1; } // forward
if (zoomChange != 0)
{
zoomLevelSelected = Mathf.Clamp(zoomLevelSelected + zoomChange, 0, ZoomLevels.Length - 1);
camera.fieldOfView = ZoomLevels[zoomLevelSelected];
}
}
}
I am new to Unity and I am trying to figure out how to move the camera over a map/terrain using touch input. The camera would be looking down at the terrain with a rotation of (90,0,0). The terrain is on layer 8. I have had no problem getting it moving with keyboard, now I am trying to move to touch and it is very different if you want to keep expected usage on iOS.
The best example I can think of on a built in iOS app is Maps where the user would touch the screen and that point on the map would stay under the finger as long as the finger stayed on the screen. So as the user moves their finger the map appears to be moving with the finger. I have not been able to find examples that show how to do it this way. I have seen may examples of moving the camera or character with the mouse but they don't seem to translate well to this style.
Also posted on Unity3D Answers:
http://answers.unity3d.com/questions/283159/move-camera-over-terrain-using-touch-input.html
Below should be what you need. Note that it's tricky to get a 1 to 1 correspondence between finger/cursor and the terrain when using a perspective camera. If you change your camera to orthographic, the script below should give you a perfect map between finger/cursor position and map movement. With perspective you'll notice a slight offset.
You could also do this with ray tracing but I've found that route to be sloppy and not as intuitive.
Camera settings for testing (values are pulled from the inspector so apply them there):
Position: 0,20,0
Orientation: 90,0,0
Projection: Perspective/Orthographic
using UnityEngine;
using System.Collections;
public class ViewDrag : MonoBehaviour {
Vector3 hit_position = Vector3.zero;
Vector3 current_position = Vector3.zero;
Vector3 camera_position = Vector3.zero;
float z = 0.0f;
// Use this for initialization
void Start () {
}
void Update(){
if(Input.GetMouseButtonDown(0)){
hit_position = Input.mousePosition;
camera_position = transform.position;
}
if(Input.GetMouseButton(0)){
current_position = Input.mousePosition;
LeftMouseDrag();
}
}
void LeftMouseDrag(){
// From the Unity3D docs: "The z position is in world units from the camera." In my case I'm using the y-axis as height
// with my camera facing back down the y-axis. You can ignore this when the camera is orthograhic.
current_position.z = hit_position.z = camera_position.y;
// Get direction of movement. (Note: Don't normalize, the magnitude of change is going to be Vector3.Distance(current_position-hit_position)
// anyways.
Vector3 direction = Camera.main.ScreenToWorldPoint(current_position) - Camera.main.ScreenToWorldPoint(hit_position);
// Invert direction to that terrain appears to move with the mouse.
direction = direction * -1;
Vector3 position = camera_position + direction;
transform.position = position;
}
}
I've come up with this script (I have appended it to the camera):
private Vector2 worldStartPoint;
void Update () {
// only work with one touch
if (Input.touchCount == 1) {
Touch currentTouch = Input.GetTouch(0);
if (currentTouch.phase == TouchPhase.Began) {
this.worldStartPoint = this.getWorldPoint(currentTouch.position);
}
if (currentTouch.phase == TouchPhase.Moved) {
Vector2 worldDelta = this.getWorldPoint(currentTouch.position) - this.worldStartPoint;
Camera.main.transform.Translate(
-worldDelta.x,
-worldDelta.y,
0
);
}
}
}
// convert screen point to world point
private Vector2 getWorldPoint (Vector2 screenPoint) {
RaycastHit hit;
Physics.Raycast(Camera.main.ScreenPointToRay(screenPoint), out hit);
return hit.point;
}
Pavel's answer helped me a lot, so wanted to share my solution with the community in case it helps others. My scenario is a 3D world with an orthographic camera. A top-down style RTS I am working on. I want pan and zoom to work like Google Maps, where the mouse always stays at the same spot on the map when you pan and zoom. This script achieves this for me, and hopefully is robust enough to work for others' needs. I haven't tested it a ton, but I commented the heck out of it for beginners to learn from.
using UnityEngine;
// I usually attach this to my main camera, but in theory you can attach it to any object in scene, since it uses Camera.main instead of "this".
public class CameraMovement : MonoBehaviour
{
private Vector3 MouseDownPosition;
void Update()
{
// If mouse wheel scrolled vertically, apply zoom...
// TODO: Add pinch to zoom support (touch input)
if (Input.mouseScrollDelta.y != 0)
{
// Save location of mouse prior to zoom
var preZoomPosition = getWorldPoint(Input.mousePosition);
// Apply zoom (might want to multiply Input.mouseScrollDelta.y by some speed factor if you want faster/slower zooming
Camera.main.orthographicSize = Mathf.Clamp(Camera.main.orthographicSize + Input.mouseScrollDelta.y, 5, 80);
// How much did mouse move when we zoomed?
var delta = getWorldPoint(Input.mousePosition) - preZoomPosition;
// Rotate camera to top-down (right angle = 90) before applying adjustment (otherwise we get "slide" in direction of camera angle).
// TODO: If we allow camera to rotate on other axis we probably need to adjust that also. At any rate, you want camera pointing "straight down" for this part to work.
var rot = Camera.main.transform.localEulerAngles;
Camera.main.transform.localEulerAngles = new Vector3(90, rot.y, rot.z);
// Move the camera by the amount mouse moved, so that mouse is back in same position now.
Camera.main.transform.Translate(delta.x, delta.z, 0);
// Restore camera rotation
Camera.main.transform.localEulerAngles = rot;
}
// When mouse is first pressed, just save location of mouse/finger.
if (Input.GetMouseButtonDown(0))
{
MouseDownPosition = getWorldPoint(Input.mousePosition);
}
// While mouse button/finger is down...
if (Input.GetMouseButton(0))
{
// Total distance finger/mouse has moved while button is down
var delta = getWorldPoint(Input.mousePosition) - MouseDownPosition;
// Adjust camera by distance moved, so mouse/finger stays at exact location (in world, since we are using getWorldPoint for everything).
Camera.main.transform.Translate(delta.x, delta.z, 0);
}
}
// This works by casting a ray. For this to work well, this ray should always hit your "ground". Setup ignore layers if you need to ignore other colliders.
// Only tested this with a simple box collider as ground (just one flat ground).
private Vector3 getWorldPoint(Vector2 screenPoint)
{
RaycastHit hit;
Physics.Raycast(Camera.main.ScreenPointToRay(screenPoint), out hit);
return hit.point;
}
}
Based on the answer from Pavel, I simplified the script and removed the unlovely "jump" when touch with more then one finger and release the second finger:
private bool moreThenOneTouch = false;
private Vector3 worldStartPoint;
void Update() {
Touch currentTouch;
// only work with one touch
if (Input.touchCount == 1 && !moreThenOneTouch) {
currentTouch = Input.GetTouch(0);
if (currentTouch.phase == TouchPhase.Began) {
this.worldStartPoint = Camera.main.ScreenToWorldPoint(currentTouch.position);
}
if (currentTouch.phase == TouchPhase.Moved) {
Vector3 worldDelta = Camera.main.ScreenToWorldPoint(currentTouch.position) - this.worldStartPoint;
Camera.main.transform.Translate(
-worldDelta.x,
-worldDelta.y,
0
);
}
}
if (Input.touchCount > 1) {
moreThenOneTouch = true;
} else {
moreThenOneTouch = false;
if(Input.touchCount == 1)
this.worldStartPoint = Camera.main.ScreenToWorldPoint(Input.GetTouch(0).position);
}
}
using UnityEngine;
// I usually attach this to my main camera, but in theory you can attach it to any object in scene, since it uses Camera.main instead of "this".
public class CameraMovement : MonoBehaviour
{
private Vector3 MouseDownPosition;
void Update()
{
// If mouse wheel scrolled vertically, apply zoom...
// TODO: Add pinch to zoom support (touch input)
if (Input.mouseScrollDelta.y != 0)
{
// Save location of mouse prior to zoom
var preZoomPosition = getWorldPoint(Input.mousePosition);
// Apply zoom (might want to multiply Input.mouseScrollDelta.y by some speed factor if you want faster/slower zooming
Camera.main.orthographicSize = Mathf.Clamp(Camera.main.orthographicSize + Input.mouseScrollDelta.y, 5, 80);
// How much did mouse move when we zoomed?
var delta = getWorldPoint(Input.mousePosition) - preZoomPosition;
// Rotate camera to top-down (right angle = 90) before applying adjustment (otherwise we get "slide" in direction of camera angle).
// TODO: If we allow camera to rotate on other axis we probably need to adjust that also. At any rate, you want camera pointing "straight down" for this part to work.
var rot = Camera.main.transform.localEulerAngles;
Camera.main.transform.localEulerAngles = new Vector3(90, rot.y, rot.z);
// Move the camera by the amount mouse moved, so that mouse is back in same position now.
Camera.main.transform.Translate(delta.x, delta.z, 0);
// Restore camera rotation
Camera.main.transform.localEulerAngles = rot;
}
// When mouse is first pressed, just save location of mouse/finger.
if (Input.GetMouseButtonDown(0))
{
MouseDownPosition = getWorldPoint(Input.mousePosition);
}
// While mouse button/finger is down...
if (Input.GetMouseButton(0))
{
// Total distance finger/mouse has moved while button is down
var delta = getWorldPoint(Input.mousePosition) - MouseDownPosition;
// Adjust camera by distance moved, so mouse/finger stays at exact location (in world, since we are using getWorldPoint for everything).
Camera.main.transform.Translate(delta.x, delta.z, 0);
}
}
// This works by casting a ray. For this to work well, this ray should always hit your "ground". Setup ignore layers if you need to ignore other colliders.
// Only tested this with a simple box collider as ground (just one flat ground).
private Vector3 getWorldPoint(Vector2 screenPoint)
{
RaycastHit hit;
Physics.Raycast(Camera.main.ScreenPointToRay(screenPoint), out hit);
return hit.point;
}
}