# 5. Worked Example: On-Screen Stick Player Part of [[Mobile Input and Touch]]. Prev: [[4. Device Simulator and Safe Area]]. Next: [[6. Mobile Input Pitfalls]]. Reuse the remappable player from [[5. Worked Example Remappable Player]]. Do not rewrite movement. The phone overlay **simulates a Gamepad** (design A in part 1) so the existing Move / Jump (or Attack) actions keep their Gamepad bindings. Scripts below were **not compiled** in Unity and were **not** play-tested on a physical device. ## 5.1 Scene setup 1. The existing player, Actions asset, and `PlayerInput` (or generated wrapper) already in the scene. Auto-switch may stay **on**; the stick will use isolated input. 2. A Screen Space Overlay **Canvas**, an **EventSystem** with the Input System UI module. 3. A full-screen child with `SafeAreaFitter` (part 4). Name it `SafeArea`. 4. Under `SafeArea`, an Image in the lower-left: `MoveStick`. Add `OnScreenStick`: - Control Path: `<Gamepad>/leftStick` - Movement Range: `80` (tune after you feel it) - **Use Isolated Input Actions:** enabled 5. Optional: a uGUI Button in the lower-right with `OnScreenButton`, path `<Gamepad>/buttonSouth` (Jump, or whatever `buttonSouth` already is in your asset). 6. A thin script that shows the overlay when a touchscreen is present (or when you force it in the Inspector for Simulator testing) and hides it for a keyboard-only playtest. ```csharp using UnityEngine; using UnityEngine.InputSystem; /// <summary> /// Shows the on-screen stick canvas when a touchscreen is present, or when forced. /// NOT compiled in Unity; not play-tested on a device. /// </summary> public sealed class OnScreenControlsVisibility : MonoBehaviour { [SerializeField] GameObject overlayRoot; [SerializeField] bool forceVisibleInEditor; void OnEnable() { InputSystem.onDeviceChange += OnDeviceChange; Refresh(); } void OnDisable() => InputSystem.onDeviceChange -= OnDeviceChange; void OnDeviceChange(InputDevice device, InputDeviceChange change) => Refresh(); void Refresh() { if (overlayRoot == null) return; bool visible = forceVisibleInEditor || Touchscreen.current != null; overlayRoot.SetActive(visible); } } ``` `InputSystem.onDeviceChange` and `Touchscreen.current` are existing Input System 1.20 APIs already in the spirit of [[2. Control Schemes Devices and Consumption Patterns]]. `forceVisibleInEditor` exists because a desktop Editor often has **no** `Touchscreen` until the Device Simulator (or a similar path) injects one. If `Touchscreen.current` stays null in the Simulator in your 1.20.x version, leave the force flag on for Editor tests and gate the overlay on `Application.isMobilePlatform` (confirm 6000.5 Scripting Reference) in a player build. Do not invent a Simulator-only API for this. ## 5.2 Do not add a second Move reader The stick writes into the Gamepad left stick. The player already reads the **Move** action. You should not also poll `OnScreenStick` in `Update`, and you should not also poll `Touch.activeTouches` to move the character. If movement is twice as fast on a phone, you added a second reader. If you use a **generated C# wrapper** *and* `PlayerInput`, the Input System series already forbids constructing a second asset: read `playerInput.actions`. The stick does not change that rule. ## 5.3 A tiny HUD so you can see the value ```csharp using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.UI; /// <summary> /// Labels the current Move action value. Wire the same action the player uses. /// NOT compiled in Unity. /// </summary> public sealed class MoveValueLabel : MonoBehaviour { [SerializeField] InputActionReference move; [SerializeField] Text label; void OnEnable() => move.action.Enable(); void OnDisable() { // Do not disable an action you do not own if PlayerInput is also enabling it. // This Enable/Disable pair is for a scene that uses the reference alone. } void Update() { if (move == null || label == null) return; var v = move.action.ReadValue<Vector2>(); label.text = quot;Move {v.x:F2}, {v.y:F2}"; } } ``` If `PlayerInput` already owns the asset, **do not** disable the action in `OnDisable` of this label (the comment is the lesson). Prefer reading from `playerInput.actions["Move"]` in that setup. ## 5.4 Playtest checklist 1. Keyboard playtest: overlay hidden (force flag off, no touchscreen) - WASD still works; remapping from [[3. Runtime Rebinding]] still works. 2. Device Simulator, Play Mode, notch device, force flag on if needed: drag the stick, `Move` is non-zero, the **player walks**, the stick does **not** snap back mid-drag. 3. Toggle `useIsolatedInputActions` off once and confirm the official snap-back, then turn it back on. 4. Optional jump button fires the same Jump action as `<Gamepad>/buttonSouth`. 5. Rotate to landscape; the stick stays inside `Screen.safeArea`. 6. A **player build** on a phone: same checklist, plus "can I reach the stick with a thumb." Multitouch is out of scope for the Simulator half of this test. ## 5.5 Exercise Ship the overlay on the remappable player. Record a Simulator screenshot (portrait + landscape) and, if you have a device, a 10-second clip of walking with the thumb stick. Write one sentence about why isolated input is on. ## Sources - [[5. Worked Example Remappable Player]] - https://docs.unity3d.com/Packages/[email protected]/manual/create-on-screen-stick-control.html - https://docs.unity3d.com/6000.5/Documentation/Manual/device-simulator-introduction.html