プログラムでマリオを作る第39回ファイアマリオの実装完結編

ファイアーマリオの実装完結編(プログラミングでマリオを作る第39回)

前回から一ヶ月以上空いてしまいましたが、今回はファイアーマリオの実装を完結させます。

今回の実装結果は以下のようになります。

プログラムでマリオを作る第39回ファイアマリオの実装完結編

実装する内容

今回実装する内容をまとめます。

1.ファイアーマリオ時にファイアーを発射できるようにする。

2.ファイアーが下方向と当たり判定があった場合はバウンドさせる

3.ファイアーが横軸あるいは上方向と当たり判定があった場合は、消滅アニメーションさせる

4.ファイアーと敵に当たり判定があった場合、敵を倒す

5.Akeyの入力を受けるようにする、ただkeyのpress状態でなくdown状態を取る必要がある。

結構実装しないといけない項目が多く、ちょっと考えるだけで、超絶めんどく、
やる気がなくなること必須の実装内容です。

実装に入る

まずは、ファイアーの動作を行うためのfire.jsを作成します。

fire.js

fireクラスのコンストラクタは以下のようになります。

function Fire(posX,posY){
	// 跳ね返り係数
	this.BOUND_POWER = 9;
	this.posX = posX;
	this.posY = posY;
	this.addX = 0;
	// start velocity is down
	this.addY = 2;	
	//  mapchip pos
	this.rightMapX = 0;
	this.leftMapX = 0;
	this.upMapY = 0;
	this.downMapY = 0;
	this.state = INACTIVE;
	// animation count
	this.animCnt = 0;
	// 切り出し範囲
	this.animX = 0;
}

これまで、作ってきたキャラクタクラスと特に変わりはありません。

続いて、描画関数です。

/*
	draw
	
	ctx:context
	texture:img class
	scrollX:X align scroll num
*/
Fire.prototype.draw = function(ctx,texture,scrollX){
	if(this.state != INACTIVE){
		// 32よりも小さいので、両端plus4で対応
		ctx.drawImage(texture,this.animX + 4,448 + 4,32 - 4,32 - 4,this.posX - scrollX,this.posY,32 - 4,32 - 4);
	}
}

コメントにも書きましたが、ファイアの画像をマップチップサイズである32よりも少し小さく作っており、
サイズが大体24pxなので、その分オフセットを入れています。

そして、ファイアはアニメーションさせるので、animX変数を使って、画像を切り替えています。

続いては、めんどうなファイアとオブジェクトとの当たり判定の実装関数を見ます。

ファイアの当たり判定

moveという移動関数を定義して、その中に当たり判定も実装しています。

/*
	move event
	
	moveNum:move amount
*/
Fire.prototype.move = function(mapChip){
	if(this.state != END_ANIMATION){
		// x方向の移動
		this.posX += this.addX;
		this.updateMapPositionX(this.posX);
		// x方向の当たり判定
		this.collisionX(mapChip,this.posX);
		
		// x軸方向の当たり判定があった場合は跳ねさせない
		if(this.state != END_ANIMATION){
			// 最初は下に向かって打つ、地面と下のマップチップが衝突したら跳ねさせるようにする
			if(this.addY > -MAX_GRAVITY){
				this.addY -= GRAVITY_POWER;
			}
			this.collisionY(mapChip,this.posY - this.addY);
			this.posY -= this.addY;
			this.updateMapPositionY(this.posY);
		}
	}
}

ファイアは下方向のオブジェクトと当たり判定があった場合跳ねるようにしますが、
まず、x軸方向にファイアを移動させ、オブジェクトと当たり判定があった場合、
ファイアを消すことで、ファイアが跳ねないようにします。

/**
 * x軸方向とのオブジェクトとの当たり判定 
 * 
*/
Fire.prototype.collisionX = function(map,posX){
	// 右側
	if(isObjectMap(map[this.downMapY][this.rightMapX]) || isObjectMap(map[this.upMapY][this.rightMapX])){
		this.collisionWithBlock();
	}
	// 左側
	else if(isObjectMap(map[this.downMapY][this.leftMapX]) || isObjectMap(map[this.upMapY][this.leftMapX])){
		this.collisionWithBlock();
	}
}

x軸との当たり判定は単純にマップチップオブジェクトとの当たり判定を行うだけです。

ファイアがオブジェクトと衝突した場合は、collisionWithBlock関数でファイアーが消えるアニメーション
を発動するようにします。

/**
 * blockとぶつかった時の処理
 */
Fire.prototype.collisionWithBlock = function(){
	this.animCnt = 0;
	this.state = END_ANIMATION;
	this.animX = 128;
}

stateにEND_ANIMATIONを代入することで、ファイアーの消滅アニメーションを
呼ぶようにしています。

/**
 * animation
 */
