Unit 2: Player Controls and Positioning

CSE434 — Game Development In 3D 8 min read

I. Orientation: The Control–Physics–Animation Loop

A 3D player character is never a single object; it is a stack of cooperating systems — an input layer that samples the device, a controller that converts intent into displacement, a physics layer that validates that displacement against the world, and an animation layer that makes it readable. This unit uses Unity (2019.3+, with the Input System 1.x and Animation Rigging 1.x packages) and the free 3D Game Kit as its reference implementation, since the Kit ships a complete, inspectable player (Ellen) built exactly on this stack.

Defining assumptions used throughout:

  • Frame model: Update() runs once per rendered frame (variable Time.deltaTime); FixedUpdate() runs on a fixed physics tick (Time.fixedDeltaTime, default 0.02 s = 50 Hz); LateUpdate() runs after all animation evaluation.
  • Left-handed coordinate space: +X right, +Y up, +Z forward. Rotations are stored as Quaternion, authored in the Inspector as Euler degrees.
  • Transform hierarchy: every positioned object has a Transform; child positions are expressed in parent-local space, which is why rig targets and weapon mounts are parented rather than copied each frame.
  • Determinism boundary: physics forces are frame-rate independent only inside FixedUpdate; direct transform writes bypass physics and must be reconciled by a controller.
  • Composition over inheritance: behaviour is assembled from components on a GameObject, so a "player" is a prefab-level contract, not a class.

II. Game Manager Physics — the rules that validate movement

A. The physics model and its components

Unity's 3D physics is a wrapper over PhysX; a manager-level configuration governs every moving actor.

  • Rigidbody: grants mass (kg), drag, and integration of forces. mass = 70 with useGravity = true yields weight ≈ 70 × 9.81 ≈ 687 N.
  • Collider: the collision shape (BoxCollider, CapsuleCollider, MeshCollider). Capsules are standard for humanoids because they slide off ledges instead of catching.
  • Project Settings → Physics: global gravity (0, -9.81, 0), default solver iterations (6), bounce threshold, and the Layer Collision Matrix, which cheaply disables whole interaction pairs (e.g. Player × PlayerAttack unchecked).
  • PhysicMaterial: static/dynamic friction and bounciness; an icy platform is simply friction 0.02, combine mode Minimum.

B. Integration and the manager's role

A GameManager-style singleton owns global physics state (time scale, gravity tuning, pause) so that no individual actor mutates it.

CSHARP
void FixedUpdate() {
    verticalVelocity += Physics.gravity.y * gravityMultiplier * Time.fixedDeltaTime;
    rb.MovePosition(rb.position + moveDir * speed * Time.fixedDeltaTime);
}
// verticalVelocity: m/s;  gravityMultiplier: dimensionless tuning (2–3 for snappy jumps)
  • Jump velocity: to reach height h, v₀ = √(2·g·h); for h = 2 m, v₀ ≈ 6.26 m/s.
  • Pausing: Time.timeScale = 0 halts FixedUpdate accumulation; anything driven by Time.unscaledDeltaTime (menus) keeps running.

C. CharacterController versus Rigidbody control

  1. Rigidbody (simulated): responds to forces, explosions and joints; realistic but prone to slide, jitter on stairs, and unpredictable air control.
  2. CharacterController (kinematic sweep): controller.Move(motion) sweeps a capsule, resolving slopeLimit (default 45°) and stepOffset (0.3 m), and reports contacts in collisionFlags. The 3D Game Kit's Ellen uses this — precise authored feel, but gravity and pushback must be coded by hand.

D. Limitations

  • Tunnelling: fast objects skip thin colliders; fix with collisionDetectionMode = ContinuousDynamic or raycast-based sweeps.
  • Cost: MeshCollider (non-convex) is static-only and expensive; prefer primitive compounds.

III. Gameplay Components — the reusable behaviour vocabulary

A. What a gameplay component is

A gameplay component is a small MonoBehaviour exposing serialized fields and events, so designers assemble mechanics in the Inspector without new code.

  • Contract: one responsibility, public tuning fields, UnityEvent hooks for outcomes.
  • Discovery: components locate one another via GetComponent<T>() cached in Awake(), never in Update().

B. The 3D Game Kit's component set

  • PlayerController: reads input, drives the CharacterController, and sets animator floats such as forward speed.
  • Damageable: holds maxHitPoints, invulnerabilityTime, a hit-angle/direction filter, and fires OnDeath, OnReceiveDamage, OnHitWhileInvulnerable.
  • Damager: a box-shaped trigger volume enabled for the active frames of an attack; calls Damageable.ApplyDamage().
  • InteractOnTrigger / InteractOnButton: raise UnityEvents when a tagged collider enters, used for doors and pressure pads.
  • CheckpointSystem / Checkpoint: stores respawn transform; on death the player is teleported and health restored.
  • SceneController: handles transition, fade and persistence between scenes.

C. Positioning-specific components

  • SimpleTransformer (translate/rotate): moves platforms on a curve between waypoints; the player parented on contact so relative position is preserved.
  • TargetScanner: cone-of-vision check (detectionAngle, detectionRadius) used by enemies to acquire the player's transform.

IV. Objects in the 3D Game Kit — the authored content layer

A. Kit structure

The Kit is a prefab library plus a set of scene-authoring tools sitting on the systems above.

  • Folders: 3DGamekit/Prefabs, /Art, /Scripts, /ScriptableObjects.
  • Kit Tools menu: creates a preconfigured scene containing player, camera rig, post-processing volume and UI.

