スターの実装処理(プログラミングでマリオを作る41回)

半年?ぐらい空いてしまいましたが、今回はスター処理を実装したいと思います。

今回のスター実装の中身はこんな感じなります。

マリオスターの実装

スターに必要な機能は以下のようになります。

  • スターの移動処理(跳ねる、上下左右にぶつかると跳ね返る)
  • マリオとスターが当たり判定を起こすとマリオが無敵状態になりエフェクトが発生する
  • 無敵状態時に敵にぶつかると敵を倒すことができる

おおまかにまとめるとだいたいこんな感じでしょうか。

機能をみてわかるように、いままで実装してきた処理で、
特別新しいことは必要ありません。

では、実装していきます。

star.jsを作る

スターオブジェクトを作成するために、star.jsを作りましょう。

コンストラクタを以下のように定義します。

function Star(posX,posY,dir){
	this.posX = posX;
	this.posY = posY;
	this.addPosX = 0;
	this.addPosY = 0;
	this.direction = dir;
	// マップチップ座標
	this.rightMapX = 0;
	this.leftMapX = 0;
	this.upMapY = 0;
	this.downMapY = 0;
	this.height = 32;
	this.state = INACTIVE;
	// ブロックからの出現アニメーションフラグ
	this.isFirstAnimation = true;
	this.offsetY = 0;
	// 跳ね返り係数
	this.BOUND_POWER = 11;
	this.ADD_X = 2;
}

中身としては、キノコオブジェクトの機能と動きはファイアの動きを実装することになります。

移動関数と当たり判定

続いて、移動関数です。

x軸方向はキノコとおなじ跳ね返り処理、y軸方向はファイアと同じ跳ねる処理ですが、上方向に当たり判定が起こった場合も同様に跳ねさせます。

/**
 * 動かす役割
 */
Star.prototype.move = function(mapChip){		
	if(this.state != INACTIVE){
		this.updateMapPosition();
		// 向きにより加算量を調整する
		let moveNum = this.direction == LEFT_DIR ? -this.ADD_X : this.ADD_X;
		// 加算量を代入する
		this.addPosX = moveNum;
		// マップチップとの当たり判定
		this.collisionX(mapChip,this.posX + this.addPosX);
		this.posX += this.addPosX;
		// 移動したのでマップ座標更新
		this.updateMapPositionX(this.posX);
		
		// x軸方向の当たり判定があった場合は跳ねさせない
		if(this.state != INACTIVE){
			// 最初は下に向かって打つ、地面と下のマップチップが衝突したら跳ねさせるようにする
			if(this.addPosY > -MAX_GRAVITY){
				this.addPosY -= GRAVITY_POWER;
			}
			this.collisionY(mapChip,this.posY - this.addPosY);
			this.posY -= this.addPosY;
			this.updateMapPositionY(this.posY);
		}
	}	
}

上方向にぶつかった場合も跳ねさせるため、collisionYはファイアとは変える必要があります。

/**
 * オブジェクトとの当たり判定Y
 */
Star.prototype.collisionY = function(map,posY){	
	// Y軸座標の更新
	this.updateMapPositionY(posY);	
	// マップ座標xを配列で保管する
	var mapsX = [this.rightMapX,this.leftMapX];
	for(var i = 0;i < 2;++i){
	  	// starの上側に当たった場合下に跳ね返る
		if(isObjectMap(map[this.upMapY][mapsX[i]])){
			// 当たったオブジェクトとの差分
			var vecY = Math.abs((this.posY + HALF_MAP_SIZE) - ((this.upMapY * MAP_SIZE) + HALF_MAP_SIZE));
			// Yの加算量調整
			this.addPosY = -1;
			// 上のオブジェクトの位置につける
			this.posY -= (Math.abs(MAP_SIZE - vecY));
		}
	}
	// スターの下側とぶつかった場合(跳ね返り処理)
	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 = this.BOUND_POWER;
		// 地面の位置につける
		this.posY += (Math.abs(MAP_SIZE - vecY));
	}	
}

最後はマリオとの当たり判定を書きます。

マリオがスターになる関数はマリオ側で実装します。

/**
 * マリオとの当たり判定
 * 
 * map:マップチップ配列
 * mario:Marioクラス
 */
