マリオのブロックアニメーション処理

ブロックマップチップ処理(プログラミングでマリオを作る第37回)

実装内容

マリオでは、キノコを採った後にブロックを破壊できるようになるので、
キノコを採ったときの破壊処理と、ブロックを叩いたときにアニメーションする処理、
叩いたブロックの上にオブジェクトがある場合は、跳ねさせるような処理を書きたいと思います。

実行結果はこんな感じになります。

マリオのブロックアニメーション処理

まずは、ブロックを上昇させるために必要な変数の宣言をします。
便宜上mario.jsに定義します。

mario.js

function Mario(posX,posY){
  // chapter37
  this.blockAttackX = [[0,0,0,0],[0,0,0,0]];		// Block破壊時の座標X
  this.blockAttackY = [[0,0,0,0],[0,0,0,0]];		// Block破壊時の座標Y
  this.blockAttackCnt = [0,0];			// animation cnt
  this.blockAttackIndex = 0;			// blockのindex
  this.isBlockUp = [false,false];		// ブロック上昇フラグ
  this.isBlockAttack = [false,false];	// 破壊フラグ
  this.blockAttackAddY = [0,0];			// ブロックの移動量Y
  this.blockUpX = [0,0];				// 上昇ブロック用座標X
  this.blockUpY = [0,0];				// 上昇ブロック用座標Y
  this.blockAttackIndexX = [0,0];		// ブロックを移動させる対象のブロックマップチップ番号X
  this.blockAttackIndexY = [0,0];		// ブロックを移動させる対象のブロックマップチップ番号Y
}

変数名には各々説明を入れています。

ブロックのアニメーションを仕込む関数

続いて、ブロックが叩かれたと判定された場合に、ブロックのanimationを仕込むための関数を
書きます。

ポイントは、対象のブロックのマップチップ座標を変数に保存しておくことです。

マップチップオブジェクトとして、描画されている座標をずらすのでなく、
カモフラージュとしてブロックの上に背景を描画し、そこに新しくブロックオブジェクトを描画して、
アニメーションするようにしています。

どっちでもいいんですが、めんどくさいのでそういうやり方にしました。

また、ブロックを破壊するanimationでは、4つのマップチップをそれぞれ、バラバラになるように
移動させているので、4つの座標を配列として保持しています。

/**
 * chapter37
 * ブロックの上昇処理か破壊処理のアニメーション設定を仕込む
 * 
 * mapIndexX : map index x座標
 * mapIndexY : map index y座標
 * map:マップチップ配列
 * isUp:破壊できないマップの場合は上昇させる
 */
Mario.prototype.blockAction = function(mapIndexX,mapIndexY,isUp,map){
	// 対象のマップチップ座標を代入
	this.blockAttackIndexX[this.blockAttackIndex] = mapIndexX;
	this.blockAttackIndexY[this.blockAttackIndex] = mapIndexY;
	// 小さいかつ上昇フラグが立っている場合は破壊できないので、上昇させる
	if(this.state == NORMAL_STATE || isUp)
	{
		this.isBlockUp[this.blockAttackIndex] = true;	// 上昇フラグon
		this.blockUpX[this.blockAttackIndex] = mapIndexX * MAP_SIZE;
		this.blockUpY[this.blockAttackIndex] = mapIndexY * MAP_SIZE;
		this.blockAttackAddY[this.blockAttackIndex] = 8;
	}
	// 大きい場合は破壊可能
	else if(this.state == KINOKO_STATE){
		// 空マップにする
		replaceEmptyMap(map,mapIndexX,mapIndexY);
		// アニメーションを仕込む
		this.isBlockAttack[this.blockAttackIndex] = true;				// block attack flag
		// 右と左で分けているんだ
		this.blockAttackX[this.blockAttackIndex][0] = this.blockAttackX[this.blockAttackIndex][1] = mapIndexX * MAP_SIZE;
		this.blockAttackX[this.blockAttackIndex][2] = this.blockAttackX[this.blockAttackIndex][3] = mapIndexX * MAP_SIZE + HALF_MAP_SIZE;
		this.blockAttackY[this.blockAttackIndex][0] = this.blockAttackY[this.blockAttackIndex][2] = mapIndexY * MAP_SIZE + HALF_MAP_SIZE;
		this.blockAttackY[this.blockAttackIndex][1] = this.blockAttackY[this.blockAttackIndex][3] = mapIndexY * MAP_SIZE - HALF_MAP_SIZE;				
		this.blockAttackAddY[this.blockAttackIndex] = 10;
	}
	// animationフラグとして利用
	this.blockAttackCnt[this.blockAttackIndex]++;
	// 対象のブロック
	if(++this.blockAttackIndex >= MAX_MAP_BLOCK)this.blockAttackIndex = 0;
}

また、ブロックを破壊した際に、ブロックマップチップを空のマップチップに入れ替える必要があるので、
そのための関数をcollisionUtils.jsに書いています。

collisionUtils.js

/**
	対象のマップチップを空のにする
*/
function replaceEmptyMap(map,mapX,mapY){
	map[mapY][mapX] = NOT_DRAW_MAP;
}

また、叩いたマップチップがブロックかどうか判定する関数もここに書きます。

/**
	chapter37
	ブロックマップチップかどうか判定する
	
	mapNumber : マップチップ番号
*/
function isBlockMap(mapNumber){
	if(mapNumber == 64){
		return true;
	}
	return false;
}

ブロックをアニメーションさせる関数

続いて、ブロックをアニメーションさせる関数を書きます。

blockのanimationとしてひとまとめにして、上昇か破壊かをifで分けています。

/**
 * chapter37
 * ブロックのアニメーション処理
 */
Mario.prototype.animateBlock = function(map){
	// ブロックの数分
	for(var i = 0;i < MAX_MAP_BLOCK;++i){
		// ブロック破壊フラグ
		if(this.isBlockAttack[i]){
			// 上昇させる
			for(var j = 0;j < 4;++j){
				this.blockAttackY[i][j] -= this.blockAttackAddY[i];
			}
			this.blockAttackAddY[i] -= 1;
			// 4つのブロックのアニメーション
			this.blockAttackX[i][0] -= 4;
			this.blockAttackX[i][1] = this.blockAttackX[i][0];
			this.blockAttackX[i][2] += 4;
			this.blockAttackX[i][3] = this.blockAttackX[i][2];
			// ブロックが画面外に出たらアニメーションを停止する
			if(this.blockAttackY[i][3] <= -32){
				this.isBlockAttack[i] = false;
			}
		}
		// ブロック上昇処理
		else if(this.isBlockUp[i]){			
			this.blockAttackAddY[i] -= 1;
			// 上下運動が終わった場合
			if(this.blockAttackAddY[i] == 0){
				this.isBlockUp[i] = false;
			}
		}
	}
}

blockを上昇させた時にクリボやキノコなどのオブジェクトを上昇させる関数

続いて、blockが上昇した時に、クリボやキノコなどのオブジェクトを上昇させる関数を書きます。
引数として当然、クリボオブジェクトが必要になります。
キノコはマリオの中でメンバー変数として保持しているので、その参照をチェックすればokです。

/**
 * chapter37
 * blockが移動したときのcollision eventを発生させる
 * collisionのところで呼ぶ
*/
Mario.prototype.blockCollisionAction = function(kuribos){
	for(var i = 0;i < MAX_MAP_BLOCK;++i){
		// ブロック破壊フラグ
		if(this.isBlockAttack[i]){
			// きのこの上昇処理(ずらした分を考慮)
			this.kinoko.blockUpAction(this.blockAttackX[i][0],this.blockAttackY[i][0] - HALF_MAP_SIZE);
			// クリボの当たり判定
			if(kuribos != null){
				for(var j = 0;j < kuribos.length;++j){
					kuribos[j].blockUpAction(this.blockAttackX[i][0],this.blockAttackY[i][0] - HALF_MAP_SIZE);
				}
			}			
		}
		// ブロック上昇処理
		else if(this.isBlockUp[i]){
			// きのこの上昇処理
			this.kinoko.blockUpAction(this.blockUpX[i],this.blockUpY[i]);
			// クリボ
			if(kuribos != null){
				for(var j = 0;j < kuribos.length;++j){
					kuribos[j].blockUpAction(this.blockUpX[i],this.blockUpY[i]);
				}
			}
		}
	}
}

