flint>flint blog>2019年>11月>20日>Catast (ウェブ復刻版) 公開

Catast (ウェブ復刻版) 公開

Catast

学生時代 (2003年) に作った Windows 用パズルアクションゲーム "Catast" を、何を思ったか、16年が経過した今頃になってウェブアプリとして作り直してみました。

実はこのゲームのウェブ版を作るという構想はかなり前からあったのですが、その実現を阻むいくつかの要素がありました。 それらを影響の大きさに従って並べてみると、次のようになるでしょうか:

  1. 当時のウェブブラウザの貧弱あるいは互換性のない JavaScript およびCSSの実装 (特に Internet Explorer)
  2. 当時のスマートフォンの性能 (画面サイズ, タッチパネルの反応精度) の低さ
  3. 私氏自身の JavaScript のコーディング技術の低さ

しかし、それから月日は流れ、殆どすべての人が (端末ベンダ謹製の得体の知れない「ブラウザ」ではなく) Chrome や Firefox がインストールされたスマートフォンを持つようになり、また世間的に「もう Internet Explorer はサポートしなくていいよね」という雰囲気 (コンセンサス) が形成されてきたこともあって、上記 1, 2 として挙げた外部的な要因はほぼ取り除かれたように思われます。 また、3 に挙げた私自身の JavaScript の腕前も、本業の方で JavaScript が絡む案件をこなしているうちにそこそこのレベルには達したと自負するに至り、ではここいらでちょっとチャレンジしてみようか、というわけで今回の Catast 復刻に着手した次第。

そこで今回は、この開発作業において苦労した点などについて話をしてみたいと思います。

タッチパネル用UIの難しさ

ゲーム専用コントローラ (DUALSHOCK 3)

私自身ゲームはかなりやる方ではあるのですが、携帯機やスマホゲーにはまったく手を出していません。 その理由はと言えば、ゲーム用に設計されたコントローラを使用できず、思い通りの操作を行うことができず、また長時間プレイした際の疲労の度合いが大きい (これはディスプレイと眼の距離が近いことも一因) ため。

今回の開発で最も苦労したのはまさにこのコントロール用UIの設計であり、とりわけ「へんないきもの」(プレイヤーキャラクタ) を移動させる方向パッドの実装は試行錯誤の繰り返しとなりました。 最終的に他ではあまりみない方式に落ち着いたので、「これ特許取れんじゃね?」なんて思っていたりします。 (← 申請のめんどくささは過去に身を以て経験しているので、行動に移す気は毛頭ない。)

最初に考えたのは、「左移動」「右移動」用の要素を隣接して配置し、それらの間で指を行ったり来たりさせる方式のもの。

しかしこの方式では「へんないきもの」を静止させるためにはパッドから指を離さなければならず、そこから移動を始めようとしたときに正しい位置へ指を置くのが難しいという問題があります。 専用コントローラのように触角によるフィードバックがなく、指の位置をチラ見で確認することでゲーム画面から視線が切れてしまうため、自分としては到底許容できないインターフェイスでした。

そこで採用したのが、完成版の方向パッドと同じように1つの要素で左右両方への移動を賄う方式。 要素内の領域に最初に指が当たった位置を psrc、スワイプ動作中の指の現在位置を pdst として、psrcpdst のx成分を移動速度として使用します。 「へんないきもの」は、指を大きく動かしたときは素早く、小さく動かしたときはゆっくりと移動するわけです。 (もちろん速度には上限があり、また速度があまりにも小さい場合は静止したままになるという「遊び」も設けてありました。)

スワイプによる左右移動

なかなか良いやり方に思えるのではないでしょうか。 私も Chrome の開発者ツールでタッチデバイスでの動作をエミュレートしてみたときには「とりあえずこれでいいだろう」と考えていました。 ところが、実際にスマートフォンを手に取って操作してみると、全然思ったような操作ができないことが発覚。 ゲームをプレイしていると、左移動中に素早く反転して右移動へ移りたい、という場面に頻繁に遭遇します。 この、いわゆる「切り返し」操作を行うために、プレイヤはパッド上で指を右方向へスライドさせるわけですが、この方式ではそれは期待通りの結果に繋がりません。

切り返しの問題

左移動中に指を右へ動かしても、しばらくの間は pdst.x < psrc.x が成立しているため、スピードを落としながらも「へんないきもの」は左へ向かって動き続けます。(上図 a) そこから更に右へと指を動かしていき、最初に指を置いた位置を通り越して psrc.x < pdst.x となった時点で初めて「へんないきもの」は反転し、右へ向かって動き出します。(上図 b) 進行方向を反転させるのに長距離のスワイプが必要となるため切り返しが不可能。 これはアクションゲームの入力インターフェイスとして致命的な欠陥です。

どうすればこの問題に対処できるかと丸2日ほど考えた末、ポインタの移動速度が psrcpdstと90度以上ズレた場合 (ベクトルの内積が負になった場合) に、psrcpdst の位置まで瞬時に移動させる、という処理を加えることに。 これにより、移動方向の瞬間的な切り替えだけでなく急停止もできるようになりました。

