HTML5 による 2D ゲームの開発: ゲームを完成させる

Snail Bait に重要な最後の微調整を加えて最終的に仕上げる

この連載では、HTML5 のエキスパートである David Geary が、HTML5 で 2D テレビ・ゲームを実装する方法について順を追って説明します。今回は連載の締めくくりとして、重要な機能を追加するとともに見栄えを良くすることで Snail Bait を完成させます。具体的には、ゲームの背景の置き換え、ゲームプレイのための微調整、スコアの保持、コントロールの半透明化、フレーム・レートの監視、残機の追跡、クレジットの表示、スコアのツイート、サーバーへのゲームのデプロイについて説明します。

David Geary, Author and speaker, Clarity Training, Inc.

David GearyCore HTML5 Canvas』の著者、David Geary は HTML5 Denver User's Group の共同設立者でもあり、Swing と JavaServer Faces に関するベストセラーの本を含め、Java に関する 8 冊の本の著者でもあります。また彼は、JavaOne、Devoxx、Strange Loop、NDC、OSCON などのカンファレンスで頻繁に講演を行っており、JavaOne Rock Star にも 3 度選ばれています。彼は developerWorks の連載記事、「HTML5 による 2D ゲームの開発」、「JSF 2 の魅力」、そして「GWT の魅力」の著者でもあります。



2013年 8月 29日

連載「HTML5 による 2D ゲームの開発」前回の記事の最後の時点で、Snail Bait はプレイできる状態にありますが、まだ仕上げがされていません。この最終回の記事では、その状態の Snail Bait を図 1 に示すように洗練された最終版へと仕上げる方法について説明します。

図 1. Snail Bait の最終版
Snail Bait の最終版のスクリーン・キャプチャー

この記事では以下の内容について説明します。

  • 背景画像を CSS によるグラデーションで置き換える方法
  • ゲームプレイとグラフィックスを微調整する方法
  • スコアを保持する方法
  • フレーム・レートを監視し、ゲームの実行速度が遅くなった場合に警告を表示する方法
  • 背景を揺らす特殊なエフェクトを実装する方法
  • 残機を追跡する方法
  • 残機が減ったときに遷移する方法
  • クレジットを表示する方法
  • スコアをツイートする方法
  • サーバーにゲームをデプロイする方法

最終版の完全な Snail Bait のコードを入手するには「ダウンロード」を参照してください。

CSS によるグラデーションで背景画像を置き換える

現状の Snail Bait は、ゲームが開始されると 3 つの画像 (Web サイトの背景、ゲームの背景、ゲームのスプライト・シート) をロードします。最終版では、Web サイトの背景画像の使用をやめ、ゲームの背景をスプライト・シートの中に統合することで、3 つの画像から 1 つの画像に減らします。Web サイトの背景画像の使用をやめるために、ここでは CSS3 の線形グラデーションを使用して図 2 の背景を作成します。

図 2. Snail Bait の背景
Snail Bait の背景のスクリーン・キャプチャー

この背景の CSS がリスト 1 です。

リスト 1. Snail Bait の背景 (game.css からの抜粋)
body {
   /* Background from CSS3 Patterns Gallery by Anna Kassner */

   background-color: #6d6aff;

   background-image:
      repeating-linear-gradient(120deg, rgba(255,255,255,.1), rgba(255,255,255,.1) 1px, 
                                transparent 1px, transparent 60px),

      repeating-linear-gradient(60deg, rgba(255,255,255,.1), rgba(255,255,255,.1) 1px, 
                                transparent 1px, transparent 60px),

      linear-gradient(60deg, rgba(0,0,0,.1) 25%, transparent 25%, transparent 75%, 
                      rgba(0,0,0,.1) 75%, rgba(0,0,0,.1)),

      linear-gradient(120deg, rgba(0,0,0,.1) 25%, transparent 25%, transparent 75%, 
                      rgba(0,0,0,.1) 75%, rgba(0,0,0,.1));

   background-size: 70px 120px;
}
...

CSS3 Patterns Gallery には、さまざまな背景を作成するためにコピー可能な CSS のスニペットが公開されています (「参考文献」を参照)。図 3 は CSS3 Patterns Gallery に用意されている背景の一部です。

図 3. CSS3 Patterns Gallery
CSS3 Patterns Gallery の一部のスクリーン・キャプチャー

私は CCC3 Patterns Gallery からリスト 1 の CSS をダウンロードし、Snail Bait に使用しました。選択したパターンは Argyle (アーガイル柄) で、背景の色は青の濃淡に変更しました。


ゲームプレイとグラフィックスを微調整する