Fire.prototype.animation = function(){
	// end animationでは、ファイアーがぶつかった時の絵にさせる
	if(this.state == END_ANIMATION){
		if(this.animCnt >= 16){
			this.state = INACTIVE;
		}
	}
	else{
		if(this.animCnt % 2 == 0){
			this.animX += 32;
			// overしたら戻す
			if(this.animX >= 128){
				this.animX = 0;
			}
		}
	}
	this.animCnt++;
}

16フレームたった後にファイアーを消すという単純な実装にしました。

一方でファイアーが発動している場合は、オフセットをずらすことで、
クルクル回るアニメーションにしています。

続いて、Y軸方向の当たり判定を見ていきます。

/**
 * objectとy軸方向との当たり判定
 */
Fire.prototype.collisionY = function(map,posY){
	// Y軸座標の更新
	this.updateMapPositionY(posY);	
	// マップ座標xを配列で保管する
	var mapsX = [this.rightMapX,this.leftMapX];
	for(var i = 0;i < 2;++i){
	  	// ファイアの上側に当たった場合は弾を消す
		if(isObjectMap(map[this.upMapY][mapsX[i]])){
			this.state = END_ANIMATION;
		}
	}
	// ファイアの下側とぶつかった場合(跳ね返り処理)
	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.addY = this.BOUND_POWER;
		// 跳ね返らせる
		this.posY -= (this.addY + Math.abs(MAP_SIZE - vecY));
	}
}

ポイントは、下方向と当たり判定があった場合、まず、地面に突き抜けたyの差分をだして、
ファイアの下と地面の上がぴったり一致するように座標に移動させ、
そこから、バウンドさせるために、跳ね返り量をaddYに代入させているとこでしょうか。

これで、ファイアーと地面の当たり判定はだいたいokです。

ファイアーのマップチップ座標更新関数

ファイアーのサイズを24と定義したので、マップチップ座標を
通常32だったものから変更しています。

/**
	update x align map pos

	posX : target object x align pos
*/
Fire.prototype.updateMapPositionX = function(posX){
	// x座標
	this.leftMapX = Math.floor(posX /  MAP_SIZE);
	this.rightMapX = Math.floor((posX + FIRE_SIZE - 1) / MAP_SIZE);
	
	// 配列外チェック
	if(this.leftMapX >= MAX_MAP_CHIP_X){
		this.leftMapX = MAX_MAP_CHIP_X - 1;
	}
	if(this.leftMapX < 0){
		this.leftMapX = 0;
	}
	if(this.rightMapX >= MAX_MAP_CHIP_X){
		this.rightMapX = MAX_MAP_CHIP_X - 1;
	}
	if(this.rightMapX < 0){
		this.rightMapX = 0;
	}
}

マップチップ座標は32で区切っていますが、
ファイアーの右の先端のマップ座標は24なので、間違えないように注意します。

y軸方向も同様の処理になります。

ファイアーが画面外に出たときの対応

ファイアーが画面外に出た場合に、ファイアーの状態をリセットしないと、
ファイアーがオブジェクトにぶつかるなどしない限り、いつまでも発射できないので、
画面外に出た時に状態をリセットする処理を実装する必要があります。

/**
 * ファイアーが画面外から消えたらフラグを戻す
 */
Fire.prototype.checkOut = function(marioPosX,mapScrollX){
	// ファイアの移動量。マリオの移動量によって、消える範囲がことなる
	if(Math.abs(this.posX - marioPosX) > DISPLAY_WIDTH - (marioPosX - mapScrollX)){
		this.state = INACTIVE;
	}
}

全体の画面サイズが640なので、マリオが画面中央まで移動していた場合は、
マリオとファイアーが消える距離は320と変化する点に注意してください。

DISPLAY_WIDTHは新しくconst.jsに定義しました。

ファイア発動用の関数

最後にファイアーを発動するための関数です。

ちょうどマリオの手の位置にくるように、座標をずらしています。

/**
 * shot fire!
 * 
 * posX : 発射されるX座標
 * posY : 発射されるY座標
 * direction : 発射される方向(定数を使う)
 */
Fire.prototype.shot = function(posX,posY,direction){
	this.posY = posY + 24;	
	this.state = NORMAL_STATE;
	if(direction == RIGHT_DIR){
		this.posX = posX + 24;	
		this.addX = 7;
	}else{
		this.posX = posX - 8;
		this.addX = -7;
	}
	this.addY = -2;
	// マップ座標を更新
	this.updateMapPosition();
}

ファイアーの実装に関しては大体こんな感じです。

ファイアークラスはマリオクラスの中に持たせますので、
続いて、マリオクラスを修正します。

mario.js

マリオクラスのコンストラクタにファイア用の変数を追加します。

function Mario(posX,posY){
  // chapter39
  this.MAX_FIRE_NUM = 3;
  this.fire = [];
  for(var i = 0;i < this.MAX_FIRE_NUM;++i){
	  this.fire[i] = new Fire(0,0);
  }
}

