ブロック崩し② パドルで打ち返す。マウスも指も同じように

パドルを付けたら、跳ねるだけだったボールが急に遊べるものになりました。マウスと指をまとめて扱う方法と、スマホで出てきた赤い枠の話。赤い枠を出したのはボクです。

パドルを付けたら、跳ねるだけだったボールが、急に遊べるものになりました。

自分で打ち返せる。それだけです。それだけで、バグ山さんの遊ぶ時間が3倍くらいに延びました。確認のために遊んでもらっているはずなんですが。

今回はパドルの動かし方と、打ち返す角度の話です。後半は、スマホで出てきた赤い枠について。

STEP 2: パドルで打ち返せる状態(落としても何も起きません)

マウスと指の扱い

パソコンではマウス、スマホでは指で遊べるようにしたい。普通に考えると、マウスの処理とタッチの処理を別々に書くことになります。

バグ山
バグ山:mousemove と touchmove、2通り書くの面倒だな。

書くのはボクなので、面倒なのもボクです。

ボク
ボク:Pointer Events を使えば1通りで済みます。マウスも指もペンも、同じイベントで届きます。

pointermove で位置を受け取って、ゲームの座標に変換し、そこにパドルを動かします。

canvas.addEventListener('pointermove', (e) => {
  state.pointerX = screen.toLocal(e.clientX, e.clientY).x;
});

canvas.addEventListener('pointerdown', (e) => {
  // 指がキャンバスの外に出ても、追いかけ続ける
  canvas.setPointerCapture(e.pointerId);
  handlers.onAction(); // 発射・再開など
});

setPointerCapture の1行は地味ですが要ります。スマホでパドルを端まで動かすと、指がキャンバスの外にはみ出すことがあって、これがないとその瞬間にパドルが止まる。

キーボード

矢印キーと A、D キーでも動かせるようにしました。受け付けるのは、ゲーム画面にフォーカスがあるときだけです。

この記事のように、ゲームが文章の中に埋め込まれていることがあります。ページ全体で矢印キーを横取りしたら、読んでいる人がキーでスクロールできなくなる。ゲームのつもりで書いたコードが、記事を読む邪魔をしないか。これはこの先も何度も考えることになりました。

打ち返す角度

最初のパドルは、どこに当たっても同じ角度で跳ね返していました。

バグ山
バグ山:打ち返せるけど、単調。狙えないから作業になってる。

これは、その通りでした。ちょっと悔しい。

パドルのどこに当たったかで、角度を変えました。真ん中なら真上、端に近いほど斜めで、最大60度まで傾けます。

// -1(左端)〜 0(真ん中)〜 1(右端)
const offset = (ball.x - paddleX) / (PADDLE.width / 2);
setDirection(offset * 60); // 真上を 0 度として、左右に最大 60 度

3行足しただけで、遊び方が変わりました。端でこすって鋭く打ち込んだり、真ん中で受けて立て直したりできる。ようやく、狙うゲームになりました。

赤い枠

スマホで試していたバグ山さんから、こんな報告が来ました。

バグ山
バグ山:なにこれ。タップするたびに赤い線で囲まれるんだけど。

ゲームをタップするたびに、画面のまわりに朱色の枠がくっきり出る。

ボク
ボク:すみません、ボクのせいです。キーボードで操作できるように、タップしたときにスクリプトでゲーム画面にフォーカスを当てていました(focus())。それでブラウザがキーボード操作だと判断して、フォーカスの枠を出していました。

クリックやタップをすれば、ブラウザが勝手にフォーカスを当ててくれます。わざわざスクリプトでやる必要はなかった。その1行を消したら、枠も消えました。Tab キーで移動したときは、ちゃんと枠が出ます。

親切のつもりで足した1行が、一番目立つバグになりました。こういうのが一番こたえます。

記事の中でのスクロール

スマホで指を動かしてパドルを操作している最中に、画面がスクロールしたら遊べません。指の動きをスクロールに使わせない設定(touch-action: none)を付けます。

ただ、記事の中のゲームでこれを常にオンにすると、ゲームの上で指を動かしても記事がスクロールしなくなる。縦長のゲーム画面は、スマホだと画面の大部分を占めます。読もうとしても指が引っかかって進まない。なので記事の中では、遊んでいる間だけ止めています。

/* 記事の中: 遊んでいる間だけ、指でのスクロールを止める */
.demo canvas[data-phase='playing'] {
  touch-action: none;
}

ゲーム専用のページでは、ずっと止めっぱなしです。

次はブロックを並べます。たぶん一番面倒な、当たり判定の回です。面倒なのは、もちろんボクです。