xarvh / elm-gamepad / Gamepad.Advanced

You should use this module only if you need more control than what Gamepad.Simple offers.

  1. Add the port code. See Adding Ports for how to do it.

  2. Decide how you want to persist the UserMappings. This module contains functions to help you encode and decode them.

  3. When your app inits, load the UserMappings. Default to emptyUserMappings if you can't load any.

  4. Decide where and when you want the remapping tool to appear within your app's UI.

  5. Use the Model, Msg, init, update, view in this module to add the remapping tool UI to your app.

  6. Every time update returns a function to update the UserMappings, update them in your app's model and persist them.

  7. In your app's subscriptions, replace Browser.Events.onAnimationFrame/Browser.Events.onAnimationFrameDelta with:

type TheAppMsg
    = OnRemappingToolMsg Gamepad.Advanced.Msg
    | ...

subscriptions : TheAppModel -> Sub TheAppMsg
subscriptions theAppModel =
    if remapingToolIsOpen theAppModel then
        GamepadPort.onBlob (Gamepad.Advanced.onBlob >> OnRemappingToolMsg)
    else
        GamepadPort.onBlob OnAnimationFrame

Blob


type alias Blob =
Gamepad.Private.Blob

The Blob contains the raw gamepad data provided by the browser.

The whole point of this library is to transform the Blob into something that is nice to use with Elm.

animationFrameDelta : Blob -> Basics.Float

This function gives the time passed between the last browser animation frame and the current one, in milliseconds.

It is the same value you get when using Browser.Events.onAnimationFrameDelta.

update msg model =
  case msg of
    OnGamepad blob ->
      let
          -- Cap the elapsed time, in case the user hides the page and comes back later.
          deltaTimeInMilliseconds = min 200 (Gamepad.animationFrameDelta blob)

    ...

animationFrameTimestamp : Blob -> Time.Posix

This function gives the Posix timestamp of the current browser animation frame.

It is the same value you get when using Browser.Events.onAnimationFrame.

update msg model =
  case msg of
    OnGamepad blob ->
      let
          posixTimestamp = Gamepad.animationFrame blob

    ...

getGamepads : List ( String, Gamepad.Digital ) -> UserMappings -> Blob -> List Gamepad.Private.Gamepad

This function returns the current states of all recognised gamepads.

Only recognised gamepads will be returned; use unmappedGamepads to see if there is any gamepad that can be configured.

update msg model =
  case OnGamepad blob ->
    let
        isFiring = Gamepad.isPressed Gamepad.A

        playerFiringByIndex =
          blob
            |> Gamepad.getGamepads model.controls model.userMappings
            |> List.map (\gamepad -> Gamepad.getIndex isFiring gamepad))
            |> Dict.fromList
    in
        updateState playerFiringByIndex

unmappedGamepads : UserMappings -> Blob -> Basics.Int

This function returns the number of connected gamepads that cannot be autoconfigured and are not in UserMappings. If there are any, ask the user to remap them!

User Mappings


type UserMappings

This type contains all the custom mappings generated by the user

emptyUserMappings : UserMappings

UserMappings without any actual user mapping.

Gamepads that the browser recognises as "standard" will still be usable.

userMappingsFromString : String -> Result Json.Decode.Error UserMappings

Creates UserMappings from a JSON string.

userMappings =
    flags.gamepadUserMappings
        |> Gamepad.userMappingsFromString
        |> Result.withDefault Gamepad.emptyUserMappings

userMappingsToString : UserMappings -> String

Transforms UserMappings into a JSON string.

saveUserMappingsToLocalStorageCmd =
    userMappings
        |> Gamepad.userMappingsToString
        |> LocalStoragePort.set 'gamepadUserMappings'

encodeUserMappings : UserMappings -> Json.Encode.Value

Encodes a UserMappings into a JSON Value.

userMappingsDecoder : Json.Decode.Decoder UserMappings

Decodes a UserMappings from a JSON Value.

Remapping tool


type Model

The Elm Architecture Model, ie, the current state of the remapping tool


type Msg

The Elm Architecture Msg type

init : List ( String, Gamepad.Digital ) -> Model

The Elm Architecture init

view : UserMappings -> Model -> Html Msg

The Elm Architecture view function.

You can use it as it is, or customise it with CSS: every element has its own class name, all class names are prefixed with elm-gamepad.

The content will be translated according to navigator.languages.

update : Msg -> Model -> ( Model, Maybe (UserMappings -> UserMappings) )

The Elm Architecture update function.

When a remapping is finished, it will return a function to update the user mappings.

You will want to persist the new user mapping, otherwise the user will need to remap every time tha page reloads.

onBlob : Blob -> Msg

This function turns a Blob into a Msg that you can feed to update.

This is how you pass the Blob information to the remapping tool, so it's very important that you do so every time the remapping tool is open, it won't work otherwise.

subscriptions : Model -> Sub Msg
subscriptions model =
    case model.state of
        RemappingTool _ ->
            GamepadPort.onBlob (Gamepad.Advanced.onBlob >> OnRemappingToolMsg)

        _ ->
            GamepadPort.onBlob OnAnimationFrame

Note: this function replaces the Elm Architecture subscription function because I found that using a Msg constructor forces you to think about how you want to consume gamepad inputs and animations while the remapping tool is open.

In general, when the remapping tool is open you want to ignore all gamepad information, but depending in your application you might still want the timing information to keep the animations going.