Packages

Reads the game controllers attached to the machine and hands their events to any process.

Current section

Files

Jump to
gamepad README.md
Raw

README.md

# 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.

```elixir
{: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

```elixir
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:

```html
<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, which `Gamepad.source/0`
  returns: `{:helper, path}` runs any program that writes `Gamepad.Wire` lines, and
  `{:linux, glob}` reads the joystick device files matching `glob`

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

```elixir
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}`.