切り返しに対応

現在でもテストプレイを繰り返してはいますが、個人的には今まで試した中ではこれがもっとも思った通りの操作ができるパッドとなっています。 現在、別タイプの移動用パッドを実装しており、次のバージョンで切り替え機能を付ける予定です。

スマートフォン独特の操作に抗う

コンテキストメニュー

各種ウェブブラウザはタッチパネルに特有の操作を提供してしています。 スワイプによるスクロールや更新、タップによる要素フォーカス、ピンチイン/ピンチアウトによる拡大/縮小などは、普段は便利な機能なのですが、ゲーム中に作動されると困るシロモノ。 そのため、タッチ操作を前提としたプログラムを作る時は、まずこの「標準動作」を止めることから始めることになります。 手始めに、主要なタッチ操作を止めるため、touchstart イベントを殺すコードを書いてみましょう:

/** [callback] Handling a "touchstart" event
  * @return [void]
  * @param event [Event]
  */
onTouchStart = function (event){
    event.preventDefault();
    return;
}

eViewport.addEventListener('touchstart', onTouchStart);

こうすれば、ビューポート上での標準タッチ操作は止まるのですが、ここでひとつ問題が浮上。 ゲーム開始前などに画面の表示位置などを調整する際、それらの操作が無効になっているととても不便なのです。 そこで今回は、ゲーム開始前 (ゲームオーバー後の待機状態を含む) や一時停止中は標準のタッチ操作を許可することでこの問題を解決しました:

/** [callback] Handling a "touchstart" event
  * @return [void]
  * @param event [Event]
  */
onTouchStart = function (event){
    if (game.player && !game.isPaused()){
        event.preventDefault();
    }
    return;
}

この他に厄介なのが、コンテキストメニューの出現。 標準タッチ操作とは異なり、このゲームは如何なる状況でもコンテキストメニューを必要としないため、contextmenu イベントは問答無用で無効化しています:

/** [callback] Handling a "contextmenu" event
  * @return [void]
  * @param event [Event]
  */
onContextMenu = function (event){
    event.preventDefault();
    return;
}
eViewport.addEventListener('contextmenu', onContextMenu);

これと併せて、ゲーム画面内の「文字」が選択されることを防ぐために user-select スタイルを none に設定しておくのも忘れずに。

div#viewport {
    user-select: none;
    -webkit-user-select: none;
}

最終的には没になりましたが、前節の「スワイプで要素をまたいで移動」するインターフェイスを実現する場合、指がタッチを開始した要素から抜け出して隣の要素に入っても pointermove イベントが最初の要素に送られ続ける、という問題にも遭遇しました。 当初は pointerout イベント のハンドラで何か上手いことすればよいのだろうと思ってあれこれ試してみたのですが上手くいかず。 そうした操作をする要素にはポインタをキャプチャしない (即時リリースする) ような動作記述が必要になるようでした:

/** [callback] Handling a "pointermove" event
  * @return [void]
  * @param event [Event]
  */
Button.onPointerMove = function (event){
    event.currentTarget.releasePointerCapture(event.pointerId);
    return;
}
eButton.addEventListener('pointermove', Button.onPointerMove);

お絵描きが大変

ゲーム内で使用する画像リソースを用意するのも、お絵描き技術のない私にとっては非常に厄介な仕事です。 とりわけ面倒なのは、アニメーションとして動かす必要のある対象。 1秒程度の時間を埋めるために16フレーム分の画像を用意しなければならない上、1ピクセルでもズレているとゲームの見た目があからさまにおかしくなるので、その編集作業の神経の磨り減ることといったらもう!

ブロックのアニメーション

さらにこれは実用アプリではなくゲームであるので、やはりそれなりには「目にも楽しい」ことも必須の要件です。 というわけで、乏しい想像力を絞りに絞って、4種類のブロック絵を用意しました。 (気力と発想が続けば、今後のアップデートでもう数種類追加する予定。) 違和感を感じさず、かつ変化を視覚的にとらえやすいアニメーションのパターンとかいうのは簡単そうでありながら、なかなかバリエーションを出しにくいものです。 例えば、「色」をそのままに「形」だけが変わるアニメーションを作ってみたところ、注視している点から離れたところで発生している「連鎖」の進行具合が殆ど把握できなくなり、ゲームの難度が跳ね上がってしまう、という失敗もありました。

ブロックからアイテムへの変化

これで音楽まで付けるとなったら完全にキャパシティ不足で、開発中止待ったなしですが、ウェブの文化ではユーザに断りもなく音を出すのは「許されざる蛮行」ということで、本作品は慎み深くBGMおよびSEなしというスタイルを選択致しました。

成田 (休日は廃ゲーマ)
このエントリーをはてなブックマークに追加

コメント

投稿者
URI
メールアドレス
表題
本文