HeadLookController

From Unify Community Wiki
(Difference between revisions)
Jump to: navigation, search
(Removing all content from page)
m (Reverted edits by Joseph05408 (Talk); changed back to last version by Runevision)
Line 1: Line 1:
 +
[[Category: MonoBehaviour]]
 +
[[Category: Scripts]]
 +
[[Category: C Sharp]]
 +
[[Category: Animation]]
 +
Author: runevision
 +
==Description==
 +
This script can be used to make a character look towards a specified point in space, smoothly turning towards it with for example the eyes, head, upper body, or whatever is specified. Multiple ranges of bones can be specified, each with different settings for responsiveness, angle constraints etc.
  
 +
==Usage==
 +
Drop this script onto a GameObject; typically the root. In the inspector, set the length of the Segments array and specify the settings for each segment. Optionally a list of joints to not be affected can be specified in the Non Affected Joints array. This is to make certain transforms keep their original orientation even though they are children of transforms specified in one of the segments. For example you can have the upper body bend, but set the joints for the shoulders to not be affected (or to be only affected a little).
 +
 +
There is more information and a demo video at the Unity Blog here:
 +
http://blogs.unity3d.com/2009/07/10/just-looking-around/
 +
 +
==Troubleshooting==
 +
Per default, the character needs to have animations that drive all the bones that the script modifies. If a body part turns or twists around out of control, it might be because there is currently no animation playing that has curves for those bones. In that case, the setting Override Animation can be turned on. This make the affected bones ignore any animation that may be affecting them and instead they are set in each frame to be identical to how they were when the object was instantiated. Normally this is not a good idea to turn on. For example, if an idle animation has a subtle breathing animation of the upper body and head, this would be overridden. However, it can be useful for enabling the use of the Head Look Controller on models that do not have any animation on the affected bones at all.
 +
 +
==C# - HeadLookController.cs==
 +
<csharp>
 +
using UnityEngine;
 +
using System.Collections;
 +
 +
[System.Serializable]
 +
public class BendingSegment {
 +
    public Transform firstTransform;
 +
    public Transform lastTransform;
 +
    public float thresholdAngleDifference = 0;
 +
    public float bendingMultiplier = 0.6f;
 +
    public float maxAngleDifference = 30;
 +
    public float maxBendingAngle = 80;
 +
    public float responsiveness = 5;
 +
    internal float angleH;
 +
    internal float angleV;
 +
    internal Vector3 dirUp;
 +
    internal Vector3 referenceLookDir;
 +
    internal Vector3 referenceUpDir;
 +
    internal int chainLength;
 +
    internal Quaternion[] origRotations;
 +
}
 +
 +
[System.Serializable]
 +
public class NonAffectedJoints {
 +
    public Transform joint;
 +
    public float effect = 0;
 +
}
 +
 +
public class HeadLookController : MonoBehaviour {
 +
   
 +
    public Transform rootNode;
 +
    public BendingSegment[] segments;
 +
    public NonAffectedJoints[] nonAffectedJoints;
 +
    public Vector3 headLookVector = Vector3.forward;
 +
    public Vector3 headUpVector = Vector3.up;
 +
    public Vector3 target = Vector3.zero;
 +
    public float effect = 1;
 +
    public bool overrideAnimation = false;
 +
   
 +
    void Start () {
 +
        if (rootNode == null) {
 +
            rootNode = transform;
 +
        }
 +
       
 +
        // Setup segments
 +
        foreach (BendingSegment segment in segments) {
 +
            Quaternion parentRot = segment.firstTransform.parent.rotation;
 +
            Quaternion parentRotInv = Quaternion.Inverse(parentRot);
 +
            segment.referenceLookDir =
 +
                parentRotInv * rootNode.rotation * headLookVector.normalized;
 +
            segment.referenceUpDir =
 +
                parentRotInv * rootNode.rotation * headUpVector.normalized;
 +
            segment.angleH = 0;
 +
            segment.angleV = 0;
 +
            segment.dirUp = segment.referenceUpDir;
 +
           
 +
            segment.chainLength = 1;
 +
            Transform t = segment.lastTransform;
 +
            while (t != segment.firstTransform && t != t.root) {
 +
                segment.chainLength++;
 +
                t = t.parent;
 +
            }
 +
           
 +
            segment.origRotations = new Quaternion[segment.chainLength];
 +
            t = segment.lastTransform;
 +
            for (int i=segment.chainLength-1; i>=0; i--) {
 +
                segment.origRotations[i] = t.localRotation;
 +
                t = t.parent;
 +
            }
 +
        }
 +
    }
 +
   
