Gamepad

Reads the game controllers attached to the machine and hands their events to any process or function. No dependency on a UI framework: a terminal app, a Phoenix channel, a Nerves device or a test can all take a pad the same way.

{:ok, _pid} = Gamepad.start_link(sink: self())

receive do
  {:gamepad, %{type: :button, button: :a, pressed: true}} -> :jump
  {:gamepad, %{type: :axis, axis: :left_x, value: value}} -> steer(value)
end

Installation

def deps do
  [{:gamepad, "~> 0.1"}]
end

Events are maps in one shape whatever the hardware — buttons and axes carry Xbox layout names (:a, :b, :left_bumper, :dpad_up, :left_x, :right_trigger, …), sticks run from -1.0 (left or up) to 1.0, triggers from 0.0 to 1.0, and a controller connecting or leaving is an event too. Gamepad.Wire lists them.

Platforms

OS Reader Build
Linux /dev/input/js*, read directly and decoded in Elixir none
macOS priv/gamepad_helper, Swift on GameController.framework mix compile, when swiftc is present
Windows priv/gamepad_helper.exe, C on XInput mix compile, with the Visual Studio Build Tools (cl and nmake) that elixir_make needs on Windows anyway
Browser priv/static/gamepad.js, the page's Gamepad API none

On Linux the device files must be readable by the user, which distributions usually grant to the input group or to everyone. On macOS the helper sees every controller macOS itself supports over Bluetooth or USB. Gamepad.available?/0 says whether the machine has a reader; without one start_link/1 returns :ignore.

In a browser

gamepad.js speaks the same line protocol to a page's own JavaScript, so a server reads a browser's controllers with the same Gamepad.Wire.parse/1 it reads a helper's lines with. Serve it from Gamepad.browser_script/0 and watch it:

<script src="/gamepad.js"></script>
<script>
  var reader = Gamepad.watch(function (line) { socket.push("pad", { line: line }) });
</script>

Every controller the page sees becomes device, button, axis and gone lines as they change, standard-mapping buttons and axes named as the wire names them and the triggers as axes. Gamepad.watch(sink, { every: 16 }) polls on a timer instead of every animation frame; reader.stop() ends it; Gamepad.available() says whether the page has the API. A page may also keep the lines to itself and map them to actions locally.

Options

Gamepad.start_link/1 takes:

It returns :ignore when the machine has no reader. Under a supervisor:

children = [
  {Gamepad, sink: &MyGame.Input.controller_event/1}
]

Supervisor.start_link(children, strategy: :one_for_one)

With Drafter

A Drafter app on drafter 0.5 or later declares use Drafter.App, controller: true and adds :gamepad to its deps; Drafter starts the reader for the local terminal session and delivers the events to handle_event/2 as {:controller, event}.