このページの本文へ

enchant.jsで懐かしのインベーダーゲームを作ろう (4/4)

2012年08月29日 11時00分更新

文●古籏一浩

  • この記事をはてなブックマークに追加
本文印刷

爆発の処理と敵のアニメーション

 爆発の処理と敵のアニメーション処理を追加します。enchant.jsではスプライト(キャラクター)をアニメーションさせる場合、CSSスプライトと同じように、1つの画像ファイルに複数のキャラクターをまとめて用意しておきます。

 爆風と敵の移動時のアニメーションをするため、以下のように画像を用意しました。横一列になっていますが、特に横一列でなくても縦横にキャラクターを用意しておいても大丈夫です。

爆風のアニメーションと敵のアニメーション画像を用意した

爆風のアニメーションと敵のアニメーション画像を用意した

 enchant.jsでは、表示するキャラクターの番号をスプライトのframeプロパティに入れると、対応する画像が表示されます。

 まず、敵のアニメーション処理です。アニメーション用のカウンタとしてtickプロパティを用意し、敵を生成する時に初期化しておきます。

enemy[count].tick = 0;  // ★アニメーション用カウンタを初期化

 次に、敵が左右に移動した時にtickプロパティに1を加算します。このtickプロパティの値を元にしてアニメーションをします。敵のアニメーションは2枚しかないのでframeプロパティの値は0か1になります。このためtickプロパティが奇数の時と偶数の時で0と1を切り替えられますが、実際にやってみると猛烈な速さでアニメーションが切り替わります。

 そこでtickプロパティの値を16で除算した結果を入れるようにします。ただし、16で除算すると言っても単純に16で割るのではなく、3回右にビットシフトします(>>>4とします)。数値は1回右にビットシフトすると1/2になります。2回なら1/4、3回なら1/8、4回なら1/16となります。この結果をframeプロパティに入れれば、ゆっくりとしたアニメーションになります。

enemy[i].tick = enemy[i].tick + 1;
enemy[i].frame = enemy[i].tick >>> 4; // ★敵をアニメーションさせる

 続いて、爆発の処理です。爆発は敵を倒した時に発生します。爆発は以下のようにして指定した座標に表示するようにします。自機のビームが1つしかでないので、爆発は同時に1つしか表示されません。

 爆発の開始処理は以下のようになります。

// ■爆発開始処理
function startBlast(sx, sy){
 blast.flag = true// 爆風発生
 blast.x = sx;
 blast.y = sy;
 blast.frame = 0// 爆風のアニメーションを最初の画像に
}

 爆発処理をするかどうかはフラグで判別しています。フラグがtrueなら爆発のアニメーションを1つ進めます。爆発パターンは8つなので、frameプロパティの値が8になったら爆発のフラグをfalseにして消します。また、爆発の表示位置を画面外にしておきます。

// ■爆発処理
function moveBlast(){
 if (!blast.flag){ return; }  // 爆風がない場合は何もしない
 blast.frame = blast.frame + 1;
 if (blast.frame == 8){ // 爆風の枚数は8枚なので、それ以上の場合は爆風処理をしない
 blast.flag = false;
 blast.y = -999;
 }
}

 これで完成です。実際のプログラムはサンプル4です。

敵がアニメーションし爆発が表示されるようになった

敵がアニメーションし爆発が表示されるようになった