 +
    void LateUpdate () {
 +
        if (Time.deltaTime == 0)
 +
            return;
 +
       
 +
        // Remember initial directions of joints that should not be affected
 +
        Vector3[] jointDirections = new Vector3[nonAffectedJoints.Length];
 +
        for (int i=0; i<nonAffectedJoints.Length; i++) {
 +
            foreach (Transform child in nonAffectedJoints[i].joint) {
 +
                jointDirections[i] = child.position - nonAffectedJoints[i].joint.position;
 +
                break;
 +
            }
 +
        }
 +
       
 +
        // Handle each segment
 +
        foreach (BendingSegment segment in segments) {
 +
            Transform t = segment.lastTransform;
 +
            if (overrideAnimation) {
 +
                for (int i=segment.chainLength-1; i>=0; i--) {
 +
                    t.localRotation = segment.origRotations[i];
 +
                    t = t.parent;
 +
                }
 +
            }
 +
           
 +
            Quaternion parentRot = segment.firstTransform.parent.rotation;
 +
            Quaternion parentRotInv = Quaternion.Inverse(parentRot);
 +
           
 +
            // Desired look direction in world space
 +
            Vector3 lookDirWorld = (target - segment.lastTransform.position).normalized;
 +
           
 +
            // Desired look directions in neck parent space
 +
            Vector3 lookDirGoal = (parentRotInv * lookDirWorld);
 +
           
 +
            // Get the horizontal and vertical rotation angle to look at the target
 +
            float hAngle = AngleAroundAxis(
 +
                segment.referenceLookDir, lookDirGoal, segment.referenceUpDir
 +
            );
 +
           
 +
            Vector3 rightOfTarget = Vector3.Cross(segment.referenceUpDir, lookDirGoal);
 +
           
 +
            Vector3 lookDirGoalinHPlane =
 +
                lookDirGoal - Vector3.Project(lookDirGoal, segment.referenceUpDir);
 +
           
 +
            float vAngle = AngleAroundAxis(
 +
                lookDirGoalinHPlane, lookDirGoal, rightOfTarget
 +
            );
 +
           
 +
            // Handle threshold angle difference, bending multiplier,
 +
            // and max angle difference here
 +
            float hAngleThr = Mathf.Max(
 +
                0, Mathf.Abs(hAngle) - segment.thresholdAngleDifference
 +
            ) * Mathf.Sign(hAngle);
 +
           
 +
            float vAngleThr = Mathf.Max(
 +
                0, Mathf.Abs(vAngle) - segment.thresholdAngleDifference
 +
            ) * Mathf.Sign(vAngle);
 +
           
 +
            hAngle = Mathf.Max(
 +
                Mathf.Abs(hAngleThr) * Mathf.Abs(segment.bendingMultiplier),
 +
                Mathf.Abs(hAngle) - segment.maxAngleDifference
 +
            ) * Mathf.Sign(hAngle) * Mathf.Sign(segment.bendingMultiplier);
 +
           
 +
            vAngle = Mathf.Max(
 +
                Mathf.Abs(vAngleThr) * Mathf.Abs(segment.bendingMultiplier),
 +
                Mathf.Abs(vAngle) - segment.maxAngleDifference
 +
            ) * Mathf.Sign(vAngle) * Mathf.Sign(segment.bendingMultiplier);
 +
           
 +
            // Handle max bending angle here
 +
            hAngle = Mathf.Clamp(hAngle, -segment.maxBendingAngle, segment.maxBendingAngle);
 +
            vAngle = Mathf.Clamp(vAngle, -segment.maxBendingAngle, segment.maxBendingAngle);
 +
           
 +
            Vector3 referenceRightDir =
 +
                Vector3.Cross(segment.referenceUpDir, segment.referenceLookDir);
 +
           
 +
            // Lerp angles
 +
            segment.angleH = Mathf.Lerp(
 +
                segment.angleH, hAngle, Time.deltaTime * segment.responsiveness
 +
            );
 +
            segment.angleV = Mathf.Lerp(
 +
                segment.angleV, vAngle, Time.deltaTime * segment.responsiveness
 +
            );
 +
           
 +
            // Get direction
 +
            lookDirGoal = Quaternion.AngleAxis(segment.angleH, segment.referenceUpDir)
 +
                * Quaternion.AngleAxis(segment.angleV, referenceRightDir)
 +
                * segment.referenceLookDir;
 +
           
 +
            // Make look and up perpendicular
 +
            Vector3 upDirGoal = segment.referenceUpDir;
 +
            Vector3.OrthoNormalize(ref lookDirGoal, ref upDirGoal);
 +
           
 +
            // Interpolated look and up directions in neck parent space
 +
            Vector3 lookDir = lookDirGoal;
 +
            segment.dirUp = Vector3.Slerp(segment.dirUp, upDirGoal, Time.deltaTime*5);
 +
            Vector3.OrthoNormalize(ref lookDir, ref segment.dirUp);
 +
           
 +
            // Look rotation in world space
 +
            Quaternion lookRot = (
 +
                (parentRot * Quaternion.LookRotation(lookDir, segment.dirUp))
 +
                * Quaternion.Inverse(
 +
                    parentRot * Quaternion.LookRotation(
 +
                        segment.referenceLookDir, segment.referenceUpDir
 +
                    )
 +
                )
 +
            );
 +
           
 +
            // Distribute rotation over all joints in segment
 +
            Quaternion dividedRotation =
 +
                Quaternion.Slerp(Quaternion.identity, lookRot, effect / segment.chainLength);
 +
            t = segment.lastTransform;
 +
            for (int i=0; i<segment.chainLength; i++) {
 +
                t.rotation = dividedRotation * t.rotation;
 +
                t = t.parent;
 +
            }
 +
        }
 +
       
 +
        // Handle non affected joints
 +
        for (int i=0; i<nonAffectedJoints.Length; i++) {
 +
            Vector3 newJointDirection = Vector3.zero;
 +
           
 +
            foreach (Transform child in nonAffectedJoints[i].joint) {
 +
                newJointDirection = child.position - nonAffectedJoints[i].joint.position;
 +
                break;
 +
            }
 +
           
 +
            Vector3 combinedJointDirection = Vector3.Slerp(
 +
                jointDirections[i], newJointDirection, nonAffectedJoints[i].effect
 +
            );
 +
           
 +
            nonAffectedJoints[i].joint.rotation = Quaternion.FromToRotation(
 +
                newJointDirection, combinedJointDirection
 +
            ) * nonAffectedJoints[i].joint.rotation;
 +
        }
 +
    }
 +
   
