Skip to content

feat(gamepad): Gamepad API for iOS, Android and Windows - #159

Merged
triniwiz merged 6 commits into
v3-v8from
feat/gamepad
Sep 28, 2026
Merged

triniwiz merged 6 commits into
v3-v8from
feat/gamepad

Conversation

@triniwiz

@triniwiz triniwiz commented Sep 28, 2026 •

Copy link
Copy Markdown
Member

Closes #139

Adds the Gamepad API as a new package, @nativescript/canvas-gamepad. navigator.getGamepads() was a stub returning [], so games built on Phaser, Pixi, Three or Babylon could not use controllers.

How it works

Games call getGamepads() every frame, so polling must not cross into native code. The native side writes each controller's state into one JS-owned Float32Array whenever input arrives, the same pattern as the canvas bounds buffer. getGamepads() and the Gamepad objects are plain TS reading that memory.

  • Fixed layout: 4 slots × 80 floats: connected, a change sequence, mapping, axis/button counts, 8 axes, 32 buttons × (value, flags).
  • The sequence counter lets TS skip unchanged pads and set timestamp only when something changed.
  • Standard mapping: 4 axes, 17 buttons (16 on Windows, which has no guide button).
  • Gamepad objects are updated in place between calls, as in Firefox. vibrationActuator is null for now.

Platforms

  • iOS / tvOS / visionOS (NSCGamepadManager, ObjC): GameController connect/disconnect notifications. Each GCExtendedGamepad's valueChangedHandler rewrites its slot. The Siri Remote (micro gamepad) is ignored and keeps its keyboard mapping in NSCCanvas.
  • Android (NSCGamepadManager, Java): InputManager device listener for connect/disconnect. Stick and button events are taken at the activity's Window.Callback, because joystick events only reach the focused view. Only events from tracked controllers are consumed, and only after JS starts monitoring, so B stays Back for apps that never use the API. D-pad hats and analog triggers are detected per device.
  • Windows (crates/canvas-gamepad-napi → canvasgamepad.node): Windows.Gaming.Input can only be polled, so getGamepads() makes one poll(buffer) call per frame. GamepadAdded/GamepadRemoved are re-posted to JS through a threadsafe function. make windows-gamepad builds it.

canvas-polyfill

  • navigator.getGamepads() and gamepadconnected/gamepaddisconnected use the package when it is installed, and fall back to [] otherwise. Nothing starts until a page polls or adds a gamepad listener.
  • On iOS the runtime already provides a global addEventListener/dispatchEvent, so the polyfill's global.emitter never reaches window listeners there. Gamepad events are dispatched through global.dispatchEvent when it exists, and through the emitter otherwise. resize.ts calls emitter.emit, which Observable does not have, so that event is probably not delivered either. Not changed here.

Testing

  • node --test 'packages/canvas-gamepad/__test__/*.test.mjs': 7 tests for the buffer reader, connect/disconnect events and polling.
  • iOS simulator (iOS 26.4): the Mac's paired DualSense shows up as a standard pad, gamepadconnected reaches window, and the new canvas-gamepad demo page renders it at 60 fps. getGamepads() plus reading every axis and button: about 320–380 ns per frame.
  • Android emulator (Pixel 9 Pro): builds, loads the Java class and starts with the shared buffer. About 340 ns per frame in a loop, about 5 µs for a single call in rAF. The emulator has no gamepad.
  • Objective-C compiles warning-free for iOS 12+ and tvOS 12+ and for visionOS. canvas-gamepad-napi passes cargo check and clippy for x86_64-pc-windows-msvc.
  • Not tested: live stick/button input on any platform, Android with a real controller (including a focused TextField), and the Windows module (not built or run yet). The demo page (--demo=canvas-gamepad / --es demo canvas-gamepad) logs GAMEPAD| lines for these.

@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 6297e3ba-e241-47e2-8055-41cb451d2437

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

New @nativescript/canvas-gamepad package: navigator.getGamepads() plus
gamepadconnected/gamepaddisconnected, standard mapping, up to 4 pads.

Native code writes controller state into one shared Float32Array as input
arrives; getGamepads() reads it in TS, so polling makes no native calls on
iOS/Android and one per frame on Windows.

- iOS/tvOS/visionOS: GameController GCExtendedGamepad (ObjC)
- Android: KeyEvent/MotionEvent taken at the activity's Window.Callback
- Windows: Windows.Gaming.Input poller (crates/canvas-gamepad-napi)
- canvas-polyfill: getGamepads() and window events use the package when
  installed; events go through the iOS runtime's own EventTarget
@triniwiz
triniwiz merged commit ea56330 into v3-v8 Sep 28, 2026
8 of 17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant