Skip to content

Repository files navigation

Universal Player

fr.jeanf.universal.playerone player, every input. A single player prefab that works in Mouse & Keyboard, Gamepad and VR, switching between them automatically, in both URP and HDRP.


Philosophy

This package was built for a hospital simulator (the UVS project) where the same scenario must be playable by a student at a desk, on a couch with a controller, or inside a headset — without authoring the interaction three times. Everything in the package follows from four principles:

  1. One player, every input. The player watches all connected devices and switches to whichever one you actually use — no menu, no setup, "most recent meaningful input wins". Using the mouse, keyboard or gamepad switches to desktop; picking up and pressing a VR controller (or donning the headset where the runtime reports presence) switches to VR — with Ctrl+Alt+K / Ctrl+Alt+V as explicit fallbacks for runtimes that don't report headset presence (e.g. some setups over Link).
  2. Feature parity across modes. Interacting, grabbing, sitting, teleporting, menus — every feature raises the same events whatever the input device. If it works in VR it works on desktop, and vice versa. Gameplay code never needs to know which mode is active.
  3. Simple to set up, loud when broken. One menu click creates a working player for your render pipeline. A validation tool (Tools/Jeanf/UniversalPlayer/ValidateSetup) and runtime guards report misconfiguration with actionable messages instead of failing silently.
  4. The project owns the game; the package owns the player. Communication happens through ScriptableObject event channels and C# delegates (single PlayerChannels hub — see Documentation~/player-channels-hub.md). The map, the inventory, the menus, the scenario logic all live in your project; the player just reports what the user did.

Features

  • Automatic input switching between Mouse & Keyboard, Gamepad and VR (plus a free-fly debug camera)
  • Locomotion & camera feel: walk, sprint, crouch, jump, momentum, head bob, turn tilt, landing dip
  • Footsteps & friction audio: surface-aware footsteps (cadence follows speed, crouch sneaks), shoe-scuffs on abrupt turns and hard stops, plus jump/landing/crouch foley driven by real locomotion events (Documentation~/footsteps.md)
  • True first-person body: a visible, animated body (Mixamo locomotion set) with one-click template setup
  • Sitting on chairs in every mode (Documentation~/sitting.md)
  • Teleportation: XRI-style stick teleport in VR, event-driven teleports (SO channels) everywhere
  • Hands: tracked hand poses, controller grip/trigger pose ladder, grab highlight + ghost-hand grab preview, fingertip UI ray
  • Hand appearance: swap gender, skin color, add gloves — all through events
  • Pose & gesture authoring tools: a step-by-step Pose Editor wizard with IK, mirroring, auto-pose and gesture presets (see below)
  • Reticle (desktop/gamepad): tints over anything usable, fills up for gaze-validated actions, flashes pinkish-red on rejected requests (see Cursor feedback)
  • Safety nets: infinite-fall recovery, XR health diagnostics, controller battery warnings + auto-pause

Installation

Add the scoped registry in Project Settings → Package Manager:

  • Click + under Scoped Registries
  • Name: jeanf
  • URL: https://registry.npmjs.com
  • Scope: fr.jeanf

Then install Universal Player from the Package Manager (My Registries). Dependencies (Input System, XR Interaction Toolkit, XR Hands, OpenXR, event system…) are pulled in automatically.

Make sure the new Input System is active: Edit → Project Settings → Player → Configuration → Active Input HandlingInput System Package (or Both).

Requires Unity 2022.3+, URP or HDRP.


Getting started

  1. In your scene's Hierarchy: Right click → Create Universal Player. This creates a player for your current rendering pipeline, with the camera bound to everything that needs it.
  2. Run Tools/Jeanf/UniversalPlayer/ValidateSetup — it checks the scene wiring and tells you exactly what to fix if anything is off.
  3. Press Play. Move with WASD, plug in a gamepad and touch a stick, or put on a headset — the player follows you.

To integrate with your project, copy the PlayerChannels asset locally (the player offers a project-local copy on request) and subscribe to its events — teleports, map/inventory toggles, interactions — from your own systems. This keeps your wiring safe from package updates.


Supported modes

