HTML5 2D 游戏开发: 实现重力和添加声音

创建逼真的下落动画,并添加音轨和音效

在这个文章系列中,HTML 专家 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 年 7 月 30 日

现在,Snail Bait 已可检测碰撞—在本系列的 上一篇文章中实现—游戏必须处理一种重要的非碰撞类型:跑步小人未落到一个平台上。在此情况下,她会开始下落。在本文中,我将展示如何结合重力来实现逼真的下落。重力和下落完成了 Snail Bait 需要的游戏设置的所有力学结构。然后,我将转变方向,展示如何将声音(包括音乐)合并到游戏中。本期文章的完整示例代码可 下载获得。

下落

Snail Bait 的跑步者会在离开平台边缘或从下面碰撞平台时掉落,如图 1 所示:

图 1. 从平台边缘下落
Snail Bait 的屏幕截图,其中的跑步小人从平台边缘下落

跑步小人在一次跳跃的下降阶段末尾未落到平台上时也会下落,如图 2 所示:

图 2. 跳跃末尾的下落
Snail Bait 的屏幕截图,其中的跑步小人在跳跃末尾下落

跑步小人借助她的下落行为来下落。清单 1 显示了跑步小人 sprite 的实例化,指定了她的行为数组:

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

在跑步小人 sprite 可见时的每个动画帧中,Snail Bait 都会调用跑步小人的下落行为—就像 sprite 的所有行为一样。下落行为在大部分时间里都不会执行任何操作。当跑步小人的 falling 属性为 true 时,下落行为会递增式地在每个动画帧中垂直移动跑步小人,使她看起来正在下落。该属性由跑步小人的 fall() 方法设置,如清单 2 所示:

清单 2. 跑步小人的 fall()方法
 SnailBait.prototype = { 
   ... 
   equipRunner: function () { 
      ... 
      this.equipRunnerForJumping(); 
      this.equipRunnerForFalling(); 
   }, 

   equipRunnerForFalling: function () { 
      ... 

      this.runner.fallAnimationTimer = new AnimationTimer(); 

      this.runner.fall= function (initialVelocity) { 
         // set attributes for the runner's fall behavior and 
         // start the fall animation timer 

         this.velocityY = initialVelocity || 0; 
         this.initialVelocityY = initialVelocity || 0; 
         this.falling = true;

         this.fallAnimationTimer.start(); 
      } 
   }, 
   ... 
 };

跑步小人的水平速度

跑步小人下落行为的惟一的关注点是垂直放置跑步小人。下落行为不需要修改跑步小人的水平速度,因为尽管跑步小人看起来像是从右向左(或从左向右)运动,但她从未发生水平运动。相反,跑步小人下方的背景在移动,从而使她看起来是在水平运动。

当游戏开始时,Snail Bait 调用它的 equipRunner()方法,该方法包含跑步小人的跳跃和下落行为。equipRunnerForFalling()方法包含跑步小人的 fall() 方法的实现。跑步小人的 fall() 方法设置跑步小人的初始速度,将跑步小人的 falling 属性设置为 true ,并启动一个动画计时器来跟踪跑步小人下落期间经历了多少时间。

当跑步小人的 fall() 方法将跑步小人的 falling 属性设置为 true 时,它就会中止跑步小人的下落行为中的一个触发器。跑步小人然后开始下落,直到游戏将跑步小人的 falling 属性重置为 false 时为止。对重力的其余讨论将重点关注该行为的实现。


合并重力

重力是一种特殊情形

重力会产生非线性运动,我在本系列的第 7 篇文章中已讨论过。在这篇文章中,我使用时间变换器实现了非线性的跳跃,通过淡出和淡入的渐变功能模拟了重力效果。如果修改这些变换器,则可生成一个无限的非线性运动范围,这就意味着重力通常是线性运动的一种特殊情形。