ゲーム開発者はゲームを実装しながら、常にゲームプレイとグラフィックスを微調整します。私は Snail Bait の最終的な微調整として、以下の変更を加えています。

  • プラットフォームの位置とサイズの変更
  • すべてのルビーとコインに対し、「バウンドする」ビヘイビアの追加
  • ゲームの終わりでのカタツムリの移動
  • 爆発を引き起こすボタンの実装
  • コインの置き換え

これらの変更のほとんどは、図 4 を見るとよくわかります (図 4 はゲームの終わり近くでカタツムリが爆弾を発射する様子を示しています)。

図 4. ゲームの終わり近くでキャラクターがカタツムリ爆弾を避けようとする様子
ゲームの終わり近くでキャラクターがカタツムリ爆弾を避けようとする様子を示す画面のスクリーン・キャプチャー

ゲームの半分あたりを過ぎると、ランナーは図 5 に示すような行き詰まった状態に出くわします。この状態では、ランナーと次のプラットフォームとの間に蜂がいます。蜂をかわして、この状態を切り抜けるには、ランナーは青色のボタンによって爆発を引き起こし、蜂を爆破しなければなりません。

図 5. 次のプラットフォームをガードする蜂を爆破するための青色のボタン
Snail Bait で次のプラットフォームをガードする蜂を爆破するための青色のボタンが表示されている画面のスクリーン・キャプチャー

図 6 は、ボタンによって爆発が引き起こされ、蜂が爆破されている様子を示しています。スプライトが爆破されている間、Snail Bait はそのスプライトを衝突検出の対象から外します。爆破されている蜂が衝突検出の対象から外されることで、ランナーはジャンプしてその蜂を通り抜けて次のプラットフォームに到達することができます。

図 6. 爆発を引き起こすボタンによって実際に蜂が爆破されている様子
Snail Bait で爆発を引き起こすボタンによって蜂が爆破されている様子を示す画面のスクリーン・キャプチャー

Snail Bait のすべてのアクションと同様、ボタンによって引き起こされる爆発もビヘイビアです。リスト 2 は「ボタンが爆発を引き起こす」ビヘイビアの実装を示しています。

リスト 2. 「ボタンが爆発を引き起こす」ビヘイビア
var SnailBait = function () {
   ...
   this.buttonDetonateBehavior = {
      execute: function(sprite, now, fps, lastAnimationFrameTime) {
         var BUTTON_REBOUND_DELAY = 1000;

         if ( ! sprite.detonating) { // trigger
            return;
         }

         sprite.artist.cellIndex = 1; // flatten the button

         snailBait.explode(snailBait.bees[5]);

         setTimeout( function () {
            sprite.artist.cellIndex = 0; // rebound
            sprite.detonating = false; // reset trigger
         }, BUTTON_REBOUND_DELAY);
      }
   }
};

リスト 3 は Snail Bait で青色のボタンに「爆発を引き起こす」ビヘイビアを追加する方法を示しています。

リスト 3. 「爆発を引き起こす」ビヘイビアを持つ青色のボタンを作成する
var SnailBait = function () {

SnailBait.prototype = {
   ... 
   createButtonSprites: function () {
      var button,
         blueButtonArtist = new SpriteSheetArtist(this.spritesheet, this.blueButtonCells),
         goldButtonArtist = new SpriteSheetArtist(this.spritesheet, this.goldButtonCells);

      for (var i = 0; i < this.buttonData.length; ++i) {
         if (i === this.buttonData.length - 1) {
            button = new Sprite('button', 
                                goldButtonArtist,  
                                [ this.paceBehavior ]);
         }
         else {
            button = new Sprite('button',
                                 blueButtonArtist, 
                                 [ this.paceBehavior, 
                                   this.buttonDetonateBehavior ]);
         }

         button.width = this.BUTTON_CELLS_WIDTH;
         button.height = this.BUTTON_CELLS_HEIGHT;

         this.buttons.push(button);
      }
   }, 
};

Snail Bait では、表示されているすべてのスプライトに対して、アニメーション・フレームごとに繰り返し処理を行うことを思い出してください。各スプライトに対し、Snail Bait はそのスプライトのビヘイビアの配列を繰り返し処理し、各ビヘイビアの execute() メソッドを呼び出します。ほとんどの場合、ボタンの detonating 属性は false であるため、「ボタンが爆発を引き起こす」ビヘイビアの execute() メソッドは何もしません。Snail Bait が青色のボタンの detonating 属性を true に設定すると (リスト 4)、「ボタンが爆発を引き起こす」ビヘイビアは以下の内容を実行します。

  1. ボタンを押しこまれた状態にする
  2. 蜂を爆破する
  3. 1 秒遅延させる
  4. ボタンの画像を最初の画像にリセットする
  5. ボタンの detonating 属性を false に設定する

これらを実行すると、「爆発を引き起こす」ビヘイビアは、次回ランナーがボタンの上に落ちてくるまで何もしません。

リスト 4. 「爆発を引き起こす」ビヘイビアをトリガーする
var SnailBait = function () {
   ...

   // The collide behavior is attached to the runner

   this.collideBehavior = {
      ...

      detonateButton: function (otherSprite) {
         otherSprite.detonating = true; // trigger
      },

      processCollision: function (sprite, otherSprite) {
         if (otherSprite.value) { 
            this.adjustScore(otherSprite);
         }

         if ('button' === otherSprite.type && sprite.falling)) {
            this.detonateButton(otherSprite);
         }
         ...
      },
   };
   ...
};