Star.prototype.collisionWithMario = function(map,mario){
  if(!mario.isDead()){
    // x軸
    if(mario.moveNumX < this.posX + 32 && mario.moveNumX + 32 > this.posX){
      // マリオの上とstarの下(starは32*32で切り取られているので、最下部は32+される)
      if(mario.posY < this.posY + 32){
        // マリオの下とstarの上
        if(mario.posY + mario.height > this.posY + (32 - this.height)){
          // マリオを無敵状態にする
          this.state = INACTIVE;
          mario.getStar();
        }
      }
    }
  }
}

マリオ側のスター処理の実装

マリオのクラスメンバーにスター処理で使用する変数を定義します。

function Mario(posX,posY){
  // chapter41
  this.star = new Star(0,0,LEFT_DIR);
  this.isStar = false;
  this.starOffsetX = 0;
  this.starTimer = 0;		// star状態の時間
}

スター用の画像

本当は画像を使わないで、マリオのイメージカラーをプログラム側で変えたかったんですが、色々制限があることがわかってしまったので、画像をスライドさせることによって、スターを表現をすることにしました。

マリオ画像を以下に変更してください。

スター用のマリオドット絵

スター処理

まず、スターと当たり判定があった時の処理を書きます。

/**
 * chapter41
 * star取得時の処理
 */
Mario.prototype.getStar = function(){
	this.isStar = true;
	this.starOffsetX = 0;
	this.starTimer = 0;		
}

続いて、スター時の演出とタイマー処理を書きます。

初代では、目安全体で12秒でした。

そのうちスターが切れかかる時に、画像変更がゆっくりになっているので、
それも考慮に入れて実装する必要があります。

/**
 * chapter41
 * star状態の処理
 */
Mario.prototype.starAction = function(){
	if(this.isStar){
		this.starTimer++;
		// 10秒まで
		if(this.starTimer < 600){
			if(this.starTimer % 4 == 0){
				this.starOffsetX += 256;
				if(this.starOffsetX >= 1024){
					this.starOffsetX = 0;
				}
			}
		// 消える期間(初代では目安12秒だった)
		}else if(this.starTimer >= 600 && this.starTimer < 720){
			if(this.starTimer % 8 == 0){
				this.starOffsetX += 256;
				if(this.starOffsetX >= 1024){
					this.starOffsetX = 0;
				}
			}
		}
		// 完全終了
		else{
			this.isStar = false;
			this.starOffsetX = 0;
			this.starTimer = 0;
		}
	}
}

続いて、マリオをスター用の画像へ変更する処理を書きます。

ただ、starOffsetXを加算するだけですね。

Mario.prototype.draw = function(ctx,texture){
	if(!this.isDead()) {				
		// draw
		ctx.drawImage(texture,this.starOffsetX + (this.animX * 32) + this.animOffsetX,(this.direction * this.height) + this.textureOffsetY,32,this.height,this.posX,this.posY,32,this.height);		    
	}
	else {
		ctx.drawImage(texture, (this.animX * 32) + this.animOffsetX,this.direction * this.height + this.textureOffsetY,32,this.height,this.posX,this.posY,32,this.height);
	}
}

これで、マリオ側の処理は終わりです。

スター時に敵にぶつかると倒せるようにする処理

スター状態で敵にぶつかると倒す処理を書きます。

今のところ敵はクリボとノコノコのみなので、これらがマリオと当たり判定があった際に、倒すような処理を書きます。

演出はファイアの時と同じです。

まずは、クリボから。
マリオと当たり判定があった場合に、スター状態ならファイアの時に使用した
collisionActionを呼ぶだけですね。

Kuribo.prototype.collisionWithMario = function(map,mario){
	if(!mario.isDead()){
		// x軸
		if(mario.moveNumX < this.posX + 32 && mario.moveNumX + 32 > this.posX)
		{
			// マリオの上とクリボの下(クリボは32*32で切り取られているので、最下部は32+される)
			if(mario.posY < this.posY + 32){
				// マリオの下とクリボの上
				// 踏みつけ判定(キャラの半分より上の場合踏みつけと判定させる)
				// マリオの下がクリボの上よりも下にある
				if(mario.posY + mario.height > this.posY + (32 - this.height)){
					// chapter41:star処理
					if(mario.isStar){
						this.setDeadCollisionAction();
						return;
					}
					// マリオの下がクリボの中間地点よりも上にある
					if(mario.posY + mario.height <= this.posY + (32 - this.height) + (this.height / 2)){
						// 潰れたアニメーションにする
						this.state = DEAD_ACTION;
						this.animY = 64;
						this.direction = LEFT_DIR;
						mario.jumpPower = STEP_UP_NUM;
					}
				 	else{
						mario.collisionWithEnemy(map);
					}
				}
			}
		}
	}
}

