HTML5 2D 游戏开发: 碰撞检测和 sprite 动画

检测和碰撞并做出反应;让 sprite 爆炸

在本系列文章中,专家 David Geary 将告诉您如何一步一个脚印地实现 HTML5 的 2D 视频游戏。在这一期的文章中,将学习 Snail Bait 如何实现碰撞检测和爆炸。

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 年 5 月 21 日

HTML 5 专题

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

碰撞检测和 sprite 动画是所有视频游戏的主要成分。Snail Bait(本系列文章中构建的游戏)也不例外。图 1 显示了 Snail Bait 的跑步小人与左上角的蜜蜂碰撞后发生爆炸。

图 1. 碰撞检测的实际应用
跑步小人与一只蜜蜂碰撞并产生爆炸的 Snail Bait 屏幕截图

在本文中,学习如何:

  • 检测碰撞
  • 将 HTML5 Canvas 上下文用于碰撞检测
  • 将碰撞检测实现为 sprite 行为
  • 处理碰撞
  • 实现 sprite 动画,比如爆炸

碰撞检测过程

碰撞检测是一个 4 步过程,第一步是实际检测碰撞:

  1. 在游戏的 sprite 上进行迭代
  2. 排除不适合碰撞检测的 sprite
  3. 检测候选 sprite 之间的碰撞
  4. 处理碰撞

碰撞检测可能需要很高的计算量,所以,避免对不可能碰撞的 sprite 进行检测非常重要。例如,在 sprite 爆炸时,Snail Bait 的跑步小人会穿过其他 sprite。因为检查一个 sprite 是否在爆炸比执行碰撞检测所需的时间更少,Snail Bait 从碰撞检测中排除了爆炸的 sprite。

我们首先将概述一下碰撞检测技术。

碰撞检测技术

可通过多种方式检测 sprite 之间的碰撞。可采用的三种流行技术(按先进程度和复杂度递增顺序排列)是:

  • 边界区域(在 3D 游戏中称为包围体)
  • 光线投射
  • 分离轴定理 (Separating Axis Theorem)

使用边界区域的碰撞检测,检测圆圈或多边形的交点。在 图 2 中的示例中,小圆圈是表示一个 sprite(一个小球)的边界区域,大圆圈是一个桶的 sprite 的边界区域,桶比球大。当两个圆形边界区域相交时,球就会掉落到桶中。

图 2. 边界区域:圆圈之间的碰撞
该图演示了圆圈之间的碰撞检测的原理

检测两个圆圈的碰撞是所有碰撞检测技术中最简单的。如果两个圆圈中心之间距离小于圆圈的半径之和,就会相交,sprite 就会爆炸。

边界区域碰撞检测很简单,但当边界区域太小或移动得太快时,检测可能失败。在这两种情况下,sprite 可在单个动画帧中彼此穿过,进而避免检测。

对于小型、快速移动的 sprite,更加可靠的一种技术是光线投射,如 图 3 所示。光线投射检测两个 sprite 的速度向量的焦点。在 图 3 中的 5 个帧中,球的速度向量是以蓝色绘制的对角线,桶的速度向量是红色的水平线(桶水平移动)。在这些向量的交点位于桶顶部的开口内,并且球在开口下方时,球就会落入桶中,如 图 3 中最右边的屏幕截图中所示。

图 3. 光线投射
该图演示了光线投射碰撞检测的原理

先验或后验碰撞检测

可在碰撞发生之前(先验)或之后(后验)它们。如果在碰撞发生之前进行检测,必须预测 sprite 未来将位于何处。如果在碰撞发生之后检测,通常需要将已碰撞的 sprite 分开。这两种方法孰优孰劣并不明显。

光线投射非常适合以下环境中的简单形状(比如 图 2 中球落在桶中),其中给出了两个形状的速度向量的焦点,很容易确定它们是否已经发生碰撞。

