HTML5 2D 游戏开发 实现重力和添加声音(2)
 
- UID
- 1066743
|

HTML5 2D 游戏开发 实现重力和添加声音(2)
合并重力重力是一种特殊情形重力会产生非线性运动,我在本系列的中已讨论过。在这篇文章中,我使用时间变换器实现了非线性的跳跃,通过淡出和淡入的渐变功能模拟了重力效果。如果修改这些变换器,则可生成一个无限的非线性运动范围,这就意味着重力通常是线性运动的一种特殊情形。
在地面附近,重力会以 9.81m/s 的加速度让物体加速下落,也就是说物体每下落一秒,它的速度就会增加近 10 m/s(或 32 ft/s)。对于游戏开发人员而言,考虑重力的结果是,不但要基于 sprite 的速度计算其位置,还必须计算它们下落时的速度。
计算重力影响下的速度的数学原理很简单:将重力加速度乘以 sprite 的已下落时间,再将该值与 sprite 开始下落时的初始速度相加。就像等式一样,令人困惑的部分常常不是数学计算,而是单位,因为上一个等式的结果的单位是 m/s。为了让这个数字有意义,Snail Bait 将其转换为像素每秒,使用以下算法来计算 sprite 位置:
- 游戏开始时:
- 以像素为单位定义游戏的宽度。
- 以米为单位任意定义游戏的宽度。
- 将以像素为单位的宽度除以以米为单位的宽度,以获得像素 / 米的比率。
- 随后,对于每个动画帧:
- 使用重力加速度 (9.81 m/s/s) 计算以 m/s 为单位的速度。
- 将以 m/s 为单位的速度乘以 中计算出的像素 / 米比率,得到像素 / 秒的比率。
- 根据以像素 / 秒为单位的速度计算位置。
现在 , 您可将前面的算法转换为代码。第一步是定义重力和游戏的像素 / 米比率,如清单 3 所示:
清单 3. 与重力和下落相关的常量1
2
3
4
5
6
7
| 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 展示了跑步小人的跳跃行为如何使用中定义的 GRAVITY_FORCE 和 PIXELS_PER_METER 来计算这一初始速度:
清单 4. 跳跃结束时的下落1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| 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. 设置 sprite 速度并计算跑步小人在当前帧中的垂直下落距离1
2
3
4
5
6
7
8
9
10
11
12
13
14
| 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()方法依据跑步小人下落的时长来设置她的速度。该方法会小心地合并可能由 中的跳跃行为设定的跑步小人初始速度。
calculateVerticalDrop()方法使用基于时间的运动 —我已在本系列的第 2 篇文章的 一节中讨论过 —基于 setSpriteVelocity()所计算的速度和当前的帧率来计算跑步小人的垂直下落。
本系列的 中已详细讨论过,Snail Bait 会在每个动画帧中迭代它的所有 sprite。对于每个可见的 sprite,Snail Bait 迭代该 sprite 的行为,从而依次调用每个行为的 execute()方法。清单 6 显示了跑步小人的下落行为的 execute()方法:
清单 6. 下落行为的 execute()方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| 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. 确定跑步小人摔死还是将落到当前轨道下方1
2
3
4
5
6
7
8
9
10
11
| 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. 确定跑步小人下方是否有一个平台,并让跑步小人落到一个平台上1
2
3
4
5
6
7
8
9
10
11
| 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()方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
| SnailBait.prototype = {
...
equipRunnerForFalling: function () {
...
this.runner.stopFalling= function () {
this.falling = false;
this.velocityY = 0;
this.fallAnimationTimer.stop();
}
},
...
};
|
|
|
|
|
|
|