スコアを保持する

この連載の最初の記事から、Snail Bait にはスコアボードがありましたが、このスコアボードはまったく機能していませんでした。ここでいよいよ、このスコアボードを動作させます。最初のステップとして、特定のタイプのスプライトに値を割り当てます。例えば、リスト 5 は各ルビーに 100 ポイントの値を割り当てる createRubySprites() メソッドを示しています。

リスト 5. スプライトに値を割り当てる
SnailBait.prototype = {
   ...
   createRubySprites: function () {
      var ruby,
          rubyArtist = new SpriteSheetArtist(this.spritesheet, this.rubyCells);
   
      for (var i = 0; i < this.rubyData.length; ++i) {
         ruby = new Sprite('ruby', rubyArtist, 
                   [ new CycleBehavior(this.RUBY_SPARKLE_DURATION,
                                          this.RUBY_SPARKLE_INTERVAL),
                     new BounceBehavior(800, 600, 120) ]);

         ruby.width = this.RUBY_CELLS_WIDTH;
         ruby.height = this.RUBY_CELLS_HEIGHT;
         
         ruby.value = 100;

         this.rubies.push(ruby);
      }
   },
   ...
};

リスト 4 を見るとわかるように、値を持つスプライトにランナーが衝突すると、「衝突する」ビヘイビアの adjustScore() メソッドはゲームのスコアを調整し、HTML の score 要素を更新します。リスト 6 は adjustScore() メソッドを示しています。

リスト 6. 衝突後にスコアを調整する
var SnailBait = function () {
   ...
   // The collide behavior is attached to the runner

   this.collideBehavior = {
      ...
      adjustScore: function (otherSprite) {
         if (otherSprite.value) {
            snailBait.score += otherSprite.value;
            snailBait.score = snailBait.score < 0 ? 0 : snailBait.score;
            snailBait.scoreElement.innerHTML = snailBait.score;
         }
      },
      ...
   };
};

フレーム・レートを監視し、必要な場合には警告を表示する

厳密に制御された環境で実行されるコンシューマー・ゲームとは異なり、HTML5 によるゲームは混沌とした環境の中で実行されます。動画プレイヤー、OS バックアップ・ソフトウェア、さらには背景が半透明のターミナル・ウィンドウのように平凡なものでさえ、最高のパフォーマンスのゲームであっても実行速度を低下させてしまう場合があります。HTML5 によるゲームでは実行環境の制御ができないため、フレーム・レートを監視し、受け入れられないほどゲームの実行速度が遅くなった場合はプレイヤーに警告する必要があります。図 7 は Snail Bait の警告を示しています。

図 7. 実行速度が遅いことの警告
Snail Bait の実行速度が遅いことの警告画面のスクリーン・キャプチャー

Snail Bait では、ゲームの実行速度が遅いことをプレイヤーに警告する際に、現在のフレーム・レートを表示しますが、そのためにゲームの実行中に静的テキストの中にフレーム・レートを挿入します。リスト 7 にその静的テキストの HTML を示します。

リスト 7. 実行速度が遅いことを警告するための HTML
<html>
   <body>
      <div id='arena'>
         ...

         <!-- Running slowly........................................-->

         <div id='running-slowly'>

            <h1>Snail Bait is running slowly</h1>

            <p id='slowly-warning'></p>
            
            <p>High-performance applications, such as video players or 
              software that backs up your computer, can slow this game down. 
              For best results, hide all other windows on your desktop and 
              close CPU- or GPU- intensive apps when you play HTML5 games.
            </p>

            <p>You should also upgrade your browser to the latest version and make 
            sure that it has hardware accelerated HTML5 Canvas. Any version of Chrome 
            starting with version 18, for example, has hardware accelerated
               Canvas. Here is a link where you can download the 
               <a href='http://www.google.com/chrome/'>latest version of Chrome</a>.
            </p>

            <a id='slowly-okay' href='#'>
               Okay
            </a>

            <a  id='slowly-dont-show' href='#'>
               Do not show this warning again
            </a>
         </div>
         ...
      </div>
   </div>
</div>

リスト 8 は実行速度が遅いという警告の表示/非表示を Snail Bait がどのようにして行っているかを示しています。