对于更复杂的场景,比如任意大小和形状的多边形之间的碰撞,分离轴定理是最可靠的(和最复杂的)碰撞检测技术之一。分离轴定理是将光线从两个不同的角度照射到两个多边形上的数学描述,如 图 4 中所示。如果多边形背后的墙上的影子露出空隙,那么多边形没有发生碰撞。

图 4. 分离轴定理
该图演示了使用分离轴定理的碰撞检测的原理

本文不会进一步介绍光线投射或分离轴定理。您可以在 Core HTML5 Canvas(Prentice Hall,2012 年)中查阅每种方法的深入讨论。(请参阅 参考资料,获取相关链接。)


Snail Bait 的碰撞检测

Snail Bait 的碰撞检测涉及到相对较大的 sprite 以较低的速度移动,所以该游戏使用边界框检测碰撞。这些边界框如 图 5 所示。

图 5. Snail Bait 的碰撞检测边界框
Snail Bail 的屏幕截图,其中每个 sprite 上叠加了一个矩形边界框

Snail Bait 将 sprite 活动(比如跑动、跳跃和爆炸)实现为 sprite 行为,请参阅文章 “实现 Sprite 行为”(developerWorks,2013 年 1 月),了解有关的更多信息。而且该游戏需要使用碰撞检测。在开发 Snail Bait 的时候,跑步小人有 3 种行为:她可以跑动、跳跃并与其他 sprite 碰撞。清单 1 给出了使用这 3 种行为来实例化跑步小人 sprite 的代码。

清单 1. 跑步小人的行为
Sprite = function () {
   ...
this.runner = new Sprite('runner',           // type
                            this.runnerArtist,  // artist
                            [ this.runBehavior, // behaviors
                              this.jumpBehavior,
                              this.collideBehavior
                            ]); 
};

清单 2 给出了跑步小人的 collideBehavior 的代码。

清单 2. 跑步小人的碰撞行为
   var SnailBait =  function () {
   ...

   // Runner's collide behavior...............................................

   this.collideBehavior = {
      execute: function (sprite, time, fps, context) {  // sprite is the runner
         var otherSprite;

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

            if (this.isCandidateForCollision(sprite, otherSprite)) {
               if (this.didCollide(sprite, otherSprite, context)) { 
                  this.processCollision(sprite, otherSprite);
               }
            }
         }
      },
      ...
      
   };
};

因为 collideBehavior 对象是一种 sprite 行为,所以 Snail Bait 对每个动画帧都调用了它的 execute() 方法。而且因为 collideBehavior 对象与跑步小人有关联,所以 Snail Bait 传递给 execute() 方法的 sprite 始终是跑步小人。请参阅文章 “实现 sprite 行为” 中的 行为基础 一节,了解 sprite 行为的更多信息。

collideBehavior 对象的 execute() 方法封装了之前列出的 4 个碰撞检测步骤。最后 3 个步骤使用以下 collideBehavior 方法表示:

  • isCandidateForCollision(sprite, otherSprite)
  • didCollide(sprite, otherSprite, context)
  • processCollision(sprite, otherSprite)

以下各节将讨论每个方法的实现。


选择碰撞检测的候选 sprite

Snail Bait sprite 在满足一下条件时有资格与跑步小人 sprite 碰撞:

  • 该 sprite 不是跑步小人
  • 该 sprite 和跑步小人均可见
  • 该 sprite 或跑步小人均未爆炸

collideBehavior 对象的 isCandidateForCollision() 方法(如 清单 3 中所示)会实现此逻辑。

清单 3. 选择碰撞检测的候选 sprite:isCandidateForCollision()
   var SnailBait =  function () {
   ...
   isCandidateForCollision: function (sprite, otherSprite) {
      return sprite !== otherSprite &&                       // not same
             sprite.visible && otherSprite.visible &&        // visible
             !sprite.exploding && !otherSprite.exploding;    // not exploding
   }, 
   ...
};

接下来,我们将查看如何检测合格 sprite 之间的碰撞。


检测跑步小人与另一个 sprite 之间的碰撞

collideBehavior 对象的 didCollide() 方法确定跑步小人是否已与另一个 sprite 碰撞,如 清单 4 所示。

清单 4. 检查碰撞:didCollide()
   var SnailBait =  function () {
   ...
   didCollide: function (sprite,      // runner
                          otherSprite, // candidate for collision
                          context) {   // for context.isPointInPath()
      var left = sprite.left + sprite.offset,
          right = sprite.left + sprite.offset + sprite.width,
          top = sprite.top,
          bottom = sprite.top + sprite.height,
          centerX = left + sprite.width/2,
          centerY = sprite.top + sprite.height/2;

      // All the preceding variables -- left, right, etc. -- pertain to the runner sprite.

      if (otherSprite.type !== 'snail bomb') {
         return this.didRunnerCollideWithOtherSprite(left, top, right, bottom,
                                                         centerX, centerY,
                                                         otherSprite, context);
      }
      else {
         return this.didSnailBombCollideWithRunner(left, top, right, bottom,
                                                       otherSprite, context);
      }
   },

didCollide() 方法计算跑步小人的边界框和它的中心,依据另一个 sprite 的身份,二者随后都传递给两个方法中的一个方法。

如果另一个 sprite 不是蜗牛炸弹,那么 didCollide() 会调用 didRunnerCollideWithOtherSprite(),如 清单 5 所示:

清单 5. didRunnerCollideWithOtherSprite()
	didRunnerCollideWithOtherSprite: function (left, top, right, bottom,
                                              centerX, centerY,
                                              otherSprite, context) {
   // Determine if either of the runner's four corners or its
   // center lies within the other sprite's bounding box. 

 context.beginPath();
 context.rect(otherSprite.left - otherSprite.offset, otherSprite.top,
                otherSprite.width, otherSprite.height);
         
   return context.isPointInPath(left,    top)     ||
          context.isPointInPath(right,   top)     ||

          context.isPointInPath(centerX, centerY) ||

          context.isPointInPath(left,    bottom)  ||
          context.isPointInPath(right,   bottom);
},

如果给定了跑步小人的边界框和它的中心坐标,那么 didRunnerCollideWithOtherSprite() 会检查一个边界框的角或它的中心是否位于另一个 sprite 的边界框内。

Canvas 上下文的功能不仅是绘制图形

Canvas 上下文的 isPointInPath() 方法检测一个点是否在当前路径中。Snail Bait 使用它确定某个点是否位于矩形内。但是,当路径是一个不规则形状时,isPointInPath() 才会真正发挥其巨大作用。通过手工计算来确定某个点是否位于不规则形状内非常困难。

确定某个点是否位于一个矩形内,这不需要大量数学敏锐力;但是,HTML5 canvas 元素的 2D 上下文通过 isPointInPath() 方法进一步简化了该操作,如果某个点位于 canvas 上下文的当前路径中,那么该方法将会返回 true

didRunnerCollideWithOtherSprite() 方法通过调用 beginPath()rect() 创建了一个矩形路径来表示另一个 sprite 的边界框。didRunnerCollideWithOtherSprite() 方法随后调用 isPointInPath() 来确定跑步小人内的 5 个点中是否有 1 个点位于另一个 sprite 的边界框内。

didRunnerCollideWithOtherSprite() 方法能正确识别跑步小人与其他所有 sprite 之间的碰撞,除了蜗牛炸弹之外,如 图 6 中所示。

图 6. 跑步小人和蜗牛炸弹
Snail Bait 的屏幕截图,其中显示了即将与蜗牛炸弹碰撞的跑步小人

它不适用于蜗牛炸弹,因为蜗牛炸弹非常小,它可穿过跑步小人,而且跑步小人的边界框的任何角或中心都不会与炸弹接触。由于跑步小人大小与炸弹大小的比例不当,当另一个 sprite 是炸弹时,清单 4 中的 didCollide() 方法会调用 didSnailBombCollideWithRunner(),如 清单 6 中所示。

清单 6. didSnailBombCollideWithRunner() 方法
   didSnailBombCollideWithRunner : function (left, top, right, bottom,
                                             snailBomb, context) {
   // Determine if the center of the snail bomb lies within
   // the runner's bounding box  

   context.beginPath();
   context.rect(left, top, right - left, bottom - top); // runner's bounding box

   return context.isPointInPath(
                 snailBomb.left - snailBomb.offset + snailBomb.width/2,
                 snailBomb.top + snailBomb.height/2);
},

didSnailBombCollideWithRunner() 方法与 didRunnerCollideWithOtherSprite() 相反:didRunnerCollideWithOtherSprite() 检查跑步小人中的点是否在另一个 sprite 内,而 didSnailBombCollideWithRunner() 检查另一个 sprite(炸弹)的中心是否在跑步小人内。

您已经了解了如何使用边界框实现碰撞检测,但该技术可以更加准确且更有效地执行。在以下各节中,将介绍如何通过修改跑步小人的边界框和分割游戏的空间,细化 Snail Bait 的碰撞检测。


细化边界框

图 5 中可以看到,碰撞检测边界框封装了它表示的 sprite。但是,在接近这些边界框的角的地方,边界框内部常常是透明的。跑步小人 sprite 就属于这种情况,如 图 7 所示。这些透明区域可能导致错误碰撞,这在两个透明区域碰撞时尤为明显。

图 7. 原始的跑步小人边界框
Snail Bait 的屏幕截图,其中显示了跑步小人上方叠加的原始的跑步小人边界框

消除透明角区域导致的错误碰撞的一种方法是,缩小 sprite 的边界框大小,如 图 8 中所示。

图 8. 修改后的跑步小人边界框
Snail Bait 的屏幕截图,其中显示了修改后的跑步小人边界框

Snail Bait 通过修改的 didCollide() 方法缩小了跑步小人的边界框的大小,如 清单 7 中所示。

清单 7. 修改跑步小人的边界框
var SnailBait =  function () {
...

didCollide: function (sprite,      // runner
                       otherSprite, // candidate for collision
                       context) {   // for context.isPointInPath()
      var MARGIN_TOP = 10,
          MARGIN_LEFT = 10,
          MARGIN_RIGHT = 10,
          MARGIN_BOTTOM = 0,
          left = sprite.left + sprite.offset + MARGIN_LEFT,
          right = sprite.left + sprite.offset + sprite.width - MARGIN_RIGHT,
          top = sprite.top + MARGIN_TOP,
          bottom = sprite.top + sprite.height - MARGIN_BOTTOM,
          centerX = left + sprite.width/2,
          centerY = sprite.top + sprite.height/2;
       ...
   },
   ...
};

缩小跑步小人的边界框,会使 Snail Bait 的碰撞检测更准确,因为它消除了错误碰撞。接下来看看如何让碰撞检测更有效地执行。


空间分割

有关空间分割的更多信息

Snail Bait 的空间分割就是简单的空间分割实现。空间分割的更复杂实现包括八叉树二进制空间分割,它们适用于拥有大量碰撞检测单元时。请参阅 参考资料,了解空间分割的更多信息。

空间分割 涉及到将一个游戏的空间分割为单元,使得只有同一个单元中的 sprite 可能发生碰撞。通过消除对位于不同单元格中的 sprite 的碰撞检测,空间分割通常会显著提高性能。Snail Bait 就是通过分割空间来提高性能的,如 图 9 中所示。

图 9. Snail Bait 的空间分割
该屏幕截图显示了 Snail Bait 中的空间分割。只有左侧分区(占屏幕的 1/10)中的 sprite 可与跑步小人碰撞。更大的右侧分区中的 sprite 无法与跑步小人碰撞。

如 清单 8 所示,Snail Bait 将 图 9 中右侧区域中的所有 sprite 排除在碰撞检测之外,这显著减少了游戏执行的碰撞检测次数。

清单 8. 细化用于碰撞检测的 sprite 选择
this.isCandidateForCollision: function (sprite, otherSprite) {
   return sprite !== otherSprite &&
          sprite.visible && otherSprite.visible &&
          !sprite.exploding && !otherSprite.exploding &&
          otherSprite.left - otherSprite.offset < sprite.left + sprite.width;


},

现在您已了解如何高效地检测碰撞,让我们看看 Snail Bait 如何处理碰撞。


处理碰撞

检测到碰撞后,必须对它们进行处理。Snail Bait 的 processCollision() 用于处理跑步小人与其他 sprite 之间的碰撞,这从 清单 9 中可以看到。

清单 9. 处理碰撞:processCollision()
var SnailBait =  function () {
   processCollision: function (sprite, otherSprite) {
      if ('coin'  === otherSprite.type    ||  // bad guys
          'sapphire' === otherSprite.type ||
          'ruby' === otherSprite.type     ||
          'button' === otherSprite.type   ||
          'snail bomb' === otherSprite.type) {
         otherSprite.visible = false;
      }

      if ('bat' === otherSprite.type   ||  // good guys
          'bee' === otherSprite.type   ||
          'snail' === otherSprite.type ||
          'snail bomb' === otherSprite.type) {
         snailBait.explode(sprite);
      }

      if (sprite.jumping && 'platform' === otherSprite.type) {
         this.processPlatformCollisionDuringJump(sprite, otherSprite);
      }
   },
   ...
};

当跑步小人与好的东西(金币、蓝宝石、红宝石和纽扣)或与蜗牛炸弹碰撞时,Snail Bait 会让另一个 sprite 消失不见,将它的 visible 属性设置为 false

当跑步小人与坏东西(蝙蝠、蜜蜂、蜗牛和蜗牛炸弹)碰撞时,processCollision() 使用 Snail Bait 的 explode() 方法让跑步小人爆炸。目前,explode() 方法简单地将 BOOM 打印到控制台。本系列的下一篇文章将讨论 explode() 方法的最终实现。

最后,清单 10 中的 processPlatformCollisionDuringJump() 方法在跑步小人跳跃时处理平台碰撞。

清单 10. processPlatformCollisionDuringJump()
processPlatformCollisionDuringJump: function (sprite, platform) {
      var isDescending = sprite.descendAnimationTimer.isRunning();
   
      sprite.stopJumping();
   
      if (isDescending) { // Collided with platform while descending
         // land on platform

         sprite.track = platform.track;
         sprite.top = snailBait.calculatePlatformTop(sprite.track) - sprite.height;
      }
      else { // Collided with platform while ascending
         sprite.fall();
      }
   }
};

