Unity Align prefab to top left (2D Game) - c#

I'm making my first game where obstacles (which are prefabs) are placed by a script into a scene. They are all different sizes in a 2D environment. I am placing them using this code below
Instantiate(normal1, new Vector3(x, y , 0), Quaternion.identity);
This works perfectly, except that I need all of the obstacles to be positioned from the top left. So if I would have 0, 0 for my x and y, only the obstacle's corner would be covering the 0, 0 position, not the entire thing. From the little I've worked with GUI elements, you can align to however you like. Is this the same with prefab, and if so, how? Or can you somehow move the origin of the object?

I assume you are talking about non-UI elements.
The easiest thing would be to give your objects a parent GameObject and arrange them in a way so the parent GameObject already has the pivot where you want it (the top-left corner). You do this by first positioning the (future) parent object correctly and then simply drag the child object into it in the hierachy (while it keeps its current position).
Then in your script you have to use Camera.ScreenToWorldPoint in order to find the top-left screen corner position in the 3D world like
// an optional offset from the top-left corner in pixels
Vector2 PixelOffsetFromTopLeft;
// Top Left pixel is at
// x = 0
// y = Screen height
// and than add the desired offset
var spawnpos = new Vector2(0 + PixelOffsetFromTopLeft.x, Screen.height - PixelOffsetFromTopLeft.y);
// as z we want a given distance to camera (e.g. 2 Units in front)
spawnedObject = Instantiate(Prefab, camera.ScreenToWorldPoint(new Vector3(spawnpos.x, spawnpos.y, 2f)), Quaternion.identity);
Full script I used as example
public class Spawner : MonoBehaviour
{
public GameObject Prefab;
public Vector2 PixelOffsetFromTopLeft = Vector2.zero;
private GameObject spawnedObject;
private Camera camera;
private void Start()
{
camera = Camera.main;
}
private void Update()
{
if (Input.anyKeyDown)
{
// you don't need this I just didn't want to mess up the scene
// so I just destroy the last spawned object before placing a new one
if (spawnedObject)
{
Destroy(spawnedObject);
}
// Top Left pixel is at
// x = 0
// y = Screen height
// and than add the desired offset
var spawnpos = new Vector2(0 + PixelOffsetFromTopLeft.x, Screen.height - PixelOffsetFromTopLeft.y);
// as z we want a given distance to camera (e.g. 2 Units in front)
spawnedObject = Instantiate(Prefab, camera.ScreenToWorldPoint(new Vector3(spawnpos.x, spawnpos.y, 2f)), Quaternion.identity);
}
}
}
This will now always spawn the object anchored to the top left. It will not keep it this way if the camera moves or the window is resized.
If your question was about keeping that position also when the window is resized you can use the same code e.g. in an Update method (later you due to efficiency you should only call it when really needed / window actually was resized) like
public class StickToTopLeft : MonoBehaviour
{
private Camera camera;
public Vector2 PixelOffsetFromTopLeft = Vector2.zero;
private void Start()
{
camera = Camera.main;
}
// Update is called once per frame
private void Update()
{
var spawnpos = new Vector2(0 + PixelOffsetFromTopLeft.x, Screen.height - PixelOffsetFromTopLeft.y);
// This time simply stay at the current distance to camera
transform.position = camera.ScreenToWorldPoint(new Vector3(spawnpos.x, spawnpos.y, Vector3.Distance(camera.transform.position, transform.position)));
}
}
This now keeps the object always anchored to the top-left corner also after resizing the window and allows to adjust its offset afterwards.

Related

How do I get the mouse position on a gameobject in unity?

