HTML5 2D 游戏开发: 结束游戏

对 Snail Bait 进行最后的基本调整和完善

在本系列文章中,HTML5 专家 David Geary 将向您展示如何逐步实现一个 HTML5 2D 视频游戏。本期文章是该系列文章的终结篇,将使用一些重要特性和一些美学装饰完善 Snail Bail。您将学习如何替换游戏背景、调整游戏设置、保留得分、控制明暗度、监控帧速率、追踪生命、显示得分、在 Tweeter 上发布得分,以及将游戏部署到服务器。

David Geary, 作者和演讲家, Clarity Training, Inc.

David GearyDavid Geary 是 Core HTML5 Canvas 的作者,也是 HTML5 Denver User's Group 的合著者和其他 8 本 Java 图书的作者,其中包括畅销书 Swing 和 JavaServer Faces。David 经常在各大会议发表演讲,包括 JavaOne、Devoxx、Strange Loop、NDC 和 OSCON,他曾三次当选为 JavaOne 之星。他还为 developerWorks 撰写了 JSF 2 fuGWT fu 系列文章。您可以通过 @davidgeary 在 Twitter 上关注他。



2013 年 8 月 19 日

HTML5 2D 游戏开发系列文章的上一期文章中,Snail Bait 已经具有可玩性,但还比较粗糙。在这最后一期文章中,我将向您介绍如何实现 Snail Bait 的最终版本,完善后的游戏如图 1 所示:

图 1. Snail Bait 的最终版
Snail Bait 最终版的屏幕截图

在本文中,您将学习如何:

  • 使用 CSS 渐变背景替换背景图像。
  • 调整游戏设置和图像。
  • 保留得分。
  • 监控帧速率,在游戏运行缓慢时显示一条警告。
  • 实现震动背景特效。
  • 追踪生命。
  • 生命之间的过渡。
  • 显示得分。
  • 在 Tweet 上发布得分。
  • 将游戏部署到服务器。

参见下载部分,以获得最终的完整 Snail Bail 代码。

使用 CSS 渐变背景替换背景图像

在当前状态下,游戏启动时 Snail Bait 可加载三个图像:网站背景、游戏背景和游戏 sprite 表单。在最终版本中,在只保留了一个图像,除去了网站背景,并将游戏背景合并到 sprite 表单中。为了消除网站背景,我使用了 CSS3 线性渐变来创建图 2 中的背景:

图 2. 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 显示了一部分 Gallery 背景:

图 3. CSS3 Patterns Gallery
部分 CSS3 Patterns Gallery 的截图

我从 CSS3 Patterns Gallery 下载了清单 1中的 CSS,以便在 Snail Bait 中使用它。我选择了 Argyle 模式,并将背景颜色改成带阴影的蓝色。


调整游戏设置和图像

在实现一个游戏时,游戏开发人员通常会不断调整动画设置和图像。对于 Snail Bait 的最后这轮调整,我将:

HTML 5 专题

HTML5 代表了 Web 业务和云业务在实现方式上的里程碑式改变。本 HTML 5专题将顺应潮流为您介绍一些和 HTML5 新特性相关的内容,及其炫酷的效果。

  • 更改平台位置和大小。
  • 向所有红宝石和硬币添加弹跳行为。
  • 将蜗牛移动到游戏的结尾处。
  • 实现一个引爆按钮。
  • 替换硬币。

显然,图 4 中展示了大多数这些更改,展示了游戏即将结束时蜗牛的射击炸弹:

图 4. 游戏即将结束时,游戏角色躲避蜗牛炸弹
游戏即将结束时,游戏角色躲避蜗牛炸弹的屏幕截图

游戏进行到一半时,跑步小人会遇到图 5 中所示的僵局:蜜蜂位于跑步小人和下一个平台之间。要越过蜜蜂并通过这段,跑步小人必须引爆可以炸死蜜蜂的蓝色按钮。

图 5. 用来炸死守卫下一个平台的蜜蜂的蓝色按钮
Snail Bait 最后一步的屏幕截图

图 6 显示了按钮引爆和蜜蜂爆炸。但 sprite 也将被炸死,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 Bai 如何将引爆行为添加到蓝色按钮上:

清单 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 在所有可见 sprite 上进行迭代。对于每个 sprite,Snail Bait 都将在 sprite 的行为数组上进行迭代,并调用每个行为的 execute() 方法。很多时候,按钮引爆行为的 execute() 方法什么都不做,因为按钮的 detonating 属性是 false 。在 Snail Bait 将蓝色按钮的 detonating 属性被设置为 true 时,如清单 4 所示,按钮引爆行为将:

  1. 使按钮变平。
  2. 使蜜蜂爆炸
  3. 延迟一秒。
  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 一直有一个处于非工作状态的计分板。现在是时候让其发挥计分板的功效了。第一步是为某些类型的 sprite 分配一些值。例如,清单 5 显示了该游戏的 createRubySprites() 方法,为每个红宝石分配了一个 100 点的值:

清单 5. 分配 sprite 值
 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所示,当跑步小人与其他具有某个值的 sprite 发生碰撞时,碰撞行为的 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 会显示当前帧速率。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,使其变成不透明的。该设置触发了一个 CSS 转换,元素从透明逐渐变成不透明大约需要 1 秒钟的时间。清单 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 会隐藏警告并设置一个标记,以便不再监控帧速率。这两个事件处理程序的代码如清单 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 
 };

