How to move a UI text relative to Camera movement in Unity?
Want to move a UItext relative to camera movement in unity.Tried code exanples such as
using UnityEngine;
using System.Collections;
public class SortingLayerExposer : MonoBehaviour
{
public string SortingLayerName = "Default";
public int SortingOrder = 0;
void Awake ()
{
gameObject.GetComponent<MeshRenderer> ().sortingLayerName = SortingLayerName;
gameObject.GetComponent<MeshRenderer> ().sortingOrder = SortingOrder;
}
}
But not able to move my text realtive to camera.
Easiest would be to add the texts to the camera-gameobject as children. They will automatically follow the camera. If you need to adjust the position further by script, you can modify the texts localpositions - as this will keep it relative.
Using the canvas builtin gameobject type, everything attatched under the canvas will follow the camera and stay in position relative to screensize. you can also lock ui elements in specific locations on the screen using anchor points
Related
(in Unity 3D)
Hi.
I want to show "HPBar" of the character on UI Canvas.
I tried the following method, but it didn't work normally.
Please tell me how to solve this problem...
/// <summary>
/// Follow target's position
/// </summary>
/// <param name="target">target(character's world position)</param>
void ChaseCharacter(Vector3 target)
{
// change world position to screen position (Main Camera)
Vector3 p1 = _worldCam.WorldToScreenPoint(target);
// change screen position to world position (UI Camera)
Vector3 p2 = _uiCam.ScreenToWorldPoint(p1);
// Apply UI Position
transform.position = p2;
}
I've built a minimal example, with a single camera. I guess with some small tweaks it should work on your multi-camera case.
public class UIFollow : MonoBehaviour
{
public GameObject target;
public Vector2 offset;
new private Camera camera;
void Start()
{
camera = GetComponentInParent<Canvas>().worldCamera;
}
void Update()
{
((RectTransform)transform).anchoredPosition = camera.WorldToScreenPoint(target.transform.position) + (Vector3)offset;
}
}
UIFollow is a Component on the "HealthBar" object inside the Canvas. Make sure it's anchors are set to the left bottom corner.
The offset vector is used to place the health bar above the object's center, it's public so it's easier to modify while designing the game.
From the hierarchy tab, add a new object, section UI then select Image, a canvas object should appear if not already created, inside should be the white image created , this Image should already follow your camera to every place because its in the UI screen section not near the player, just resize, make transparent and add your health bar inside it.
Thank you all for your advice.
I solved this problem, but I don't know how it was solved.
I'll upload the resolved code.
Vector3 p1 = _worldCam.WorldToScreenPoint(target);
p1.z = 100;
Vector3 p2 = _uiCam.ScreenToWorldPoint(p1);
transform.position = p2;
I'm trying to modify a script that lets the player zoom the camera. Before, scrolling to change the fov worked fine but i wasn't happy with the high fov stretching around the edges so I decided to switch to physical camera movement. For some reason my scroll input doesn't work anymore? This is my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class cameraManager : MonoBehaviour
{
public GameObject cameraAnchor;
public int cameraState;
public Transform playerPos;
public float maxZoomX;
public float maxZoomY;
public float zoomSensitivity;
// Update is called once per frame
void Update()
{
cameraAnchor.transform.position = (playerPos.position + new Vector3(6,10,0)); //camera anchor position relative to player
if (cameraState == 3){
Vector3 zoom = Camera.current.transform.position;
zoom.x += Input.GetAxis("Mouse ScrollWheel") * zoomSensitivity; // changing camera location with scroll wheel
zoom.y += Input.GetAxis("Mouse ScrollWheel") * zoomSensitivity; // changing camera location with scroll wheel
zoom.x = Mathf.Clamp(zoom.x, cameraAnchor.transform.position.x, cameraAnchor.transform.position.x + maxZoomX); // clamping zoom
zoom.y = Mathf.Clamp(zoom.y, cameraAnchor.transform.position.y, cameraAnchor.transform.position.y + maxZoomY); // clamping zoom
print(Input.GetAxis("Mouse ScrollWheel"));//testing scroll input
Camera.current.transform.position = zoom; //setting zoom
}
else{
Camera.current.transform.position = cameraAnchor.transform.position; //reset camera anchor if not in 3rd person
}
}
}
As you can see above, i've tested scrolling with print, which constantly produces a 0, regardless of whether i'm scrolling or not. I've looked at the input settings for the project and everything looks fine there.
Edit: here's a screenshot of my input page
I had this same problem. I loaded up a new project, copied the Mouse ScrollWheel entry from the input settings, and then pasted it into my old project. This resolved the issue. I didn't figure out what caused the issue in the first place.
I have been trying to experiment with moving UI Elements using the position of the mouse, Trying to make it behave like a cursor. I was able to move the Element when the Canvas Render Mode was set to "Screen Space". I was able to clamp it within the Canvas as well.
But I want it to work when the Canvas Render Mode is "World Space" as well. If I use the same code that I used for the Screen Space, the Element leaves its boundaries and gets messed up as the angles are being varied.
I really need help. Any clue how to do it?
You can view how the scene looks in this image below. : https://img.techpowerup.org/200624/screen.png
If Raycasting is the solution, can someone please help me out and provide a snippet of code or something.
What you are looking for is probably RectTransformUtility.ScreenPointToWorldPointInRectangle
Transform a screen space point to a position in world space that is on
the plane of the given RectTransform.
The cam parameter should be the camera associated with the screen
point. For a RectTransform in a Canvas set to Screen Space - Overlay
mode, the cam parameter should be null.
When ScreenPointToWorldPointInRectangle is used from within an event
handler that provides a PointerEventData object, the correct camera
can be obtained by using PointerEventData.enterEventData (for hover
functionality) or PointerEventData.pressEventCamera (for click
functionality). This will automatically use the correct camera (or
null) for the given event.
You didn't post your code but you could probably use it in a GraphicRaycaster.Raycast. Something like
GraphicRaycaster m_Raycaster;
PointerEventData m_PointerEventData;
EventSystem m_EventSystem;
Canvas canvas;
void Start()
{
//Fetch the Raycaster from the GameObject (the Canvas)
m_Raycaster = GetComponent<GraphicRaycaster>();
//Fetch the Event System from the Scene
m_EventSystem = GetComponent<EventSystem>();
canvas = GetComponnet<Canvas>();
//Set up the new Pointer Event
m_PointerEventData = new PointerEventData(m_EventSystem);
}
void Update()
{
//Check if the left Mouse button is clicked
if (Input.GetKey(KeyCode.Mouse0))
{
//Set the Pointer Event Position to that of the mouse position
m_PointerEventData.position = Input.mousePosition;
//Create a list of Raycast Results
List<RaycastResult> results = new List<RaycastResult>();
//Raycast using the Graphics Raycaster and mouse click position
m_Raycaster.Raycast(m_PointerEventData, results);
//For every result returned, output the name of the GameObject on the Canvas hit by the Ray
foreach (RaycastResult result in results)
{
Debug.Log("Hit " + result.gameObject.name);
// until here it was the API example for GraphicRaycaster.Raycast
// Now get the first hit object
var rect = result.gameObject.GetComponent<RectTransform>();
if(rect)
{
// check which camera to get (main or null for ScreenSpace Overlay)
var camera = canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : Camera.main;
if(RectTransformUtility.ScreenPointToWorldPointInRectangle(rect, result.screenPosition, camera, out var worldPosition))
{
cursor3D.transform.position = worldPosition;
cursor3D.transform.rotation = result.gameObject.transform.rotation;
break;
}
}
}
}
}
Especially note also
Attach this script to your Canvas GameObject.
Also attach a GraphicsRaycaster component to your canvas by clicking the Add Component button in the Inspector window.
Also make sure you have an EventSystem in your hierarchy.
I have two main problems in this case :
NAVI is child of the player (FPSController) and therefore the NAVI is moving along with the player. But NAVI is also position a bit in front of the player and the result is that the raycast is hitting first time all the time the NAVI instead hitting the Interactable object.
If there is a door between the player and some Interactable object it will detect the object. But if there is a door and other objects between the player and the interactable object/s it should not detect them. The player not see them but yet it's detecting.
This script is attached to the plaher (FPSController) :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class DetectInteractable : UnityEngine.MonoBehaviour
{
public Camera cam;
public float distanceToSee;
public string objectHit;
public bool interactableObject = false;
public Transform parentToSearch;
public Scaling scaling;
public int spinX = 0;
public int spinY = 0;
public int spinZ = 0;
public GameObject navi;
public GameObject itemsDescriptionCanvas;
public Text itemsDescriptionText;
private RaycastHit whatObjectHit;
private Transform[] childrenToSearhc;
private void Start()
{
childrenToSearhc = parentToSearch.GetComponentsInChildren<Transform>();
}
private void Update()
{
if (cam.enabled == true)
{
if (Input.GetMouseButtonDown(0) && !scaling.scaleUp)
{
if (whatObjectHit.collider != null)
ExecuteActions(whatObjectHit.collider.gameObject);
}
Debug.DrawRay(cam.transform.position, cam.transform.forward * distanceToSee, Color.magenta);
if (Physics.Raycast(cam.transform.position, cam.transform.forward, out whatObjectHit, distanceToSee))
{
objectHit = whatObjectHit.collider.gameObject.name;
interactableObject = true;
print("Hit ! " + whatObjectHit.collider.gameObject.name);
if (scaling.objectToScale.transform.localScale == scaling.minSize)
{
scaling.objectToScale.transform.Rotate(spinX, spinY, spinZ);
}
ProcessItemsDescripations();
itemsDescriptionCanvas.SetActive(true);
}
else
{
if (scaling.objectToScale.transform.localScale == scaling.minSize)
{
navi.transform.rotation = new Quaternion(0, 0, 0, 0);
}
itemsDescriptionCanvas.SetActive(false);
print("Not Hit !");
}
}
}
private void ExecuteActions(GameObject go)
{
var ia = go.GetComponent<ItemAction>();
if (ia != null)
{
ia.ItemMove();
}
}
void ProcessItemsDescripations()
{
foreach (Transform child in childrenToSearhc)
{
if (child.GetComponent<ItemInformation>() != null)
{
ItemInformation iteminformation = child.GetComponent<ItemInformation>();
if (child.name == objectHit)
{
itemsDescriptionText.text = iteminformation.description;
}
}
}
}
public class ViewableObject : UnityEngine.MonoBehaviour
{
public string displayText;
public bool isInteractable;
}
}
And this small script is attach to each Interactable object :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ItemInformation : UnityEngine.MonoBehaviour
{
[TextArea]
public string description;
}
This is a screenshot of the player (FPSController) Inspector settings :
This screenshot is of the NAVI that is child of the player :
You can see the NAVI at the left game window and scene window :
This is a screenshot example of the window. The window should be Interactable object and should be detected by the raycast :
The window Tag is set to Untagged and the Layer to Default
This is a screenshot while the game is working a situation when the player is standing behind a door in the other side of the door there is a room with the Interactable window. Nothing will prevent from the raycast to pas through the door and detect the window. And that's not logic. The player can't see the window. So the window should not be detected :
On the left the top the magenta color of the raycast pass through the door to the window. On the bottom left the player is standing behind the door and see the window at all but it's detecting the window :
But when using a breakpoint you can see that what was really hit is the NAVI not the door not the window but the NAVI since the NAVI is always standing in front of the player :
Before all that I tried to use raycast mask layer in my script and then changed the window layer to be Interactable and then I saw it's detecting through the door.
At the end the main goal is :
To detect Interactable objects in the game but not to detect the NAVI he is not Interactable object so somehow to make that NAVI will not block the raycast !
And to be able to detect bject only when there is a logic view of the player to the objects. And not to detect objects from behind doors or walls or other objects.
You can use the additional optional parameter layerMask of Physics.Raycast
A Layer mask that is used to selectively ignore Colliders when casting a ray.
I would recommned to use a LayerMask
public LayerMask targetLayer;
configure the wanted layers using the Unity Inspector (select every Layer you want to hit) and finally pass it to the raycast like
if(Physics.Raycast(
cam.transform.position,
cam.transform.forward,
out whatObjectHit,
distanceToSee,
targetLayer.value))
{
...
}
In the example Unity used a bitmask directly (btw. in a very unefficient way in Update):
// 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;
as said I didn't like this way of hardcoding it. Additionally you now only exclude the layer 8 but hit everything else.
To rather include only exactly one specific layer remove the line
layerMask = ~layerMask;
so you only hit layer 8.
Anyway using my way from before is way more flexible and you rather select those layers you actually want to hit instead of excluding one. And using the LayerMask is way easier than having to code some bitmask operations in order to construct the layermask you want.
I'm looking to create a piece of terrain in unity using only a script (c# preferably) to do this rather than the menu options on the editor. So far I only have this code below, but I don't know what to do next to get it to appear on the scene, can anyone help?
Thank you
using UnityEngine;
using System.Collections;
public class terraintest : MonoBehaviour {
// Use this for initialization
void Start () {
GameObject terrain = new GameObject();
TerrainData _terraindata = new TerrainData();
terrain = Terrain.CreateTerrainGameObject(_terraindata);
}
// Update is called once per frame
void Update () {
}
}
Simply adding :
Vector3 position = ... //the ingame position you want your terrain at
GameObject ingameTerrainGameObject = Instantiate(terrain, position, Quaternion.identity);
should make the terrain appear ingame. The Instantiate method returns a reference to the gameobject spawned ingame, so if you later want to access it, you can use that reference.