B. Object categories

  • Character objects: Ellen (player prefab: mesh, CharacterController, PlayerInput, Damageable, staff Damager); enemies Chomper, Spitter, Grenadier, each with EnemyController, TargetScanner and a behaviour state machine.
  • Interactive objects: doors, pressure pads, moving platforms, destructible boxes, acid pits — all InteractOnTrigger + UnityEvent compositions.
  • Environment objects: modular ProBuilder-friendly meshes snapped to a 1-unit grid so positioning stays consistent.

C. Positioning conventions

  • Pivot at feet: character prefabs place the origin at ground level so transform.position.y equals ground height.
  • Snapping: hold Ctrl while dragging to snap by the Grid Snapping increment; use V (vertex snap) to align modular pieces exactly.

V. Advanced Input System — device-agnostic intent

A. Purpose and architecture

The Input System package replaces polling of hard-coded axes with a data asset that maps physical controls to named actions, resolved at runtime per device.

  • Input Actions asset (.inputactions): contains Action Maps (Player, UI), Actions, Bindings, and Control Schemes (Keyboard&Mouse, Gamepad).
  • Action types: Value (continuous, e.g. Move → Vector2), Button (Jump), Pass Through.
  • Composite binding: 2D Vector composite binds W/A/S/D into one Vector2; a Stick Deadzone processor filters drift.

B. Reading input

CSHARP
public void OnMove(InputAction.CallbackContext ctx) {
    moveInput = ctx.ReadValue<Vector2>();   // x = strafe, y = forward, each in [-1,1]
}
public void OnJump(InputAction.CallbackContext ctx) {
    if (ctx.performed) jumpQueued = true;   // phases: started → performed → canceled
}
  • PlayerInput component: behaviours Send Messages, Broadcast Messages, Invoke Unity Events, or Invoke C# Events; also handles device pairing and split-screen for local multiplayer.
  • Generated C# class: tick "Generate C# Class" for compile-time-checked access, e.g. controls.Player.Move.performed += OnMove; with controls.Enable() in OnEnable().

C. Camera-relative movement

Raw input is in screen space and must be re-expressed in world space before it reaches the controller.

CSHARP
Vector3 fwd = Vector3.ProjectOnPlane(cam.forward, Vector3.up).normalized;
Vector3 right = Vector3.Cross(Vector3.up, fwd);
Vector3 desired = fwd * moveInput.y + right * moveInput.x;
  • Rebinding: action.PerformInteractiveRebinding() lets players remap at runtime and serialise overrides as JSON.

VI. Event Handling — decoupling cause from consequence

A. Principle

Events invert dependencies: the emitter knows nothing about the listener, so a pressure pad can open a door, play audio and score points without referencing any of them.

B. Mechanisms

  1. UnityEvent: serialized, wired in the Inspector, designer-facing; slower (reflection) and limited to public methods with ≤ 4 arguments.
  2. C# event/Action: code-only, fast, type-safe; must be unsubscribed in OnDisable() to avoid leaked references.
CSHARP
public UnityEvent<int> OnHealthChanged;      // designer-wired
public static event Action<Damageable> OnDeath;  // code-wired observer

C. Engine-sent messages

  • Physics callbacks: OnCollisionEnter/Stay/Exit (solid contacts, gives ContactPoint), OnTriggerEnter/Exit (volumes with isTrigger = true; at least one body must have a Rigidbody).
  • Animation Events: keyframed on a clip to enable a Damager on the swing frame and disable it on the follow-through — the standard way to sync hitboxes to animation.
  • UI: the EventSystem GameObject routes pointer and navigation events through Graphic Raycaster to IPointerClickHandler implementations.

VII. Animation Rigging — procedural correction of authored motion

A. Purpose and evaluation order

The Animation Rigging package layers constraint solvers after the Animator evaluates clips, letting runtime data (aim direction, ground normal) modify bone transforms without new animations.

  • Order: Animator plays clip → Rig Builder's animation jobs solve constraints → LateUpdate scripts read final pose.
  • Setup: RigBuilder on the root with the Animator; one or more Rig GameObjects, each with weight ∈ [0,1]; constraints as children.

B. Core constraints

  • Two Bone IK: solves upper/lower limb to a Target transform with a Hint for the elbow/knee pole — used for foot planting and gripping a weapon.
  • Multi-Aim: rotates a bone so its Aimed Axis points at a source; head look-at, with Source Objects weighted.
  • Multi-Parent / Multi-Position: blends between attachment points, e.g. a sword moving from back to hand.
  • Damped Transform: propagates motion with damping for secondary sway (ponytail, holster).
  • Twist Correction / Chain IK: distributes forearm twist; solves tails and ropes over an arbitrary bone chain.

C. Foot IK worked example

Goal: keep feet on a 15° slope.

TEXT
1. Raycast down from each foot bone, length 0.5 m, layer = Ground.
2. footTarget.position = hit.point + Vector3.up * footOffset;   // 0.02–0.05 m
3. footTarget.rotation = Quaternion.FromToRotation(Vector3.up, hit.normal) * footBone.rotation;
4. Blend: constraint.weight = Mathf.Lerp(constraint.weight, grounded ? 1 : 0, 10 * Time.deltaTime);

Fading weight to 0 in air prevents the leg snapping to a stale hit point.

D. Limitations

  • Cost: every constraint is an animation job; deep chains on many characters justify LOD — drop rig weight to 0 beyond ~15 m.
  • Authoring risk: IK targets fighting an authored clip cause knee popping; constrain the hint, and never scale rigged bones non-uniformly.