# 6. Worked Example — Locomotion, Look-At, and Hand IK
Part of [[Animation]]. Prev: [[5.Animation Rigging and Script IK]]. Next: [[7.Animation Pitfalls]].
**Goal:** a simple humanoid that (1) blends Idle/Walk/Run from planar speed, (2) looks at a target, (3) reaches a hand to a grip point. Gameplay brain stays outside the Animator — same philosophy as [[Enemy AI State Machine]].
> None of this C# was compiled in Unity. APIs used: verified Mecanim setters + Animation Rigging types from part 5 docs. Input uses either legacy axes or Input System — wire one.
## 6.1 Scene kit
1. Humanoid character with Animator + Avatar (Humanoid).
2. Clips: `Idle`, `Walk`, `Run` (Loop Time on). Prefer in-place + script move **or** root-motion consistently (part 4). This example uses **in-place + CharacterController**.
3. Empty `LookAtTarget` and `GripPoint` transforms in the scene (move them in Play mode to test).
4. Package **Animation Rigging** installed.
## 6.2 Animator Controller
Asset: `AC_SimpleHumanoid`.
**Parameters**
| Name | Type |
|---|---|
| Speed | Float |
| MoveX | Float *(optional if you only do 1D)* |
| MoveY | Float |
| Grounded | Bool |
| Jump | Trigger |
**Base Layer**
1. Create Blend Tree state `Locomotion` (default/orange).
2. 1D tree, parameter `Speed`, motions:
- Idle threshold `0`
- Walk threshold `1.4` *(or Compute Thresholds → Speed)*
- Run threshold `4.0`
3. Optional: transition to `Jump` state on trigger `Jump` with `Grounded == false` exit, return when `Grounded`.
Layers: leave a single base layer for this example. Add upper-body mask layer later if you layer a wave animation.
## 6.3 Rig hierarchy
```
Player
├── Animator (AC_SimpleHumanoid), CharacterController, RigBuilder
├── (skeleton…)
└── Rig_Setup Rig component → RigBuilder layer 0
├── HeadAim MultiAimConstraint
│ └── HeadLook_Source (source effector)
└── RightHandIK TwoBoneIKConstraint (Auto Setup from Tip = right hand)
├── RH_Target
└── RH_Hint
```
MultiAim: Constrained Object = head bone (or head aim bone); Aim Axis usually **Z** or **Y** depending on skeleton forward — adjust until the nose tracks; World Up Type **Scene Up** to start.
TwoBoneIK: Root = upper arm, Mid = forearm, Tip = hand; place Hint along the elbow pole.
## 6.4 Movement + Animator driver
```csharp
using UnityEngine;
[RequireComponent(typeof(Animator))]
[RequireComponent(typeof(CharacterController))]
public sealed class SimpleHumanoidMotor : MonoBehaviour
{
static readonly int SpeedHash = Animator.StringToHash("Speed");
static readonly int GroundedHash = Animator.StringToHash("Grounded");
static readonly int JumpHash = Animator.StringToHash("Jump");
[SerializeField] float walkSpeed = 1.4f;
[SerializeField] float runSpeed = 4.0f;
[SerializeField] float rotateSpeed = 720f;
[SerializeField] float gravity = -20f;
[SerializeField] float jumpSpeed = 6f;
[SerializeField] float speedDamp = 0.1f;
Animator _anim;
CharacterController _cc;
float _vertical;
Transform _cam;
void Awake()
{
_anim = GetComponent<Animator>();
_cc = GetComponent<CharacterController>();
_cam = Camera.main != null ? Camera.main.transform : null;
_anim.applyRootMotion = false;
}
void Update()
{
// Legacy axes — swap for Input System ReadValue<Vector2>() if you use that package.
float h = Input.GetAxisRaw("Horizontal");
float v = Input.GetAxisRaw("Vertical");
bool run = Input.GetKey(KeyCode.LeftShift);
Vector2 input = Vector2.ClampMagnitude(new Vector2(h, v), 1f);
Vector3 camForward = _cam != null ? Vector3.ProjectOnPlane(_cam.forward, Vector3.up).normalized : transform.forward;
Vector3 camRight = _cam != null ? Vector3.ProjectOnPlane(_cam.right, Vector3.up).normalized : transform.right;
Vector3 wish = camForward * input.y + camRight * input.x;
float targetSpeed = (run ? runSpeed : walkSpeed) * input.magnitude;
if (wish.sqrMagnitude > 0.001f)
{
Quaternion face = Quaternion.LookRotation(wish, Vector3.up);
transform.rotation = Quaternion.RotateTowards(transform.rotation, face, rotateSpeed * Time.deltaTime);
}
bool grounded = _cc.isGrounded;
if (grounded && _vertical < 0f) _vertical = -1f;
if (grounded && Input.GetButtonDown("Jump"))
{
_vertical = jumpSpeed;
_anim.SetTrigger(JumpHash);
grounded = false;
}
_vertical += gravity * Time.deltaTime;
Vector3 velocity = wish.normalized * targetSpeed;
velocity.y = _vertical;
_cc.Move(velocity * Time.deltaTime);
_anim.SetBool(GroundedHash, _cc.isGrounded);
_anim.SetFloat(SpeedHash, new Vector3(velocity.x, 0f, velocity.z).magnitude, speedDamp, Time.deltaTime);
}
}
```
## 6.5 Look-at + hand IK (Animation Rigging)
```csharp
using UnityEngine;
using UnityEngine.Animations.Rigging;
public sealed class SimpleIkFacade : MonoBehaviour
{
[SerializeField] Rig rig;
[SerializeField] MultiAimConstraint headAim;
[SerializeField] Transform headAimSource;
[SerializeField] TwoBoneIKConstraint rightHandIk;
[SerializeField] Transform lookTarget;
[SerializeField] Transform gripPoint;
[SerializeField] float weightSpeed = 8f;
[SerializeField] KeyCode gripKey = KeyCode.E;
float _lookW = 1f;
float _handW;
void Update()
{
if (lookTarget != null && headAimSource != null)
headAimSource.position = lookTarget.position;
if (Input.GetKey(gripKey)) _handW = 1f;
else _handW = 0f;
if (gripPoint != null && rightHandIk != null && rightHandIk.data.target != null)
{
Transform t = rightHandIk.data.target;
t.SetPositionAndRotation(gripPoint.position, gripPoint.rotation);
}
}
void LateUpdate()
{
if (headAim != null)
headAim.weight = Mathf.MoveTowards(headAim.weight, lookTarget != null ? _lookW : 0f, weightSpeed * Time.deltaTime);
if (rightHandIk != null)
rightHandIk.weight = Mathf.MoveTowards(rightHandIk.weight, _handW, weightSpeed * Time.deltaTime);
// Optional: fade entire secondary rig.
if (rig != null)
rig.weight = 1f;
}
}
```
Wire: drag `LookAtTarget` → `lookTarget`, `GripPoint` → `gripPoint`, constraints and source from the Rig hierarchy.
## 6.6 Optional — same goals via OnAnimatorIK
If you skip Animation Rigging for a prototype, enable **IK Pass** on Base Layer and use `HumanoidLookAndHandIk` from part 5. Do **not** also run TwoBoneIK on the same hand without zeroing one system's weights.
## 6.7 Playtest checklist
1. Walk/run thresholds feel matched to `walkSpeed` / `runSpeed` (no foot slide).
2. Head tracks `LookAtTarget`; MultiAim weight 0 when target null.
3. Hold **E**: hand reaches grip; release: weight returns and clip pose resumes.
4. Jump trigger fires; grounded bool recovers.
5. Frame Debugger / Scene view Bone Renderer: constraints only under `Rig_Setup`.
**Design note ([[Level Up! (Review)]]):** look-at and hand IK are Character readability — the player should *see* attention and intention. Keep weights snappy enough that Control still feels authored, not mushy.
**AI note:** an enemy brain ([[Enemy AI State Machine]] + [[Organic Enemy Sensing]]) can assign `lookTarget` to the sensed player transform without touching Animator state names.
## 6.8 Verified vs unverified
**Verified:** parameter API; TwoBoneIK `data.target`; MultiAim weight; Rig.weight; CharacterController move pattern is standard (not a special animation API).
**Unverified:** Input System drop-in (legacy axes shown); best Aim Axis for your specific skeleton; Jump state graph details if you add more than a trigger flash.
**Not compiled:** `SimpleHumanoidMotor`, `SimpleIkFacade`.