I have this project where I click on an object and a canvas will show up to the player select an option inside the canvas. I needed the canvas to be world space so the player can move its head and the canvas will stay static in front. The problem is, there is a ton of objects around the scene and I need to update the position of the canvas everytime the player clicks an object.
I've tried to use "transform.position" but it doesn't work the way I wanted.
obs:
painel_escolha = canvas with panel;
transform_tela = camera.
painel_escolha.transform.position = transform_tela.transform.position;
Use this to move the canvas/panel in front of the camera and make it face the camera
// move the canvas distance meters in front of the camera
painel_escolha.transform.position = transform_tela.position + transform_tela.transform.forward * distance;
// make the canvas point in the same direction as the camera
painel_escolha.transform.rotation = transform_tela.transform.rotation;
Doing this in LateUpdate(! so after User input and position and rotation changes are processed) makes your panel absolutely head stable meaning it always stays in front of the user and he can't look away basically.
Often you rather want to use some kind of smoothed lerping instead e.g. doing
[Range(0,1)]
public float interpolationRatePosition = 0.5f;
[Range(0,1)]
public float interpolationRateRotation = 0.5f;
privtae Vector3 lastPosition;
private Quaternion lastRotation;
privtae void LateUpdate()
{
painel_escolha.transform.position = Vector3.Lerp(painel_escolha.transform.position, transform_tela.position, interpolationRatePosition);
painel_escolha.transform.rotation = Quaterinon.Lerp(painel_escolha.transform.rotation, transform_tela.rotation, interpolationRateRotation);
}
this resuts basically in the same end position but makes it looking a bit smoother.
Canvas positions are basically 2D screen positions. You could get the clicked objects world position with transform.position but for a canvas element to point to that (be on top of it) you need to transform that world position to the screen position via https://docs.unity3d.com/ScriptReference/Camera.WorldToScreenPoint.html
Related
I'm trying to move the player to the position of the mouse in my game.
Right now movement along the x axis works fine, but I want the mouse y axis to control the characters movement along the z axis (because of my top-down camera's y being world z).
Right now mouse y controls player y, which looks like this in game.
And the code for it looks like this:
public Vector2 mousePos;
private Vector3 playerPos;
void update()
{
// Get mouse position from player
mousePos = Input.mousePosition;
// Move player with mouse
playerPos = new Vector3(mousePos.x, 0, mousePos.y);
transform.position = Camera.main.ScreenToWorldPoint(playerPos);
}
I then tried to just swap the y and z like this
playerPos = new Vector3(mousePos.x, mousePos.y, 0);
But instead of allowing me to control the z axis this snippet instead causes the player to lose all movement.
I'm very new to coding so I might be missing something completely obvious. What am I doing wrong?
Unity version: 2018.4.21f1
The example you gave isn't working because you're providing a z coordinate of 0 and when called on a perspective camera, Camera.ScreenToWorldPoint does some vector math, so in your scene view, your player is probably floating right on top of your camera. I can't explain the actual math because I don't understand it, but luckily for both of us, we don't need to! Essentially, the z coordinate is saying how far away from the camera to place the point, and since the view frustum of a perspective camera gets narrower closer to the camera, placing it at 0 means there's nowhere for it to go. how moving the object farther from the camera requires it to move farther to match the mouse position.
The bigger problem is that your method is wrong and there's a better way. Camera.ScreenToWorldPoint converts a mouse coordinate to a world space coordinate depending on what the camera is looking at, so you're essentially flippng the y and z coordinate, then feeding it into a method that figures out what coordinates need to be flipped.
It looks like this script is attached to your player gameObject, so it should look like this:
Camera cam;
[Range(1,10)] //this just creates a slider in the inspector
public int distanceFromCamera;
void Start()
{
//calling Camera.main is a shortcut for
//FindGameObjectsWithTag("MainCamera")
//so you should avoid calling it every frame
cam = Camera.main;
}
void Update()
{
//This will work for both perspective and orthographic cameras
transform.position = cam.ScreenToWorldPoint(
Input.mousePosition + new Vector3(0,0,distanceFromCamera));
}
If your camera is going to move closer or farther from the plane, make sure to add something that keeps the distance from camera updated.
That said, for most games having the player tied to the mouse cursor isn't usually what you're looking for. Generally you want some kind of input to move the player in some direction a certain amount. If that's what you're looking for, this part of the space shooter tutorial is a good introduction to player movement (though the code itself may be outdated).
I have got a canvas in world space. A panel is the child of this canvas.
I have got another sphere that moves with reference to the MainCamera.
I would like to clamp the image to the Sphere in world space. Following is my code,
public class ClampImage : MonoBehaviour
{
public Camera FirstpersonCamera;
public GameObject panel;
void Update()
{
//get the position of the sphere in the worldspace
Vector3 spherePosition = FirstpersonCamera.WorldToScreenPoint(this.transform.position);
//assign the world position of the sphere to the image
panel.transform.position = spherePosition;
}
}
This script has been hooked to the sphere Gameobject. Unfortunately, the panel is not moving at all. How do I clamp the panel to the sphere?
Well it worked with a Screenspace Overlay because then your line
Vector3 spherePosition = FirstpersonCamera.WorldToScreenPoint(this.transform.position);
made sense and placed your panel in pixel space coordinates.
Now that your Panel is in Worldspace you do not want to convert the World-Space position into pixel display space anymore. So simply get rid of this step and rather directly use the world space position you already have
//assign the world position of the sphere to the image
panel.transform.position = transform.position;
Then to make it always look to the camera you could simply do
panel.transform.rotation = FirstPersonCamera.transform.rotation;
Or if you want it more correct and not depend on the direction you're looking in you can use
panel.transform.LookAt(panel.transform.position + Camera.transform.forward);
Since the forward vector of the panel has to actually look exactly away from the camera.
In case you want the image have an additional offset e.g. in front of the sphere you could also do
panel.transform.position = transform.position - FirstPersonCamera.transform.forward * SomeOffsetInUnits;
In a project I'm currently working on, I am having users click on an area for where they want to place things into the environment later on down the line. I'd like to visualize what they're placing with simple markers placed on the canvas, so that as they add and remove points the markers come and go as well.
I've found some resources on how to start, listing how to instantiate prefabs into the canvas, but it never seems to work for me. I feel it must have something to do with how I'm using coordinates but I'm not entirely sure.
public GameObject markerPrefab;
Then later on in another function
GameObject boatMarker = Instantiate(markerPrefab, Input.mousePosition, Quaternion.identity);
boatMarker.transform.SetParent(GameObject.FindGameObjectWithTag("Canvas").transform, false);
The code runs, and the prefabs do spawn into the scene, but they all appear in the top right hand corner of the canvas, all sort of one on top of the other. Any ideas what I've done wrong here? Also, while I don't want to ask you guys to write my code for me, any suggestions for a jumping point on how to remove specific instances of the prefab later down the line?
The main issue I'ld say is that you are using SetParent with the second parameter false
If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before.
In your case you want to keep the same world space position.
Since your canvas is Screenspace overlay its width and height (in Unity units) match exactly the display/window pixel width and height. Therefore when you do
GameObject boatMarker = Instantiate(markerPrefab, Input.mousePosition, Quaternion.identity);
The object already is in the correct position. To visualize that I just gave it a cube as child so you see it spawns already where I clicked (you can't see the image yet beacuase it's not a child of a Canvas):
What happens if you pass in that false parameter to the SetParent is that it doesn't keep it's current worldspace position but instead keeps its current localPosition and moves to that relative position within its parent. Since it is a Canvas and your prefab probably also using a RectTransform the resulting position depends on a lot of things like the e.g. the pivot and anchor settings of the prefab's RectTransform but also e.g. the Canvas Scaler -> Scale Factor.
If your prefab e.g. is anchored on the center (usually the default) and you click exactly on the center of your window it will appear on the upper right corner instead.
Why?
you click at (windowWidth / 2, WindowHeight / 2). So the prefab is originally spawned here
Than you use SetParent with false so it keeps that position but this time relative to the center of the Canvas
=> Center of canvas position is (windowWidth / 2, WindowHeight / 2) so adding the local prefab coordinates (windowWidth / 2, WindowHeight / 2) results in the final position (WindowWidth, WindowHeight) == upper right corner.
So you could fix that either by making the prefab beiing anchored on the lower left corner
or do not pass false as parameter to SetParent.
boatMarker.transform.SetParent(_canvas.transform);
you could actually then also do it in one single call
Instantiate(markerPrefab, Input.mousePosition, Quaternion.identity, _canvas.transform);
Additionally you should not use FindObjectWidthTag again and again. I would rather only get it once or even reference it via the Inspector if possible:
public GameObject markerPrefab;
[SerializeField] private Canvas _canvas;
private void Awake()
{
// If no canvas is provided get it by tag
if (!_canvas) _canvas = GameObject.FindGameObjectWithTag("Canvas").GetComponent<Canvas>();
}
// Update is called once per frame
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
Instantiate(markerPrefab, Input.mousePosition, Quaternion.identity, _canvas.transform);
}
}
Try This :
1- insert the button into the Canvas
2- get a Gameobject reference for this button
3- try this code after assigning your button to the button variable and the Game Canvas to the code Canvas variable, and it will work fine
public GameObject refButton;
public GameObject canvas;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
GameObject button = Instantiate(refButton, Input.mousePosition,Quaternion.identity);
button.transform.SetParent(canvas.transform);
}
}
The main camera's output is set to a render texture, which is applied to a material, which is applied to a quad that's scaled up to 128x72. The secondary camera is set to only see what is rendered to the child quad, who has the material with the render texture on it.
However Camera.main.ScreenToWorldPoint(Input.mousePosition) is returning values that aren't even close to the GameObject. I.E. The GameObject is instantiated at (0, 0, 0), and hovering over it shows the mouse at (307, 174). Moving the Rotating Object to the right edge of the screen will only return an x position of 64 (half of the 128px wide quad) so I'm not sure where the 300+ is coming from. Not sure if the quad/camera set up is responsible for this.
EDIT: Using a single orthographic camera, all properties the same except for using a render texture, instead of the setup I have now results in accurate ScreenToWorldPoint output.
The Input.mousePosition property will only return the x and y axis of the mouse position in pixels.
ScreenToWorldPoint requires the z axis too which Input.mousePosition doesn't provide. The z-axis value supposed to be the nearClipPlane of the camera. It will give you a position that's right in front of the camera.
Depending on the size of the 3D object you want to instantiate where mouse button is pressed, you will need to apply an offset to it to make it totally visible to the screen. For a simple cube created in Unity, an offset of 2 is fine. Anything bigger than that, you will need to increase the offset.
Below is a complete example of how to properly use ScreenToWorldPoint with Camera.nearClipPlane and an offset to instantiate a 3D object where mouse is clicked:
public GameObject prefab;
public float offset = 2f;
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Camera cam = Camera.main;
Vector2 mousePos = Vector3.zero;
mousePos.x = Input.mousePosition.x;
mousePos.y = Input.mousePosition.y;
Vector3 worldPoint = cam.ScreenToWorldPoint(new Vector3(mousePos.x, mousePos.y, cam.nearClipPlane + offset));
Instantiate(prefab, worldPoint, Quaternion.identity);
}
}
You may not be calling the Camera.ScreenToWorldPoint method correctly. In particular, the z position of the screen position parameter that's passed to this method should be defined as world units from the camera. See the Unity documentation on Camera.ScreenToWorldPoint.
Instead of Camera.main.ScreenToWorldPoint(Input.mousePosition), I think this is the correct way to call Camera.ScreenToWorldPoint:
var cameraPosition = Camera.main.transform.position;
// assuming `transform` is the transform "Virtual Screen Quad"...
float zWorldDistanceFromCamera = transform.position.z - cameraPosition.z;
var screenPoint = new Vector3(Input.mousePosition.x, Input.mousePosition.y, zWorldDistanceFromCamera);
var worldPoint = Camera.main.ScreenToWorldPoint(screenPoint);
Debug.LogFormat("mousePosition: {0} | zWorldDistanceFromCamera: {1} | worldPoint: {2}",
Input.mousePosition,
zWorldDistanceFromCamera,
worldPoint.ToString("F3"));
(If this isn't working, could you update your question or reply to this post with a comment with details showing the values that are logged at each step?)
I was just struggling with this problem and this question helped me find the answer, so thank you for posting it!
The issue has nothing to do with the z axis or how you're calling Camera.ScreenToWorldPoint. The issue is that the camera you're calling it on is rendering to a RenderTexture, and the dimensions of the RT don't match the dimensions of your game window. I wasn't able to find the implementation of the method in the reference source, but whatever it's doing is dependent on the resolution of the RenderTexture.
To test this, click the stats button in the game window to display the game window's screen size. The coordinates you get will match the ratio between that and the RenderTexture resolution.
Solutions:
Don't call this method on a camera targeting a rendertexture, either target the screen (none) or create a child camera that matches the position of the camera you need
Match the RT resolution to the screen. Obviously this may have performance implications, or cause issues if the screen size changes.
Don't use Camera.ScreenToWorldPoint. Depending on the use case, using a raycast may be simpler or more reliable.
Since using a default camera was returning the correct values, I simply added another one to detect the mouse position independent of the render texture/quad setup.
I'm trying to write a camera script which will maintain the perspective point which I'm looking at while rotating.
public void RegisterRotationControls()
{
var horizontal = CrossPlatformInputManager.GetAxis(InputAxisName.Horizontal.ToString());
offsetX = Quaternion.AngleAxis(horizontal * turnSpeed, Vector3.up) * offsetX;
transform.position = player.position + offsetX;
transform.LookAt(player.position + PlayerOffset);
}
Currently this script is only working properly when the camera is looking at the player, however the camera has freedom to move position yet the rotation should maintain relativity to the center.
The top row dipicts the current behavior, while the bottom depicts what should be occuring
Manually calculating the rotation to move a camera in a circle while retaining its original (relative) facing is somewhat cumbersome, and requires you to do a lot more math than you need to.
A popular solution is to parent the camera under an empty GameObject, and centering that GameObject on the point/object you want the camera to rotate around. (This basically internalizes a lot of the calculations, letting the engine do the heavy lifting.) Then, you can locally translate the camera as needed and then rotate the container GameObject to move the camera in a circle.