I have currently started a developing a pixel art program in unity. I started with getting the mouse coordinates(x, y) on the ui image(texture) I used the code below but the get the mouse position in world space. I wanna get it in the space of the ui image only. The texture image is 32, 32 pixel wide and tall (x, y respected)
[SerializeField] private Text coordinatesText;
private Vector3 mousePos;
private float mouseX, mouseY;
void Update()
{
mousePos = Input.mousePosition;
mouseX = mousePos.x;
mouseY = mousePos.y;
coordinatesText.text = (mouseX + ", " + mouseY).ToString();
}
For like, when I hover the mouse on the very bottom left of the texture, which is a ui image, I get 0, 0 and when it's in the very top right, it's 32, 32.
Thanks in advance for your help!
Depending on the Render Mode your Canvas is set to, the answer differs ever so slightly. I also want to point out, RectTransform by default has a pivot point on its center, so the (0,0) will be in the center, not on the bottom-left.
The function you are looking for is RectTransformUtility. ScreenPointToLocalPointInRectangle. To use it, you need the RectTransform of your UI object, if the Render Mode of your Canvas is set to ScreenSpaceCamera, you will need the camera and the screen space position, which is the mouse position. I mentioned that it was different depending on the RenderMode due to the camera parameter needing to be null if your RenderMode is OverlaySpace for the function to work.
Another note, you will most likely want to implement IHandlers to know when the cursor is inside of the specific UI element.
Here is some example code:
using UnityEngine;
using UnityEngine.EventSystems;
public class TestScript : MonoBehaviour, IPointerClickHandler
{
[SerializeField] private Canvas parentCanvas = null; // the parent canvas of this UI - only needed to determine if we need the camera
[SerializeField] private RectTransform rect = null; // the recttransform of the UI object
// you can serialize this as well - do NOT assign it if the canvas render mode is overlay though
private Camera UICamera = null; // the camera that is rendering this UI
private void Start()
{
if (rect == null)
rect = GetComponent<RectTransform>();
if (parentCanvas == null)
parentCanvas = GetComponentInParent<Canvas>();
if (UICamera == null && parentCanvas.renderMode == RenderMode.ScreenSpaceCamera)
UICamera = parentCanvas.worldCamera;
}
public void OnPointerClick(PointerEventData eventData)
{
// this UI element has been clicked by the mouse so determine the local position on your UI element
RectTransformUtility.ScreenPointToLocalPointInRectangle(rect, eventData.position, UICamera, out Vector2 localPos);
// we now have the local click position of our rect transform, but as you want the (0,0) to be bottom-left aligned, need to adjust it
localPos.x += rect.rect.width / 2f;
localPos.y += rect.rect.height / 2f;
Debug.Log(localPos);
}
}
Here is a gif of the code working in a test scene. The image I have has a width and height of 32. I zoomed in the window so the clicks could be as closer to the edge to show the coordinates are properly set.

Unity3D Placing a cube in front VR player and move with player position and rotation

I am developing a VR scene using Oculus Quest. I need to place a cube in front and a few units below the user eye level. The cube has to move when the user moves. E.g. if the user turns or moves the cube user secure its position by always staying in front. How can I do this?
Should I take the OVRCameraRig position and rotation and apply that to the cube? But I read from StackOverflow question Unity3D Getting position of OVRCameraRig that OVRCameraRig doesn't move and needs to consider the CenterEyeAnchor details.
Can I extract CenterEyeAnchor rotation and position as below?
OVRCameraRig overCameraRig;
var position = overCameraRig.centerEyeAnchor.position;
var rotation = overCameraRig.centerEyeAnchor.rotation;
//OR should I use lines below?
var position = overCameraRig.centerEyeAnchor.transform.position;
var rotation = overCameraRig.centerEyeAnchor.transform.rotation;
How can I apply the extracted potion and rotation of CenterEyeAnchor to the cube gameObject if that is going to solve this problem?
I tried the following script and add it to the cube but the cube didn't move with the user movement.
Further, I see "Test position" log but no "Normal " or "Transform " logs.
public class MoveCube : MonoBehaviour
{
private OVRCameraRig overCameraRig;
public float offset = 10;
private GameObject itemObject;
private float distance = 3.0f;
// Update is called once per frame
void Update()
{
Debug.Log("Test position");
Debug.Log("Normal " +overCameraRig.centerEyeAnchor.position);
Debug.Log("Transform " + overCameraRig.centerEyeAnchor.transform.position);
if (itemObject != null)
{
itemObject.transform.position = overCameraRig.centerEyeAnchor.transform.position + overCameraRig.centerEyeAnchor.transform.forward * distance;
itemObject.transform.rotation = new Quaternion(0.0f, overCameraRig.centerEyeAnchor.transform.rotation.y, 0.0f, overCameraRig.centerEyeAnchor.transform.rotation.w);
}
}
}
The best thing to do is set the cube as a child of the player in the hierarchy.
only will that not rotate with the player, but i suspect that your camera is on your player so do this:
but this will rotate on all axes. you can also freeze its rotation using the rigidbody freeze rotation.

Why the player is falling down after moving it's position?