在地面附近,重力会以 9.81m/s 的加速度让物体加速下落,也就是说物体每下落一秒,它的速度就会增加近 10 m/s(或 32 ft/s)。对于游戏开发人员而言,考虑重力的结果是,不但要基于 sprite 的速度计算其位置,还必须计算它们下落时的速度。

计算重力影响下的速度的数学原理很简单:将重力加速度乘以 sprite 的已下落时间,再将该值与 sprite 开始下落时的初始速度相加。就像等式一样,令人困惑的部分常常不是数学计算,而是单位,因为上一个等式的结果的单位是 m/s。为了让这个数字有意义,Snail Bait 将其转换为像素每秒,使用以下算法来计算 sprite 位置:

  • 游戏开始时:
    1. 以像素为单位定义游戏的宽度。
    2. 以米为单位任意定义游戏的宽度。
    3. 将以像素为单位的宽度除以以米为单位的宽度,以获得像素 / 米的比率。
  • 随后,对于每个动画帧:
    1. 使用重力加速度 (9.81 m/s/s) 计算以 m/s 为单位的速度。
    2. 将以 m/s 为单位的速度乘以 第 3 步中计算出的像素 / 米比率,得到像素 / 秒的比率。
    3. 根据以像素 / 秒为单位的速度计算位置。

现在 , 您可将前面的算法转换为代码。第一步是定义重力和游戏的像素 / 米比率,如清单 3 所示:

清单 3. 与重力和下落相关的常量
 var SnailBait = { // SnailBait constructor function 
   ... 
  
   this.GRAVITY_FORCE = 9.81, 
   this.PIXELS_PER_METER = this.canvas.width / 10; // 10 meters, randomly selected width 
   ... 
 }

当跑步小人跑离平台的边缘或从下方与平台相撞时,她以垂直速度 0 开始下落。但是,当跑步小人跳跃结束未落到平台上时,她就开始以跳跃下降过程的结束时的垂直速度下落。清单 4 展示了跑步小人的跳跃行为如何使用清单 3中定义的 GRAVITY_FORCEPIXELS_PER_METER 来计算这一初始速度:

清单 4. 跳跃结束时的下落
 this.jumpBehavior= { 
   ... 

   finishDescent: function (sprite) { 
      sprite.stopJumping(); 

      if (snailBait.isOverPlatform(sprite) !== -1) { 
         sprite.top = sprite.verticalLaunchPosition; 
      } 
      else { 
         // The argument passed to the sprite's fall() method 
         // is the sprite's initial velocity when it begins to fall 
   
         sprite.fall(snailBait.GRAVITY_FORCE* 
                     (sprite.descendAnimationTimer.getElapsedTime()/1000) * 
                     snailBait.PIXELS_PER_METER); 
      } 
   }, 
 };

跑步小人在下降结束时的垂直速度是重力加速度乘以下降所用时间,再乘以游戏的像素 / 米比率:

(9.81 m/s/s) * (所用的下降秒数)* (800 像素 / 10 m)

计算结果以像素 / 秒为单位,并表示跑步小人在跳跃的下降阶段结束时的垂直速度。(请参阅本系列的 第 6 篇文章,查看跳跃行为实现的其余部分。)

Snail Bait 使用 GRAVITY_FORCEPIXELS_PER_METER 计算的跑步小人速度的另一位置是在跑步小人的下落行为中,如清单 5 所示:

清单 5. 设置 sprite 速度并计算跑步小人在当前帧中的垂直下落距离
 this.fallBehavior= { 
   ... 
   setSpriteVelocity: function (sprite) { 
      sprite.velocityY = sprite.initialVelocityY + 

                         snailBait.GRAVITY_FORCE* 
                         (sprite.fallAnimationTimer.getElapsedTime()/1000) * 
                         snailBait.PIXELS_PER_METER; 
   }, 

   calculateVerticalDrop: function (sprite, fps) { 
      return sprite.velocityY / fps; 
   }, 
 };

下落行为的 setSpriteVelocity()方法依据跑步小人下落的时长来设置她的速度。该方法会小心地合并可能由 清单 2中的跳跃行为设定的跑步小人初始速度。