続いて、ノコノコ。
こちらも同じ。

Noko.prototype.collisionWithMario = function(map,mario){
	if(!mario.isDead()){
		// x軸
		if(mario.moveNumX < this.posX + 32 && mario.moveNumX + 32 > this.posX){
			// 当たり判定の範囲が小さいので、2分割して処理する
			var addY = mario.addPosY / 2;
			// 移動する前の座標を保存する
			var marioPosY = mario.posY + mario.addPosY;
			for(var i = 0;i < 2;++i){
				// 移動する量を2分割する
				marioPosY -= addY;
				// マリオの上とノコノコの下(ノコノコは32*32で切り取られているので、最下部は32+される)
				if(marioPosY <= this.posY + this.height){
					// マリオの下がノコノコの上よりも上にある
					if(marioPosY + mario.height >= this.posY){
						// chapter41:マリオがstar状態
						if(mario.isStar){
							this.setDeadCollisionAction()
							return;
						}
						// ノコノコの動きを止めるアクション、ノコノコが歩いている時と甲羅移動中の当たり判定はクリボと同じ
						if(this.state == NORMAL_STATE || this.state == NOKO_ATTACK_STATE){
							// マリオの下がクリボの中間地点よりも上にある
							if(marioPosY + mario.height <= this.posY + (this.height / 2)){
								if(!this.isSticky){
									if(this.height != this.NORMAL_HEIGHT){
										// 縮まるので、高さを変える
										this.height = this.ATTACK_HEIGHT;
										this.posY += this.NORMAL_HEIGHT - this.ATTACK_HEIGHT;										
									}
									// 止まった状態の甲羅にする
									this.state = NOKO_WAIT_STATE;
									// 歩く状態に戻るアニメーションカウントを戻す
									this.normalBackCnt = 0;
									this.animY = 32;
									mario.jumpPower = STEP_UP_NUM;
									this.isSticky = true;
								}
								return;
							}
						 	else{
								if(!this.isSticky){
									mario.collisionWithEnemy(map);
									this.isSticky = true;								
								}
								return;
							}
						}
					
						// 甲羅待機状態の時は、どこに当たっても甲羅を移動させる
						else if(this.state == NOKO_AWAKING_STATE || this.state == NOKO_WAIT_STATE){
							if(!this.isSticky){
								// 甲羅の移動方向を決める
								// 甲羅の中心座標
								var nokoCenterX = this.posX / 2;
								// マリオの中心位置
								var marioCenterX = mario.moveNumX / 2;
								// 甲羅の中心とマリオの中心位置を割り出して、マリオが左側ならば右に、マリオが右側ならば左に移動させる
								this.direction = marioCenterX <= nokoCenterX ? RIGHT_DIR : LEFT_DIR;
								// 甲羅突進状態
								this.state = NOKO_ATTACK_STATE;
								this.isSticky = true;
							}
							return;
						}
					}
				}				
			}
		}
	}
	// 連続してノコノコがマリオに触れている場合は当たり判定を起こさせないためのフラグ
	this.isSticky = false;
}

これで、当たり判定もokです。

マップにスターを出現させる

最後にマップにスターブロックを出現させます。

以下のようにマップチップを描き直しました。
4番目の箱をスター用の箱にしています。

こちらを新しく、ダウンロードしてください。

で、スターブロック用のマップチップ番号83マップ内に入れれば完成です。

まとめ

全体のコードはgithubにあげてますので、参照してください。

感想

もっとめんどくさいと思っていたけど、わりと楽だった。

脱出ゲームに専念していたので、しばらくできなかったが、だらだらやると、どんどん忘れて効率が下がるので、これからは、2週間に一度のペースで更新していきたい。