实现重力

    技术2024-03-20  92

    现在,Snail Bait可以检测到碰撞(如本系列文章的前一篇文章所述),游戏必须处理一种重要的非碰撞类型:跑步者缺少平台。 在这种情况下,她开始跌倒。 在本文中,我将向您展示如何结合重力来实现逼真的下落。 重力和摔倒完善了Snail Bait需要的所有游戏玩法。 之后,我将切换齿轮并向您展示如何将声音(包括音乐)整合到游戏中。 此部分的完整示例代码可供下载 。

    坠落

    Snail Bait的跑步者从平台边缘跑下或从下方与平台碰撞时会掉下,如图1所示:

    图1.从平台边缘掉下来

    如图2所示,当她在跳台下降结束时错过平台时,她也会跌倒。

    图2.在跳跃结束时跌倒

    跑步者由于摔倒行为而摔倒 。 清单1显示了跑步小精灵的实例化,并指定了她的行为数组:

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

    当精灵可见时,Snail Bait会为每个动画帧调用跑步者的跌倒行为(与所有精灵行为一样)。 在大多数情况下,跌倒行为无能为力。 当跑步者的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为止。 本引力讨论的其余部分集中于该行为的实现。

    结合重力

    重力是特例

    重力会产生非线性运动,我将在本系列的第七篇文章中对此进行讨论。 在那篇文章中,我通过使用时间传感器实现了非线性跳跃,该时间传感器使用缓动和缓动补间功能来近似重力。 如果您改变这些换能器,则可以产生无限范围的非线性运动,这意味着重力通常是线性运动的特例。

    在地球表面附近,重力以每秒9.81米/秒的速度加速坠落的物体,这意味着物体每秒掉落的速度会增加近10 m / s(或32 ft / s)。 重力对游戏开发者的影响是,除了根据精灵的速度计算精灵的位置外,还必须计算精灵掉落时的速度。

    在重力影响下计算速度的数学方法很简单:将重力乘以精灵的经过的下降时间,然后将其值添加到精灵开始下降时的初始速度。 与方程式一样,令人困惑的部分不是数学而是单位,因为前面方程式的结果使您每秒只有米。 为了使该数字有意义,Snail Bait使用以下用于计算子画面位置的算法将其转换为每秒像素数:

    游戏开始时: 定义游戏的宽度(以像素为单位)。 任意定义游戏的宽度(以米为单位)。 将以像素为单位的宽度除以以米为单位的宽度,以得到像素/米的比率。 随后,对于每个动画帧: 使用重力加速度(9.81 m / s / s)来计算速度,以米/秒为单位。 将速度(以m / s为单位)乘以在步骤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 ... }

    当跑步者跑出平台的末端或从下面与平台碰撞时,她开始没有垂直速度坠落。 但是,当跑步者在跳步结束时没有降落在平台上时,她开始以跳步下降结束时的垂直速度坠落。 清单4显示了跑步者的跳跃行为如何使用清单3中定义的GRAVITY_FORCE和PIXELS_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)

    该计算结果以像素/秒为单位,表示跑步者在其跳下下降结束时的垂直速度。 (请参阅本系列的第六篇文章 ,以了解其余的跳转行为的实现。)

    Snail Bait使用GRAVITY_FORCE和PIXELS_PER_METER计算跑步者速度的另一个地方是跑步者的跌倒行为,如清单5所示:

    清单5.设置精灵速度并计算当前帧的跑步者垂直下落
    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()方法使用基于时间的运动(我将在本系列第二篇文章的基于时间的运动部分中进行讨论) 基于 setSpriteVelocity()和当前帧计算的速度来计算跑步者的垂直下落。率。

    正如我在本系列第五篇文章中详细讨论的那样,Snail Bait在每个动画帧上遍历其所有精灵。 对于每个可见的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()方法使用了四种便捷方法。 清单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(); } }, ... };

    跑步者摔倒时暂停

    正如我在本系列第七篇文章的“ 暂停行为”部分所讨论的那样,行为必须实现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.声音和音乐控件

    清单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以编程方式访问复选框元素,并维护两个变量( soundOn和musicOn ),事件处理程序使这些变量与复选框保持同步。 音乐复选框事件处理程序还会播放或暂停游戏的音乐配乐,与声音效果不同,音乐配乐会不断在后台播放。

    清单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> <audio id='soundtrack'> <source src='sounds/soundtrack.mp3' type='audio/mp3'> <source src='sounds/soundtrack.ogg' type='audio/ogg'> </audio> <audio id='plop-sound' > <source src='sounds/plop.mp3' type='audio/mp3'> <source src='sounds/plop.ogg' type='audio/ogg'> </audio> <audio id='chimes-sound' > <source src='sounds/chimes.mp3' type='audio/mp3'> <source src='sounds/chimes.ogg' type='audio/ogg'> </audio> <audio id='whistle-down-sound' > <source src='sounds/whistledown.mp3' type='audio/mp3'> <source src='sounds/whistledown.ogg' type='audio/ogg'> </audio> <audio id='thud-sound' > <source src='sounds/thud.mp3' type='audio/mp3'> <source src='sounds/thud.ogg' type='audio/ogg'> </audio> <audio id='jump-sound' > <source src='sounds/jump.mp3' type='audio/mp3'> <source src='sounds/jump.ogg' type='audio/ogg'> </audio> <audio id='coin-sound' > <source src='sounds/coin.mp3' type='audio/mp3'> <source src='sounds/coin.ogg' type='audio/ogg'> </audio> <audio id='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.在JavaScript中访问Snail Bait的音频元素
    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中看到所有前述方法和属性的使用( 清单12和清单14中使用的pause()除外):

    清单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会创建一个包含八个audio元素的数组。 Snail Bait的playSound()方法遍历该数组,并使用当前未播放的第一个audio元素播放声音。

    意识到Snail Bait绝不会通过清单15中HTML中指定的原始audio元素来播放声音效果。 相反,该游戏通过清单19中以编程方式创建的八个音频元素来播放声音效果。 游戏将声音从原始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); }; },

    当精灵爆炸时,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 }, ... };

    当精灵降落在平台上时,它们会发出轰鸣声。 当它们落在轨道下方时,它们会发出啸叫声(不同于跳跃的啸叫声):

    清单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); } } } ... } }; ... };

    精灵之间的碰撞会发出各种声音,具体取决于碰撞中涉及的精灵,如清单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游戏开发系列。


    翻译自: https://www.ibm.com/developerworks/java/library/wa-html5-game9/index.html

    相关资源:微信小程序源码-合集6.rar
    Processed: 0.017, SQL: 9