71
50

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

CAMPFIREAdvent Calendar 2020

Day 7

走る!音が出る!HTMLでミニゲームをつくりました

Last updated at Posted at 2020-12-06
FIRE RUNNER SAN 会社非公式な感じの、シンプルな**ランゲーム**を作りました。

https://hasegawa-campfire.github.io/fire-runner-san/
こちらから遊べます。
PC推奨です。IEは死にました。すこし音も出ます。

実際に開くと、なんだか下のほうがゴチャゴチャしていますね。
ここ数日、色々と後付けをしてこうなりました。
ランキングとか。リプレイとか。SNSシェアとか。効果音とか。
ああ、付けてみたいなと思ってしまったのですから、これは仕方のないことです。

その辺り、思うところや紆余曲折などイロイロありますが。
モロモロは note にしたためて、こちらは技術的な記録を残します。

※note
会社でランゲームをつくりました

技術概要

HTML5 でゲーム作りと言えば、やはり WebGL でしょう。
描画が高速で、表現力も高い。ライブラリやフレームワークも充実しています。

しかし今回は WebGL どころか canvas ですらなく、本当に HTML で構成されています。
ライブラリも特に使っていません。(Firebase を除く)

概略
<div class="app">
  <div class="world" style="transform: translate(32px, 256px);">
    <svg class="player" style="transform: translate(0px, 0px);">...</svg>
    <svg class="item" style="left: 200px; top: 0px;">...</svg>
    ...
  </div>
  <div class="status show">...</div>
  ...
</div>

div を連ねて、 positoin で配置して、 border background などで彩る。
addEventListener を仕掛けて、transform などで動かす。

Webフロントとしては、いつもの見慣れた HTML + CSS + JS です。

つよいところ

  • とにかく書き慣れていること
  • 複雑な仕組みが要らないこと
  • 簡単なアニメーションが、本当に簡単なこと

あと今回に関して言えば、Webフロントエンジニアの発表物として、
HTML と CSS を活用したほうが紹介しやすい部分も多いだろう、
という目論見もありました。
当初は。

つらいところ

  • とにかく遅いこと
  • ゲームとしての表現力は弱いこと
  • 表示の見え方についても動作環境に振り回されること

あと今回に関して言えば、スクリーンショットが取れないこと。
SNSシェアに使いたかったんです。
しかし自前で実現しようと思ったら、ものすごく頑張る必要があります。
html2canvas でもダメだったので、諦めました。

ゲーム表示対応

実は、見慣れた内容になりすぎて、改めて紹介できそうな部分がとても少なくなりました。
とはいえ、
さすがに普段作っているWebページでは使わないようなものも、いくつかあります。

描画速度の改善

HTMLの要素をゴリゴリ動かすので描画が遅くなりがちです。特にスマホ。
完成してから、初めて手元のスマホから見て頭を抱えたりもしましたが、もう遅い。

というわけで描画改善ですが、ただおもむろに will-change をつけるだけです。
簡単ですね。
これは予めどう動くのか宣言しておくことで、
その要素についてGPUを確保するなど最適化をしてくれるそうです。

ただし、乱用するとGPUやメモリを圧迫して逆に遅くなるとも言います。
あと意図しない表示バグを引き起こしたりもします。

.hoge {
  will-change: transform, opacity;
}

とはいえ効果は絶大。ガタガタしていたゲームが、突然ヌルヌルになるほど。

スマホのスペックの目安がわからなくて、どの程度まで動けばいいのか判断に困るのですが、
とりあえず手元の Rakuten Mini で動けばだいたい動くのでは?
という調整で作っています。

※ただしゲーム的にはPC推奨。スマホでのクリアは相当困難です。

GPUで困った時のおまじない

will-changetransform を使っていると、時々、想定外の事態に出くわします。
表示が欠けるとか、ボケるとか。重なりが z-index を無視するとか。

そんな時は backface-visibility: hidden を書くと解決する、こともあります。

.hoge {
  backface-visibility: hidden;
}

まるで魔法のようですが、私には解決する理由がわからないので、だいたい魔法です。
意味合いとしては裏面を表示しないようにする、つまりカリングだと思うんですが。
どうしてそれで直るんでしょうね。