リスト 8. 実行速度が遅いという警告の表示/非表示の処理
SnailBait.prototype = {
   ...
   revealRunningSlowlyWarning: function (now, averageSpeed) {
      this.slowlyWarningElement.innerHTML =
      "Snail Bait is running at " +
      "<font color='red'> averageSpeed.toFixed(0)" + "</font>" +
      " frames/second (fps), but it needs more than " +
      this.runningSlowlyThreshold + " fps for the game to work correctly."

      this.runningSlowlyElement.style.display = 'block';

      setTimeout( function () {
         snailBait.runningSlowlyElement.style.opacity = 1.0;
      }, this.SHORT_DELAY);

      this.lastSlowWarningTime = now;
   },  

   hideRunningSlowlyWarning: function () {
      snailBait.runningSlowlyElement.style.display = 'none'; 
      snailBait.runningSlowlyElement.style.opacity = 0;
   },
   ...
};

実行速度が遅いという警告を表示する際、Snail Bait は警告のテキストを作成し、そのテキストを slowly-warning という段落要素に割り当てます。そしてブラウザーに警告テキストが表示されるように、この要素の display 属性を ‘block’ に設定しますが、この要素の初期状態は透明であるため、短時間の遅延の後、revealRunningSlowlyWarning() メソッドによって要素の不透明度を 1.0 に設定することで、要素を不透明にします。この設定により、1 秒間で要素を透明から不透明へと変化させる CSS のトランジションがトリガーされます。リスト 9 は running-slowly 警告要素に対する CSS を示しています。

リスト 9. running-slowly 警告要素に対する CSS
#running-slowly {
   position: absolute;
   width: 600px;
   margin-top: 85px;
   margin-left: 90px;
   text-align: center;
   background: rgba(255,255,255,0.85);
   text-align: left;
   padding: 0px 20px 20px 20px;
   color: navy;
   text-shadow: 1px 1px 1px rgba(255,255,255,0.5);

   -webkit-transition: opacity 1s;
   -moz-transition: opacity 1s;
   -o-transition: opacity 1s;
   transition: opacity 1s;
   
   -webkit-box-shadow: rgba(0,0,0,0.5) 4px 4px 8px;
   -moz-box-shadow: rgba(0,0,0,0.5) 4px 4px 8px;
   -o-box-shadow: rgba(0,0,0,0.5) 4px 4px 8px;
   box-shadow: rgba(0,0,0,0.5) 4px 4px 8px;

   opacity: 0;
   display: none;

   z-index: 2;
}

Snail Bait は実行しながらフレーム・レートを監視します (リスト 10)。

リスト 10. フレーム・レートを監視する
SnailBait.prototype = {
   ...
   animate: function (now) { 
      if (snailBait.paused) {
         setTimeout( function () {
            requestNextAnimationFrame(snailBait.animate);
         }, snailBait.PAUSED_CHECK_INTERVAL);
      }
      else {
         snailBait.fps = snailBait.calculateFps(now); 

         if (snailBait.windowHasFocus && !snailBait.paused &&
             snailBait.showSlowWarning &&
             now - snailBait.lastSlowWarningTime > 
             snailBait.FPS_SLOW_CHECK_INTERVAL) {
            snailBait.checkFps(now); 
         }

         snailBait.draw(now);
         requestNextAnimationFrame(snailBait.animate);
      }
   },

   checkFps: function (now) {
      var averageSpeed;

      this.updateSpeedSamples(snailBait.fps);

      averageSpeed = this.calculateAverageSpeed();

      if (averageSpeed < this.runningSlowlyThreshold) {
         this.revealRunningSlowlyWarning(now, averageSpeed);
      }
   },
   ...
};

FPS_SLOW_CHECK_INTERVAL 秒 (4 秒に設定されています) ごとに、Snail Bait は速度サンプルの配列を更新し、平均速度を計算することによってフレーム・レートをチェックします。算出された平均速度が、ゲームの実行速度が遅いと判断されるしきい値を下回ると、Snail Bait は実行速度が遅いという警告を表示します。リスト 11 は、速度サンプルを更新して平均速度を計算するための Snail Bait のメソッドを示しています。

リスト 11. 速度サンプルを更新して平均速度を計算する
SnailBait.prototype = {
   ...
   updateSpeedSamples: function (fps) {
      this.speedSamples[this.speedSamplesIndex] = fps;

      if (this.speedSamplesIndex !== this.NUM_SPEED_SAMPLES-1) {
         this.speedSamplesIndex++;
      }
      else {
         this.speedSamplesIndex = 0;
      }
   },

   calculateAverageSpeed: function () {
      var i,
      total = 0;

      for (i=0; i < this.NUM_SPEED_SAMPLES; i++) {
         total += this.speedSamples[i];
      }

      return total/this.NUM_SPEED_SAMPLES;
   },
   ...
};