 +
    // The angle between dirA and dirB around axis
 +
    public static float AngleAroundAxis (Vector3 dirA, Vector3 dirB, Vector3 axis) {
 +
        // Project A and B onto the plane orthogonal target axis
 +
        dirA = dirA - Vector3.Project(dirA, axis);
 +
        dirB = dirB - Vector3.Project(dirB, axis);
 +
       
 +
        // Find (positive) angle between A and B
 +
        float angle = Vector3.Angle(dirA, dirB);
 +
       
 +
        // Return angle multiplied with 1 or -1
 +
        return angle * (Vector3.Dot(axis, Vector3.Cross(dirA, dirB)) < 0 ? -1 : 1);
 +
    }
 +
}
 +
</csharp>

Revision as of 19:20, 19 October 2009

Author: runevision

Contents

Description

This script can be used to make a character look towards a specified point in space, smoothly turning towards it with for example the eyes, head, upper body, or whatever is specified. Multiple ranges of bones can be specified, each with different settings for responsiveness, angle constraints etc.

Usage

Drop this script onto a GameObject; typically the root. In the inspector, set the length of the Segments array and specify the settings for each segment. Optionally a list of joints to not be affected can be specified in the Non Affected Joints array. This is to make certain transforms keep their original orientation even though they are children of transforms specified in one of the segments. For example you can have the upper body bend, but set the joints for the shoulders to not be affected (or to be only affected a little).

There is more information and a demo video at the Unity Blog here: http://blogs.unity3d.com/2009/07/10/just-looking-around/

Troubleshooting

Per default, the character needs to have animations that drive all the bones that the script modifies. If a body part turns or twists around out of control, it might be because there is currently no animation playing that has curves for those bones. In that case, the setting Override Animation can be turned on. This make the affected bones ignore any animation that may be affecting them and instead they are set in each frame to be identical to how they were when the object was instantiated. Normally this is not a good idea to turn on. For example, if an idle animation has a subtle breathing animation of the upper body and head, this would be overridden. However, it can be useful for enabling the use of the Head Look Controller on models that do not have any animation on the affected bones at all.

