当前位置:嗨网首页>书籍在线阅读

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)。此处没有必要展示对每个函数进行的修改。