実行速度が遅いという警告の「OK」ボタンをプレイヤーがクリックすると、Snail Bait はその警告を非表示にし、速度サンプルをリセットします。プレイヤーが「Do not show this warning again (この警告を表示しない)」ボタンをクリックすると、Snail Bait は警告を非表示にし、もうフレーム・レートの監視は行わないようにフラグを設定します。この 2 つのイベント・ハンドラーのコードをリスト 12 に示します。

リスト 12. 実行速度が遅い場合のイベント・ハンドラー
snailBait.slowlyDontShowElement.onclick = function (e) {
   snailBait.hideRunningSlowlyWarning();
   snailBait.showSlowWarning = false;
};


snailBait.slowlyOkayElement.onclick = function (e) {
   snailBait.hideRunningSlowlyWarning();
   snailBait.speedSamples = [60,60,60,60,60,60,60,60,60,60]; // reset
};

特殊なエフェクトを追加する

Snail Bait の最終版では、ランナーが別のスプライトと衝突して爆発するとゲームの背景を揺らします。リスト 13 に示すのはランナーの「衝突する」ビヘイビアからの抜粋で、このような衝突が発生すると、このビヘイビアは Snail Bait の shake() メソッドを呼び出します。

リスト 13. 背景を揺らす
var SnailBait = function () {
   ...
   // The collide behavior is attached to the runner

   this.collideBehavior = {
      ...
      processCollision: function (sprite, otherSprite) {
         ...
         if ('bat' === otherSprite.type || 'bee' === otherSprite.type   ||
         'snail' === otherSprite.type || 'snail bomb' === otherSprite.type) {
            snailBait.explode(sprite);

            snailBait.shake();

            setTimeout( function () {
               snailBait.loseLife();
               snailBait.reset();
               snailBait.fadeAndRestoreCanvas();
            }, snailBait.EXPLOSION_DURATION);
         }
      },
   };
   ...
};

Snail Bait の shake() メソッドは、setTimeout() の呼び出しがネストされたコードで構成されています (リスト 14)。

リスト 14. shake() メソッドの実装
SnailBait.prototype = function () {
   ... 
   shake: function () {
      var SHAKE_INTERVAL = 90, // milliseconds
          v = snailBait.BACKGROUND_VELOCITY,
          ov = snailBait.bgVelocity; // ov means original velocity
   
      this.bgVelocity = -this.BACKGROUND_VELOCITY;

      setTimeout( function (e) {
       snailBait.bgVelocity = v;
       setTimeout( function (e) {
          snailBait.bgVelocity = -v;
          setTimeout( function (e) {
             snailBait.bgVelocity = v;
             setTimeout( function (e) {
                snailBait.bgVelocity = -v;
                setTimeout( function (e) {
                   snailBait.bgVelocity = v;
                   setTimeout( function (e) {
                      snailBait.bgVelocity = -v;
                      setTimeout( function (e) {
                         snailBait.bgVelocity = v;
                         setTimeout( function (e) {
                            snailBait.bgVelocity = -v;
                            setTimeout( function (e) {
                               snailBait.bgVelocity = v;
                               setTimeout( function (e) {
                                  snailBait.bgVelocity = -v;
                                  setTimeout( function (e) {
                                     snailBait.bgVelocity = v;
                                     setTimeout( function (e) {
                                        snailBait.bgVelocity = ov;
                                     }, SHAKE_INTERVAL);
                                  }, SHAKE_INTERVAL);
                               }, SHAKE_INTERVAL);
                            }, SHAKE_INTERVAL);
                         }, SHAKE_INTERVAL);
                      }, SHAKE_INTERVAL);
                   }, SHAKE_INTERVAL);
                }, SHAKE_INTERVAL);
             }, SHAKE_INTERVAL);
          }, SHAKE_INTERVAL);
       }, SHAKE_INTERVAL);
     }, SHAKE_INTERVAL);
   },
   ...
};

Snail Bait の shake() メソッドは 90 ミリ秒ごとにゲームの背景の速度の方向を反転させ、それによってゲームが揺れているかのような錯覚を作り出します。


残機を追跡する

Snail Bait の最終版では、プレイヤーはゲームの開始時に 3 つの残機が与えられます。ランナーが敵のスプライトと衝突して爆発すると、プレイヤーは残機を 1 つ失い、Snail Bait はランナーをゲームの最初に戻します。

Snail Bait では、ランナーのミニチュア・アイコンで残機を表すことにより、ゲームのキャンバス上部に残機の数を表示します。これらの画像はゲームの HTML の中で指定されます (リスト 15)。