当跑步小人在跳跃时与一个平台碰撞的时候,如果她处于跳跃的下降阶段,她会停止跳跃并落在平台上。如果跑步小人正在上升,她从下面与平台碰撞,那么她会降落。现在,跑步小人的 fall() 方法的实现如 清单 11 中所示。

清单 11. 用于下降的 Stopgap 方法
var SnailBait =  function () {

   ...


   this.runner.fall = function () {
      snailBait.runner.track = 1;
      snailBait.runner.top = snailBait.calculatePlatformTop(snailBait.runner.track) -
                             snailBait.runner.height;
   };
   ...
};

跑步小人的 fall() 方法直接将跑步小人放在底部轨道上,即最矮的平台上。在本系列的下一篇文章中,您将了解如何使用逼真的下落来重新实现该方法,并考虑重力因素。


监视碰撞检测性能

90% 空闲?

图 10 中所示的表中最顶部的条目表示,Snail Bait 在 90% 的时间里都处于空闲状态。Snail Bait 拥有如此优秀的性能是因为,图 10 中的配置文件源自 Chrome 浏览器(26 版,它像所有现代浏览器一样)对 canvas 元素进行了硬件加速。浏览器供应商通常通过将针对 Canvas API 的调用转换为 WebGL 来实现对 canvas 元素的加速,所以可以同时获得 Canvas API 的便捷性和 WebGL 的高性能。