C# - HeadLookController.cs

<csharp> using UnityEngine; using System.Collections;

[System.Serializable] public class BendingSegment {

   public Transform firstTransform;
   public Transform lastTransform;
   public float thresholdAngleDifference = 0;
   public float bendingMultiplier = 0.6f;
   public float maxAngleDifference = 30;
   public float maxBendingAngle = 80;
   public float responsiveness = 5;
   internal float angleH;
   internal float angleV;
   internal Vector3 dirUp;
   internal Vector3 referenceLookDir;
   internal Vector3 referenceUpDir;
   internal int chainLength;
   internal Quaternion[] origRotations;

}

[System.Serializable] public class NonAffectedJoints {

   public Transform joint;
   public float effect = 0;

}

public class HeadLookController : MonoBehaviour {

   public Transform rootNode;
   public BendingSegment[] segments;
   public NonAffectedJoints[] nonAffectedJoints;
   public Vector3 headLookVector = Vector3.forward;
   public Vector3 headUpVector = Vector3.up;
   public Vector3 target = Vector3.zero;
   public float effect = 1;
   public bool overrideAnimation = false;
   
   void Start () {
       if (rootNode == null) {
           rootNode = transform;
       }
       
       // Setup segments
       foreach (BendingSegment segment in segments) {
           Quaternion parentRot = segment.firstTransform.parent.rotation;
           Quaternion parentRotInv = Quaternion.Inverse(parentRot);
           segment.referenceLookDir =
               parentRotInv * rootNode.rotation * headLookVector.normalized;
           segment.referenceUpDir =
               parentRotInv * rootNode.rotation * headUpVector.normalized;
           segment.angleH = 0;
           segment.angleV = 0;
           segment.dirUp = segment.referenceUpDir;
           
           segment.chainLength = 1;
           Transform t = segment.lastTransform;
           while (t != segment.firstTransform && t != t.root) {
               segment.chainLength++;
               t = t.parent;
           }
           
           segment.origRotations = new Quaternion[segment.chainLength];
           t = segment.lastTransform;
           for (int i=segment.chainLength-1; i>=0; i--) {
               segment.origRotations[i] = t.localRotation;
               t = t.parent;
           }
       }
   }
   