calculateVerticalDrop()方法使用基于时间的运动 —我已在本系列的第 2 篇文章的 基于时间的运动一节中讨论过 —基于 setSpriteVelocity()所计算的速度和当前的帧率来计算跑步小人的垂直下落。

本系列的 第 5 篇文章中已详细讨论过,Snail Bait 会在每个动画帧中迭代它的所有 sprite。对于每个可见的 sprite,Snail Bait 迭代该 sprite 的行为,从而依次调用每个行为的 execute()方法。清单 6 显示了跑步小人的下落行为的 execute()方法:

清单 6. 下落行为的 execute()方法
 this.fallBehavior= { 
      execute: function (sprite, time, fps) { // sprite is the runner 
         var deltaY; 

         if (sprite.jumping) { 
            return; 
         } 

         if (this.isOutOfPlay(sprite) || sprite.exploding) { 
            if (sprite.falling) { 
               sprite.stopFalling(); 
            } 
            return; 
         } 
         
         if (!sprite.falling) { 
            if (!sprite.exploding && !this.isPlatformUnderneath(sprite)) { 
               sprite.fall(); 
            } 
            return; 

         } 

         this.setSpriteVelocity(sprite); 
         deltaY = this.calculateVerticalDrop(sprite, fps); 
               
         if (!this.willFallBelowCurrentTrack(sprite, deltaY)) { 
            sprite.top += deltaY; 
         } 
         else { // will fall below current track 
            if (this.isPlatformUnderneath(sprite)) { 
               this.fallOnPlatform(sprite); 
               sprite.stopFalling(); 
            } 
            else { 
               sprite.top += deltaY; 
               sprite.track--; 
            } 
         } 
      } 
   },

当跑步小人跳跃、掉下来摔死或爆炸时,下落行为的 execute()方法会启动或停止下落并返回。如果她在下落过程中摔死或爆炸了,该方法会调用她的 stopFalling()方法。如果跑步小人未下落,目前未爆炸,并且她的下方没有平台,那么该方法会调用她的 fall()方法。

满足这些前提条件后,下落行为的 execute()方法会计算跑步小人的当前速度和位置。如果这个新位置未将跑步小人放在其当前平台下方,那么该方法会将她移动到那里。否则,跑步小人会下落到其平台下方,所以该方法检查她下方是否有另一个平台。如果有,该方法将她放在这个平台上并停止下落。如果跑步小人下落到其平台下方,并且她下方没有任何平台,那么该方法会将她移动到新位置并递减她当前的轨道。

下落行为的 execute()方法使用 4 个便捷方法。清单 7 中的两个方法确定跑步小人是否摔死或降落到当前轨道下方:

清单 7. 确定跑步小人摔死还是将落到当前轨道下方
 this.fallBehavior = { 
   isOutOfPlay: function (sprite) { 
      return sprite.top > snailBait.TRACK_1_BASELINE; 
   }, 

   willFallBelowCurrentTrack: function (sprite, deltaY) { 
      return sprite.top + sprite.height + deltaY > 
             snailBait.calculatePlatformTop(sprite.track); 
   }, 
   ... 
 };

清单 8 中的便捷方法确定跑步小人下方是否有一个平台,并让跑步小人落到一个平台上:

清单 8. 确定跑步小人下方是否有一个平台,并让跑步小人落到一个平台上
 this.fallBehavior= { 
   isPlatformUnderneath: function (sprite) { 
      return snailBait.isOverPlatform(sprite) !== -1; 
   }, 

   fallOnPlatform: function (sprite) { 
      sprite.top = snailBait.calculatePlatformTop(sprite.track) - sprite.height; 
      sprite.stopFalling(); 
   }, 
   ... 
 };

一定要认识到,下落行为的 execute()方法仅在跑步小人的 falling属性为 true时才垂直移动她。跑步小人的 stopFalling()方法将该属性设置为 false,如清单 9 所示,将跑步小人的垂直速度设置为 0并停止跑步小人的下落动画计时器:

清单 9. 下落行为的 stopFalling()方法
 SnailBait.prototype = { 
   ... 

   equipRunnerForFalling: function () { 
      ... 

      this.runner.stopFalling= function () { 
         this.falling = false;
         this.velocityY = 0; 
         this.fallAnimationTimer.stop(); 
      } 
   }, 
   ... 
 };

在跑步小人下落时暂停

本系列第 7 篇文章的 暂停行为一节中已讨论过,行为必须实现 pause()unpause()方法,以便它们可与整个游戏一致地暂停和恢复。跑步小人的下落行为满足该需求,如清单 10 所示:

清单 10. 暂停和取消暂停下落行为
 var SnailBait = function () { 
   ... 

   this.fallBehavior = { 
      pause: function (sprite) { 
         sprite.fallAnimationTimer.pause(); 
      }, 

      unpause: function (sprite) { 
         sprite.fallAnimationTimer.unpause(); 
      }, 
   } 
   ... 
 }

下落行为使用跑步小人的下落动画计时器跟踪下落所经历的时间。因此,该行为的 pause()unpause()方法只是暂停和取消暂停该计时器。

现在,您已看到 Snail Bait 如何让跑步小人下落,是时候看看一个完全不相关的方面了:游戏的声音。


控制音效和音乐

Snail Bait 可同时播放一个音乐音轨和音效。图 3 的左下角是声音和音乐复选框,用户可使用它们控制游戏是否播放音效和 / 或音乐:

图 3. 声音和音乐控制
Snail Bait 的屏幕截图,其中显示了用于控制声音和音乐的复选框

清单 11 显示了这些复选框的 HTML:

清单 11. Snail Bait 的声音和音乐复选框
 <div id='sound-and-music'> 
   <div class='checkbox-div'> 
      Sound <input id='sound-checkbox' type='checkbox' checked/> 
   </div> 
          
   <div class='checkbox-div'> 
      Music <input id='music-checkbox' type='checkbox'/> 
   </div> 
 </div>

可使用一个复选框的 checked属性(没有值)来控制复选框最初是否已选择。如果存在该属性,则表示复选框最初已选择;否则未选择;这从 图 3清单 11中可以看出。

在清单 12 中,Snail Bait 以编程方式访问这些复选框元素并维护两个变量 —soundOnmusicOn—事件处理程序会将这两个变量与复选框同步。音乐复选框事件处理程序还会播放或暂停游戏的音乐音轨,这种音乐音轨(不同于音效)会在后台持续播放。

清单 12. 声音和音乐复选框事件处理程序
 var SnailBait = function () { 
   ... 
   this.soundCheckbox= document.getElementById('sound-checkbox'); 
   this.musicCheckbox= document.getElementById('music-checkbox'); 

   this.soundOn= this.soundCheckbox.checked; 
   this.musicOn= this.musicCheckbox.checked; 
   ... 
 }; 
 ... 

 snailBait.soundCheckbox.onchange = function (e) { 
   snailBait.soundOn = snailBait.soundCheckbox.checked; 
 }; 

 snailBait.musicCheckbox.onchange = function (e) { 
   snailBait.musicOn = snailBait.musicCheckbox.checked; 

   if (snailBait.musicOn) { 
      snailBait.soundtrack.play(); 
   } 
   else { 
      snailBait.soundtrack.pause(); 
   } 
 };

如果音乐已打开,那么 Snail Bait 的 startGame()方法会播放音轨,如清单 13 所示:

清单 13. 启动游戏
 SnailBait.prototype = { 
 SnailBait.prototype = { 
   ... 

   startGame: function () { 
      if (this.musicOn) { 
         this.soundtrack.play(); 
      } 
      requestNextAnimationFrame(this.animate); 
   }, 
   ...

我还修改了游戏的 togglePaused()方法,以依据游戏是否暂停来暂停或播放音轨,如清单 14 所示:

清单 14. 暂停音乐
 SnailBait.prototype = { 
   ... 

   togglePaused: function () { 
      ... 

      if (this.paused && this.musicOn) { 
         this.soundtrack.pause(); 
      } 
      else if ( ! this.paused && this.musicOn) { 
         this.soundtrack.play(); 
      } 
   }, 
 };

实现音效

Snail Bait 在游戏的各个点播放音效 —例如,在跑步小人与一只蜜蜂或蝙蝠碰撞时 —并且有时必须同时播放多种声音。游戏使用 HTML5 audio元素来实现音效。

HTML audio元素

Snail Bait 为它的每种声音创建一个 audio元素,如清单 15 所示:

清单 15. Snail Bait 的 audio元素
 <!DOCTYPE html> 
 <html> 
   <head> 
      <title>Snail Bait</title> 
      <link rel='stylesheet' href='game.css'/> 
   </head> 
 
   <body> 
      <audioid='soundtrack'> 
        <source src='sounds/soundtrack.mp3' type='audio/mp3'> 
        <source src='sounds/soundtrack.ogg' type='audio/ogg'> 
      </audio> 

      <audioid='plop-sound' > 
        <source src='sounds/plop.mp3' type='audio/mp3'> 
        <source src='sounds/plop.ogg' type='audio/ogg'> 
      </audio> 

      <audioid='chimes-sound' > 
        <source src='sounds/chimes.mp3' type='audio/mp3'> 
        <source src='sounds/chimes.ogg' type='audio/ogg'> 
      </audio> 

      <audioid='whistle-down-sound' > 
        <source src='sounds/whistledown.mp3' type='audio/mp3'> 
        <source src='sounds/whistledown.ogg' type='audio/ogg'> 
      </audio> 

      <audioid='thud-sound' > 
        <source src='sounds/thud.mp3' type='audio/mp3'> 
        <source src='sounds/thud.ogg' type='audio/ogg'> 
      </audio> 

      <audioid='jump-sound' > 
        <source src='sounds/jump.mp3' type='audio/mp3'> 
        <source src='sounds/jump.ogg' type='audio/ogg'> 
      </audio> 

      <audioid='coin-sound' > 
        <source src='sounds/coin.mp3' type='audio/mp3'> 
        <source src='sounds/coin.ogg' type='audio/ogg'> 
      </audio> 

      <audioid='explosion-sound' > 
        <source src='sounds/explosion.mp3' type='audio/mp3'> 
        <source src='sounds/explosion.ogg' type='audio/ogg'> 
      </audio> 
      ... 

  </body> 
 </html>

在每个 audio元素内部,我指定了两个不同格式的声音文件;浏览器选择其可播放的格式。所有现代浏览器都支持 MP3 或 OGG 格式;请参阅 参考资料了解 HTML5 音频格式的更多信息。

此外,Snail Bait 使用文档的 getElementById()方法访问 JavaScript 中的每个 audio元素,如清单 16 所示:

清单 16. 访问 Snail Bait 的 JavaScript 音频元素
 SnailBait = function () { 
   ... 

   this.coinSound           = document.getElementById('coin-sound'), 
   this.chimesSound         = document.getElementById('chimes-sound'), 
   this.explosionSound      = document.getElementById('explosion-sound'), 
   this.fallingWhistleSound = document.getElementById('whistle-down-sound'), 
   this.plopSound           = document.getElementById('plop-sound'), 
   this.jumpWhistleSound    = document.getElementById('jump-sound'), 
   this.soundtrack          = document.getElementById('soundtrack'), 
   this.thudSound           = document.getElementById('thud-sound'), 
   ... 

 };

音量

对于其播放的每种声音,Snail Bait 定义了一个音量级,从 0.0(静音)到 1.0(最大音量)。清单 17 展示了 Snail Bait 为音量定义(而且我凭经验确定)的常量:

清单 17. 定义 Snail Bait 的声音的音量级
 SnailBait = function () { 
   // Sound-related constants 

   this.COIN_VOLUME            = 1.0, 
   this.CHIMES_VOLUME          = 1.0, 
   this.EXPLOSION_VOLUME       = 0.25, 
   this.FALLING_WHISTLE_VOLUME = 0.10, 
   this.JUMP_WHISTLE_VOLUME    = 0.05, 
   this.PLOP_VOLUME            = 0.20, 
   this.SOUNDTRACK_VOLUME      = 0.12, 
   this.THUD_VOLUME            = 0.20, 
   ... 

 };

有了对音频元素和表示音量级的常量的引用,Snail Bait 即可在游戏启动时初始化音频元素,如清单 18 所示:

清单 18. 设置 Snail Bait 的声音的音量级
 SnailBait.prototype = { 
   ... 

   initializeSounds: function () { 
      this.coinSound.volume         = this.COIN_VOLUME; 
      this.chimesSound.volume       = this.CHIMES_VOLUME; 
      this.explosionSound.volume    = this.EXPLOSION_VOLUME; 
      this.fallingWhistleSound.volume= this.FALLING_WHISTLE_VOLUME; 
      this.plopSound.volume         = this.PLOP_VOLUME; 
      this.jumpWhistleSound.volume  = this.JUMP_WHISTLE_VOLUME; 
      this.soundtrack.volume        = this.SOUNDTRACK_VOLUME; 
      this.thudSound.volume         = this.LANDING_VOLUME; 
   }, 

   start: function () { 
      this.createSprites(); 
      this.initializeImages(); 
      this.initializeSounds(); 
      this.equipRunner(); 
      this.splashToast('Good Luck!'); 
      ... 
   }, 
   ...   
   
 };

同时播放多种声音

HTML5 audio元素有一个非常简单的 API,其中包括 Snail Bait 用来播放声音的以下方法:

  • play()
  • pause()
  • load()

Snail Bait 还使用了以下 audio元素属性:

  • currentTime
  • ended

可在清单 19 中看到所有前述方法和属性的使用(除了 pause(),它用在 清单 12清单 14中):

清单 19. 使用 Snail Bait 的音轨播放声音
 SnailBait = function () { 
   ... 
   this.soundOn= true, 

   this.audioTracks= [ // 8 tracks is more than enough 
      new Audio(), new Audio(), new Audio(), new Audio(), 
      new Audio(), new Audio(), new Audio(), new Audio() 
   ], 
   ... 

   // Playing sounds....................................................... 

   soundIsPlaying: function (sound) { 
      return !sound.ended && sound.currentTime > 0; 
   }, 

   playSound: function (sound) { 
      var track, index; 

      if (this.soundOn) { 
         if (!this.soundIsPlaying(sound)) { 
            sound.play(); 
         } 
         else { 
            for (i=0; index < this.audioTracks.length; ++index) { 
               track = this.audioTracks[index]; 
            
               if (!this.soundIsPlaying(track)) { 
                  track.src = sound.currentSrc; 
                  track.load(); 
                  track.volume = sound.volume; 
                  track.play(); 

                  break; 
               } 
            } 
         }              
      } 
   }, 
   ... 
 };

为了同时播放多种声音,Snail Bait 创建了一个包含 8 个 audio元素的数组。Snail Bait 的 playSound()方法迭代该数组,并使用当前未播放的第一个 audio元素来播放该声音。

请注意,Snail Bait 绝不通过 清单 15中的 HTML 中指定的原始 audio元素来播放音效。相反,该游戏通过其在 清单 19中以编程方式创建的 8 个 audio 元素来播放音效。该游戏从原始 audio元素将声音加载到以编程方式创建的元素中,然后播放这个以编程方式创建的元素。


播放 Snail Bait 的音效

清单 20 到清单 23 给出了在各个点播放 Snail Bait 的音效的代码段。我在本系列以前的文章中已讨论过这些清单中的所有代码,所以在这里不会重复讨论。我留下了围绕对 playSound()的调用的足够逻辑来告知原始上下文。

当跑步小人跳跃时,Snail Bat 播放一种口哨声,如清单 20 所示:

清单 20. 跑步小人在跳跃时发出的声音
 var SnailBait = function () { 
   ... 

   this.equipRunnerForJumping: function() { 
       ... 
      this.runner.jump = function () { 
         if (this.jumping) // 'this' is the runner 
            return; 

         this.runAnimationRate = 0; 
         this.jumping = true; 
         this.verticalLaunchPosition = this.top; 
         this.ascendAnimationTimer.start(); 

         snailBait.playSound(snailBait.jumpWhistleSound); 
      }; 
   },

当 sprite 爆炸时,Snail Bait 播放 explosionSound,如清单 21 中所示:

清单 21. 爆炸
 SnailBait.prototype = { 
   ... 

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

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

当 sprite 落在一个平台上时,它们会发出重击声;当它们落到一个轨道下方时,会发出口哨声(不同于跳跃的口哨声):

清单 22. 与下落相关的声音
 SnailBait = function () { 
   ... 

   this.fallBehavior= { 
      ... 

      fallOnPlatform: function (sprite) { 
         sprite.top = snailBait.calculatePlatformTop(sprite.track) - sprite.height; 
         sprite.stopFalling(); 
         snailBait.playSound(snailBait.thudSound); 
      }, 

      execute: function (sprite, time, fps) { 
         var deltaY; 

         if (!this.willFallBelowCurrentTrack(sprite, deltaY)) { 
            sprite.top += deltaY; 
         } 
         else { // will fall below current track 
            if (this.isPlatformUnderneath(sprite)) { 
               this.fallOnPlatform(sprite); 
               sprite.stopFalling(); 
            } 
            else { 
               sprite.track--; 

               sprite.top += deltaY; 

               if (sprite.track === 0) { 
                  snailBait.playSound(snailBait.fallingWhistleSound); 
               } 
            } 
         } 
         ... 
      } 
   }; 
   ... 
 };

sprite 之间的碰撞会发出各种声音,具体取决于哪些 sprite 参与了碰撞,如清单 23 所示:

清单 23. 碰撞声音
 var SnailBait=  function () { 
   ... 

   this.collideBehavior= { 
      ... 

      processCollision: function (sprite, otherSprite) { 
         if (otherSprite.value) { // Modify Snail Bait sprites so they have values 
            // Keep score... 
         } 

         if ('coin'  === otherSprite.type    || 
             'sapphire' === otherSprite.type || 
             'ruby' === otherSprite.type     || 
             'button' === otherSprite.type   || 
             'snail bomb' === otherSprite.type) { 
            otherSprite.visible = false; 

            if ('coin' === otherSprite.type) { 
               snailBait.playSound(snailBait.coinSound); 
            } 
            if ('sapphire' === otherSprite.type || 'ruby' === otherSprite.type) { 
               snailBait.playSound(snailBait.chimesSound); 
            } 
         } 

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

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

   processPlatformCollisionDuringJump: function (sprite, platform) { 
      var isDescending = sprite.descendAnimationTimer.isRunning(); 

      sprite.stopJumping(); 

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

结束语

在下一期文章中,我将完成 HTML5 2D 游戏开发系列的介绍,探讨最终的游戏播放和美化,比如各种生命之间的过渡和一种游戏结束动画。


下载

描述名字大小
样例代码code.zip11.9MB

参考资料

学习

获得产品和技术

  • Replica Island:您可以下载这个面向 Android 的流行的开源平台视频游戏的源代码。Snail Bait 的大部分 sprite 都来自 Replica Island(需要使用权限)。
  • 下载 IBM 产品评估版,亲自使用来自 DB2®、Lotus®、Rational®、Tivoli®和 WebSphere®的应用程序开发工具和中间件产品。

讨论

  • 加入 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, Java technology
ArticleID=938958
ArticleTitle=HTML5 2D 游戏开发: 实现重力和添加声音
publish-date=07302013