碰撞检测很容易成为性能瓶颈,尤其是对于数学计算更加密集的碰撞检测算法,比如分离轴定理。本文介绍了一些可用来提高性能的简单技术,比如细化边界框和空间分割。但不断监视游戏的性能也是一个不错的想法,这样您就可以在出现性能问题后尽快发现和修复它们。

所有现代浏览器都附带了复杂的开发环境;例如,Chrome、Safari、Firefox、Internet Explorer 和 Opera 都允许您分析正在运行的代码。图 10 显示了 Chrome 的探查器,该探查器描绘了您在各个方法中花费的时间与总时间的相对比例。

图 10. 碰撞检测性能
Chrome 探查器显示了每个 Snail Bait 方法使用的执行时间百分比

图 10 中可以看到,Snail Bait 的 didCollide() 方法仅花了游戏时间的 0.05%。(Self 列的 didCollide() 值为 0.01%,只表示直接花费在一个方法中的时间,不包括花费在该方法调用的方法中的时间。)

当跑步小人与坏东西碰撞时,就会发生爆炸。接下来看看如何实现该爆炸。


Sprite 动画

图 11(从上到下)演示了在跑步小人遇到坏东西(比如一只蜜蜂)时,Snail Bait 显示的爆炸动画。

图 11. 跑步小人在一次碰撞后爆炸
该 Snail Bait 屏幕截图显示了跑步小人与一只蜜蜂碰撞后爆炸