I created a glass with reflection.
The idea is when the player is getting close to a window to see the player reflection like the player is looking on the window.
The glass reflection is working and it's disabled in the Hierarchy. I'm enabling the glass reflection later in the game.
This is a screenshot of the glass when it's turned on :
I positioned the glass to on the window :
This is the player screenshot. The player have a child name NAVI. The NAVI is positioned a bit in front of the player :
The player have a Rigidbody. In the screenshot on the left the NAVI object and in the right the player Inspector settings :
And this script is attached to the window not to the glass but to the window :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ItemAction : MonoBehaviour
{
public RotateCamera rotatecamera;
public GameObject glass;
public GameObject player;
public void Init()
{
player.transform.localPosition = new Vector3(transform.localPosition.x - 1, transform.localPosition.y, transform.localPosition.z);
glass.SetActive(true);
}
}
This is a screenshot of the window with the script attached to it :
The idea is when some where in the game when it's calling the method Init it will position the player as much as close to the window to see the player reflection on the glass.
But instead the player is falling down through the floor and keep falling nonstop.
You're using the window's local position to set the player's local position. Instead, use position and only change the player's x component:
float paddingDistance = 1f; // Change this to adjust the padding
player.transform.position = new Vector3(transform.position.x - paddingDistance,
player.transform.position.y,
player.transform.position.z);
The problem with this is that it only works for glass rotated in exactly such a way. If you want to make this work for any such glass orientation, you can make a plane parallel to the glass but separated some distance and move the player to the closest point on the plane. Assuming the glass is normal to its transform.left direction:
float paddingDistance = 1f; // Change this to adjust the padding
Vector3 paddingPoint = transform.position + transform.left * paddingDistance;
float paddingToPlayerAlongLeft = Vector3.Dot(transform.left,
player.transform.position - paddingPoint);
// closest point to player.transform.position on padding plane
player.transform.position = player.transform.position
- paddingToPlayerAlongLeft * transform.left;
But even this only works for one side of the glass. If you wanted to make this work for either side of the glass, you need to determine if the player's on the left side or not (we can do this using Vector3.Dot), then make the plane in the corresponding direction:
float paddingDistance = 1f;// Change this to adjust the padding
float glassToPlayerAlongLeft = Vector3.Dot(player.transform.position - transform.position,
transform.left);
float paddingLeftMod = glassToPlayerAlongLeft > 0f ? 1f : -1f;
float paddingToPlayerAlongLeft = glassToPlayerAlongLeft - paddingDistance * paddingLeftMod;
// closest point to player.transform.position on padding plane
player.transform.position = player.transform.position
- paddingToPlayerAlongLeft * transform.left;
However, even this only works if the glass is perfectly vertical. If the glass is not always vertical, then you could find the line intersection of the horizontal plane at the player's height and the padding plane and then set the player's position to the closest point on that line

Move UI RectTransform to world position

