🐍

React+SVGでスネークゲームを作った

2024/05/07に公開

ReactとSVGを組み合わせてスネークゲーム的なものを作った。こういうの。

https://kurehajime.github.io/inyago/

ゲーム内容

ゲーム内容はこんな感じです。

いにゃご。こいつを増やすのがゲームの目的。餌を食べると増殖する。壁や障害物や他のいにゃごと衝突するとゲームオーバー。

餌。いにゃごが1匹増える。

すごい餌。いにゃごが3匹増える。なぜか取りづらいハイリスクな場所にだけ発生する。

うんち。当たるとゲームオーバー。餌を1つ食べると1つ増える。

餌を集めて隊列を伸ばしながら壁や自分自身を避けるゲーム。いわゆるスネークゲームです。
一般的なスネークゲームとの違いは、ウンチによって同一経路をぐるぐる回る戦略を封じているのと、餌を2つ出現させることでプレイヤーに選択を迫っているところ。

技術

ざっくり実装面についても書き残しておきます。

描画まわり

React+SVGでゲーム画面を作っています。
SVGはXMLのタグによって画像をあらわす画像形式なので、HTMLと同様にReactで組み立てることも可能です。
1枚のSVGを複数のReactコンポーネントで組み立て、ゲーム状態が更新されるたびにReactが出力するタグを変化させることで、アニメーションのようなことができます。

このゲームではざっくりこのような構成で1枚のSVG画像を出力しています。

<svg>
┣━フィールドコンポーネント
┃ ┣━背景のタイル
┃ ┣━いにゃごコンポーネント
┃ ┣━餌コンポーネント
┃ ┣━うんちコンポーネント
┃ ┗━スコア表示コンポーネント
┗━タッチ判定コンポーネント
</svg>

SVGのなにげに嬉しいところは、CSSもしっかり効くところです。
ゲームオーバーはfilter: invert();の色反転で表現しています。

SVG内の各部品単位で個別にスタイルを適用することもできます。

ゲームループ

ハイスペックなマシンで遊んだときでも良識的な難易度で遊べるように、useTimerというフックを利用しています。

const { time, start } = useTimer({
     interval: 17,
});

このようにuseTimerにオプションを指定すると、約0.017秒に1度変数timeに現在のカウントが入ってきます。

    useEffect(() => {
       // 状態更新
    }, [time]);

timeを監視して状態を更新すれば、0.017秒間隔(60fps)で状態更新を行うことができ、(十分なスペックの環境であれば)一定の速度でゲームを動かすことが出来ます。

このゲームでは画面の更新を1フレームに1度、ゲームの状態の更新(進行方向の変更、あたり判定)を10フレームに1度行っています。

操作周り

操作はキーボード(↑↓←→)、およびタッチ操作(スワイプ)が可能です。

特殊なロジックとしては、いにゃごが2匹以上いる状態での進行方向と逆向きへの方向転換(自滅する方向への方向転換)は無視されます。

キーボードについてはシンプルにキーイベントで制御すれば良いのですが、タッチ操作に関しては悩みました。
検討した案は以下の3パターンです。

  1. バーチャル方向ボタンを画面に表示してボタンを押させる
  2. 『指が画面に触れた座標』と『指が画面から離れた座標』を比較し方向を判断
  3. 指が画面に触れた状態で一定時間経つごとに座標を記録し方向を判断
  4. 指が画面に触れた状態で一定距離移動するたびに座標を記録し方向を判断

①はボタンが指で隠れて今なんのボタンを押しているのか混乱するので却下しました。
②は一番シンプルな実装なものの、連続方向転換するのが大変なので却下しました。
③は画面から指を離さずに操作できるものの、意図してない微細な指の動きまで拾ってしまうので却下しました。

採用したのは④で、この方式なら画面から指を離さずに連続方向転換が可能で、かつ移動距離の閾値を調整することで感度も調整できます。

その他

hue-rotate

gif画像ではちょっと色合いが分かりづらいですが、一度に3匹増える餌は色がキラカードのように変化しています。

もとの画像はこのような静止画です。

これをCSSアニメーションで色相を変化させることで実現しています。

.special {
    animation: huerotator 1s infinite alternate;
}

@keyframes huerotator {
    0% {
        filter: hue-rotate(0deg);
    }

    50% {
        filter: hue-rotate(360deg);
    }

    100% {
        filter: hue-rotate(0deg);
    }
}

dvh,dvw

画面の下部にフッターを固定で表示しているのですが、これが100vhだと画面に収まらずはみ出してしまいます。これはスクロールに応じて隠れるアドレスバーが悪さをしています。
dvhを使えばアドレスバーの表示状態に応じて良い感じに計算してくれるので、スマホでも常に画面下部に固定できます。

さいごに

ぜひ遊んでみてください。

https://kurehajime.github.io/inyago/

自分のベストスコアは52です。

Discussion