This is probably more of a rant than a question, but I wanted to get it in writing in case other beginners are having the same issue.
In all the examples of transform.LookAt I’ve seen, it says that you can use LookAt to have the object look at the target (for example the camera). Great, I thought… I can have my character’s head look at the camera as I move around. However, when I try it on various game models it sometimes works and sometimes doesn’t.
It took some experimentation, but it seems that many game objects ‘forward’ direction (ie, the nose on a face) do not match the OBJECT’s forward direction (the ‘Z’ vector). As indeed the object’s ‘up’ direction (the top of the head) sometimes does not match the OBJECT’s up direction (the ‘Y’ vector). In order to get a character to look at the camera (player) as I move around, I had to find a rotation that first rotated the object to face the TRUE forward in relation to the character it belongs to by finding the rotational difference between the character’s initial rotation and the object’s initial rotation and storing that. Afterwards, I could use this rotation and THEN apply the the look rotation. My final code is shown below, but requires a link to the character object.
I guess my question is… is this the normal way of doing things, or am I over-complicating things? Also, will this work in all circumstances (ie, if the model is in a moving vehicle)?
using UnityEngine;
using System.Collections;
public class LookFollow : MonoBehaviour {
// public variables (UI)
public GameObject CharacterObject;
// private variables
private Quaternion forwardRotation;
// Use this for initialization
void Start () {
// find rotation needed to get the object's z facing forward and y facing upwards
forwardRotation = Quaternion.Inverse(CharacterObject.transform.rotation) * this.transform.rotation;
}
// Update is called once per frame
void Update () {
}
void LateUpdate () {
// now look at player by rotating the true forward rotation by the look at rotation
Vector3 toCamera = Camera.main.transform.position - this.transform.position;
Quaternion lookQuat = Quaternion.LookRotation(toCamera) * forwardRotation;
this.transform.rotation = lookQuat;
}
}
Yes, well, grandchild, sortof. Though I’m adjusting the script a little to attach to the character and have a reference to the head object instead. Now that I’m trying to add eye rotation too, it seems to make more sense that way.
Yeah, it’s surprising how often something that’s trivial to a person is a near impossibility to a computer.
To a computer, though, it’s not looking at a head or a face or a vase or a car. It’s looking at a list of triangles. And it hasn’t got a freaking clue about which way is “up” or “forward”. In fact, it doesn’t even really know what “up” or “forward” are - they’re actually just names we’ve given to arbitrary directions based on how we sometimes use them.
Seems like you ended up learning this the hard way, but thumbs up for figuring it out, and even bigger thumbs up for coming and sharing it afterwards.
As a suggestion, rather than using scripts to correct the forward/up directions of your stuff at runtime, if you made the objects yourself or have access to the source a better solution is generally to re-orient the model so that it’s forward/up vectors are what Unity expects. At work, if a content developer doesn’t respect those conventions, along with a bunch of others, the model doesn’t pass QA.
If you can’t do that, another option is to make the model a child of another GameObject, and rotate the model so that it’s intended forward/up line up with the parent’s z/y axes. You can then use the standard LookAt on the parent object, rather than needing a special component just for that.
P.S: You’ve got an empty Update function in there. That’ll be chewing up some cycles each frame even though it’s not doing anything. In practice it’s probably not a big deal on its own, but it’s a good habit to be in good habits.
Thank you. I hear you about the conventions, but as a (wanna-be?) indie developer there’s a definite attraction to be able to pull stock models in and out at will regardless of quality. It means I can concentrate on the game mechanics and make the graphics better later with little or no code changes.
Appreciate the tips. I’m re-jigging the code a little and will put up a final version after I’ve tested it with the character moving around and stuff. Thanks!
The child-with-offset approach should achieve this, though, and I suspect it’ll be less fiddling around than finding appropriate values to plug into your script would be, 'cause you can just point it where you want with the Editor gizmo and be done with it.
For those who are interested, here is the final script. I put links to the eyes too, so you can have them turn before the head. It’s still pretty rudimentary, but hopefully it will serve as a simple example of using look at for characters when the models do not always have the head and eye axis pointing to true forward and up.
using UnityEngine;
using System.Collections;
public class LookFollow : MonoBehaviour {
// public variables (UI)
public GameObject HeadObject;
public GameObject LeftEyeObject;
public GameObject RightEyeObject;
public float MaxTurnAngle = 50.0f;
// have to store last rotation to undo animation, otherwise slerp doesn't work
private Quaternion lastHeadRotation;
private Quaternion lastLeftEyeRotation;
private Quaternion lastRightEyeRotation;
// private variables
private Quaternion headOffsetRotation;
private Quaternion leftEyeOffsetRotation;
private Quaternion rightEyeOffsetRotation;
// Use this for initialization
void Start () {
// find rotation needed to get the object's z facing forward and y facing upwards relative to the body
headOffsetRotation = Quaternion.Inverse(this.transform.rotation) * HeadObject.transform.rotation;
leftEyeOffsetRotation = Quaternion.Inverse(this.transform.rotation) * LeftEyeObject.transform.rotation;
rightEyeOffsetRotation = Quaternion.Inverse(this.transform.rotation) * RightEyeObject.transform.rotation;
}
void Update()
{
lastHeadRotation = HeadObject.transform.rotation;
lastLeftEyeRotation = LeftEyeObject.transform.rotation;
lastRightEyeRotation = RightEyeObject.transform.rotation;
}
void LateUpdate () {
// process in order
ProcessLookFor(HeadObject, headOffsetRotation, lastHeadRotation, 8.0f);
ProcessLookFor(LeftEyeObject, leftEyeOffsetRotation, lastLeftEyeRotation, 10.0f);
ProcessLookFor(RightEyeObject, rightEyeOffsetRotation, lastRightEyeRotation, 10.0f);
}
// process look for object
void ProcessLookFor(GameObject inObject, Quaternion inOffsetRotation, Quaternion lastRotation, float inSpeed)
{
// now look at player by rotating the true forward rotation by the look at rotation
Vector3 toCamera = Camera.main.transform.position - inObject.transform.position;
// look to camera. this rotates forward vector towards camera
// make sure to rotate by the object's offset first, since they aren't always forward
Quaternion lookToCamera = Quaternion.LookRotation(toCamera);
// find difference between forward vector and look to camera
Quaternion diffQuat = Quaternion.Inverse(this.transform.rotation) * lookToCamera;
// if outside range, lerp back to middle
if (diffQuat.eulerAngles.y > MaxTurnAngle && diffQuat.eulerAngles.y < 360.0f-MaxTurnAngle)
inObject.transform.rotation = Quaternion.Slerp(lastRotation, this.transform.rotation * inOffsetRotation, inSpeed * Time.deltaTime);
else
// lerp rotation to camera, making sure to rotate by the object's offset since they aren't always forward
inObject.transform.rotation = Quaternion.Slerp(lastRotation, lookToCamera * inOffsetRotation, inSpeed * Time.deltaTime);
}
}