クリボとキノコのクラスにそれぞれ、跳ねるブロックと当たり判定があった場合に、
上昇させる処理を関数として定義しています。

Kinoko.jsにてキノコのブロック上昇関数を書きます。

/**
 * chapter37
 * ブロックの上にのっていた時にきのこを上昇させる処理 
 * 
 * blockPosX : ブロックのX座標
 * blockPosY : ブロックのY座標
*/
Kinoko.prototype.blockUpAction = function(blockPosX,blockPosY){
	// キノコが上にあった場合キノコを上昇させる
	if(this.state == NORMAL_STATE){				
		// Y座標チェック
		if(blockPosY == this.posY + MAP_SIZE){
			// x座標チェック
			if(blockPosX < this.posX + MAP_SIZE  && blockPosX + MAP_SIZE > this.posX){
				this.addPosY = BLOCK_UP_ADD_Y;
			}
		}
	}		
}

つづいて、Kuribo.jsにてブロック上昇関数を書きます。
中身は変わらないので、共通の親クラスを定義して書く的なことも

/**
 * chapter37
 * ブロックの上にのっていた時に上昇させる処理 
 * 
 * blockPosX : ブロックのX座標
 * blockPosY : ブロックのY座標
*/
Kuribo.prototype.blockUpAction = function(blockPosX,blockPosY){
	// クリボが上にいた場合クリボを上昇させる
	if(this.state == NORMAL_STATE){				
		// Y座標チェック
		if(blockPosY == this.posY + MAP_SIZE){
			// x座標チェック
			if(blockPosX < this.posX + MAP_SIZE  && blockPosX + MAP_SIZE > this.posX){
				this.addPosY = BLOCK_UP_ADD_Y;
			}
		}
	}		
}

BLOCK_UP_ADD_Yという定数を定義したので、それをconst.jsに定義します。

const.js

// chapter37
var BLOCK_UP_ADD_Y = -10;

ブロックとの当たり判定時にブロックのアニメーションを仕込む関数を発動させるようにする

マリオではcollsionYという関数に上下方向のマップチップとの当たり判定を仕込んでいるので、
そこに、ブロックアニメーション発動関数を仕込みます。

Mario.prototype.collisionY = function(map,posY){
	this.updateMapPositionY(posY);
  // マップ座標xを配列で保管する
  var mapsX = [this.rightMapX,this.leftMapX];
  for(var i = 0;i < 2;++i){
  	// マリオの上側に当たった場合
  	if(isObjectMap(map[this.upMapY][mapsX[i]])){
      
      // chapter37ブロックのアニメーション
      if(isBlockMap(map[this.upMapY][mapsX[i]])){
    	  var posX = mapsX[i] * MAP_SIZE;
    	  var posY = this.upMapY * MAP_SIZE;
    	  this.blockAction(mapsX[i],this.upMapY,false,map);
      }

      // (加算される前の)中心点からの距離をみる
      var vecY = Math.abs((this.posY + HALF_MAP_SIZE) - ((this.upMapY * MAP_SIZE) + HALF_MAP_SIZE));
      // Yの加算量調整
      this.addPosY = Math.abs(MAP_SIZE - vecY);
      // 落下させる
      this.jumpPower = 0;
  	}
  }
}

ここは、今までやってきたこと特別変わりません。

最後に定義した関数をmarioのupdate関数内で呼びます。

Mario.prototype.update = function(mapChip,kuribos){
        // 省略しています
	if(!this.isDead()){
	  // blockが動いたことによる当たり判定
	  this.blockCollisionAction(kuribos);
	  // scroll処理
	  this.doMapScrollX();
	}
	// 死亡後処理
	this.deadAction();
}

あとは、main.jsのところで、block animation関数とブロックを描画するための関数を仕込みます。

main.js

blockの描画関数を書きます。

先にも書いたように、上昇ブロックのアニメーションの際には、カモフラージュ用の
背景マップチップをブロックの上から描画しています。

/**
 * chapter37
 * animationブロックの描画
 */