右クリックメニューや文字選択の禁止

Webコンテンツを守る不毛な対策。ではありません。
スマホでの長押し操作は、文字の選択やメニューの表示でキャンセルされてしまうため、
それを回避します。

addEventListener('contextmenu', (e) => {
  e.preventDefault()
})
html {
  user-select: none;
}

スマホのビューポート

だいたい定型文で済まされる viewport の定義。
しかしスマホやゲームなら、ちょっと立ち止まってみる価値があるかもしれません。

width に数値を入れると、画面はその幅で表示してくれます。可能な限り。
もうスマホの画面に合わせようと、スクリプトで頑張らなくてもいいんですね。
画面が合わせます。

ダブルタップや、ピンチアウトでの画面の拡大。
それを禁止するのは良くないことだと言われますが、ゲームでは知ったことではありません。

<meta name="viewport" content="width=640,user-scalable=no">

Pull to Refreshの抑制

画面を下に引っ張って、ページを読み直す。
これもゲームでは操作の間違いになりますから、動かないようにします。

body {
  overscroll-behavior-y: none;
}

キーボードでのスクロールの抑制

当然ですが、スペースキーや矢印キーを押すと、ページがスクロールします。
これもゲームでは嬉しくないのでストップ。
なのですが、テキストの入力も出来なくなってしまいますので、
実際にはそういったものを除外するような記述が必要になります。

addEventListener('keydown', (e) => {
  if (e.key === ' ') {
    e.preventDefault()
  }
})

ところで keydown ってキャンセルしても keyup が動くんですね。
keypress は動かなくなりますけど。

音周り

JavaScriptにおいて、音の再生はとても簡単です。
<audio> を作って play() するだけ。ありがたい。

const audio = new Audio('hoge.mp3')
audio.play()

しかしこれではスマホで遅延が起きます。
読み込みので話ではありません。1度再生をした音でも毎回再生で遅延します。

とはいえ、遅延くらい別にいいのでは。PC推奨ですしね。と思っていたのですが。

Safari対応

実はこのままだとSafariでほとんど音が鳴りません。

Chromeでも何か操作をするまで再生できないというセキュリティ仕様がありますが、
Safariは更に厳格で、毎回、ユーザー操作のタイミングでしか再生できないようです。

当然のように回避策はあって、初回のユーザー操作時に再生して、すぐに一時停止して、
あとは好きなタイミングで再開すれば良いとかなんとか。なるほど。

しかし1度再生し終えたら、また再生するには同じことが必要になりそうなので、
効果音として使うのは難しそうでした。
(動きを見ないで次に進んだため、試していません。違っていたらすみません)

Web Audio API

そこで Web Audio API
こちらにも似たセキュリティ仕様はありますが、大本の AudioContext をなんとかすれば、
あとはそれぞれの音を好きなタイミングで鳴らせます。スマホでの遅延もなくなります。
素敵ですね!

今回はこんな感じのものを書いて AudioContext を取りました。

const audioContextPromise = new Promise(resolve => {
  for (const type of ['touchend', 'mouseup', 'keyup']) {
    addEventListener(type, resolve)
  }
}).then(async () => {
  const ctx = new AudioContext()
  await ctx.resume()
  return ctx
})

※参考
Web Audio APIの闇

Firebase

今回、気まぐれにランキングやアカウントの紐付けを実装したくなったので、
初めて Firebase を利用してみました。

これは、なんと言いますか、
とても簡単に使えすぎて知見としてはあまり残せそうにないのが難ですね。
ありがたいことです。

しいて言うと、
Firebase Authentication がユーザー名を持ってくれるので便利だとか、
ランキングは Firestore から1度読んだら、あとは放置しても自動更新されて楽だとか、
そんな感じです。

※参考
0から始める Firestore + Firebase Authentication
Firestore Security Rules の書き方と守るべき原則

おわりに

Webフロントはたのしいですね!
Webフロントはたのしいですよ!

※ FIRE RUNNER SAN リポジトリ
https://github.com/hasegawa-campfire/fire-runner-san

71
50
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
71
50

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?