リスト 15. 残機を表す要素
<html>
   ...
   <body>
      <div id='arena'>
         <div id='header'>
            <div id='score'>0</div>

            <div id='lives'>
               <img id='life-icon-left'   src='images/runner-small.png'/>
               <img id='life-icon-middle' src='images/runner-small.png'/>
               <img id='life-icon-right'  src='images/runner-small.png'/>
            </div>
         </div>
         ...
      </div>
      ...
   <body>
</html>

Snail Bait の loseLife() メソッド (ランナーが敵のスプライトと衝突すると呼び出されます (リスト 13 を参照)) は実行時に lives 要素を更新します。リスト 16 は updateLivesElement 関数と loseLife() メソッドを示しています。

リスト 16. lives 要素を更新する
SnailBait.prototype = {
   ...

   updateLivesElement: function () {
      if (this.lives === 3) {
         this.lifeIconLeft.style.opacity   = 1.0;
         this.lifeIconMiddle.style.opacity = 1.0;
         this.lifeIconRight.style.opacity  = 1.0;
      }
      else if (this.lives === 2) {

         this.lifeIconLeft.style.opacity   = 1.0;
         this.lifeIconMiddle.style.opacity = 1.0;
         this.lifeIconRight.style.opacity  = 0;
      }
      else if (this.lives === 1) {
         this.lifeIconLeft.style.opacity   = 1.0;
         this.lifeIconMiddle.style.opacity = 0;
         this.lifeIconRight.style.opacity  = 0;
      }
      else if (this.lives === 0) {
         this.lifeIconLeft.style.opacity   = 0;
         this.lifeIconMiddle.style.opacity = 0;
         this.lifeIconRight.style.opacity  = 0;
      }
   },

   loseLife: function () {
      this.lives--;
      this.updateLivesElement();

      if (this.lives === 1) {
         snailBait.revealToast('Last chance!');
      }

      if (this.lives === 0) {
         this.gameOver();
      }
   },
   ...
};

残機が減ったときの遷移

Snail Bait では、プレイヤーの残機が減ったときには、残機のアイコンの数を減らすとともに、ほぼ透明になるまでキャンバスをフェードアウトします (図 8)。その後、完全に不透明になるまでキャンバスをフェードインし、新たな残機で開始されることを強調するために、(最も高い) 3 番目のトラックからランナーを落下させます。

図 8. 残機が減ってゲームが遷移する際のキャンバスのフェードアウト
残機が減ったときに Snail Bait が遷移する様子を示す画面のスクリーン・キャプチャー

リスト 17 に、ランナーの「衝突する」ビヘイビア (リスト 13) から呼び出される reset() メソッドを示します。

リスト 17. キャンバスのフェードアウトと復元
SnailBait.prototype = {
   ...
   fadeAndRestoreCanvas: function () {
      snailBait.canvas.style.opacity = 0.2;

      setTimeout( function () {
         snailBait.canvas.style.opacity = 1.0;
      }, 2500);
   },

   resetRunner: function () {
      snailBait.runner.exploding = false; 
      snailBait.runner.visible = false;
      snailBait.runner.opacity = snailBait.OPAQUE;
      snailBait.runner.artist.cells = snailBait.runnerCellsRight;

      if (snailBait.runner.jumping) { snailBait.runner.stopJumping(); }
      if (snailBait.runner.falling) { snailBait.runner.stopFalling(); }
   },

   reset: function () {
      var CANVAS_TRANSITION_DURATION = 2000,
          CONTINUE_RUNNING_DURATION = 1000;

      this.resetRunner();

      setTimeout( function () {
         snailBait.backgroundOffset = 
            snailBait.INITIAL_BACKGROUND_OFFSET;

         snailBait.spriteOffset = snailBait.INITIAL_BACKGROUND_OFFSET;
         snailBait.bgVelocity = snailBait.INITIAL_BACKGROUND_VELOCITY;

         snailBait.runner.track = 3;
         snailBait.runner.top = snailBait.calculatePlatformTop(snailBait.runner.track) - 
                                snailBait.runner.height;

         for (var i=0; i < snailBait.sprites.length; ++i) { 
            snailBait.sprites[i].visible = true;
         }

         setTimeout( function () {
            snailBait.runner.runAnimationRate = 0; // stop running
         }, CONTINUE_RUNNING_DURATION);
      }, CANVAS_TRANSITION_DURATION);
   },
   ...
};

reset() メソッドはゲームをリセットし、新しい残機の準備をするために、以下の内容を実行します。

  1. スプライトと背景のオフセットを初期値にリセットする
  2. ランナーを 3 番目のトラックに配置する
  3. ゲームのスプライトをすべて表示する

reset() が呼び出されると、ランナーは次のアニメーション・フレームで落下を開始します。これは、ランナーは 3 番目のトラック上にいるものの、ランナーの下にはプラットフォームがないためです。つまりランナーの「落下する」ビヘイビアによって落下が開始され、ランナーは最初のプラットフォームに着地するまで落下を続けます。