function drawBlock(){
	for(var i = 0;i < MAX_MAP_BLOCK;++i){
		// 破壊ブロック
		if(gMario.isBlockAttack[i]){
			g_Ctx.drawImage(gMapTex, 0, 128 , 16, 16, gMario.blockAttackX[i][1] - gMario.mapScrollX,gMario.blockAttackY[i][1], 16, 16);	//
			g_Ctx.drawImage(gMapTex, HALF_MAP_SIZE, 128 , 16, 16, gMario.blockAttackX[i][3] - gMario.mapScrollX,gMario.blockAttackY[i][3], 16, 16);	//
			g_Ctx.drawImage(gMapTex, HALF_MAP_SIZE, 144 , 16, 16, gMario.blockAttackX[i][2] - gMario.mapScrollX,gMario.blockAttackY[i][2], 16, 16);	//
			g_Ctx.drawImage(gMapTex, 0, 144 , 16, 16, gMario.blockAttackX[i][0] - gMario.mapScrollX,gMario.blockAttackY[i][0], 16, 16);	//
		}
		// 上昇ブロック
		if(gMario.isBlockUp[i]){
			// カモフラージュ用
			var indexX = 32 * ((gBackGroundMapChip[gMario.blockAttackIndexY[i]][gMario.blockAttackIndexX[i]] + 16) % 16);
			var indexY = 32 * Math.floor(gBackGroundMapChip[gMario.blockAttackIndexY[i]][gMario.blockAttackIndexX[i]] / 16);
			g_Ctx.drawImage(gMapTex, indexX, indexY , 32, 32, gMario.blockUpX[i] - gMario.mapScrollX,gMario.blockUpY[i], 32, 32); 
			g_Ctx.drawImage(gMapTex, 0, 128 , 32, 32, gMario.blockUpX[i] - gMario.mapScrollX,gMario.blockUpY[i] - gMario.blockAttackAddY[i], 32, 32);
		}
	}
}

Draw関数内で定義した関数を呼びます。

function Draw(){
  // chapter37
  // 省略しています
  drawBlock();
  changeMapAnim();
  gMario.kinoko.draw(g_Ctx,gMapTex,gMario.mapScrollX)
  gMario.animateBlockCoin();
  // chapter37
  gMario.animateBlock();
}

クリボを配列にして、marioクラスに渡す

便宜上、上昇したブロックとクリボの当たり判定をするための引数を配列にしたので、
クリボを配列として宣言します。

var gKuribos = [,];

クリボのインスタンスを生成します。

gKuribos[0] = new Kuribo(256,256,LEFT_DIR);
gKuribos[1] = new Kuribo(160,384,RIGHT_DIR);

marioのupdate関数にてクリボの配列を渡します。

function move(){
  gMario.update(gMapChip,gKuribos);
  enemyMove();
  gMario.kinoko.update(gMapChip,gMario);
}

これで、ブロックの処理はokです。

クリボに重力処理を追加する

クリボに重力処理を入れていなかったため、重力処理を追加します。
重力がないと落下しないので。。。

基本の実装は今までと一緒ですが、手順をまとめてみます。

まず、addPosY変数を追加します。

function Kuribo(posX,posY,dir){
	// chapter37
	this.addPosY = 0;
}

続いて、重力処理をするための関数を定義します。
中身はKinokoのそれと変わりません

/**
 * chapter37
 * 重力動作
 * mapChip :対象のマップチップ配列
*/
Kuribo.prototype.gravityAction = function(mapChip){
	// 重力を加算
	this.addPosY += GRAVITY_POWER;
	// 落下量調整
	if(this.addPosY >= MAX_GRAVITY){
	  this.addPosY = MAX_GRAVITY;
	}
	// Y軸方向の当たり判定(地面に接触している場合は、addPosYは0になる)
	this.collisionY(mapChip,this.posY + this.addPosY);
	this.posY += this.addPosY;
}

続いてY軸方向の当たり判定関数を定義します。

クリボはジャンプしないので、上方向の当たり判定は必要ないはずですが、
今回、ブロックでジャンプするようになるので、本来は上方向の当たり判定も必要です。