I have a GameObject named Player, and a Canvas. The HealthBar GameObject is a child of the Canvas (Canvas>HealthBar). I have the HP bar following the player with a script. It is supposed to follow the player at a fixed position above it, so it sits near the top of the sprite.
Here is the 'follow' part of the HP Follow Script.
void Update ()
{
Vector3 currentPos = transform.position;
Vector3 playerPos = Camera.main.WorldToViewportPoint (Player.transform.position);
transform.position = playerPos;
The problem is that the HP bar moves with the character, but at a very small fraction of the speed of which the player is moving. For example, if the player moves one unit, the bar moves 0.1 units.
Video of error: https://streamable.com/vaz7h
You want to make a UI Object(Image) follow a GameObject is a SpriteRenderer or MeshRenderer.
In another way, this can be described as converting world point to UI point.
This simple function below converts world position to UI space. It takes a the Canvas of the UI as parameter then the position you want to convert to UI position which in your case is the Player.transform.position variable.
The key for moving a UI object is the RectTransformUtility.ScreenPointToLocalPointInRectangle function which converts the screen point to ui rectangle local point.
public Vector3 worldToUISpace(Canvas parentCanvas, Vector3 worldPos)
{
//Convert the world for screen point so that it can be used with ScreenPointToLocalPointInRectangle function
Vector3 screenPos = Camera.main.WorldToScreenPoint(worldPos);
Vector2 movePos;
//Convert the screenpoint to ui rectangle local point
RectTransformUtility.ScreenPointToLocalPointInRectangle(parentCanvas.transform as RectTransform, screenPos, parentCanvas.worldCamera, out movePos);
//Convert the local point to world point
return parentCanvas.transform.TransformPoint(movePos);
}
Usage:
public GameObject Player;
public Canvas canvas;
void Update()
{
//Convert the player's position to the UI space then apply the offset
transform.position = worldToUISpace(canvas, Player.transform.position);
}
Now, let's say that you have already positioned the UI to be where it is supposed to be and you now want it to follow another Object(Player), you need to implement a simple offset with the code above. This is really easy. Below is what it is supposed to look like:
public GameObject Player;
public Canvas canvas;
Vector3 Offset = Vector3.zero;
void Start()
{
Offset = transform.position - worldToUISpace(canvas, Player.transform.position);
}
void Update()
{
//Convert the player's position to the UI space then apply the offset
transform.position = worldToUISpace(canvas, Player.transform.position) + Offset;
}
public Vector3 worldToUISpace(Canvas parentCanvas, Vector3 worldPos)
{
//Convert the world for screen point so that it can be used with ScreenPointToLocalPointInRectangle function
Vector3 screenPos = Camera.main.WorldToScreenPoint(worldPos);
Vector2 movePos;
//Convert the screenpoint to ui rectangle local point
RectTransformUtility.ScreenPointToLocalPointInRectangle(parentCanvas.transform as RectTransform, screenPos, parentCanvas.worldCamera, out movePos);
//Convert the local point to world point
return parentCanvas.transform.TransformPoint(movePos);
}
If the canvas is ScreenSpace, you can use:
healthbarTransform.position = camera.WorldToScreenPoint(playerTransform.position);
(if it is screen space, it can be child of player or whatever as long as you set the position every frame)
.... About your problem, there are some factors that will solve the issue:
You are setting the healthbar position in the update function, to let unity know to execute this code after player moves for sure, you need to add HPFollow script to below Default time in the script execution order (if you want to use Update).
Or you can use lateUpdate to set the ui positions. which I highly recommend.
If your problem is just flickering ui, it should be about ui rect transform scales...
Here's the solution:
void Start () {
Canvas.willRenderCanvases += canvasUpdate;
}
void canvasUpdate () {
var screenPoint = RectTransformUtility.WorldToScreenPoint( canvas.worldCamera, worldPoint );
Vector2 localPoint;
RectTransformUtility.ScreenPointToLocalPointInRectangle( canvas.GetComponent<RectTransform>(), screenPoint, canvas.worldCamera, out localPoint );
transform.localPosition = localPoint;
}

something wrong with rotation controls in 2d top down shooter in unity3d

I am trying to get my character to face the mouse by rotating on its y axis. It rotates a little, but it do not face the mouse. The Camera's position is directly above the player, and its in orthographic view I see many other examples of top down shooters and there almost exactly like mine so i don't know whats the problem please help thank you.
using UnityEngine;
using System.Collections;
public class playermovementscript : MonoBehaviour {
public float movementspeed = 5f;
public float bulletspeed = 10f;
public GameObject bullet;
public GameObject shooter;
public Vector3 target;
public Camera camera;
// Use this for initialization
void Start () {
//refrence to main camera
Camera camera;
}
// Update is called once per frame
void Update () {
//call movement function in every frame
movement ();
// cheek for input of f in every frame if true execute shoot function
if (Input.GetKeyDown (KeyCode.F)) {
shoot();
}
}
void movement(){
// makes refrence to gameobjects rigidbody and makes its velocity varible to equal values of axis x and y
gameObject.GetComponent<Rigidbody>().velocity = new Vector3(Input.GetAxisRaw("Horizontal")*movementspeed,0,Input.GetAxisRaw("Vertical")*movementspeed);
// makes vector 3 type target equall to mouse position on pixial cordinates converted in to real world coordniates
target = camera.ViewportToWorldPoint (new Vector3(Input.mousePosition.x,Input.mousePosition.y,camera.transform.position.y - transform.position.y));
//target.x -= transform.position.x;
//target.z -= transform.position.z;
// states the vector 3 values of target
Debug.Log (target);
// makes object local z face target and iniziates up axis
transform.LookAt (target,Vector3.up);
}
Going to make an attempt to explain what's going on...
target = camera.ViewportToWorldPoint (new Vector3(Input.mousePosition.x,Input.mousePosition.y,camera.transform.position.y - transform.position.y));
The above code is trying to convert a mouse position which is in Screen Space (which measures the position from (0,0) to the width/height of the screen in pixels) to a Viewport Space (which measure the same screen but with a different measure, it measures the positions from (0,0) to (1,1)).
In Unity
Screen Space Measurement: Origin (0,0) to the max width/height
Viewport Measurement: Origin (0,0) to (1,1)
Reference: http://docs.unity3d.com/ScriptReference/Camera.html
If you desire to still use "ViewportToWorldPoint" then you could do a "ScreenToViewportPoint" then follow it with a "ViewPortToWorldPoint".
Otherwise, you could look into using "ScreenToWorldPoint" from the start.

Categories