Skip to content

Virtual gamepad plugin, built from the platformer example's touch controls #1719

Closed

Description

@obiot

Summary

The platformer example already ships working on-screen touch controls: a joypad and a jump button in packages/examples/src/examples/platformer/entities/controls.ts, shown on touch devices from play.ts. Every mobile game needs something like it, but today the only way to get it is to copy that file and adapt it by hand.

This issue proposes turning it into a reusable plugin, @melonjs/gamepad-plugin, alongside debug-plugin and spine-plugin, and filling in what the example leaves out.

What the example already does (the starting point)

  • Joypad: a UISpriteElement with a moving thumb, tracking the pointer and releasing when it leaves the pad
  • Button: a UISpriteElement that presses and releases on click
  • VirtualJoypad: a floating, persistent Container holding both, repositioned on VIEWPORT_ONRESIZE
  • input is fed to the game through input.triggerKeyEvent, so existing keyboard bindings just work

What's missing

Controls

  • Full stick: up/down as well as left/right (currently a TODO in controls.ts), with 8-way digital output and an analog value (x/y in -1..1)
  • Deadzone on the stick, matching input.setGamepadDeadzone
  • Configurable buttons: count, position, size and which key or action each one triggers (today one button, hard-wired to SPACE)
  • Optional second stick for twin-stick games
  • Floating stick option: the stick appears where the thumb lands instead of at a fixed spot
  • Buttons that follow the game: a button can be relabelled, dimmed or hidden at runtime, so a game can light its use button only when there is something in reach

Integration

  • Report through the existing gamepad API, so code that reads a real pad (bindGamepad / GAMEPAD constants) works unchanged, with key events kept as an option
  • Multi-touch: hold the stick and press a button at the same time, each touch tracked by its pointer id
  • Pointers that start on the controls are consumed and don't reach the game world underneath
  • A key bound with lock (input.bindKey(key, action, true)) fires once per tap, as it does on a keyboard, however long the button is held
  • Drawn over the whole game: above every other floating renderable whatever its z (a game can keep a full-screen overlay at a very high z and its HUD above that), and only by the main camera, never inside a second camera such as a minimap
  • Across stages: the controls survive a stage change and can be shown or hidden at runtime, so a stage that only needs a tap, like an end screen, can hide them
  • A game can tell when the controls are showing (enabled, readable at runtime), so its prompts can say "tap" instead of naming a key
  • Hide when a real gamepad is connected (option, on by default): the virtual controls hide on GAMEPAD_CONNECTED and come back on GAMEPAD_DISCONNECTED, or when the screen is touched again. Browsers only report a pad once one of its buttons is pressed, so in practice they hide as soon as the player picks up the pad. The readable enabled state follows, so prompts stay right

Look and setup

  • Draws itself with renderer primitives by default (circles and rounded rects, tinted and semi-transparent), so it needs no texture atlas; sprite regions remain an option for custom skins
  • One-line setup, e.g. plugin.register(GamepadPlugin, { buttons: 2 }), with an enabled flag that defaults to device.touch
  • Layout from the viewport, with safe margins, kept on resize and orientation change

Also worth considering

  • Pinch to zoom reported the same way as the mouse wheel, so one handler covers desktop and touch

Done when

  • the plugin is published as its own package, with README, CHANGELOG and tests
  • the platformer example uses the plugin and entities/controls.ts is removed
  • the isometric dungeon example gets its touch controls from it: an 8-way stick and its use button, alongside its minimap camera and its darkness overlay
  • melonjs-input skill documents it, with a symptom table entry for "touch controls do nothing"
  • works on the Canvas and GPU backends

Activity

  1. obiot commented on Oct 30, 2026

    @obiot
    MemberAuthor

    Done in #1799 (the @melonjs/gamepad-plugin package and the engine's virtual gamepad slot) and #1800 (the platformer moved onto the plugin, plus fixes from trying it on a phone: no selection on a long press, the stick answering at half way, a light vibration on button presses).

    Custom skins from a texture atlas moved to #1802.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions