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:
:sink- required. A pid, sent{:gamepad, event}for each event, or a one-argument function called with each event:source- a reader in place of the detected one, whichGamepad.source/0returns:{:helper, path}runs any program that writesGamepad.Wirelines, and{:linux, glob}reads the joystick device files matchingglob
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}.