04-渲染其他游戏对象
9.1.2 渲染其他游戏对象
使用与渲染玩家飞船类似的方法对陨石、飞碟、导弹和碎片进行渲染。先来看飞碟渲染函数的代码。
1.渲染飞碟
飞碟的图片表中并没有包含多个图片,但是为了保持一致,在渲染时会假设它有多个图片。这样,以后会为飞碟增加更多的动画图片。
function renderSaucers(){
var tempSaucer = {};
var saucerLength = saucers.length-1;
for (var saucerCtr=saucerLength;saucerCtr>=0;saucerCtr--){
//ConsoleLog.log("saucer: " + saucerCtr);
tempSaucer = saucers[saucerCtr];
context.save(); //将当前状态保存到栈中
var sourceX = 0;
var sourceY = 0;
context.drawImage(saucerTiles, sourceX, sourceY, 30, 15,
tempSaucer.x,tempSaucer.y,30,15);
context.restore(); //将原有状态恢复到屏幕
}
}
对于飞碟来说,没有必要实际计算sourceX和sourceY的值,因为飞碟只有一张图片。在这个例子中,可以直接设置为0。例如,用硬编码代替saucer.width(30)和saucer.height(15)。但是对于游戏中其余的对象,将使用对象的width和height属性,而不用数值。
接下来,看看陨石的渲染。这个过程与玩家飞船和飞碟的渲染过程有些不同。
2.渲染陨石
陨石的图片表根据陨石的体积(大、中、小)分割成3个独立的图片表。对于每个陨石,都只是用了5张图片。因为陨石是正方形的对称图案,所以对于3种大小的陨石,只需要预先创建四分之一的旋转效果就可以了。
以下是renderRocks()函数。注意一点,必须根据陨石的体积(1=大型,2=中型,3=小型)进行切换,以选择合适的图片进行渲染。
function renderRocks(){
var tempRock = {};
var rocksLength = rocks.length-1;
for (var rockCtr=rocksLength;rockCtr>=0;rockCtr--){
context.save(); //将当前状态保存到栈中
tempRock = rocks[rockCtr];
var sourceX = Math.floor((tempRock.rotation)% 5)* tempRock.width;
var sourceY = Math.floor((tempRock.rotation)/5)*tempRock.height;
switch(tempRock.scale){
case 1:
context.drawImage(largeRockTiles, sourceX, sourceY,
tempRock.width,tempRock.height,tempRock.x,tempRock.y,
tempRock.width,tempRock.height);
break;
case 2:
context.drawImage(mediumRockTiles, sourceX,
sourceY,tempRock.width,tempRock.height,tempRock.x,tempRock.y,
tempRock.width,tempRock.height);
break;
case 3:
context.drawImage(smallRockTiles, sourceX,
sourceY,tempRock.width,tempRock.height,tempRock.x,tempRock.y,
tempRock.width,tempRock.height);
break;
}
context.restore(); //将原有的状态恢复到屏幕
}
}
在renderRocks()函数中,没有像Geo Blaster Basic中那样使用rock.rotation属性作为旋转角度。取而代之的是,将rotation属性改为表示在图片表中要渲染的当前图片的编号(0~4)。
在第8章的版本中,用户可以通过为每个陨石设置一个随机的rotationInc值来模拟陨石不同的旋转速度(或快或慢)的效果。这个值为负时,表示逆时针;为正时,表示顺时针。并且,该值会在每帧中叠加到rotation属性上。在新的使用图片表的版本中,只使用5帧的动画,因此不能试图跳过某帧(这样会使动画看起来不流畅)。作为替代方案,为每个陨石添加两个新的属性:animationCount和animationDelay。
animationDelay属性表示在图片变化时对于给定的陨石间隔的帧数。animationCount变量将在每次图片变化后设置为0,并在随后的每一帧中加1。当animationCount大于animationDelay时,rock.rotation的值才会增加(顺时针)或减少(逆时针)。以下是updateRocks()函数的新代码。
tempRock.animationCount++;
if (tempRock.animationCount > tempRock.animationDelay){
tempRock.animationCount = 0;
tempRock.rotation += tempRock.rotationInc; if (tempRock.rotation > 4){
tempRock.rotation = 0;
}else if (tempRock.rotation <0){
tempRock.rotation = 4;
}
}
请注意,在检查图片编号最大值和最小值时,使用硬编码4和0。另外,也可以在这里使用一个常量或者两个变量。
3.渲染导弹
玩家导弹和飞碟导弹使用的渲染方式是相同的。对于每个导弹,用户仅需要知道图片编号就可以了。在particleTiles图像中有4幅图片,编号代表了要显示的图片。玩家导弹的图片编号是1,飞碟导弹图片编号是0。
下面快速浏览一下这两个函数。
function renderPlayerMissiles(){
var tempPlayerMissile = {};
var playerMissileLength = playerMissiles.length-1;
//ConsoleLog.log("render playerMissileLength=" + playerMissileLength);
for (var playerMissileCtr=playerMissileLength; playerMissileCtr>=0;
playerMissileCtr--){
//ConsoleLog.log("draw player missile " + playerMissileCtr)
tempPlayerMissile = playerMissiles[playerMissileCtr];
context.save(); //将当前状态保存到栈中
var sourceX = Math.floor(1 % 4)* tempPlayerMissile.width;
var sourceY = Math.floor(1 / 4)* tempPlayerMissile.height;
context.drawImage(particleTiles, sourceX, sourceY,
tempPlayerMissile.width,tempPlayerMissile.height,
tempPlayerMissile.x,tempPlayerMissile.y,tempPlayerMissile.width,
tempPlayerMissile.height);
context.restore(); //将原有的状态恢复到屏幕
}
}
function renderSaucerMissiles(){
var tempSaucerMissile = {};
var saucerMissileLength = saucerMissiles.length-1;
//ConsoleLog.log("saucerMissiles= " + saucerMissiles.length)
for (var saucerMissileCtr=saucerMissileLength;
saucerMissileCtr >= 0;saucerMissileCtr--){
//ConsoleLog.log("draw player missile " + playerMissileCtr)
tempSaucerMissile = saucerMissiles[saucerMissileCtr];
context.save(); //将当前状态保存到栈中
var sourceX = Math.floor(0 % 4)* tempSaucerMissile.width;
var sourceY = Math.floor(0 / 4)* tempSaucerMissile.height;
context.drawImage(particleTiles, sourceX, sourceY,
tempSaucerMissile.width,tempSaucerMissile.height,
tempSaucerMissile.x,tempSaucerMissile.y,tempSaucerMissile.width,
tempSaucerMissile.height);
context.restore(); //将原有的状态恢复到屏幕
}
}
使用位图图片表渲染爆炸碎片,它的代码与导弹的代码非常相似,本书会在下一节详细介绍。
4.渲染碎片
碎片使用包含4张图片的parts.png图像渲染(见图9-8),与渲染导弹时相同。第8章的Geo Blaster Basic游戏使用了单独的白色碎片表示所有爆炸效果。本章将替换之前游戏中的createExplode函数,在新函数中将用不同的颜色表示每一种爆炸。这样,陨石、飞碟和玩家飞船都可以不同颜色的爆炸效果。
新的createExplode()函数中将在参数列表中添加一个名为type的参数,以下是函数代码。
function createExplode(x,y,num,type){
playSound(SOUND_EXPLODE,.5);
for (var partCtr=0;partCtr<num;partCtr++){
if (particlePool.length > 0){
newParticle = particlePool.pop();
newParticle.dx = Math.random()*3;
if (Math.random()<.5){
newParticle.dx *= -1;
}
newParticle.dy = Math.random()*3;
if (Math.random()<.5){
newParticle.dy *= -1;
}
newParticle.life = Math.floor(Math.random()*30+30);
newParticle.lifeCtr = 0;
newParticle.x = x;
newParticle.width = 2;
newParticle.height = 2;
newParticle.y = y;
newParticle.type = type;
//ConsoleLog.log("newParticle.life=" + newParticle.life);
particles.push(newParticle);
}
}
}
由于particle对象是在crateExplode()函数中创建的,因此要为其增加type属性。当在checkCollisions()函数中触发一个爆炸效果时,调用createExplode()函数时会根据被摧毁的对象传递type值。每个陨石都已经有了scale参数,并用数值1~3表示不同的体积。将使用这些值作为陨石的type值传入。现在,只需要设置玩家飞船和飞碟的type值:飞碟的type值使用0,玩家飞船的type值使用4。将这些编号的值抽取出来。也可以使用99表示飞碟,200表示玩家飞船。之所以不能使用1~3,仅仅是因为已将它们用于陨石而已。type的分类表如下所示:
- Saucer: type=0;
- Large rock: type=1;
- Medium rock: type=2;
- Small rock: type=3;
- Player: type=4。
在renderParticles()函数中,需要使用switch语句判断type的值,以决定应该使用4张图片中的哪一张渲染给定的碎片。以下是函数代码。
function renderParticles(){
var tempParticle = {};
var particleLength = particles.length-1;
for (var particleCtr=particleLength;particleCtr>=0;particleCtr--){
tempParticle = particles[particleCtr];
context.save(); //将当前状态保存到栈中
var tile;
console.log("part type=" + tempParticle.type)
switch(tempParticle.type){
case 0: // 飞碟
tile = 0;
break;
case 1: //大型陨石
tile = 2
break;
case 2: //中型陨石
tile = 3;
break;
case 3: //小型陨石
tile = 0;
break;
case 4: //玩家
tile = 1;
break;
}
var sourceX = Math.floor(tile % 4)* tempParticle.width;
var sourceY = Math.floor(tile / 4)* tempParticle.height;
context.drawImage(particleTiles, sourceX, sourceY,
tempParticle.width, tempParticle.height, tempParticle.x,
tempParticle.y,tempParticle.width,tempParticle.height);
context.restore(); //将原有的状态恢复到屏幕
}
在checkCollisions()函数中,将type参数传递给createExplode()函数。这样,type可以被复制给爆炸的碎片。以下是在createExplode()函数中处理陨石对象的调用代码。
createExplode(tempRock.x+tempRock.halfWidth,tempRock.y+tempRock.halfHeight,
10,tempRock.scale);
之所以使用tempRock.scale作为最终参数,是因为使用了陨石的体积作为type值。
飞碟对象的调用代码如下。
createExplode(tempSaucer.x+tempSaucer.halfWidth,
tempSaucer.y+tempSaucer.halfHeight,10,0);
对于飞碟和玩家飞船,将向createExplode()函数中传递数字值。在飞碟的例子中传递0,在玩家飞船的例子中传递4。
createExplode(player.x+player.halfWidth, player.y+player.halfWidth,50,4);
对于玩家飞船,在 playerDie()函数中调用 createExplode()函数。playerDie()函数是在checkCollisions()中被调用的。
提示
在讨论完添加声音和碎片对象池之后,本书将展示用于替换 Geo Blaster Basic的完整的源代码(例A-2)。此处没有必要展示对每个函数进行的修改。