Snail Bait 使用 sprite 动画生成器 实现 sprite 动画,如 图 11 中所示的动画。sprite 动画生成器更改一个 sprite 的一段被指定持续时间绘制的单元。例如,爆炸 sprite 动画生成器在 500 毫秒内将跑步小人的动画单元更改为 图 12 中所示的单元。

图 12. 来自 Snail Bait 的 spritesheet 的爆炸单元
爆炸 sprite 动画生成器更改的爆炸 spritesheet 单元

sprite 动画生成器对象的构造函数如 清单 12 中所示。

清单 12. Sprite 动画生成器构造函数
// Sprite Animators...........................................................

var SpriteAnimator = function (cells, duration, callback) {
   this.cells = cells;
   this.duration = duration || 1000;
   this.callback = callback;
};

SpriteAnimator 构造函数接受 3 个参数。第一个参数是 Snail Bait 的 spritesheet 中的一个边界框数组;这些边界框是临时动画单元,该参数是强制性的。第二和第三个参数是可选的。第二个参数是动画持续时间,第三个参数是一个回调函数,在超过动画持续时间时,sprite 动画生成器会调用该函数。

SpriteAnimator 方法在对象的原型中定义,如 清单 13 中所示。

清单 13. Sprite 动画生成器方法
SpriteAnimator.prototype = {
   start: function (sprite, reappear) {
      var originalCells = sprite.artist.cells,
          originalIndex = sprite.artist.cellIndex,
          self = this;

      sprite.artist.cells = this.cells;
      sprite.artist.cellIndex = 0;
      
      setTimeout(function() {
         sprite.artist.cells = originalCells;
         sprite.artist.cellIndex = originalIndex;

         sprite.visible = reappear;

         if (self.callback) {
            self.callback(sprite, self);
         }
      }, self.duration); 
   },
};

SpriteAnimatorstart() 方法会启动动画,保存原始动画单元和指向当前单元的索引,分别将它们替换为临时单元和 0。然后,在动画持续时间过去后,start() 方法将 sprite 的动画单元和原始索引恢复到动画发生之前的状态。

清单 14 显示了 Snail Bait 如何使用一个 sprite 动画生成器让跑步小人爆炸。