クレジットを表示する

Snail Bait は、ゲームの最後に図 9 のようなクレジットを表示します。

図 9. ゲームの最後にクレジットを表示する
Snail Bait がゲームの最後に表示するクレジットのスクリーン・キャプチャー

クレジットの表示、非表示に関連する Snail Bait のメソッドをリスト 18 に示します。

リスト 18. クレジットの表示と非表示
SnailBait.prototype = {
   ...
   
   gameOver: function () {
      snailBait.revealCredits();
   },

   restartGame: function () {
      this.hideCredits();

      this.lives = this.MAX_NUMBER_OF_LIVES;
      this.updateLivesElement();

      this.score = 0;
      this.updateScoreElement();
   },

   revealCredits: function () {
      this.creditsElement.style.display = 'block';
      this.revealLivesIcons();

      this.tweetElement.href = TWEET_PREAMBLE + this.score + TWEET_PROLOGUE;

      setTimeout( function () {
         snailBait.creditsElement.style.opacity = 1.0;
      }, snailBait.SHORT_DELAY);
   },

   hideCredits: function () {

      var CREDITS_REVEAL_DELAY = 2000;

      this.creditsElement.style.opacity = this.TRANSPARENT;

      setTimeout( function (e) {
         snailBait.creditsElement.style.display = 'none';
      }, this.CREDITS_REVEAL_DELAY);
   }, 
};

Snail Bait の gameOver() メソッドは loseLife() メソッド (リスト 16 を参照) に呼び出されてクレジットを表示する一方、restartGame() メソッドはクレジットを非表示にします。restartGame() メソッドはクレジットの「Play again (再プレイ)」リンクに関連付けられたイベント・ハンドラーによって呼び出されます。そのイベント・ハンドラーがリスト 19 です。

リスト 19. クレジット画面のイベント・ハンドラー
 snailBait.newGameLink.onclick = function (e) {
   snailBait.restartGame();
};

スコアをツイートする

HTML5 によるゲームには多くの場合、スコアをツイートしたり、ゲーム中に他のプレイヤーとやり取りしたり、といったソーシャルな側面があります。ゲームの終了時、Snail Bait のプレイヤーはクレジット表示の「Tweet my score (私のスコアをツイート)」リンク (図 9 を参照) をクリックすることで、自分のスコアをツイートすることができます。プレイヤーのブラウザーが新しいタブで Twitter を開くと、Snail Bait は送信待ち状態だったツイートを、新規ツイートを作成するためのダイアログに自動的に入力します。図 10 はその一例です。

図 10. スコアをツイートする
Snail Bait のスコアをツイートする場合の Twitter の画面のスクリーン・キャプチャー

Snail Bait は URL ストリングを作成し、そのストリングを「Tweet my score (私のスコアをツイート)」リンクの href 属性に割り当てることによってスコアをツイートします (リスト 20)。

リスト 20. スコアをツイートする
SnailBait = function () {
   ...
   this.TWEET_PREAMBLE = 'https://twitter.com/intent/tweet?text=I scored ';
   this.TWEET_PROLOGUE = ' playing this HTML5 Canvas platformer: ' +
                         'http://bit.ly/NDV761 &hashtags=html5';
   ...
};

SnailBait.prototype = {
   ...
   
   revealCredits: function () {
      this.creditsElement.style.display = 'block';
      this.revealLivesIcons();

      this.tweetElement.href = TWEET_PREAMBLE + this.score + TWEET_PROLOGUE;

      setTimeout( function () {
         snailBait.creditsElement.style.opacity = 1.0;
      }, snailBait.SHORT_DELAY);
   },
   ...
};

サーバーにデプロイする

HTML5 によるゲームをサーバーにデプロイする場合、主な懸案事項が 2 つ浮かび上がってきます。それは、ゲーム開始時にクライアントからサーバーに送信しなければならないデータの量と、クライアントとサーバーとの間で行わなければならないやり取りの回数です。理想的には、送信データ量と HTTP リクエストの数を可能な限り減らす必要があります。こうした目標を容易に実現するために、Snail Bait では以下の 3 つのステップを実行します。

  1. JavaScript ファイルを圧縮する
  2. すべての JavaScript ファイルを 1 つの JavaScript ファイル (all.js) にコピーする
  3. JavaScript、CSS、HTML を圧縮する

まず、単純なシェル・スクリプトで Snail Bait のすべての JavaScript ファイルを検索し、各ファイルを YUI Compressor (詳しくは、「参考文献」を参照) で圧縮します (リスト 21)。

リスト 21. Snail Bait の JavaScript ファイルを圧縮するシェル・スクリプト
#!/bin/bash

mkdir tmp