Mode How it activates Notes
Mouse & Keyboard Any key press, click or mouse movement First-person, center-screen reticle (tints green over anything usable, fills up for gaze-validated actions)
Gamepad Any button/stick input on a connected gamepad Same first-person experience as Mouse & Keyboard
VR Press a VR controller button, or don the headset where the runtime reports presence (Ctrl+Alt+V to force) XR Interaction Toolkit rig: tracked hands with poses, grab preview (highlight + ghost hand), fingertip UI ray
Free camera Toggled from Mouse & Keyboard or Gamepad (see bindings) Detached fly-through camera for debugging/inspection; toggling again returns to the previous mode

Leaving VR: any deliberate keyboard, mouse or gamepad input drops back to desktop; Ctrl+Alt+K forces it even when the headset still reports as worn. Entering VR: a controller button or Ctrl+Alt+V is the reliable path on runtimes that don't report userPresence. A debug HUD (Ctrl+Alt+H) shows the live mode/presence state.


Controls & bindings

All desktop/gamepad bindings live in Runtime/InputActions/UniversalPlayer_InputActions.inputactions (FPS action map; UI in the XRI UI map, VR in the XRI Head/XRI LeftHand/XRI RightHand maps) and can be remapped per project.

Map and Inventory only report the press on their ScriptableObject event channels (PlayerChannelsSO.toggleMap / toggleInventory) — the project owns both UIs and their open/close state.

Opening the main menu (any mode) pauses the game and fades the world to black; closing it fades back to whatever the world was doing (still loading → stays black, head in wall → desaturated, otherwise clear).

Mouse & Keyboard

Mouse & Keyboard default bindings

Action Binding
Move W A S D / arrow keys
Look around Mouse
Sprint Left Shift (hold)
Crouch (toggle) C
Jump Space
Stand up from crouch C again, Space or sprint
Interact / take object E / left click
Scroll UI lists/tables (element under the cursor) Mouse wheel
Draw primary item 1
Draw secondary item 2
Map (raises toggleMap channel) M
Inventory (raises toggleInventory channel) I
Main menu Esc
Pause P
Toggle free camera Ctrl+Alt+F (also Ctrl+Shift+C)

Gamepad

Gamepad default bindings

Action Binding
Move Left stick
Look around Right stick
Sprint Left stick press (L3)
Crouch (toggle) Right stick press (R3)
Jump Y
Stand up from crouch R3 again, Y or L3
Interact / take object A or RT (right trigger)
Scroll UI lists/tables (element under the reticle) RB (up) / LB (down)
Free cursor (menu open / primary item drawn) Right stick moves the cursor, A / RT click
Scroll the content under the free cursor Hold LT + right stick
Draw primary item D-pad up
Draw secondary item D-pad down
Map (raises toggleMap channel) D-pad left
Inventory (raises toggleInventory channel) D-pad right
Main menu Start
Pause Select
Toggle free camera Hold D-pad down + Y
Menu navigation Sticks / D-pad, A submit, B cancel

X and B are otherwise unbound in gameplay.

VR

The VR rig follows the XR Interaction Toolkit conventions; item, seat and UI interactions raise the same events as on desktop.

VR default bindings

Action Binding
Walk Left thumbstick
Turn Right thumbstick (snap turn)
Teleport Push a thumbstick forward, aim the arc, release
Grab Grip, both hands (highlight + ghost-hand grab preview)
Interact / use / UI click Trigger or X / A (lower face button), both hands — fingertip ray for UI
Draw primary item Y / B (upper face button, either hand)
Main menu Menu button (left controller)
Hand tracking Pinch = select / UI press

Gaze-timed validation applies where the scene uses it.

Free camera (while active)

Action Mouse & Keyboard Gamepad
Move W A S D Left stick
Look around Mouse (hold right click to rotate) Right stick
Up / down E / Q Right trigger / left trigger
Exit Ctrl+Alt+F (or Ctrl+Shift+C) Hold D-pad down + Y

System & debug chords (keyboard, any mode)

Chord Effect
Ctrl+Alt+K Force Keyboard & Mouse (leave VR, even if the headset still reports as worn)
Ctrl+Alt+V Force VR (also: press any VR controller button)
Ctrl+Alt+F Toggle free camera
Ctrl+Alt+H Toggle the XR mode debug HUD
Ctrl+Alt+J Log a full XR state snapshot to the Console

Cursor feedback

Animated reticle states

The desktop / gamepad reticle is driven by a single CursorStateController on the Player's _settings object — one place for every cursor color and icon:

