- Home /
Get a (GUI) 2d-arrow to turn to a specific position in 3d-space (similar to a compass)
I've been fiddling with this for a couple of days now, trying out both GUI-skins and 3d-objects but can't seem to get it working properly. I've tried out this solution http://forum.unity3d.com/threads/43967-compass-on-GUI-rotate-with-camera! but it doesn't really apply to what I'm trying to achieve.
So what I'm trying to do is an arrow as GUI that moves around a pivot in the center of the screen, looking at new waypoints. The directions can be on all axises as the player is a flying object (which can roll, turn and elevate in all angles) but the arrow should always remain flat towards the camera but rotate around its z-axis towards the active waypoint. When the object is close enough in angle (when the player looks directly at it) it's supposed to fade out. The big problem is to get the arrow to look at the object with the correct rotation and still remain flat to the camera.
This could also be used as a damage direction notifier (where the bullet came from and hit the player) for instance in an FPS, showing hits as a clock around a pivot centered to the screen. How would you solve such a thing? I've been looking into WorldToScreenPoint and angles, but can't figure it out - the more I dig the more confused I get.
I've gone completely blind here, a little help would be much appreciated!
Edit: This is what I've come up with so far:
var cam : Camera; private var targetPos : Vector3; private var screenMiddle : Vector3; private var angle : float = 0.0;
function Update() { targetPos = cam.WorldToScreenPoint (CheckpointScript.nextCheckpoint.transform.position); screenMiddle = Vector3(Screen.width/2, Screen.height/2, 0); angle = Mathf.Atan2(targetPos.x-screenMiddle.x, targetPos.y-screenMiddle.y) * 180 / Mathf.PI; print(angle); }
This prints out the angle between the object and the target (which is set in the CheckpointScript). My problem is to set the eulerAngles so the arrow only rotates on one axis (to remain flat towards the camera). Besides from having a blonde moment here, what am I supposed to do?
Answer by save · May 15, 2011 at 09:21 PM
Working with rotations can be really tricky especially when you have to work with 2d to 3d coordinates. If someone want to implement this here's a quick version:
var cam : Camera; //Camera to use var target : Transform; //Target to point at (you could set this to any gameObject dynamically) private var targetPos : Vector3; //Target position on screen private var screenMiddle : Vector3; //Middle of the screen
function Update() { //Get the targets position on screen into a Vector3 targetPos = cam.WorldToScreenPoint (target.transform.position); //Get the middle of the screen into a Vector3 screenMiddle = Vector3(Screen.width/2, Screen.height/2, 0); //Compute the angle from screenMiddle to targetPos var tarAngle = (Mathf.Atan2(targetPos.x-screenMiddle.x,Screen.height-targetPos.y-screenMiddle.y) * Mathf.Rad2Deg)+90; if (tarAngle < 0) tarAngle +=360;
//Calculate the angle from the camera to the target
var targetDir = target.transform.position - cam.transform.position;
var forward = cam.transform.forward;
var angle = Vector3.Angle(targetDir, forward);
//If the angle exceeds 90deg inverse the rotation to point correctly
if(angle<90){
transform.localRotation = Quaternion.Euler(-tarAngle,90,270);
} else {
transform.localRotation = Quaternion.Euler(tarAngle,270,90);
}
}
The GUI in 3d-space has to be put relative to the camera at all times (child to main camera or a camera only seeing the GUI-element rendered on top of the main camera). Preferably the last mentioned as you mostly wouldn't want the GUI to fall behind objects in 3d-space. Big thanks to The_r0nin and the tarAngle computation.
Answer by The_r0nin · May 14, 2011 at 05:42 PM
Here's how I make an airplane rotate based on the direction of the mouse pointer from the center of the screen. This should be able to be modified to do what you want (use a WorldToScreenPoint to get your point, or just use the direction based on subtracting Vector3s):
screenVec = Vector3(Input.mousePosition.x - Global.sCenter.x, Global.sHeight - Input.mousePosition.y - Global.sCenter.y, 0);
tarAngle= (Mathf.Atan2(screenVec.y,screenVec.x) * Mathf.Rad2Deg)+90;
if (tarAngle < 0) tarAngle +=360;
if (tarAngle != 0) transform.RotateAround(transform.position,transform.forward, -tarAngle);
Global.sCenter is the screen center pixel coordinates. I simply find the angle between the mouse pointer and the center of the screen (using Atan2) and then roll the target using RotateAround the z-axis of the transform. You could roll your object around its z-axis using the relative x & y coordinate difference...
Thanks r0nin, pushes in the right direction. But this really beats me, I have modified it a bit to fit my code:
targetPos = cam.WorldToScreenPoint (target.position); screenVec = Vector3(targetPos.x - Screen.width/2, Screen.height - targetPos.y - Screen.height/2, 0); var tarAngle = ($$anonymous$$athf.Atan2(screenVec.y,screenVec.x) * $$anonymous$$athf.Rad2Deg)+90;
But what I don't understand is how to rotate the arrow (remaining flat) while pointing towards tarAngle, I've tried with setting the eulers but everything just winds up wrong. It would help so much if I could see a code example of that, if you have the time
If someone could just point out how I could rotate the arrow (which is in 3d space and set to a gui-camera) according to the angle variable in my first post I'd appreciate it a bunch!
Direction is solved, get back soon with an answer, thanks r0nin!
Did you ever find out how to set the rotation of a GUITexture?
Answer by Antony-Blackett · May 14, 2011 at 01:50 PM
If you use a 3D arrow and place it in front of the camera you can use the Transform.LookAt() function to set its facing direction. If you use the camera's up vector is should keep the arrow's up vector the same as the camera's up vector.
void Update()
{
arrowObject.transform.LookAt( waypoint.transform, cam.transform.up );
}
Thanks for the fast answer. This solution does unfortunately not work as the arrow still rotates around all degrees. I'll update my problem with an image describing it better hopefully.
Answer by arckex · Aug 04, 2015 at 08:49 PM
I projected the distance of my target onto a point roughly the same distance away but in front of my camera, and then found the angle from that projected point's up vector required to make it rotate and point towards the target.
Vector3 up = targetTransform.up;
float dist = Vector3.Magnitude(targetTransform.position - camera.position);
Vector3 over = targetTransform.position - (camera.position + camera.forward * dist);
float ang = Vector3.Angle(up, over);
if (over.x < 0)
this.transform.localRotation = Quaternion.Euler(0, 0, ang);
else
this.transform.localRotation = Quaternion.Euler(0, 0, -ang);
Answer by Cherno · Aug 04, 2015 at 09:05 PM
Here's a sccript I found online, it allows to to rotate any GUI element around a pivot; I have used it as a player marker myself.
using UnityEngine;
[ExecuteInEditMode()]
public class RotatableGuiItem : MonoBehaviour
{
public Texture2D texture = null;
public float angle = 0;
public Vector2 size = new Vector2(128, 128);
//this will overwrite the items position
public AlignmentScreenpoint ScreenpointToAlign = AlignmentScreenpoint.TopLeft;
public Vector2 relativePosition = new Vector2(0, 0);
Vector2 pos = new Vector2(0, 0);
Rect rect;
Vector2 pivot;
void Start()
{
UpdateSettings();
}
public void UpdateSettings()
{
Vector2 cornerPos = new Vector2(0, 0);
//overwrite the items position
switch (ScreenpointToAlign)
{
case AlignmentScreenpoint.TopLeft:
cornerPos =new Vector2(0, 0);
break;
case AlignmentScreenpoint.TopMiddle:
cornerPos =new Vector2(Screen.width/2, 0);
break;
case AlignmentScreenpoint.TopRight:
cornerPos = new Vector2(Screen.width, 0);
break;
case AlignmentScreenpoint.LeftMiddle:
cornerPos = new Vector2(0, Screen.height / 2);
break;
case AlignmentScreenpoint.RightMiddle:
cornerPos = new Vector2(Screen.width, Screen.height / 2);
break;
case AlignmentScreenpoint.BottomLeft:
cornerPos = new Vector2(0, Screen.height);
break;
case AlignmentScreenpoint.BottomMiddle:
cornerPos = new Vector2(Screen.width/2, Screen.height);
break;
case AlignmentScreenpoint.BottomRight:
cornerPos = new Vector2(Screen.width, Screen.height);
break;
default:
break;
}
pos = cornerPos + relativePosition;
rect = new Rect(pos.x - size.x * 0.5f, pos.y - size.y * 0.5f, size.x, size.y);
pivot = new Vector2(rect.xMin + rect.width * 0.5f, rect.yMin + rect.height * 0.5f);
}
void OnGUI() {
GUI.depth = 1;
if (Application.isEditor)
{
UpdateSettings();
}
Matrix4x4 matrixBackup = GUI.matrix;
GUIUtility.RotateAroundPivot(angle, pivot);
GUI.DrawTexture(rect, texture);
GUI.matrix = matrixBackup;
}
public enum AlignmentScreenpoint {
TopLeft, TopMiddle, TopRight,
LeftMiddle, RightMiddle,
BottomLeft, BottomMiddle, BottomRight
}
}
Your answer
Follow this Question
Related Questions
How to make a 2D Horizontal Compass? 1 Answer
compass GUI jumps at angle 180. 0 Answers
ExecuteEvents.Execute makes game crash 0 Answers
Can I set a specific Pointer for the EventTrigger on Buttons for example? 0 Answers
EventTrigger runtime added callbacks to OnPointerClick/OnPointerUp/OnPointerDown don't work. 1 Answer