   void LateUpdate () {
       if (Time.deltaTime == 0)
           return;
       
       // Remember initial directions of joints that should not be affected
       Vector3[] jointDirections = new Vector3[nonAffectedJoints.Length];
       for (int i=0; i<nonAffectedJoints.Length; i++) {
           foreach (Transform child in nonAffectedJoints[i].joint) {
               jointDirections[i] = child.position - nonAffectedJoints[i].joint.position;
               break;
           }
       }
       
       // Handle each segment
       foreach (BendingSegment segment in segments) {
           Transform t = segment.lastTransform;
           if (overrideAnimation) {
               for (int i=segment.chainLength-1; i>=0; i--) {
                   t.localRotation = segment.origRotations[i];
                   t = t.parent;
               }
           }
           
           Quaternion parentRot = segment.firstTransform.parent.rotation;
           Quaternion parentRotInv = Quaternion.Inverse(parentRot);
           
           // Desired look direction in world space
           Vector3 lookDirWorld = (target - segment.lastTransform.position).normalized;
           
           // Desired look directions in neck parent space
           Vector3 lookDirGoal = (parentRotInv * lookDirWorld);
           
           // Get the horizontal and vertical rotation angle to look at the target
           float hAngle = AngleAroundAxis(
               segment.referenceLookDir, lookDirGoal, segment.referenceUpDir
           );
           
           Vector3 rightOfTarget = Vector3.Cross(segment.referenceUpDir, lookDirGoal);
           
           Vector3 lookDirGoalinHPlane =
               lookDirGoal - Vector3.Project(lookDirGoal, segment.referenceUpDir);
           
           float vAngle = AngleAroundAxis(
               lookDirGoalinHPlane, lookDirGoal, rightOfTarget
           );
           
           // Handle threshold angle difference, bending multiplier,
           // and max angle difference here
           float hAngleThr = Mathf.Max(
               0, Mathf.Abs(hAngle) - segment.thresholdAngleDifference
           ) * Mathf.Sign(hAngle);
           
           float vAngleThr = Mathf.Max(
               0, Mathf.Abs(vAngle) - segment.thresholdAngleDifference
           ) * Mathf.Sign(vAngle);
           
           hAngle = Mathf.Max(
               Mathf.Abs(hAngleThr) * Mathf.Abs(segment.bendingMultiplier),
               Mathf.Abs(hAngle) - segment.maxAngleDifference
           ) * Mathf.Sign(hAngle) * Mathf.Sign(segment.bendingMultiplier);
           
           vAngle = Mathf.Max(
               Mathf.Abs(vAngleThr) * Mathf.Abs(segment.bendingMultiplier),
               Mathf.Abs(vAngle) - segment.maxAngleDifference
           ) * Mathf.Sign(vAngle) * Mathf.Sign(segment.bendingMultiplier);
           
           // Handle max bending angle here
           hAngle = Mathf.Clamp(hAngle, -segment.maxBendingAngle, segment.maxBendingAngle);
           vAngle = Mathf.Clamp(vAngle, -segment.maxBendingAngle, segment.maxBendingAngle);
           
           Vector3 referenceRightDir =
               Vector3.Cross(segment.referenceUpDir, segment.referenceLookDir);
           
           // Lerp angles
           segment.angleH = Mathf.Lerp(
               segment.angleH, hAngle, Time.deltaTime * segment.responsiveness
           );
           segment.angleV = Mathf.Lerp(
               segment.angleV, vAngle, Time.deltaTime * segment.responsiveness
           );
           
           // Get direction
           lookDirGoal = Quaternion.AngleAxis(segment.angleH, segment.referenceUpDir)
               * Quaternion.AngleAxis(segment.angleV, referenceRightDir)
               * segment.referenceLookDir;
           
           // Make look and up perpendicular
           Vector3 upDirGoal = segment.referenceUpDir;
           Vector3.OrthoNormalize(ref lookDirGoal, ref upDirGoal);
           
           // Interpolated look and up directions in neck parent space
           Vector3 lookDir = lookDirGoal;
           segment.dirUp = Vector3.Slerp(segment.dirUp, upDirGoal, Time.deltaTime*5);
           Vector3.OrthoNormalize(ref lookDir, ref segment.dirUp);
           
           // Look rotation in world space
           Quaternion lookRot = (
               (parentRot * Quaternion.LookRotation(lookDir, segment.dirUp))
               * Quaternion.Inverse(
                   parentRot * Quaternion.LookRotation(
                       segment.referenceLookDir, segment.referenceUpDir
                   )
               )
           );
           
           // Distribute rotation over all joints in segment
           Quaternion dividedRotation =
               Quaternion.Slerp(Quaternion.identity, lookRot, effect / segment.chainLength);
           t = segment.lastTransform;
           for (int i=0; i<segment.chainLength; i++) {
               t.rotation = dividedRotation * t.rotation;
               t = t.parent;
           }
       }
       
       // Handle non affected joints
       for (int i=0; i<nonAffectedJoints.Length; i++) {
           Vector3 newJointDirection = Vector3.zero;
           
           foreach (Transform child in nonAffectedJoints[i].joint) {
               newJointDirection = child.position - nonAffectedJoints[i].joint.position;
               break;
           }
           
           Vector3 combinedJointDirection = Vector3.Slerp(
               jointDirections[i], newJointDirection, nonAffectedJoints[i].effect
           );
           
           nonAffectedJoints[i].joint.rotation = Quaternion.FromToRotation(
               newJointDirection, combinedJointDirection
           ) * nonAffectedJoints[i].joint.rotation;
       }
   }
   
   // The angle between dirA and dirB around axis
   public static float AngleAroundAxis (Vector3 dirA, Vector3 dirB, Vector3 axis) {
       // Project A and B onto the plane orthogonal target axis
       dirA = dirA - Vector3.Project(dirA, axis);
       dirB = dirB - Vector3.Project(dirB, axis);
       
       // Find (positive) angle between A and B
       float angle = Vector3.Angle(dirA, dirB);
       
       // Return angle multiplied with 1 or -1
       return angle * (Vector3.Dot(axis, Vector3.Cross(dirA, dirB)) < 0 ? -1 : 1);
   }

} </csharp>

Personal tools
Namespaces

Variants
Actions
Navigation
Extras
Toolbox