六、创建街机射击游戏
在第五章中,我们研究了游戏设计并创造了我们自己的打地鼠(或者我应该说是打地鼠机器人?)游戏。在这一章中,我们来看另一个游戏,一个比打地鼠更复杂的街机射击游戏。
在街机射击游戏中,我们包括几个有助于有趣游戏的功能:背景音乐,不可玩的角色(如好人和坏人),爆炸,滚动背景,等等。将所有这些元素整合在一起需要在游戏设计方面进行仔细的规划,因为我们创建的所有类都必须相互交互。例如,从玩家的船上发射的子弹应该会让敌人消失。除了介绍街机射击游戏是如何设计和编码的,我们还介绍了几个新的主题,包括指示器、关卡和旋转。
汇总列表
- 探索游戏和设置
- 为游戏奠定基础
- 创建玩家的船
- 创建拍摄类
- 创建 PlayerShoot 类并使船射击
- 制造敌人
- 创建敌人射击类并让敌人射击
- 让敌人呈弧形移动
- 爆炸
- 添加滚动背景
- 添加生活量表
建造街机射击游戏
让我们从我们能制作的最简单的街机射击游戏开始,然后逐步增加复杂度。本节我们编写的游戏是一个简单的射击游戏。当玩家触摸屏幕时,飞船会移动到触摸的位置,只要用户触摸它,飞船就会发射子弹穿过屏幕。敌人的船出现在屏幕的对面,向玩家的船射击。如果玩家的船被击中,游戏结束。玩家每消灭一艘敌舰,分数就会增加。游戏的目标是在被击中之前获得尽可能高的分数。
在第 5 章中,我们的方法是写一小段简单的代码,然后在开发过程中不断改进,在每一次迭代中玩游戏。我们在这里也是这样做的,但是因为街机射击游戏比打地鼠游戏更复杂,所以有更长的步骤。
探索游戏,设置
如果你在我们开始编码之前玩了我们将要编码的游戏,你会对游戏的工作原理有更好的了解。执行以下操作来熟悉游戏,并设置您将在其中编码的环境:
- 在
http://9leap.net/games/1034
玩几次游戏,感受一下我们将要制作的游戏。特别注意敌人是如何随机出现的,当屏幕上有很多敌人子弹时,游戏会变得多么困难。这款游戏因其重玩价值而在9leap.net
上广受欢迎。由于随机产生的敌人,每次的体验都不一样。
* 在
http://code.9leap.net/codes/show/29839叉模板。该模板包含您需要的必要图像文件。
`为游戏打基础
和往常一样,最好先从一个游戏最简单的元素开始,然后再加入更复杂的元素。在这里,我们设置了Core
对象,设置了背景,并创建了一个乐谱。执行以下操作进行设置:
-
Initialize the enchant.js library and create the
Core
entity by copying the code in Listing 6-1 into the blank template. Because of the complexity of this section, we label sections of code with comments so we can refer to them later in the chapter.清单 6-1。 街机射手的基础
```js enchant(); //Class Definitions
window.onload = function() { game = new Core(320, 320); //Game Properties game.fps = 24;
game.onload = function() { };
game.start(); }; ```
-
Create a black background by typing the code in Listing 6-2 into the
game.onload
function. This specifies thebackgroundColor
ofrootScene
to be black.清单 6-2。 创建黑色背景
js //In-Game Variables and Properties game.rootScene.backgroundColor = 'black';
rootScene
的backgroundColor
属性可以接受所有标准颜色的名称和颜色的十六进制值(例如,#FF0000
代表红色)。 -
单击运行。屏幕应该会变黑。
-
Create a game variable for the player’s score by adding the code in Listing 6-3 to the
//Game Properties
section. Later, we'll make this variable increase in value whenever enemies are hit by bullets from the player’s ship.清单 6-3。 创建分数变量
js game.score = 0;
-
Create a
ScoreLabel
and add it torootScene
by adding the code in Listing 6-4 to the//In-game Variables and Properties
section. The(8,8)
specifies the top-left corner of the label to be placed 8 pixels to the right and 8 pixels down from the top-left corner of the game. Later, we’ll update the value of theScoreLabel
with the value ofgame.score
every frame.清单 6-4。 为 rootScene 创建并添加分数标签
js scoreLabel = new ScoreLabel(8, 8); game.rootScene.addChild(scoreLabel);
ScoreLabel
类是名为ui.enchant.js
的插件的一部分,该插件包含在enchantjs.com
的下载包中。如果你早点从 code.9leap.net 接手这个项目的话,它也包括在内。如果没有,在继续之前,您需要确保行<script src='/static/enchant.js-latest/plugins/ui.enchant.js'></script>
被添加到您的index.html
文件中。 -
单击运行。
ScoreLabel
应出现在屏幕顶部,并带有单词“SCORE:
”。
如果您的代码遇到任何问题,您可以在http://code.9leap.net/codes/show/29841
找到一个完整的工作示例。
创建玩家的船
下一步是创建玩家的船,我们将创建一个类。执行以下操作来创建它:
-
Create a class definition for the player by entering the code in Listing 6-5 to the
//Class Definitions
section. For a refresher, theenchant.Sprite
declaration creates the new class as an extension of theSprite
class, which means all properties and methods of theSprite
class will work on thePlayer
class as well. Everything in theinitialize
function will be run when aPlayer
object is created.清单 6-5。 玩家类
```js // Player class var Player = enchant.Class.create(enchant.Sprite, { initialize: function(x, y){
} }); ```
-
Preload
graphic.png
, which contains all the images used in this game, by adding the code in Listing 6-6 to the//Game Properties
section. Figure 6-1 shows the graphic.清单 6-6。 预加载图像
js game.preload('graphic.png');
图 6-1 。Graphic.png
-
Define the
Player
class as a 16x16 instance of theSprite
class and specifygraphic.png
as its image by entering the code in Listing 6-7 into theinitialize
function of thePlayer
class.清单 6-7。 指定尺寸和图像
js enchant.Sprite.call(this, 16, 16); this.image = game.assets['graphic.png'];
-
Make the location of
Player
to be whatever was specified when it was created and set theframe
by adding the code in Listing 6-8 directly under what you just added.清单 6-8。 设置位置和帧
js this.x = x; this.y = y; this.frame = 0;
-
Create a variable to keep track of when the screen is being touched by adding the code in Listing 6-9 to
//Game Properties
. This will set in the touch event listeners we’ll create next, and will be used to determine if bullets should be fired from the ship later.js game.touched = false;
-
Back in the
initialize
function, add an event listener to move thePlayer
to Y position of the touch event when atouchstart
event occurs by entering the code in Listing 6-10. When thetouchstart
event occurs, we also set thegame.touched
variable totrue
. Notice how we don’t need to put a line break betweenplayer.y = e.y;
andgame.touched = true;
. The semicolons delineate the commands.清单 6-10。 添加 Touchstart 事件监听器
js game.rootScene.addEventListener('touchstart', function(e){ player.y = e.y; game.touched = true; });
-
Below that, add event listeners for both
touchend
andtouchmove
events to take care of all possible interaction from the player by adding the code in Listing 6-11.清单 6-11。 附加事件监听器
js game.rootScene.addEventListener('touchend', function(e){ player.y = e.y; game.touched = false; }); game.rootScene.addEventListener('touchmove', function(e){ player.y = e.y; });
-
Create an instance of the
Player
class and add it torootScene
by entering the code in Listing 6-12 directly below what you just added, still inside theinitialize
function.清单 6-12。 将
Player
添加到rootScene
js game.rootScene.addChild(this);
-
Create an instance of
Player
by adding the code in Listing 6-13 to the//In-game Variables and Properties
section. The instance ofPlayer
is automatically added torootScene
because of the last step.清单 6-13。 创建
Player
的实例js player = new Player(0, 152);
-
Click Run. The ship appears on the screen. If you click and hold, the ship will follow your cursor up and down on the screen.
如果您在本节中遇到问题,可以在
http://code.9leap.net/codes/show/29845
找到一个工作代码示例。
创建拍摄类
这个游戏中会出现两种弹药:玩家船射的弹药和敌人射的弹药。这两种类型使用相同的图像,所以我们将从创建一个通用的Shoot
类开始。执行以下操作来创建它:
-
Create the basic
Shoot
class by entering the code in Listing 6-14 beneath the//Player Class
definition. It should have two functions:initialize
andremove
. It should also extend theSprite
class. When an instance of theShoot
class is created, we will pass three values to it: an X coordinate, a Y coordinate, and a direction.js // Shoot class var Shoot = enchant.Class.create(enchant.Sprite, { initialize: function(x, y, direction){ }, remove: function(){ } });
-
Create the
Shoot
class as a 16x16 instance of theSprite
class, and create and assign necessary variables by entering the code in Listing 6-15 into the initialize function of theShoot
class. We will use themoveSpeed
variable next to control the speed of movement and allow for easy modification of ammunition speed later on.清单 6-15。
Shoot
类的实例变量js enchant.Sprite.call(this, 16, 16); this.image = game.assets['graphic.png']; this.x = x; this.y = y; this.frame = 1; this.direction = direction; this.moveSpeed = 10;
-
Create an
enterframe
event listener to control movement by entering the code in Listing 6-16 directly beneaththis.movespeed = 10;
. Code entered here will be run on instances ofShoot
every frame.清单 6-16。 创建一个
enterframe
事件监听器js this.addEventListener('enterframe', function(){ });
我们将在下一节中向这个事件监听器添加代码。
用 Cos 和 Sin 控制方向
我们创建了Shoot
类来接受方向,但是我们还不知道传递什么样的值来指示方向。我们也有一个事件监听器来处理运动,但是我们还不知道如何处理偏离方向的运动。我们如何做到这一点?具有功能cosine
和sine
,通常简称为cos
和sin
。
要理解这些功能,首先需要理解单位圆的方向,如图图 6-2 所示。
图 6-2 。单位圆
单位圆用弧度表示方向。一弧度等于一个圆的半径的长度,在它的圆周上排成一行。圆圈中的pi
符号是一个数学常数,等于一个圆的周长除以其直径(约 3.14)。这有什么关系?因为 cos 和 sin 只接受以弧度表示的值,而左右主方向分别等于pi
和0
(或2 * pi
)。
假设你想从圆心画一条长度为 1 的线。Cos
和sin
将分别给出该线端点的 x 和 y 坐标,根据pi
给出一个方向。例如,如果我们想向点的右边移动 1,我们可以将pi
传递给cos
来找出我们需要沿着 x 轴移动多少才能到达那里(-1)。我们还将通过pi
到sin
来找出我们需要沿着 y 轴(0)移动多少。
代码就是这样处理运动的。我们根据pi
选择一个方向,然后使用cos
和sin
来计算每一帧精灵移动多少。
-
4. Inside the event listener you just created, specify how instances of the
Shoot
class should move based offcos
andsin
by entering the code in Listing 6-17. Multiplying the results of the calculations bymoveSpeed
allows the ammunition to be manipulated in terms of speed later, if needed.清单 6-17。 用
Cos
和Sin
控制移动js this.x += this.moveSpeed * Math.cos(this.direction); this.y += this.moveSpeed * Math.sin(this.direction);
-
5. Designate instances of the
Shoot
class to call theremove
function if the shots stray far outside the bounds of the game screen by entering the code in Listing 6-18 inside theenterframe
event listener, below what you just added. We could use 0 for the minimum allowed values of X and Y before theremove
function is called, but using–this.width
and–this.height
ensures the shots don’t disappear off the screen unnaturally. Do not worry about defining what theremove
function does just yet.清单 6-18。 调用
remove
函数js if(this.y > 320 || this.x > 320 || this.x < -this.width || this.y < -this.height){ this.remove(); }
-
6. Inside the definition of the
remove
function, under the definition of theinitialize
function, specify what should happen when theremove
function is called by entering the code in Listing 6-19. Thedelete
command removes a given instance of theShoot
class from memory. In a very long game, if this is not specified it could bog down the system.清单 6-19。
remove
功能js game.rootScene.removeChild(this); delete this;
-
7. Add instances of the
Shoot
class torootScene
on creation by adding the code in Listing 6-20 to theinitialize
function, after the event listener.清单 6-20。 将
Shoot
添加到rootScene
js game.rootScene.addChild(this);
创建 PlayerShoot 类并让船射击
我们创建了通用的Shoot
类,但是现在我们需要为玩家的船发射的弹药创建一个类,并在船发射时创建它的实例。执行以下操作来创建该类:
-
In the
//Class Definitions
section, create thePlayerShoot
class and itsinitialize
function by entering the code in Listing 6-21.清单 6-21。 创建 PlayerShoot 类
js // PlayerShoot class var PlayerShoot = enchant.Class.create(Shoot, { // Succeeds bullet class initialize: function(x, y){ } });
-
Create the
PlayerShoot
class as an instance of theShoot
class, specifying 0 as the direction, by inserting the code in Listing 6-22 into theinitialize
function. Remember the unit circle? The value of 0 is equal to a direction facing the right side of the screen. Because the ship is on the left side of the screen, bullets fired will head toward the right.清单 6-22。 创建
Shoot
类的实例js Shoot.call(this, x, y, 0);
-
We now need make the ship fire instances of the
PlayerShoot
class. Go back to thePlayer
class definition and create anenterframe
event listener inside theinitialize
function by entering the code in Listing 6-23 right above the linegame.rootScene.addChild(this);
.清单 6-23。 Enterframe 事件监听器
js this.addEventListener('enterframe', function(){ });
-
Inside this event listener, add the code in Listing 6-24 to create an
if
statement to be executed once every three frames and to be executed if the game is being touched.清单 6-24。 If 语句控制出手
js if(game.touched && game.frame % 3 === 0){ }
-
Inside the
if
statement, create a new instance of thePlayerShoot
class by entering the code in Listing 6-25.清单 6-25。 创建一个
PlayerShoot
实例js var s = new PlayerShoot(this.x, this.y);
-
单击运行。当你点击屏幕时,你的船会发射弹药穿过屏幕。
如果您在本节中遇到问题,您可以在http://code.9leap.net/codes/show/29907
找到一个完整的工作代码示例。
创造敌人
我们的船现在可以发射子弹,但现在我们需要为船创造一些可以射击的东西。执行以下操作为敌人创建一个职业并将他们添加到游戏中:
-
Create the basic
Enemy
class definition with aninitialize
andremove
function by adding the code in Listing 6-26 to the//Class Definitions
section, below thePlayer
class definition.清单 6-26。 基础
Enemy
类js //Enemy class var Enemy = enchant.Class.create(enchant.Sprite, { initialize: function(x, y){ }, remove: function(){ } });
-
Make the
Enemy
class a 16x16 instance of theSprite
class and assign theframe
,x
, andy
variables by entering Listing 6-27 into theinitialize
function.清单 6-27。 制作 Sprite 实例并赋值变量
js enchant.Sprite.call(this, 16, 16); this.image = game.assets['graphic.png']; this.x = x; this.y = y; this.frame = 3;
-
Specify the direction of movement for enemies to be to the left in terms of the unit circle (
Math.PI
), and create a variable for movement speed by entering the code in Listing 6-28 below what you just entered.清单 6-28。 为方向和运动创建变量
js this.direction = 0; this.moveSpeed = 3;
-
Under what you just entered, create an event listener to move the enemy using the variables you just created by entering the code in Listing 6-29.
清单 6-29。 移动敌人
js // Define enemy movement this.addEventListener('enterframe', function(){ this.x -= this.moveSpeed * Math.cos(this.direction); this.y += this.moveSpeed * Math.sin(this.direction); });
-
Make the
Enemy
call theremove
function (which we’ll define soon) if it is outside the dimensions of the screen by entering the code in Listing 6-30 directly under the linethis.y += this.moveSpeed * Math.sin(this.direction);
.清单 6-30。 除去屏幕外的敌人若
js // Disappear when outside of screen if(this.y > 320 || this.x > 320 || this.x < -this.width || this.y < -this.height){ this.remove(); }
-
Finally, have the
Enemy
add itself torootScene
when it is created by entering the code in Listing 6-31 into theinitialize
function, under the event listener you just added.清单 6-31。 给 rootScene 添加敌人
js game.rootScene.addChild(this);
-
Define the
remove
function by entering Listing 6-32 into theremove
function. This will remove the enemy fromrootScene
and delete it from memory. Also, it will remove the enemy from an array we’re going to create to keep track of enemies. (Hence thedelete enemies[this.key];
, which will be explained soon.)清单 6-32。 清除功能
js game.rootScene.removeChild(this); delete enemies[this.key]; delete this;
-
In the
//In-Game Variables and Properties
section, create an array to keep track of enemies by entering the code in Listing 6-33.清单 6-33。 制造敌阵
js enemies = [];
-
Under the new array, create an
enterframe
event listener for the game to create enemies randomly by entering the code in Listing 6-34. We create a variable inside theEnemy
calledenemy.key
and assign the game’s current frame to it because this gives us something to keep track of the enemies with. If we do not do this, we would not be able to reference a specific enemy later on, which is needed when the enemies stray off screen or are hit with ammunition from the ship. Enemies have approximately a 1 in 10 chance of being created because ofif(Math.random()*100 < 10)
and, if created, are placed randomly on the y-axis.清单 6-34。 在游戏中制造敌人
js game.rootScene.addEventListener('enterframe', function(){ if(Math.random()*100 < 10){ var y = Math.random() * 320; var enemy = new Enemy(320, y); enemy.key = game.frame; enemies[game.frame] = enemy; } });
-
Update the game’s
scoreLabel
every frame by entering the code in Listing 6-35 underneath theif
statement, but still inside the event listener.清单 6-35。 每帧更新游戏分数
js scoreLabel.score = game.score;
-
点击运行。敌人被创造出来并飞过屏幕。然而,当船上的子弹击中他们时,什么也没有发生。
-
Make the ship’s ammunition destroy enemies with an event listener by entering the code in Listing 6-36 into the
PlayerShoot
class definition, underShoot.call(this, x, y, 0);
. The way thisfor
loop is constructed causes the program to go through every single member of the enemies array, checking to see if the given bullet is in contact with it. If so, both the bullet and the enemy in the array are removed, and the player’s score is increased by 100.清单 6-36。 制伏敌人
js this.addEventListener('enterframe', function(){ // Judges whether or not player's bullets have hit enemy for(var i in enemies){ if(enemies[i].intersect(this)){ // Eliminates enemy if hit this.remove(); enemies[i].remove(); //Adds to score game.score += 100; } } });
-
单击运行。你现在可以通过射击来消灭敌人。如果您在本节中遇到问题,可以在
http://code.9leap.net/codes/show/29929
找到一个工作代码示例。
创建敌人射击类并让敌人射击
在这一点上,没有办法输掉比赛,这不是很引人注目。让我们创建一个敌人弹药的职业,让敌人向飞船开火,通过以下方式增加难度:
-
Create the
enemyShoot
class by adding the code in Listing 6-37 in the//Class Definitions
section. Create it as an instance of theShoot
class, with the direction set asMath.PI
, as that faces left in the unit circle.清单 6-37。 排敌班
js // Class for enemy bullets var EnemyShoot = enchant.Class.create(Shoot, { // Succeeds bullet class initialize: function(x, y){ Shoot.call(this, x, y, Math.PI); } });
-
Add an
enterframe
event listener inside theinitialize
function by adding the code in Listing 6-38 under the line that begins withShoot.call
. This event listener should contain anif
statement specifying that the game should end if the center ofplayer
and the center of a given enemy bullet is 8 pixels or less at any given time.清单 6-38。 指定
playerShoot
和player
之间的点击次数js this.addEventListener('enterframe', function(){ if(player.within(this, 8)){ game.end(game.score, "SCORE: " + game.score); } });
-
Make the
Enemy
class create instances of theenemyShoot
class every 10 frames by changing theif
statement inside theEnemy enterframe
event listener to match what is shown in Listing 6-39. The variable age can be called on anyEntity
and gives the number of frames theEntity
has been alive.清单 6-39。 制敌射击
js // Disappear when outside of screen if(this.y > 320 || this.x > 320 || this.x < -this.width || this.y < -this.height){ this.remove(); }else if(this.age % 10 === 0){ // Fire every 10 frames var s = new EnemyShoot(this.x, this.y); }
-
点击运行。敌人现在开始反击。
如果您在本节中遇到问题,可以在http://code.9leap.net/codes/show/30105
找到一个工作代码示例。
使敌人沿弧线移动
敌人现在沿直线移动。这使得玩游戏相当直接,但有点简单。让我们让敌人以弧线移动,让游戏更有趣。我们将通过创建一个变量来指定方向应该向上运动还是向下运动(theta ),这取决于敌人在哪里被创建,然后在每一帧稍微改变方向。为此,请执行以下操作:
-
Each enemy needs to have a variable that will be used to change its direction. Create a variable (
theta
) that is passed as an argument when anEnemy
is created by changing the opening line of theinitialize
function to match the code in Listing 6-40.清单 6-40。 添加了
theta
变量js initialize: function(x, y, theta){
-
Although direction is specified in terms of the unit circle, it’s easier to work in degrees for specific amounts other than
0
orMath.PI
. We’ll be passing a value in degrees totheta
, so convert it to radians by entering the code in Listing 6-41 right before the line that readsthis.direction = 0;
.清单 6-41。 将
theta
转换成弧度js this.theta = theta * Math.PI / 180;
-
Inside the
enterframe
event listener of theEnemy
class, maketheta
change the direction of theEnemy
every frame by entering the code in Listing 6-42 directly under the line that readsthis.addEventListener('enterframe', function(){
.清单 6-42。 递增
Enemy
方向js this.direction += this.theta;
-
Now enemies can accept a value for
theta
, and this will change the direction of theEnemy
every frame, but we need to specify how the enemies are created to really use this. To accomplish this, change theif
statement inside the game’srootScene enterframe
event listener to match the code in Listing 6-43. If theEnemy
is created in the upper half of the screen, the enemy will arc upwards (direction angle will increase by 1 each frame). If it is created in the lower half of the screen, it will arc downwards (direction angle will decrease by 1 each frame).清单 6-43。 制造敌人那道弧线
js if(rand(100) < 10){ // Make enemies appear randomly var y = rand(320); if (y < 160) { theta = 1; } else { theta = -1; } var enemy = new Enemy(320, y, theta); enemy.key = game.frame; enemies[game.frame] = enemy; }
-
Click Run. Enemies move in an arc, making the game more compelling. Your game should appear as it does in Figure 6-3.
图 6-3 。简单的射击游戏
如果您在本节中遇到问题,可以在http://code.9leap.net/codes/show/30564
找到一个工作代码示例。
强化游戏
目前,我们的游戏在目前的状态下是一个工作游戏。我们可以把它留在这里,转到另一个游戏,但让我们研究一些方法,通过添加一些功能来使游戏更引人注目。
我们将对原来的游戏做一些补充,并在接下来的部分解释如何做。
爆炸
先来加点爆款。目前,当敌舰被击落时,它们就消失了。让我们通过在敌舰被击中时引起爆炸来增加一点刺激。
-
Download the explosion (Figure 6-4) sprite sheet from
http://enchantjs.com/assets/img/effect0.gif
and add it to your project. We’ll use this sprite sheet for our explosion.图 6-4 。爆炸效果图
-
Create a basic class, called
Blast
, as an extension of theSprite
class by adding the code in Listing 6-44 into the//Class Definitions
section. The class should have aninitialize
andremove
function.清单 6-44。 爆炸类
js // Class for explosions var Blast = enchant.Class.create(enchant.Sprite, { initialize: function(x, y){ }, remove: function(){ } });
-
Inside the
initialize
function, create theBlast
class as a 16x16 instance of theSprite
class, and pass thex
andy
arguments to the local variablesx
andy
by entering the code in Listing 6-45.清单 6-45。 在爆炸中分配变量
js enchant.Sprite.call(this,16,16); this.x = x; this.y = y;
-
Jump down to the
window.onload
function and add a preload statement undergame.preload('graphic.png');
to add the sprite sheet of the explosion by entering the code in Listing 6-46.清单 6-46。 预载爆炸图像
js game.preload('effect0.gif');
-
Back in the
initialize
definition of theBlast
class, underthis.y = y;
, add a statement to useeffect0.gif
as the image for the explosion by adding the code in Listing 6-47.清单 6-47。 指定
effect0.gif
为Blast
图像js this.image = game.assets['effect0.gif'];
-
Specify the
frame
to start at 0, and specify a duration of 20 frames by entering Listing 6-48 on the next line. We will use the duration soon to draw out the animation over a specific amount of time.清单 6-48。 指定起始帧和持续时间
js this.frame = 0; this.duration = 20;
-
Below that, create an
enterframe
event listener by entering the code in Listing 6-49. We’ll use this event listener to control theframe
of the explosion.清单 6-49。 事件监听器为
Blast
js this.addEventListener('enterframe', function(){ }
-
Inside the event listener, create a statement to set the
frame
of the explosion to go from 0 to 4 over a period of 20 frames by entering the code in Listing 6-50. This is accomplished with an algorithm. This algorithm first takes the current number of frames the explosion has been alive for (this.age
) and divides it by the desired duration of the animation (this.duration
) to get a fraction representing how far through the animation sequence the explosion should be. This is multiplied by 5 because the total sequence, shown in the sprite sheet, is 5 frames long. At this point, the result most likely has a decimal value (such as 4.42), so it is rounded down withMath.floor
, and the result is what is assigned to the current frame of the explosion. The result is that the explosion progresses smoothly over 20 frames, and this value can be changed simply by editing the value ofduration
from 20 to something else.清单 6-50。 给
frame
赋值js // Explosion animation this.frame = Math.floor(this.age/this.duration * 5);
-
Beneath that, but still in the event listener, enter the code in Listing 6-51 to create an
if
statement that calls theremove
function if the explosion has been alive for the desired duration. Note that if there is only one statement after theif
statement, curly braces ({}
) are not required.清单 6-51。 调用
remove
函数js if(this.age == this.duration) this.remove();
-
Under the
if
statement, outside of the event listener, but still inside theinitialize
function, add the blast torootScene
by entering the code in Listing 6-52.清单 6-52。 给
rootScene
添加冲击波js game.rootScene.addChild(this);
-
Make the
remove
function remove the blast fromrootScene
by entering the code in Listing 6-53 into the definition of theremove
function.清单 6-53。 清除根景爆炸
js game.rootScene.removeChild(this);
-
Make
playerShoot
create an instance of theBlast
class if it hits an enemy by rewriting the definition ofplayerShoot
to match the code in Listing 6-54. The line you should add is in bold type.清单 6-54。
playerShoot
创建Blast
的实例js // PlayerShoot class var PlayerShoot = enchant.Class.create(Shoot, { // Succeeds bullet class initialize: function(x, y){ Shoot.call(this, x, y, 0); this.addEventListener('enterframe', function(){ // Judges whether or not player's bullets have hit enemy for(var i in enemies){ if(enemies[i].intersect(this)){ //Start Explosion var blast = new Blast(enemies[i].x,enemies[i].y); // Eliminates enemy if hit this.remove(); enemies[i].remove(); //Adds to score game.score += 100; } } }); } });
-
单击运行。尝试玩游戏,看看当敌人被玩家船上的子弹击中时,爆炸是如何出现的。如果你做的一切都正确,游戏应该会出现在图 6-5 中。
图 6-5 。具有爆炸功能的街机射击游戏
如果您在本节中遇到问题,您可以在http://code.9leap.net/codes/show/30633
找到一个完整的工作代码示例。
添加滚动背景
现在,我们已经加入了爆炸,事情开始看起来有点好,但我们仍然有一段路要走。为了增加准现实主义的元素,让我们为我们的游戏创建一个滚动背景。
首先,我们需要一个正好是屏幕两倍宽的背景图像。图像的左右两边应该完全一样,最左边或最右边都没有星星。我们一会儿就知道为什么了。
我们一次向左滚动一个像素,当我们到达终点时,我们循环回到起点,在整个游戏中不断重复这个过程。这就是为什么我们希望背景图片的左右两边是一样的。如果玩家注意到背景中的突然变化,看起来会很不自然。我们将使用图 6-6 中所示的图像。
图 6-6 。png 图形
通过创建Background
类并加入这个循环动作,我们创造了一个无休止滚动的背景效果。执行以下操作来创建Background
类并实现它:
-
14. In the
//Class Definitions
section, create a class for the background as an extension of theSprite
class by entering the code in Listing 6-55. The only method needed is theinitialize
method, as we never remove it fromrootScene
.清单 6-55。
Background
类js // Background class var Background = enchant.Class.create(enchant.Sprite, { initialize: function(){ } });
-
15. Inside the
initialize
function, createBackground
as a 640x320Sprite
by entering in Listing 6-56. This dimension is just as tall and twice as wide as the game screen.清单 6-56。 创建为精灵的实例
js enchant.Sprite.call(this,640,320);
-
16.转到
http://enchantjs.com/?p=731
并下载图像文件bg.png
,用作背景。 - 17.将文件上传到您在
code.9leap.net
中的项目。 -
18. Inside the
window.onload
function, under the//Game Properties section
, add Listing 6-57 to preloadbg.png
.清单 6-57。 预加载背景图像
js game.preload('bg.png');
-
19. Back inside the
initialize
function of theBackground
class, add Listing 6-58 to the variable declarations to position the background and assignbg.png
to be used as the background image.清单 6-58。 定位和图像变量
js this.x = 0; this.y = 0; this.image = game.assets['bg.png'];
-
20. Below that, but still inside the initialize function, add an
enterframe
event listener to move the background to the left by one pixel every frame by entering in the code in Listing 6-59.清单 6-59。 移动
Background
每一帧js this.addEventListener('enterframe', function(){ this.x--; });
-
21. Inside the event listener, underneath
this.x--;
, write a statement to reset the position ofBackground
if it has been scrolled all the way over by entering the code in Listing 6-60. We know if the image has been scrolled all the way over if its x position is-320
or less.清单 6-60。 重置背景位置
js if(this.x<=-320) this.x=0;
-
22. Under the event listener, but still inside the
initialize
function, addBackground
torootScene
by entering the code in Listing 6-61.清单 6-61。 给 rootScene 添加背景
js game.rootScene.addChild(this);
-
23. Finally, in the
game.onload
function, replace the line that readsgame.rootScene.backgroundColor = 'black';
with the code in Listing 6-62 to create an instance ofBackground
.清单 6-62。 创建
Background
的实例js background = new Background();
-
24.单击运行。游戏应该出现在图 6-7 中。
图 6-7 。背景滚动的街机射击游戏
如果您在本节中遇到问题,可以在http://code.9leap.net/codes/show/30704
找到一个工作代码示例。
添加生命量表
目前,玩家只需一击就会死亡。如果他们在至少三次击中后倒下,似乎会公平得多。考虑到这一点,让我们添加一些东西,让玩家有多条命,并允许他们在死亡前承受几次打击。为此,请执行以下操作:
-
In the
game.onload
function, under the//In-Game Variables and Properties
section, add a variable as part of theCore
object (game
) to keep track of a player’s life by entering the code in Listing 6-63.清单 6-63。 创建一个生命变量
js game.life = 3;
我们现在已经初始化了一个代表玩家剩余生命数的变量。因为我们在
game.onload
内部这样做,并使用game.life
作为变量名(使用game.
前缀使其成为这里称为game
的Core
对象的一部分),所以它可以在游戏内部的任何地方被引用。 -
Rewrite the
EnemyShoot
class to reduce the amount of life the player has by 1 if hit by an enemy bullet by making the class match the code in Listing 6-64. You should erase the line that readsgame.end(game.score, "SCORE: " + game.score);
and replace it with the code in bold type.清单 6-64。 命中时减少生命
js // Class for enemy bullets var EnemyShoot = enchant.Class.create(Shoot, { // Succeeds bullet class initialize: function(x, y){ Shoot.call(this, x, y, Math.PI); this.addEventListener('enterframe', function(){ if(player.within(this, 8)){ // Bullet has hit player game.life--; } }); } });
-
Under
game.life--;
, but still inside theif
statement, write anotherif
statement to end the game if the player’s life is less than or equal to 0 by entering the code in Listing 6-65.清单 6-65。 结束游戏如果没有生命
js if(game.life<=0) game.end(game.score, "SCORE: " + game.score);
我们已经重写了游戏,当玩家被击中时生命值减少 1,如果生命值为 0,游戏就结束了。我们可以创建一个类来显示生活,但是不需要在屏幕上添加太多内容,所以在这种情况下,简单地在
game.onload
中创建指示器会更有效。我们将使用作为
ui.enchant.js
插件的一部分提供的MutableText
类来制作指示器。MutableText
类与Label
类非常相似,但是它不使用计算机中已经安装的字体在屏幕上创建文本,而是使用 sprite 表中文本字符的图像,很像Sprite
。注意
ScoreLabel
类是MutableText
类的扩展,行为类似。如果我们在这里使用常规的
Label
类,结果看起来会很便宜,因为将使用系统字体。通过使用MutableText
,你可以使用更好看的字体,并且因为使用图像而不是系统字体,你可以确保字母看起来总是一样的,不管游戏运行在什么样的浏览器或操作系统上。 -
Within the
game.onload
function, below the line that readsplayer = new Player(0, 152);
, create a new instance ofMutableText
by entering the code in bold type from Listing 6-66. The first argument (8
), specifies the X position of the upper-left corner of the new instance, the second argument (320 – 32
) specifies the Y position, the third (game.width
) specifies the width of the label, and the fourth specifies the text that should be shown.清单 6-66。 创造了
lifeLabel
```js game.onload = function() { //In-Game Variables and Properties background = new Background(); game.life = 3;
scoreLabel = new ScoreLabel(8, 8); game.rootScene.addChild(scoreLabel); player = new Player(0, 152); enemies = []; // Display life lifeLabel = new MutableText(8, 320 - 32, game.width, ""); ```
-
Beneath the line that reads
lifeLabel = new MutableText(8, 320 – 32, game.width, "");
, but still inside thegame.onload
function, create an event listener to update the text oflifeLabel
with a number of 0s equal to however many lives the player has left by entering the code in Listing 6-67.清单 6-67。 显示生命数量
js lifeLabel.addEventListener('enterframe', function(){ this.text = "LIFE " + "OOOOOOOOO".substring(0, game.life); });
因为我们在一个
enterframe
事件侦听器中定义了lifeLabel
的文本,所以生命量表将在每一帧中更新。我们来看看
"OOOOOOOOO".substring(0, game.life);
部分。这个“O”代表游戏中的单身生活。"OOOOOOOOO"
是一个 JavaScript 字符串对象,所以我们可以直接调用应用于该字符串的方法。我们可以通过使用substring()
方法从一个字符串的开头到指定的点提取文本。应该注意的是,
substring()
方法的第二个参数表示子字符串应该从中提取字符的点。请记住,字符串和数组中的位置引用以 0 开头。这意味着如果game.life
等于 3,子串用"OOOOOOOOO".substring(0, game.life);
处理,子串将在位置 0、位置 1 和位置 2 返回 0,但在到达第三个位置之前会停止。这也是为什么游戏以三条命开头,在游戏中用三个 0 来代表(
"LIFE 000"
)。随着生命的减少,它会显示"LIFE OO"
然后是"LIFE O."
正如你所看到的,enchant.js 允许你轻松地创建游戏指示器,而不必创建一个全新的类。
-
Finally, under the event listener, add the
lifeLabel
torootScene
by entering in the code in Listing 6-68.清单 6-68。 给 rootScene 添加 life label
js game.rootScene.addChild(lifeLabel);
-
单击运行。寿命计将出现在图 6-8 的中。
图 6-8 。带生命计的街机射击游戏
如果您在本节中遇到问题,您可以在http://code.9leap.net/codes/show/30801
找到一个完整的工作代码示例。
结论
希望你能看到最终拍摄程序的总体流程与我们在本章前面看到的简单原型没有太大的不同。事实上,Background
类和许多其他类与我们在本章开始时检查的原型完全相同。
正如我们看到的第五章一样,在重击机器人的例子中,通过一点一点地重写和改进原型,一个成熟而复杂的游戏可以被开发出来。
在这一章中,我们探索了射击游戏的开发,看看如何为子弹、敌人和玩家创建类,以及如何处理子弹和敌人或玩家飞船之间的碰撞检测。然后,我们研究了可以用来改进基本游戏的更高级的概念,包括爆炸、生命标尺等等。
在下一章,我们将看看如何在code.9leap.net
之外创建独立游戏,并研究如何使用 3D 来创建带有gl.enchant.js
插件的游戏。`
版权属于:月萌API www.moonapi.com,转载请注明出处