Skip to content

Latest commit

 

History

History
278 lines (196 loc) · 6.61 KB

3. Subscriptions.md

File metadata and controls

278 lines (196 loc) · 6.61 KB

3. Subscriptions

Subscriptions is the way we can listen to external input in Elm. When subscribing the some input we need to define a Msg type that will be triggered in our update function. Subscriptions is described in more detail here.

Adding subscriptions

We need a way to hook into the browsers requestAnimationFrame loop, so that our game tick is synchronized with the browser rendering. We can do this by installing the AnimationFrame package:

elm package install elm-lang/animation-frame

And import it at the top:

import AnimationFrame

First off, we need to update our Msg type. The AnimationFrame subscription is going to include a value for the time delta between each frame. We therefore need to update the Tick type to have a Float associated to it:

type Msg =
	Tick Float

Now we can set up the actual subscription. We can define our subscriptions in a function called subscriptions that looks like this:

subscriptions : Model -> Sub Msg
subscriptions model =
	...

As you can see from the type signature, this function returns a subscription of the Sub. We're not really going to use the model parameter here, but we have to include it since it's required by Html.program.

The AnimationFrame package has a subscription called diffs which will trigger an event on each animation frame and pass in the time delta to our Tick message. We can just return this in our subscriptions function:

subscriptions : Model -> Sub Msg
subscriptions model =
	AnimationFrame.diffs Tick

We also need to change our update function so that it handles the new Tick message:

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
	case msg of
		Tick delta ->
			( model, Cmd.none)

Here we use pattern matching on our msg parameter to match on the Tick type and get the time delta. We're just going to leave the model unchanged for now.

Html.program

In the previous examples we were using Html.beginnerProgram. This is a great way to get started, but it does not support subscriptions. To get that, we have to use Html.program. Here's how the type signature looks like:

{ init : (model, Cmd msg)
, update : msg -> model -> (model, Cmd msg)
, subscriptions : model -> Sub msg
, view : model -> Html msg
}

We see that just like before it takes an update and a view function. The model attribute that we had in beginnerProgram is now called init. We also see that we have a new field called subscriptions.

One thing to notice is that our init and update functions have changed a little. Both now return a tuple with a model and a command.

In Elm, commands (Cmd) are how we tell the runtime to execute things that involve side effects. For example, making http requests or saving something to local storage. We're not really going to use commands in our game, so we can just return Cmd.none in places that require us to return a command. If you're interested, you can read more about commands.

Our init function will now look something like this:

init : ( Model, Cmd Msg )
init =
    ( { ball = initBall
      , paddleLeft = initPaddle 20
      , paddleRight = initPaddle (boardWidth - 25)
      }
    , Cmd.none
    )

It's pretty much identical to our previous implementation, only that we now put our model in a tuple along with some command.

Let's change our update function as well:

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
	    Tick ->
		    ( model, Cmd.none )

More details on commands and subscriptions

The final thing we need to do is update from Html.beginnerProgram to Html.program. Just update your main function to this:

main =
    Html.program
        { init = init
        , update = update
        , view = view
        , subscriptions = subscriptions
        }

Nothing is really happening on the screen, but that's just because we're not updating our model yet. If your using elm-reactor though, your should be able to open the time-travel debugger and see that the Tick event gets fired quite a lot.

By now, your code should look something like this:

module Main exposing (..)

import Html exposing (..)
import Svg exposing (..)
import Svg.Attributes exposing (..)
import AnimationFrame


boardWidth =
    500


boardHeight =
    300



-- MODEL


type alias Model =
    { ball : Ball
    , paddleLeft : Paddle
    , paddleRight : Paddle
    }


type alias Ball =
    { x : Float
    , y : Float
    , vx : Float
    , vy : Float
    , radius : Float
    }


type alias Paddle =
    { x : Float
    , y : Float
    , vx : Float
    , vy : Float
    , width : Float
    , height : Float
    }


init : ( Model, Cmd Msg )
init =
    ( { ball = initBall
      , paddleLeft = initPaddle 20
      , paddleRight = initPaddle (boardWidth - 25)
      }
    , Cmd.none
    )


initBall : Ball
initBall =
    { x = boardWidth / 2
    , y = boardHeight / 2
    , vx = 0.3
    , vy = 0.3
    , radius = 8
    }


initPaddle : Float -> Paddle
initPaddle x =
    { x = x
    , y = 0
    , vx = 0.4
    , vy = 0.4
    , width = 5
    , height = 80
    }



-- UPDATE


type Msg
    = Tick Float


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Tick time ->
            ( model, Cmd.none )



-- VIEW


view : Model -> Html Msg
view model =
    svg
        [ width (toString boardWidth)
        , height (toString boardHeight)
        ]
        [ rect
            [ width (toString boardWidth)
            , height (toString boardHeight)
            , fill "black"
            ]
            []
        , ballView model.ball
        , paddleView model.paddleLeft
        , paddleView model.paddleRight
        ]


ballView : Ball -> Svg Msg
ballView model =
    circle
        [ cx (toString model.x)
        , cy (toString model.y)
        , r (toString model.radius)
        , fill "white"
        ]
        []


paddleView : Paddle -> Svg Msg
paddleView model =
    rect
        [ width (toString model.width)
        , height (toString model.height)
        , x (toString model.x)
        , y (toString model.y)
        , fill "white"
        ]
        []



-- SUBSCRIPTIONS


subscriptions : Model -> Sub Msg
subscriptions model =
    AnimationFrame.diffs Tick



-- MAIN


main =
    Html.program
        { init = init
        , update = update
        , view = view
        , subscriptions = subscriptions
        }