STACKERゲームを作ろう その5

nekoroll.hatenablog.com
の続き

今回やること

  • Spaceキーで止められるようにする
  • 止めたら上の段に移動する

完成品はコチラ ellie-app.com

実装解説

Spaceキーの入力を待ち受けるようにする

subscription

subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.batch
        [ Time.every 500 Blinking
        , Browser.Events.onKeyDown (D.map KeyDown keyDecoder)
        ]

Sub.Batchで複数のsubscriptionを定義した

キー入力を判別する

type KeyType
    = Space
    | Other


keyDecoder : Decoder KeyType
keyDecoder =
    D.map toKey (D.field "key" D.string)


toKey : String -> KeyType
toKey key =
    case key of
        " " ->
            Space

        _ ->
            Other

browser/keyboard.md at master · elm/browser · GitHub
Browser.Events - browser 1.0.2
を参考にした
Browser.keyDownonKeyDown : Decoder msg -> Sub msgになっているので
受け取ったキー入力をDecodeしてSpaceとそれ以外で分けるようにした
ググるkeyCodeがたくさん出てきたんだけど、APIkeyを推奨しているのでkeyにしてみた

update

type Msg
    = Blinking Time.Posix
    | KeyDown KeyType


type KeyType
    = Space
    | Other


        KeyDown keyType ->
            case keyType of
                Space ->
                    let
                        currentPoint = 
                            case model.lightPoints of
                                [] ->
                                    { x = 0, y = 0 }

                                headPoint :: rest ->
                                    headPoint
                        nextRow =
                            { rowSize = currentPoint.x - 1, columnSize = 6 }
                    in
                    ( { model
                        | stoppedLightPoints = model.stoppedLightPoints ++ model.lightPoints
                        , lightPoints = setStartPoints nextRow 3
                      }
                    , Cmd.none
                    )

                Other ->
                    ( model, Cmd.none )

新たにKeyDownVariantを定義してSpaceOtherでパターンマッチ
Spaceの時は現在の行(厳密にはPoint)を取得し、光る箇所を一段上の行の右端にするようにした
また、積み重ねていく必要があるためSpaceを押した時点の光っている箇所を保存するstoppedLightPointsも定義した

光らせる部分

type alias Model =
    { boxList : List Box
    , fieldSize : FieldSize
    , lightPoints : List Point
    , moveMode : MoveMode
    , stoppedLightPoints : List Point
    }

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Blinking _ ->
            let
                concatLightPoints = model.lightPoints ++ model.stoppedLightPoints
                blinking box =
                    if List.member box.point concatLightPoints then
                        { box | lightMode = LightOn }

                    else
                        { box | lightMode = LightOff }

stoppedLightPointsSpaceを押した時点の光っている箇所、
lightPointsに現在動いている行の光っている箇所がはいっているので
マージし一つの配列にして、配列内に含まれるPointを光らせる様にした

これで積み上げることが出来た!
…と思ったら何かラグい。スペース押したら1マス分絶対ずれる

AB先生のエスパー

マジもんの超能力者でビックリした
画面の更新にTIme.everyを使っているけどNGだった
正しくはBrowser.Events.onAnimationFrameだった

Time - time 1.0.0
ちなみにTime.everyのDocに「アニメーションには使わないほうがいいよ」って書いてあった(大反省)
しかも親切にBrowser.Events.onAnimationFrameのリンクまである
コンパイラ外でも助けてくれるElmママの優しさに涙が止まりません

onAnimationFrameを使う

subscription

subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.batch
        [ Browser.Events.onAnimationFrame Blinking
        , Browser.Events.onKeyDown (D.map KeyDown keyDecoder)
        ]

1秒で出来た

ただ問題があって、1秒に60回更新されるのでめちゃくちゃ難化してしまった
ズレはなくなった…気がする(多分)

f:id:nekorollykk:20200923232837g:plain
さながらParty Parrot

何らかの方法で60回を半分にするとか出来ると思うので、後々対応していく
まずは積み上げて動かすことが出来たので一旦ゴール

まとめ

  • Docはちゃんと読みましょう
  • onAnimationFrame便利すぎる
    jsだとsetTimeoutとかでやって大変だった記憶がある

次回

  • 積み上げた際に下の段と重なっていないマスを落とす
  • 1つも重なっていなければゲームオーバー
  • 動き速すぎ問題対処

の予定