■サンプル4:main.js(実際のゲームの実行ページ

enchant(); // ライブラリの初期化
window.onload = function(){
 var game = new Game(360, 480); // 360×480画面(Canvas)を作成
 game.fps = 30// フレームレートの設定。30fpsに設定
 // 画像データをあらかじめ読み込ませる
 game.preload("images/droid.png", "images/beam.png", "images/apple.png", "images/laser.png", "images/blast.png");
 game.rootScene.backgroundColor = "black"; // ゲームの背景色を黒色に設定
 game.score = 0// スコアを入れる変数を用意する
 // スコアを表示するラベルを作成
 var scoreLabel = new Label("SCORE : 0");
 scoreLabel.font = "16px Tahoma";
 scoreLabel.color = "white";
 scoreLabel.x = 10// X座標
 scoreLabel.y = 5; // Y座標
 game.rootScene.addChild(scoreLabel);
 // 敵の総数を入れるカウンタ変数
 var count = 0;
 // 敵を格納する配列
 var enemy = new Array();
 // 敵のレーザービームを格納する配列(最大でも8つまで)
 var enemyLaser = new Array();
 var maxLaser = 8;
 // 消した敵の総数を入れる変数
 var total = 0;
 // データの読み込みが完了したら処理
 game.onload = function(){
 // 自機の設定
 var fighter = new Sprite(32, 32);
 fighter.image = game.assets["images/apple.png"];
 fighter.x = game.width/2; // X座標
 fighter.y = game.height - 40; // Y座標
 fighter._style.zIndex = 1;
 game.rootScene.addChild(fighter);
 // ビームの設定
 var beam = new Sprite(4, 16);
 beam.flag = false// ビームが発射されているかどうかのフラグ
 beam.x = fighter.x + 14// 自機の中央に設置
 beam.y = fighter.y - 8// 自機より少し上のY座標に設置
 beam.image = game.assets["images/beam.png"];
 beam._style.zIndex = 2;
 game.rootScene.addChild(beam);
 // ZキーをAボタンとして割り当てる
 game.keybind(90, "a");
 // 敵全体を移動させるための座標を用意する
 var enemyDX = 3;
 var enemyDY = 0;
 // 敵を描く
 drawEnemy();
 // 敵のレーザービームを初期化する
 initEnemyLaser();
 // 爆風の設定
 var blast = new Sprite(32, 32);
 blast.image = game.assets["images/blast.png"];
 blast.y = -9999// Y座標
 blast._style.zIndex = 10; // 爆風は一番手前に重なるようにする
 game.rootScene.addChild(blast);
 // フレームイベントが発生したら処理
 game.rootScene.addEventListener(Event.ENTER_FRAME, function(){
  startBeam(); // ビームの発射を確認
  moveBeam(); // ビームを移動させる
  moveFighter(); // 自機を移動させる(キーボード対応)
  moveEnemy(); // 敵を移動させる
  startEnemyLaser(); //敵のレーザービームを発射する
  moveEnemyLaser(); // 敵のレーザービームを移動させる
  moveBlast(); // 爆発の処理を行う
  hitCheck();  // ビームと敵の接触判定
  hitCheckLaser(); // レーザービームと自機の接触判定
  // =============== 各種処理 ==================
  // ------------ ■ビームを移動させる -----------------
  function moveBeam(){
  if (beam.flag){
   beam.y = beam.y - 8// 8を減算するとビームは上に移動する
   // 画面外かどうか調べる
   if (beam.y < -32){ beam.flag = false; }
  }
  }
  // ------------ ■ビームを発射する -----------------
  function startBeam(){
  if (!beam.flag){
   // Aボタンが押されたらビームを発射
   if (game.input.a){
   beam.flag = true; // trueにしてビームが発射されている事を示すようにする
   beam.x = fighter.x + 14// 自機の中央から出す
   beam.y = fighter.y - 11// 自機より少し上のY座標から出す
   }
  }
  }
  // ------------ ■自機を移動させる -----------------
  function moveFighter(){
  // キーボード操作の場合
  if (game.input.left){
   fighter.x = fighter.x - 4// パドルを左に移動
   if (fighter.x < 0){ fighter.x = 0; } // 左端かどうか調べる
  }
  if (game.input.right){
   fighter.x = fighter.x + 4// パドルを右に移動
   if (fighter.x > (game.width-fighter.width)){ fighter.x = game.width - fighter.width; } // 右端かどうか調べる
  }
  // ビームが発射されていない場合は自機と一緒に移動
  if (!beam.flag){
   beam.x = fighter.x + 14// 自機の中央に設置
   beam.y = fighter.y - 11// 自機より少し上のY座標に設置
  }
  }
  // ------------ ■敵を移動させる -----------------
  function moveEnemy(){
  var reverseFlag = false;
  for(var i=0; i<count; i++){
   enemy[i].x = enemy[i].x + enemyDX; // X座標の移動処理
   enemy[i].y= enemy[i].y + enemyDY; // Y座標の移動処理
   enemy[i].tick = enemy[i].tick + 1;
   enemy[i].frame = enemy[i].tick >>> 4; // ★敵をアニメーションさせる
   // 左右の端に到達したか調べる
   if ((enemy[i].y > 0) && ((enemy[i].x < 0) || (enemy[i].x > 330))){ reverseFlag = true; }
   if (enemy[i].y > 415){
   game.rootScene.backgroundColor = "red"// ゲームの背景色を赤色に設定
   game.stop();
   alert("A帝国はG帝国に征服されました。スコアは"+game.score+"点でした");
   return// 以後の処理は行わないようにする
   }
  }
  // 左右どちらかの端に到達した敵がいた場合の処理
  if (reverseFlag){
   enemyDX = -enemyDX;
   enemyDY = 6;
  }else{
   enemyDY = 0;
  }
  }
  // ------------ ■敵とビームの接触判定を行う -----------------
  function hitCheck(){
  if (!beam.flag){ return; } // ビームが発射されていない場合は処理しない
  for(var i=0; i<count; i++){
   if (beam.intersect(enemy[i])){
   startBlast(enemy[i].x, enemy[i].y);  // ★爆風発生
   beam.flag = false// 接触した場合はビームを消す
   enemy[i].y = -9999// 見えない場所に移動
   game.score = game.score + 1// スコアを加算(1点)
   total = total - 1// 総敵数から1を引く
   if (total < 1){  // 全部倒したか調べる
    setTimeout("drawEnemy()", 2000); // 2秒後に敵を再描画
   }
   }
  }
  scoreLabel.text = "SCORE : "+game.score;
  }
  // ------------ ■自機と敵のレーザービームの接触判定を行う -----------------
  function hitCheckLaser(){
  // 自機の判定用に仮のオブジェクトを作成しXY座標と幅を設定する
  var temp = {
   x : fighter.x+4,
   y : fighter.y+12,
   width : 24,
   height: 20
  }
  for(var i=0; i<maxLaser; i++){
   if (enemyLaser[i].intersect(temp)){  // 接触したらゲームオーバー
   game.rootScene.backgroundColor = "red"// ゲームの背景色を赤色に設定
   game.stop();
   alert("自機が破壊されました。もう駄目です。スコアは"+game.score+"点でした");
   return// 以後の処理は行わないようにする
   }
  }
  }
  // ■爆発開始処理
  function startBlast(sx, sy){
  blast.flag = true// 爆風発生
  blast.x = sx;
  blast.y = sy;
  blast.frame = 0// 爆風のアニメーションを最初の画像に
  }
  // ■爆発処理
  function moveBlast(){
  if (!blast.flag){ return; }  // 爆風がない場合は何もしない
  blast.frame = blast.frame + 1;
  if (blast.frame == 8){ // 爆風の枚数は8枚なので、それ以上の場合は爆風処理をしない
   blast.flag = false;
   blast.y = -999;
  }
  }
 });
 }
 // ゲーム処理開始
 game.start();
 // ------------ 敵を描く -----------------
 function drawEnemy(){
 count = 0// 敵の総数を示すカウンタを0にする
 // ビームの設定を縦横の数だけ繰り返し生成
 for(var y=0; y<5; y++){
  for(var x=0; x<7; x++){
  enemy[count] = new Sprite(32, 32);
  enemy[count].image = game.assets["images/droid.png"];
  enemy[count].x = x * (32+10); // X座標
  enemy[count].y = y * 32 + 30; // Y座標
  enemy[count]._style.zIndex = 2// Z座標
  enemy[count].tick = 0// ★アニメーション用カウンタを初期化
  game.rootScene.addChild(enemy[count]);
  count = count + 1// 敵の総数を示すカウンタを増やす
  }
 }
 total = count; // 消す敵の総数を変数に入れる
 }
 window.drawEnemy = drawEnemy;
 // ------------ 敵のレーザービームを初期化 -----------------
 function initEnemyLaser(){
 for(var i=0; i<maxLaser; i++){
  enemyLaser[i] = new Sprite(4, 16);
  enemyLaser[i].image = game.assets["images/laser.png"];
  enemyLaser[i].flag = false// レーザービームが存在するかどうかのフラグ
  enemyLaser[i].x = 0// X座標
  enemyLaser[i].y = -999// Y座標
  enemyLaser[i]._style.zIndex = 1// Z座標
  game.rootScene.addChild(enemyLaser[i]);
 }
 }
 // ------------ 敵のレーザービームを発射する -----------------
 function startEnemyLaser(){
 var pointer = Math.floor(Math.random() * 100); // レーザービームを発射する敵の配列位置を求める
 if (!enemy[pointer] || enemy[pointer].y < 0 ){ return; } // 敵が存在しない場合は発射しない
 for(var i=0; i<maxLaser; i++){
  if (!enemyLaser[i].flag){ // 空いているレーザービームの配列要素があるか
  enemyLaser[i].flag = true// 発射するレーザービームの存在をONにする
  enemyLaser[i].x = enemy[pointer].x + 14// X座標を設定 
  enemyLaser[i].y = enemy[pointer].y + 16// Y座標を設定
  return// 以後の処理はしない
  }
 }
 }
 // ------------ 敵のレーザービームを移動する -----------------
 function moveEnemyLaser(){
 for(var i=0; i<maxLaser; i++){
  if (!enemyLaser[i].flag){ continue; } // レーザービームがない場合は繰り返しの先頭に
  enemyLaser[i].y= enemyLaser[i].y + 4; // Y座標の移動処理
  if (enemyLaser[i].y > game.height){  // 画面外か?
  enemyLaser[i].flag = false// 発射するレーザービームの存在をOFFにする
  enemyLaser[i].y = -999// 発射するレーザービームのY座標を設定
  }
 }
 }
}

 サンプル4には冗長な部分もありますが、シューティングゲームはどれも似たような仕組みで動作しています。このゲームを元にしていろいろ作成してみるとよいでしょう。また、ステージの表示やランク調整などを改良したバージョンを以下にアップしてあります。ステージが進むと敵の発射するビームの数が多くなります。サンプル4と見比べてみるとよいでしょう。

前へ 1 2 3 4 次へ

この連載の記事

一覧へ
Web Professionalトップページバナー

この記事の編集者は以下の記事をオススメしています

ASCII.jp会員サービス 週刊Web Professional登録

Webディレクター江口明日香が行く