添加特殊效果

当跑步小人和其他 sprite 碰撞并发生爆炸时,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 次生命。当跑步小人与怀有恶意的 sprite 相撞并发生爆炸时,玩家会失去一次生命,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() 方法(在跑步小人与怀有恶意的 sprite 相撞时调用)会更新 lives 元素。清单 16 显示了 updateLivesElement 函数和 loseLife() 方法:

清单 16. 更新生命元素
 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 所示。然后让画布逐渐淡入,直至完全不透明。Snail Bait 还会让跑步小人从第三个(最高的)跑道上掉下来,以强调新生命的开始。

图 8. 在发生两次生命之间的游戏过渡时,画布会显示淡出效果
Snail Bait 屏幕截图显示了两次生命之间的过渡

清单 17 显示了 Snail Bait 的 reset() 方法,该方法将调用清单 13中跑步小人的碰撞行为:

清单 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. 将 sprite 和背景偏移量重置为其初始值。
  2. 将跑步小人重新放回第三条跑道。
  3. 让所有游戏 sprite 可见。

调用 reset() 后,在下一个动画帧开始时,跑步小人开始降落,因为她在第三跑道上,下面没有平台。因此,她的 fall 行为启动了下降操作,她会不断下降,直至降落在第一个平台上。


显示得分

游戏结束后,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() 方法是由(与得分的 “再玩一次” 链接相关的)事件处理程序调用。事件处理程序如清单 19 所示:

清单 19. 得分屏幕事件处理程序
 snailBait.newGameLink.onclick = function (e) { 
   snailBait.restartGame(); 
 };

在 Tweet 上发布得分

许多 HTML5 游戏都具有社交层面,比如在 Twitter 上发布得分,或者与游戏中的其他玩家进行交互。游戏结束后,Snail Bait 玩家可以通过单击得分显示(参见图 9)中的 Tweet my score链接,将其得分发布在 Twitter 上。玩家浏览器在一个新的选项卡中打开 Twitter,Snail Bait 自动进入 compose-new-tweet 对话框中的某个准备发送的推文中。图 10 显示了一个示例:

图 10. 发布一个得分推文
发布 Snail Bait 得分推文的屏幕截图

Snail Bait 通过构建一个 URL 字符串并将它分配给 “Tweet my score” 链接的 href 属性在 Tweet 上发布得分,如清单 20 所示:

清单 20. 在 Tweet 上发布得分
 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 游戏部署到服务器会带来两个主要问题:游戏开始时必须将大量数据从客户端传输到服务器端,而且必须在客户端和服务器之间多次往返。理想情况下,应该传输尽可能少的数据,尽可能地少发送 HTTP 请求。为了实现这些目标,Snail Bait 将执行以下三个步骤:

  1. 压缩 JavaScript 文件。
  2. 将所有 JavaScript 文件复制到某个文件(all.js)中。
  3. 压缩 JavaScript、CSS 和 HTML。

首先,一个简单的 shell 脚本将会找到所有 Snail Bait JavaScript 文件,然后使用 YUI 压缩器对每个文件进行压缩(参阅参考资料,获取关于压缩器的更多信息),如清单 21 所示:

清单 21. 压缩 Snail Bait 的 JavaScript 文件的 Shell 脚本
 #!/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 的 JavaScript 文件,它包含在 Snail Bait 的 HTML 文件中。

如清单 22 所示,其他 shell 脚本可通过运行清单 21中的压缩脚本并压缩游戏的 JavaScript、HTML 和 CSS 来部署游戏:

清单 22. 部署 Snail Bait 的 shell 脚本
 #!/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 文件,让其使用压缩文件而不是原始文件。各个服务器中的配置细节可能各不相同。


结束语

本文介绍了很多基础内容,整个系列中都有许多基础内容。您已经了解了整个 Snail Bait 开发过程,从第一期(仅显示了背景、跑步小人和平台)中的简单介绍,到一个经过完善的游戏。您还学习了如何使用 2D Canvas API 以及其他 HTML5 API(比如 requestAnimationFrame )来实现滚动背景和视差。您已经实现了 sprite 和 sprite 行为。您还了解了如何让时间停滞来实现非线性运动,以及如何如何检测碰撞并为 sprite 制作动画效果。但是 2D 游戏开发还涉及到我在本系列文章中没有讨论的其他许多方面,比如:

  • 实现一个时间系统来修改整个游戏的时间流动。
  • 在服务器上保存最高得分和实时游戏指标。
  • 创建一个开发人员后门,为开发人员提供对特殊能力的访问。
  • 实现模拟烟火的实践系统。
  • 调整游戏,使之适用于移动设备。

我鼓励您学习所有这些主题,然后开始开发您自己的 HTML5 2D 游戏。


下载

描述名字大小
样例代码wa-html5-game10-code.zip12.9MB

参考资料

学习

获得产品和技术

  • CSS3 模式库:访问 Lea Verou 的库来下载或提交模式。
  • Replica Island:您可以下载这个面向 Android 的流行开源平台视频游戏的资源。大多数 Snail Bait sprite 都来自 Replica Island(在得到许可的情况下使用)。

讨论

  • 加入developerWorks 社区。探索由开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户进行交流。

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development
ArticleID=942671
ArticleTitle=HTML5 2D 游戏开发: 结束游戏
publish-date=08192013