清单 14. 创建爆炸动画生成器
var SnailBait =  function () {
   this.canvas = document.getElementById('game-canvas'),
   this.context = this.canvas.getContext('2d'),
   ...

   this.RUN_ANIMATION_RATE = 17,     // frames/second
   this.EXPLOSION_CELLS_HEIGHT = 62, // pixels
   this.EXPLOSION_DURATION = 500,    // milliseconds

   this.explosionCells = [
      { left: 1,   top: 48, width: 50, height: this.EXPLOSION_CELLS_HEIGHT },
      { left: 60,  top: 48, width: 68, height: this.EXPLOSION_CELLS_HEIGHT },
      { left: 143, top: 48, width: 68, height: this.EXPLOSION_CELLS_HEIGHT },
      { left: 230, top: 48, width: 68, height: this.EXPLOSION_CELLS_HEIGHT },
      { left: 305, top: 48, width: 68, height: this.EXPLOSION_CELLS_HEIGHT },
      { left: 389, top: 48, width: 68, height: this.EXPLOSION_CELLS_HEIGHT },
      { left: 470, top: 48, width: 68, height: this.EXPLOSION_CELLS_HEIGHT }
   ],
   ...

   this.explosionAnimator = new SpriteAnimator(
      this.explosionCells,          // Animation cells
      this.EXPLOSION_DURATION,      // Duration of the explosion

      function (sprite, animator) { // Callback after animation
         sprite.exploding = false; 

         if (sprite.jumping) {
            sprite.stopJumping();
         }
         else if (sprite.falling) {
            sprite.stopFalling();
         }

         sprite.visible = true;
         sprite.track = 1;
         sprite.top = snailBait.calculatePlatformTop(sprite.track) - sprite.height;
         sprite.artist.cellIndex = 0;
         sprite.runAnimationRate = snailBait.RUN_ANIMATION_RATE;
      }
   );
};

Snail Bait 使用 图 12 中所示的动画调用创建了一个 sprite 动画生成器。该动画的持续时间为 500ms,在结束爆炸时,sprite 动画生成器调用爆炸动画生成器的回调函数,后者将跑步小人放在最底层的平台堆栈上。不幸的是,在以后的一篇文章中,我们将重新实现这个回调函数,减去一条命并重新启动当前级别。

清单 15 显示了跑步小人的 explode() 方法(有点虎头蛇尾)。

清单 15. Snail Bait 的 explode() 方法
SnailBait.prototype = {
   ...

   explode: function (sprite, silent) {
      if (sprite.runAnimationRate === 0) {
         sprite.runAnimationRate = this.RUN_ANIMATION_RATE;
      }
               
      sprite.exploding = true;

      this.explosionAnimator.start(sprite, true);  // true means sprite reappears
   },
};

当跑步小人跳跃时,没有动画效果,因为她的动画速率为 0。explode() 方法将她的动画速率设置为标准值,以便她能够以动画形式进入爆炸单元。然后,explode() 方法将跑步小人的 exploding() 属性设置为 true 并启动爆炸动画生成器。


结束语

在本 系列文章 的下一篇文章中,您将看到如何通过纳入重力来实现逼真的下落过程,以及如何向 Snail Bait 添加声音和音乐。


下载

描述名字大小
样例代码wa-html5-game8-code.zip1.2MB

参考资料

学习

获得产品和技术

  • Replica Island:您可以下载这个面向 Android 的流行开源平台视频频游的资源。Snail Bait 的大部分 sprite 都来自 Replica Island(需要权限才能使用)。
  • IBM 产品评估版:下载或 在 IBM SOA 沙盒中体验在线试用版,动手使用来自 DB2、Lotus、Rational、Tivoli 和 WebSphere 的应用程序开发工具和中间件产品。

讨论

条评论

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, Java technology
ArticleID=929120
ArticleTitle=HTML5 2D 游戏开发: 碰撞检测和 sprite 动画
publish-date=05212013