for f in `find . -name "*.js"`
   do
      echo 'Compressing ' $f
         java -jar ~/Utilities/yuicompressor-2.4.7.jar $f >> tmp/all.js
   done

圧縮されたファイルは連結され、all.js という 1 つの JavaScript ファイルになって Snail Bait の HTML ファイルの中で読み込まれます。

もう 1 つのシェル・スクリプト (リスト 22) は、リスト 21 の圧縮スクリプトを実行することによって Snail Bait をデプロイし、その後で Snail Bait の JavaScript、HTML、CSS を圧縮します。

リスト 22. Snail Bait をデプロイするシェル・スクリプト
#!/bin/bash

rm -rf tmp
rm all.js
rm *.gz

echo 'Compressing...'
echo
./compress.sh

echo 'Deploying...'
echo

gzip < all.js > all.js.gz
gzip < index.html > index.html.gz
gzip < snailbait.css > snailbait.css.gz

rm -rf tmp

ZIP ファイルを受け付けるようにサーバーを構成し、オリジナルのファイルではなく、zip されたファイルを使用できるようにする必要があります。構成の詳細はサーバーによって異なります。


まとめ

この記事、そしてこの連載全体では、多岐にわたる内容を取り上げました。皆さんはこれまで Snail Bait が、単純に背景、ランナー、プラットフォームのみを表示する最初の記事での簡素な始まりから、洗練されたゲームへと進化する様子を見てきました。その中で、2D Canvas API を他の HTML5 の API (requestAnimationFrame など) とともに使用して背景のスクロールやパララックスを実装する方法を学びました。またスプライトの実装やスプライトのビヘイビアの実装についても学びました。さらに、時間を思い通りに操作することによって、リニアでない動きを実装する方法、衝突を検出する方法、スプライトをアニメーション化する方法を学びました。しかし 2D ゲームの開発には、この連載では説明しなかった側面が他にも数多くあります。下記はその一例です。

  • 時刻システムを実装することによってゲーム全体の時間の流れを変更する
  • ハイスコアや、ゲーム内のリアルタイムのメトリクスをサーバーに保存する
  • 開発者が特別な機能にアクセスできるように、開発者のためのバックドアを作成する
  • パーティクル・システムを実装して煙や火をシミュレーションする
  • モバイル機器用にゲームを微調整する

皆さんもぜひ、これらのトピックに関して調べ、HTML5 による 2D ゲームを皆さん自身で作成してみてください。


ダウンロード

内容ファイル名サイズ
Sample codewa-html5-game10-code.zip12.9MB

参考文献

学ぶために

  • Core HTML5 Canvas』(David Geary 著、Prentice Hall、2012年): Canvas API とゲーム開発について広範にわたって説明している David Geary の著書です。関連する Web サイトとブログにもアクセスしてください。
  • Snail Bait: HTML5 対応の任意のブラウザーで Snail Bait をオンラインでプレイしてみてください (Chrome のバージョン 18 またはそれ以降のバージョンが最適です)。
  • YUI Compressor: YUI Compressor についての説明を読んでください。
  • HTML5 Canvas を使用した目を見張るアプリケーション: Strange Loop 2011 での David Geary のプレゼンテーションを見てください。
  • HTML5 Game Development」: NDC (Norwegian Developer's Conference) 2011 での David Geary のプレゼンテーションを見てください。
  • Platform games」: Wikipedia でプラットフォーム・ゲームについての説明を読んでください。
  • 「横スクロール」テレビ・ゲーム: ウィキペディアで横スクロール・テレビ・ゲームについての説明を読んでください。
  • 「Strategy パターン」: ウィキペディアで Strategy デザイン・パターンの項目を調べてください。
  • 「HTML5 の基礎」: developerWorks の Knowledge path で HTML5 の基本について学んでください。
  • developerWorks の Web development ゾーン: Web ベースのさまざまなソリューションを解説した記事が豊富に用意されています。Web development 技術文書一覧に用意された、さまざまな技術記事やヒント、チュートリアル、技術標準、IBM Redbooks をご覧ください。

製品や技術を入手するために

  • CSS3 Patterns Gallery: Lea Verou 氏によるギャラリーを訪れ、さまざまなパターンをダウンロードまたは送信してみてください。
  • レプリカアイランド: Android 用として人気の、オープンソースのプラットフォーム・ゲームのソースをダウンロードしてください。Snail Bait のスプライトの大部分は (許可を得て) レプリカアイランドのスプライトを使用しています。

議論するために

  • developerWorks コミュニティーに参加してください。ここでは他の developerWorks ユーザーとのつながりを持てる他、開発者によるブログ、フォーラム、グループ、Wiki を調べることができます。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development
ArticleID=942691
ArticleTitle=HTML5 による 2D ゲームの開発: ゲームを完成させる
publish-date=08292013