# 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`.