ファイアーが打てる最大数を3にしました。

ファイアーの発動関数

マリオクラスの中でファイアの発動関数を書いています。

今回、ブラウザのaKeyを押すことで、ファイアが発動するようにしました。
ただ、今までの実装だと、Aを押し続けることで、ファイアが連続発射してしまうので、
keyPress状態でなく、keyDown状態を取得する必要があるので、
そのあたりを新しくmain.jsに書いています。

/**
 * chapter39
 * ファイアマリオ状態時にファイアを打たせる
 */
Mario.prototype.shotFire = function(){
	if(this.state == FIRE_STATE){
		for(var i = 0;i < this.MAX_FIRE_NUM;++i){
			// 非活性のものを探す
			if(this.fire[i].state == INACTIVE){
				this.fire[i].shot(this.moveNumX,this.posY,this.direction);
				break;
			}
		}
	}
}

update関数にて、Akeyが押された時にファイアーを発動できるように処理を追加します。

/**
	マリオの更新関数
*/
Mario.prototype.update = function(mapChip,kuribos){
	if(!this.isDead()){
	  // fireShot処理
	  if(gADown){
		  // key状態を解除
		  gADown = false;
		  this.shotFire();
	  }	  
	}
	// update fire
	for(var i = 0;i < this.MAX_FIRE_NUM;++i){
		this.fire[i].update(mapChip,this.moveNumX,this.mapScrollX);
	}
}

main.js

AKeyの入力が処理されるように新しく処理を追加します。

// key
var gAPush = false;		// A key for fire
var gADown = false;		// A key for fire
// keyの定義
var A_KEY = 65;

A_KEYは65と規定で決められています。

続いて、keyDown関数にAが押されてときの処理を追加します。

/*
	キーを押した時の操作
*/
function keyDown(event) {
	var code = event.keyCode;       // どのキーが押されたか
	switch(code) 『
	    case A_KEY:
	    	event.returnValue = false;	// ie
        	event.preventDefault();	// firefox
        	if(gAPush == false){
        		gADown = true;
        	}
	    	gAPush = true;
	    	break;
	}
}

keyDown状態を取るために、一度keyが離されないと、
gADownフラグを立てないようにしています。

また、前処理と同様Aキーが離されたときにフラグをオフにします。

最後にクリボーとファイアーの当たり判定の実装をします。

クリボーとファイアーとの当たり判定

kuribo.jsにファイアとの当たり判定を追加します。

/**
 * chapter39
 * collision with fire
 * 
 * fire:fire class
 */
Kuribo.prototype.collisionWithFire = function(fire){
	if(fire.state == NORMAL_STATE){
		// x軸
		if(fire.posX < this.posX + 32 && fire.posX + FIRE_SIZE > this.posX){
			// y軸
			if(fire.posY < this.posY + 32 && fire.posY + FIRE_SIZE > this.posY){
				// fire用の死亡アニメーション
				this.state = DEAD_FIRE_ACTION;
				// 少しジャンプさせる
				this.addPosY = -8;
				// ファイアーにも消えるアニメーションを設定する
				fire.collisionWithBlock();
			}
		}
	}
}

x軸とy軸の四角形と四角形との当たり判定を書いています。

ファイアーと当たり判定があった場合に、潰れるアニメーションではまずいので、
上にホップして、地面に落ちていくアニメーションを新しく書く必要があります。

/**
	chapter28
	死亡時のアニメーション
*/
Kuribo.prototype.deadAction = function(){
	if(this.state == DEAD_ACTION)
	{
		if(this.deadCnt++ == DEAD_ANIM_FRAME){
			this.state = DEAD;
		}
	}
	else if(this.state == DEAD_FIRE_ACTION){
		// 重力を加算
		this.addPosY += GRAVITY_POWER;
		// 落下量調整
		if(this.addPosY >= MAX_GRAVITY){
		  this.addPosY = MAX_GRAVITY;
		}
		this.posY += this.addPosY;
		// 画面外まで落ちたら、処理を止める
		if(this.posY >= DISPLAY_HEIGHT){
			this.state = DEAD;
		}
	}
}

ただ、重力処理をかけるだけの簡単なアニメーションです。

実装解説は以上になります。

全体の修正コードはgithubにあげてあるので、確認してみてください。

感想のコーナー

はじめからわかってはいたけど、実装するのは、非常にめんどかった。
が、ファイアが思い通りに動いた時は、ちょっと楽しかった。

あと、めんどくさいのは、ノコノコの実装とゴール処理だなぁー
実装に関して特に悩むようなことはもうないだろうから、後はひたすら単純作業なのが辛いが、
完成させると決めていたので、やらねばいかん。

次は、めんどくさい処理の筆頭ノコノコの実装をしたいと思います。