/**
 * chapter37
 * クリボ上下の当たり判定
*/
Kuribo.prototype.collisionY = function(map,posY){
	this.updateMapPositionY(posY);
	// キャラの下側と接触した場合
	if(isObjectMap(map[this.downMapY][this.rightMapX]) || isObjectMap(map[this.downMapY][this.leftMapX])){
		// (加算される前の)中心点からの距離を見る
		var vecY = Math.abs((this.posY + HALF_MAP_SIZE) - ((this.downMapY * MAP_SIZE) + HALF_MAP_SIZE));
		// Yの加算量調整
		this.addPosY = Math.abs(MAP_SIZE - vecY);
		// 地面についた
		this.posY += this.addPosY;
		this.addPosY = 0;
	}
}

最後にupdate関数にて重力処理関数を呼び出すようにします。

Kuribo.prototype.update = function(map,mario,moveNum){
	if(!this.isDead()){
		this.move(map,moveNum);
		// chapter37
		this.gravityAction(map);
		this.collisionWithMario(map,mario);
	}
	this.deadAction();
}

今回は、まだやることがあります。

キノコがブロックから出現するときのアニメーション処理

キノコのブロックとブロックが並んでいた時に、そのブロックの間でブロック上昇処理が仕込まれた時に、
キノコが上昇してしまう可能性があるので、キノコ出現時にアニメーションさせることにしました。

キノコをアニメーションさせるためのメンバー変数の定義

function Kinoko(posX,posY,dir){
	// chapter37
	// ブロックからの出現アニメーションフラグ
	this.isFirstAnimation = true;
	this.offsetY = 0;
}

offsetY変数は、キノコの画像を徐々に出現させるためのものです。

キノコのアニメーション関数

キノコのアニメーション関数を書きます。

offsetが32になった時、つまりキノコが最後まで描画されたときに、アニメーションフラグを
falseにして、キノコを移動させるようにします。

/**
 * chapter37
 * きのこの出現時のアニメーションを行う
 * 
*/
Kinoko.prototype.appearingAnimation = function(){
	this.posY -= 1;
	this.offsetY += 1;
	if(this.offsetY == 32){
		this.isFirstAnimation = false;
	} 
}

続いて、update関数にて、animationフラグがonの時は、出現アニメーション処理を
動作させるようにします。

Kinoko.prototype.update = function(map,mario){
	if(this.state != INACTIVE){
		// chapter37初回出現アニメーション
		if(this.isFirstAnimation){
			this.appearingAnimation();
		}
		else{
			this.move(map,2);
			this.gravityAction(map);
		}
		this.collisionWithMario(map,mario);
	}
}

キノコの出現フラグをオンにする関数の修正

続いて、キノコの出現フラグをオンにする関数を修正します。
animationフラグとoffsetを0にするようにします。

Kinoko.prototype.activate = function(posX,posY,dir){
  this.posX = posX;
  this.posY = posY;
  this.state = NORMAL_STATE;
  this.dir = dir;
  this.isFirstAnimation = true;
  this.offsetY = 0;
}

続いて、offsetYが有効になるようにdraw関数を修正します。

Kinoko.prototype.draw = function(ctx,texture,scrollX){
	if(this.state != INACTIVE){
		ctx.drawImage(texture, 0,480,32,this.offsetY,this.posX - scrollX,this.posY,32,this.offsetY);
	}
}

キノコマップ座標Yの更新処理を入れる

キノコのY座標が変化した時に、マップ座標を更新する処理が漏れていたので、
this.collisionY関数を追加します。

Kinoko.prototype.gravityAction = function(mapChip){
  // 重力を加算
  this.addPosY += GRAVITY_POWER;
  // 落下量調整
  if(this.addPosY >= MAX_GRAVITY){
    this.addPosY = MAX_GRAVITY;
  }
  // Y軸方向の当たり判定(地面に接触している場合は、addPosYは0になる)
  this.collisionY(mapChip,this.posY + this.addPosY);
  this.posY += this.addPosY;
}

今回は、以上になります。
全体のコードはgithubにあげているので、確認してみてください。

1年ぶりだったので、かなり忘れていた