U
tab 03Camera & Input6 lessons — tap to open
06 / 06 · 20 min
Camera & Input★ exercise

Exercise: playable with one thumb

your project so far

Assets/

Scenes/

Arena.unity

Ultimate vcam

Scripts/

CameraDirector.cs

Settings/

UI action map

everything from tab 01–03

you already havethis lesson adds

exercise2–3 h

Give the Arena a control rig that works on keyboard, gamepad and touch from one single action map — with a third-person camera that never clips through walls, never jitters, shakes on impact, and cuts to a dramatic angle for the ultimate.

what you reuse

Input Actions assetGenerated C# classCinemachineImpulse source / listenerCamera-relative movementOn-screen stickScreen.safeArea
animated diagram

Three devices, one action map, one camera rig.

Part 1 — one action map for everything

Every action gets bindings for all three devices at once. If you find yourself writing if (touch) … else if (keyboard) …, stop — that is exactly what the action map exists to prevent.

PlayerControls.inputactions — the map you must build
Map: Gameplay

  Move     Value / Vector2
     ├─ 2D Vector Composite  W A S D
     ├─ Gamepad              <Gamepad>/leftStick
     └─ On-Screen Stick      (bound from the UI prefab)

  Look     Value / Vector2
     ├─ Pointer delta        <Mouse>/delta
     ├─ Gamepad              <Gamepad>/rightStick
     └─ Touch drag           (right half of the screen)

  Jump     Button
     ├─ <Keyboard>/space     ├─ <Gamepad>/buttonSouth   └─ On-Screen Button

  Skill1 / Skill2 / Skill3   Button
     ├─ Q E R                ├─ West / North / buttonEast   └─ On-Screen Buttons

  Pause    Button
     ├─ <Keyboard>/escape    └─ <Gamepad>/start

Map: UI          ← enabled only while paused
  Navigate · Submit · Cancel
InputRouter.cs — the single place that reads input
using UnityEngine;
using UnityEngine.InputSystem;

public class InputRouter : MonoBehaviour
{
    private PlayerControls controls;

    public Vector2 Move { get; private set; }
    public Vector2 Look { get; private set; }
    public bool JumpQueued { get; private set; }

    [SerializeField] private SkillCaster caster;

    void Awake()
    {
        controls = new PlayerControls();
        var g = controls.Gameplay;

        g.Move.performed += c => Move = c.ReadValue<Vector2>();
        g.Move.canceled  += _ => Move = Vector2.zero;
        g.Look.performed += c => Look = c.ReadValue<Vector2>();
        g.Look.canceled  += _ => Look = Vector2.zero;

        g.Jump.performed   += _ => JumpQueued = true;
        g.Skill1.performed += _ => caster.TryCast(0);
        g.Skill2.performed += _ => caster.TryCast(1);
        g.Skill3.performed += _ => caster.TryCast(2);
        g.Pause.performed  += _ => TogglePause();
    }

    void OnEnable()  => controls.Enable();
    void OnDisable() => controls.Disable();
    void LateUpdate() => JumpQueued = false;

    void TogglePause()
    {
        bool paused = Time.timeScale > 0f;
        Time.timeScale = paused ? 0f : 1f;

        // swap which map is live
        if (paused) { controls.Gameplay.Disable(); controls.UI.Enable(); }
        else        { controls.UI.Disable(); controls.Gameplay.Enable(); }

        Cursor.lockState = paused ? CursorLockMode.None : CursorLockMode.Locked;
        Cursor.visible = paused;
    }
}

Part 2 — the camera rig

  • A Cinemachine camera with Third Person Follow, Follow = Knight, LookAt = CameraTarget at chest height.

  • Camera Collision Filter set to your Environment layer, so it pulls in at walls instead of clipping.

  • Damping 0.3 on all three axes — enough to feel weighty, not enough to feel drunk.

  • A second camera for the ultimate, Priority 0 normally, raised to 20 for 1.5 s when R fires.

  • An Impulse Listener on both cameras, an Impulse Source on the player, fired on every hit.

CameraDirector.cs
using System.Collections;
using UnityEngine;
using Unity.Cinemachine;

public class CameraDirector : MonoBehaviour
{
    [SerializeField] private CinemachineCamera gameplayCam;
    [SerializeField] private CinemachineCamera ultimateCam;
    [SerializeField] private CinemachineImpulseSource impulse;

    /// call from SkillCaster when a hit lands
    public void Shake(float strength = 0.35f) => impulse.GenerateImpulseWithForce(strength);

    /// call when the ultimate starts
    public void PlayUltimateShot(float seconds = 1.5f) => StartCoroutine(Cut(seconds));

    IEnumerator Cut(float seconds)
    {
        ultimateCam.Priority = 20;
        yield return new WaitForSeconds(seconds);
        ultimateCam.Priority = 0;
    }
}

Part 3 — the touch layer

  • A Canvas set to Scale With Screen Size, reference 1080 × 1920, Match 0.5.

  • Bottom-left: On-Screen Stick bound to Move. Bottom-right: three skill buttons and jump.

  • SafeAreaFitter on the root panel, and every control anchored to a corner, never the centre.

  • The whole touch canvas is disabled on desktop — check Application.isMobilePlatform in Awake.

C# script
void Awake()
{
    bool mobile = Application.isMobilePlatform;

#if UNITY_EDITOR
    mobile = forceTouchInEditor;    // a bool you tick to test on desktop
#endif

    touchCanvas.SetActive(mobile);
}

Done when

  • ✓The same build responds to keyboard, a plugged-in gamepad, and on-screen touch controls with no code branches.
  • ✓Pressing W always walks away from the camera, at every camera angle.
  • ✓Walking the character into a wall pulls the camera in smoothly instead of showing the inside of the wall.
  • ✓The character never vibrates while moving — camera logic is in LateUpdate.
  • ✓Every landed hit shakes the camera slightly; the shake ends inside 0.25 s.
  • ✓The ultimate cuts to a second camera and blends back automatically.
  • ✓Pressing Esc pauses, frees the cursor, and gameplay input stops — the character does not keep walking.
  • ✓There is no Input.GetAxis or Input.GetKey left anywhere in the project.

Numbers to hit

MetricTargetWhy
Camera damping0.2–0.4 sBelow 0.2 it snaps, above 0.5 the player loses the character.
Shake duration≤ 0.25 sLonger reads as a bug and makes people motion sick on mobile.
Pitch clamp-25° … 65°Outside that range the camera flips over the head.
Touch target size≥ 44 ptSmaller and thumbs miss constantly on a real phone.

If you want more

bonus 1

Add a Sensitivity slider and an Invert Y toggle that persist between runs.

bonus 2

Detect the active device and swap the on-screen key hints between Q/E/R and gamepad glyphs.

bonus 3

Add a lock-on camera: click the middle mouse button to frame the nearest enemy. (Tab already cycles characters.)

bonus 4

Zoom the camera in when the player is close to a wall, using a Cinemachine extension.