mdgriffith / elm-animator

A timeline-based animation engine for Elm

Home Page:https://package.elm-lang.org/packages/mdgriffith/elm-animator/latest/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Unexpected behaviour when trying to use elm-animator for a game

w0rm opened this issue · comments

Hi! I am working on a game and trying to use elm-animator for it. The package is really great and API looks easy to use.

I faced three unexpected issues though:

  1. Interpolation to 0. I am queueing the striking animation, and then back to the resting position, however the animated value seems to jump to 0. Inserting Animator.millis 1 seems to fix it.
    interpolation
eel.timeline
  |> Animator.queue
    [ Animator.event Animator.quickly (Eel.Striking mouse)
    -- uncommenting this fixes the interpolation issue:
    --, Animator.wait (Animator.millis 1)
    , Animator.event Animator.quickly Eel.Resting
    ]
  1. Queuing return animation in the middle of existing animation causes overshooting of values. Can be reproduced by clicking near eel's head when its moving up.

eels

  1. Queuing animation in the middle of existing animation sometimes jumps to the final state. Can sometimes be reproduced by clicking near eel's head when it is biting the mouse.

eels (1)

I captured these issues in the branch as we discussed on slack.

SSCCEE for item 2:

module Main exposing (main)

import Animator exposing (Animator, Timeline)
import Browser
import Browser.Events
import Html exposing (Html)
import Html.Attributes
import Html.Events
import Time


type Msg
    = Click
    | Tick Time.Posix


main : Program () (Timeline Float) Msg
main =
    Browser.element
        { init =
            \_ ->
                ( Animator.init 0
                    |> Animator.go (Animator.seconds 2) 1
                , Cmd.none
                )
        , view = view
        , update = update
        , subscriptions = \_ -> Browser.Events.onAnimationFrame Tick
        }


update : Msg -> Timeline Float -> ( Timeline Float, Cmd Msg )
update msg timeline =
    case msg of
        Click ->
            ( Animator.queue
                [ Animator.event (Animator.seconds 1) 0
                , Animator.wait (Animator.seconds 5)
                , Animator.event (Animator.seconds 2) 1
                ]
                timeline
            , Cmd.none
            )

        Tick time ->
            ( Animator.update time animator timeline
            , Cmd.none
            )


view : Timeline Float -> Html Msg
view timeline =
    let
        width =
            Animator.linear timeline Animator.at * 500
    in
    Html.div
        [ Html.Attributes.style "margin" "20px"
        , Html.Attributes.style "width" "500px"
        , Html.Attributes.style "height" "8px"
        , Html.Attributes.style "background-color" "black"
        , Html.Attributes.style "padding" "1px"
        ]
        [ Html.div
            [ Html.Attributes.style "width" (String.fromFloat width ++ "px")
            , Html.Attributes.style "height" "8px"
            , Html.Attributes.style "background-color" "white"
            ]
            []
        , Html.button
            [ Html.Attributes.style "margin" "10px 0 0", Html.Events.onClick Click ]
            [ Html.text "Click during animation to see the jump" ]
        ]


animator : Animator (Timeline any)
animator =
    Animator.watching identity always Animator.animator

issue

Expected behaviour: When clicking the button during animation, I expect the bar to go until the end, and then return back.

This can be a test case that shows that queuing during animation breaks the animated value. Using the linear scale here, so the value should be a bit more than 0.5. However, with the new event queued, it becomes more than 0.6.

val : ( Float, Float )
val =
    let
        animator =
            Animator.watching identity always Animator.animator
        init =
            Animator.init 0
                |> Animator.update (Time.millisToPosix 0) animator
                |> Animator.go (Animator.seconds 2) 1
                -- count 1 second (not sure why I need to call update twice here to work?)
                |> Animator.update (Time.millisToPosix 0) animator
                |> Animator.update (Time.millisToPosix 1000) animator
        withoutQueue =
            init
                |> Animator.update (Time.millisToPosix 1001) animator
        withQueue =
            init
                |> Animator.queue [ Animator.event (Animator.seconds 1) 0 ]
                |> Animator.update (Time.millisToPosix 1001) animator
    in
    ( Debug.log "without queue" (Animator.linear withoutQueue Animator.at)
      -- without queue: 0.5007324216421694
    , Debug.log "with queue" (Animator.linear withQueue Animator.at)
      -- with queue: 0.6258543726289645
    )

Ok! So, I fixed a queuing bug as well as a few other issues in 4461b4f

It does fix the loading bar example you posted. I'm hoping it fixes all of these!

Will publish a patch release shortly.

Published as 1.0.2!

@mdgriffith thanks a lot! I confirm that this fixed all 3 issues I had in the game.

🙌 wonderful!