Field When it shows
Normal / Tablet Cursor Color resting color (tablet = while the primary item is drawn)
Normal / Tablet Cursor Sprite optional icon overrides (tablet falls back to normal, normal falls back to the authored sprite)
Hover Color aiming at anything usable — interactables, seats, pickables, tooltip objects
Click Color while the interact/take button is held on something usable
Invalid Cursor Color (pinkish red) flashed when a click/interaction is rejected

Signalling an invalid request

When your game logic rejects a click ("you can't do that here", not enough resources, locked door, …), flash the reticle its invalid color from any script:

using jeanf.universalplayer;

// e.g. inside your interaction handler when the request is denied:
PlayerEvents.RaiseInvalidAction();

That raises the package's internal InvalidActionSignaled delegate; CursorStateController flashes the reticle Invalid Cursor Color for Invalid Flash Seconds (default 0.35 s), overriding hover/click/resting, then returns to the correct resting color. No inspector wiring — it's a static call. Tune the color and duration on the CursorStateController.

VR hides the reticle, so this is M&K / gamepad feedback. A rejected VR poke/ray should be signalled on the interactable itself (haptics / sound). If you drive rejections from a ScriptableObject "action denied" channel today, call PlayerEvents.RaiseInvalidAction() from that channel's listener — a dedicated hub slot arrives with the channel-hub phase 2.


Hand pose authoring

Open Tools/Jeanf/UniversalPlayer/Pose Editor. The window is a step-by-step wizard:

  1. Pose — select an existing pose asset or create a new one
  2. Target — is the pose for a held object or a bare hand gesture?
  3. Object — assign the object (held-object poses only; the hands auto-spawn around it at correct scale, whatever the object's scale)
  4. Edit — pose in the Scene view, live-saved into the asset
  5. Save / mirror — mirror left↔right across a selectable, visualized plane

Editing tools in the Scene view:

  • Joint dots (deep blue, depth-graded) — click to get a rotation handle; joints only rotate, bone lengths can never change
  • Orange root dot — move/rotate the whole hand
  • Pink fingertip cubes — IK targets with a plane-drag move gizmo; the solver is a planar hinge chain with anatomical flexion limits (no twisted or broken fingers)
  • Auto-pose — curls the fingers until they contact the object or an already-posed finger
  • Gesture presets — one-click fist, point, thumbs up, rock horns, "I love you"… with a flexibility slider driving anatomical coupling (neighbour drag, fingertip lag, convergence)
  • Toggles for the VR controller model, finger colliders, mirror plane and player-view reference
  • A Reset footer if things ever get janky

Hands get per-phalanx box colliders at runtime (the bind-pose mesh collider is replaced automatically); a button in the editor bakes them into your hand prefabs.


Documentation

Design docs live in Documentation~/:

  • player-channels-hub.md — how the package talks to your project (delegates + one channel bridge)
  • true-first-person-body.md — the visible body architecture
  • sitting.md — Seat + SitController across modes
  • footsteps.md — surface-aware footsteps + friction scuffs (FootstepAudio)
  • editor-validation.md — the inspector validation contract ([Validation] / RequiredIf / IValidatable) and the tests that guard it
  • action-animation-binding.md — SO-based action → animation binding (proposal)
  • gesture-wheel.md — controller gesture radial menu (design, upcoming)

Developing the package

To work on the package itself (instead of consuming it from the registry):

  • Add the scoped registry (see Installation) and install Event System and Property Drawer from it
  • Clone this repository into your project's Assets/ folder
  • Unity resolves the remaining dependencies from the manifest; if anything is missing, ValidateSetup and the EditMode integrity tests will point at it

⚠️ Pushing to main publishes a signed release to the registry. Work on branches; merge deliberately.


Compatibility

  • Unity 2022.3+
  • URP and HDRP
  • OpenXR (incl. Oculus/Meta) via XR Interaction Toolkit 3.x

License


Credits

  • This repo was started during an artist residency called fantomas at Medrar in Cairo, October–December 2022, teaching artists to create artworks in VR.
  • Since then it is supported by a research group at UQAR university (Canada): Laboratoire Onirique, and specifically the project UVS (Unité virtuelle de soins / Virtual Health Unit) initiated by Daniel Milhomme and Frédérique Banville.
  • [3D] Jonathan Lepinay
  • [Code] Nicolas Chouin, Felix Cotes-Charlebois & Jean-François Robin
  • Partly inspired by Unity Open Project 1 for the event system.

About

Universal player (Mouse+Keyboard, VR)

Resources

Stars

0 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages