キノコを出現させる

キノコを出現させる(プログラムでマリオを作る第34回)

今回は、マップチップを叩いた時にキノコを出現させる処理を書きます。

このキノコはマリオと同じようにキノコと当たり判定があった場合に巨大化させるようにします。

キノコを出現させる

キノコ画像を作ったので、これをresourceフォルダの中に入れてください。
もしくは、githubから参照してください。

キノコ入りマップチップ

プログラムを書く

まず、const.jsを開いて新しい定数を定義します。

const.js

// chapter34
var INACTIVE = -1; 
// chapter27
var NORMAL_STATE = 1;

状態定数の1つに不活性であることを示すINACTIVEを定義します。
これをキノコの初期状態にセットします。

続いて、キノコ用のクラスkinoko.jsを新しく作成します。

kinoko.js

基本的な中身はクリボと変わりないので、Kuribo.jsのコピペをベースとします。
Kuriboクラスにない部分だけ解説したいと思います。
また、共通の性質を持つときは、親クラスを定義してもいいですが、今回は省略。。。

まずは、コンストラクタから定義します。

function Kinoko(posX,posY,dir){
	this.posX = posX;
	this.posY = posY;
	this.addPosX = 0;
	this.direction = dir;
	// マップチップ座標
	this.rightMapX = 0;
	this.leftMapX = 0;
	this.upMapY = 0;
	this.downMapY = 0;
	this.height = 32;
	this.state = INACTIVE;
}

状態を示すstateに先ほど定義したINACTIVEを代入しています。

続いて描画関数です。

/*
	描画関数
	ctx:context
	texture:img class
	scrollX:X軸のスクロール量
*/
Kinoko.prototype.draw = function(ctx,texture,scrollX){
	if(this.state != INACTIVE){
		ctx.drawImage(texture, 0,480,32,32,this.posX - scrollX,this.posY,32,32);
	}
}

定義したINACTIVEを活用して、描画するかの管理をしています。

続いて、Y軸方向の当たり判定と、重力処理を書いていきます。

Y軸方向の当たり判定と重力処理

クリボを作る時には、地面が空のマップチップだった場合の処理を書いていませんでしたが、
マリオにおいて、キノコは地面のマップチップが空だった場合落下するので、新しく処理を書く必要があります。

キノコに重力を効かせるための関数はマリオクラスでいうjumpAction関数に該当し書く処理自体は同じです。

/**
  重力動作
  mapChip:対象のマップチップ配列
*/
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;
}

処理はマリオのjumpAction関数と同じです。
続いて、マップチップとの当たり判定を行うcollisionY関数を書きます。

/**
	オブジェクトとの当たり判定Y
*/
Kinoko.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;
	}
}

キノコは落下するのみなので、上方向の当たり判定は含まれていません。

キノコとマリオが接触したときの処理

マリオでは、キノコと当たり判定が行われたとき、でかくなり当たり判定も変わりますが、今回は長くなるので省力して、キノコを表示されないような処理を書きます。

/*
  マリオとの当たり判定

  map:マップチップ配列
  mario:Marioクラス
*/
Kinoko.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)){
          // マリオを大きくする処理を書く
          this.state = INACTIVE;
        }
      }
    }
  }
}

続いて、マリオがキノコブロックを叩いた時に、キノコを出現させる処理を書きます。

/**
  キノコを出現させる処理

  posX:出現X座標
  posY:出現Y座標
*/
Kinoko.prototype.activate = function(posX,posY,dir){
  this.posX = posX;
  this.posY = posY;
  this.state = NORMAL_STATE;
  this.dir = dir;
}

これで、説明が必要そうなキノコの処理は書き終えました。
あとで全体のソースを載せます。

キノコのオブジェクトをどこで共有するか問題

マリオがキノコブロックを叩いた時に、キノコオブジェクトを参照する必要がありますが、そのオブジェクトをどのクラスに持たせるかという問題があります。

ソースコード的にいいのは、全体を司るmain.jsでキノコオブジェクトを持ち、
マリオがキノコに接触した時に、そのキノコオブジェクトの処理を呼び出すことですが、
今回は、そのままマリオにキノコオブジェクトを保持させて、マリオ経由でキノコオブジェクトを参照できるようにします。

続いて、マリオがキノコブロックを叩いたら、キノコを出現させる処理を書きます。
前と同様叩いたブロックがキノコブロックかどうか判定する関数を書くので、
collisionUtils.jsを開いてください。

collisionUtils.js

/**
  kinokoブロックかどうか判定する

  mapNumber : マップチップ番号
*/
function isKinokoBlock(mapNumber){
  if(mapNumber == 82){
    return true;
  }
  return false;
}

82番目のブロックをキノコ用のブロックとして使います。

続いて、mario.jsを開いてください。

mario.js

まずは、先ほど解説したように、Kinokoオブジェクトをメンバー変数として宣言します。

function Mario(posX,posY){
  // chapter34
  this.kinoko = new Kinoko(0,0,LEFT_DIR);
}

続いて、接触したマップチップがキノコブロックだった時に、
キノコを出現させる処理です。

Mario.prototype.collisionY = function(map,posY){
  this.updateMapPositionY(posY);
  // マップ座標xを配列で保管する
  var mapsX = [this.rightMapX,this.leftMapX];
  for(var i = 0;i < 2;++i){
      // ...

      // キノコブロックだった場合(chapter34)
      if(isKinokoBlock(map[this.upMapY][mapsX[i]])){
        // キノコを出現させる
        var kinokoX = mapsX[i] * MAP_SIZE;
        // 一つ上にセットする
        var kinokoY = (this.upMapY - 1) * MAP_SIZE;
        this.kinoko.activate(kinokoX,kinokoY,LEFT_DIR);
        // ボックスを空にする
        replaceEmptyBoxMap(map,mapsX[i],this.upMapY);
      }

      // (加算される前の)中心点からの距離をみる
      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;
  	}
  }
}

キノコクラスのactivate関数を呼び、処理を行わせるようにしています。

main.jsでキノコの更新処理を呼び出す。

キノコの更新処理を呼び出すために、main.jsを修正しましょう。

まずは、マップチップにキノコブロックを追加します。

var gMapChip = [
  [255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],
  [255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],
  [255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],
  [255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],
  [255,255,255,255,255,255,255,255,255,255,255,255,255,64,255,255,255,255,255,64,255,255,255,255,255,255,255,64,255,255,255,255,255,255,255,64,64,64,64,64,255,255,64,64,64,64,64,255,255,64,64,64,64,64,255,255,64,255,255,255,255,255,64,255,255,255,255,255,255,255,64,255,255,255,255,255,255,255,64,64,64,64,64,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],
  [255,255,255,255,255,255,255,255,255,255,255,255,255,64,64,255,255,255,64,64,255,255,255,255,255,255,64,255,64,255,255,255,255,255,255,64,255,255,255,64,255,255,255,255,64,255,255,255,255,64,255,255,255,64,255,255,64,64,255,255,255,64,64,255,255,255,255,255,255,64,255,64,255,255,255,255,255,255,64,255,255,255,64,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],
  [255,255,255,255,255,33,33,33,255,255,255,255,255,64,255,64,255,64,255,64,255,255,255,255,255,64,255,255,255,64,255,255,255,255,255,64,255,255,255,64,255,255,255,255,64,255,255,255,255,64,255,255,255,64,255,255,64,255,64,255,64,255,64,255,255,255,255,255,64,255,255,255,64,255,255,255,255,255,64,255,255,255,64,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],
  [255,255,255,255,255,255,255,255,255,255,255,255,255,64,255,255,64,255,255,64,255,255,255,255,64,255,255,255,255,255,64,255,255,255,255,64,64,64,64,64,255,255,255,255,64,255,255,255,255,64,255,255,255,64,255,255,64,255,255,64,255,255,64,255,255,255,255,64,255,255,255,255,255,64,255,255,255,255,64,64,64,64,64,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],
  [255,255,255,255,255,255,255,255,255,255,255,255,255,64,255,255,255,255,255,64,255,255,255,64,64,64,64,64,64,64,64,64,255,255,255,64,255,255,64,255,255,255,255,255,64,255,255,255,255,64,255,255,255,64,255,255,64,255,255,255,255,255,64,255,255,255,64,64,64,64,64,64,64,64,64,255,255,255,64,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],
  [255,255,255,255,64,80,82,80,64,255,255,255,255,64,255,255,255,255,255,64,255,255,64,255,255,255,255,255,255,255,255,255,64,255,255,64,255,255,255,64,255,255,64,64,64,64,64,255,255,64,64,64,64,64,255,255,64,255,255,255,255,255,64,255,255,64,255,255,255,255,255,255,255,255,255,64,255,255,64,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,64],
  [255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],
  [255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],
  [255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],
  [112,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,114],
  [128,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,130]
];

82番がキノコ用のマップチップブロックになります。

続いて、更新処理です。

function move(){
  gMario.update(gMapChip);

  enemyMove();
  gMario.kinoko.update(gMapChip,gMario);
}
/*
	Draw
	描画
*/
function Draw(){
  // 背景
  drawMap(gBackGroundMapChip);
  // オブジェクト
  drawObjectMap(gMapChip);
	gMario.draw(g_Ctx,gMarioTex);
  gKuribo.draw(g_Ctx,gKuriboTex,gMario.mapScrollX);

  drawCoin(630,10,gMario.coinNum);
  drawBlockCoin();
  changeMapAnim();
  gMario.kinoko.draw(g_Ctx,gMapTex,gMario.mapScrollX)
  gMario.animateBlockCoin();
}

最後に、index.htmlに追加したkinoko.jsを読み込ませましょう。

index.html

<script lang="JavaScript" src=utils.js></script>
<script lang="JavaScript" src=kinoko.js></script>
<script lang="JavaScript" src=mario.js></script>

最後にkinoko.jsの全体ソースコードを載せます。
全体のソースはgithubを参照してください。

function Kinoko(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;
}

/*
	描画関数
	ctx:context
	texture:img class
	scrollX:X軸のスクロール量
*/
Kinoko.prototype.draw = function(ctx,texture,scrollX){
	if(this.state != INACTIVE){
		ctx.drawImage(texture, 0,480,32,32,this.posX - scrollX,this.posY,32,32);
	}
}

/*
	動かす役割

	moveNum:移動量
*/
Kinoko.prototype.move = function(mapChip,moveNum){
	this.updateMapPosition();
	// 向きにより加算量を調整する
	moveNum = this.direction == LEFT_DIR ? -moveNum : moveNum;
	// 加算量を代入する
	this.addPosX = moveNum;
	// マップチップとの当たり判定
	this.collisionX(mapChip,this.posX + this.addPosX);

	this.posX += this.addPosX;
}

/**
  重力動作
  mapChip:対象のマップチップ配列
*/
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;
}

/**
	x軸方向のマップチップ座標の更新

	posX : マップチップ更新対象となるx座標
*/
Kinoko.prototype.updateMapPositionX = function(posX){
	// x座標
	this.leftMapX = Math.floor(posX / MAP_SIZE);
	this.rightMapX = Math.floor((posX + MAP_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;
	}
}

/**
	Y軸方向のマップチップの更新
*/
Kinoko.prototype.updateMapPositionY = function(posY){
	// y
	this.upMapY = Math.floor(posY / MAP_SIZE);
	this.downMapY = Math.floor((posY + MAP_SIZE - 1) / MAP_SIZE);

	// 配列外チェック
	if(this.upMapY >= MAX_MAP_Y - 1){
		this.upMapY = MAX_MAP_Y - 1;
	}
	if(this.upMapY < 0){
		this.upMapY = 0;
	}
	if(this.downMapY >= MAX_MAP_Y - 1){
		this.downMapY = MAX_MAP_Y - 1;
	}
	if(this.downMapY < 0){
		this.downMapY = 0;
	}
}

/**
	マップチップ座標を更新する
*/
Kinoko.prototype.updateMapPosition = function(){
	this.updateMapPositionX(this.posX);
	this.updateMapPositionY(this.posY);
}

/**
	オブジェクトとの当たり判定X
*/
Kinoko.prototype.collisionX = function(map,posX){
	this.updateMapPositionX(posX);
	// キノコの右側
	if(isObjectMap(map[this.downMapY][this.rightMapX]) || isObjectMap(map[this.upMapY][this.rightMapX])){
		// (加算される前の)中心点からの距離を取る
		var vecX = Math.abs((this.posX + HALF_MAP_SIZE) - ((this.rightMapX * MAP_SIZE) + HALF_MAP_SIZE));
		this.addPosX = Math.abs(MAP_SIZE - vecX);
		this.direction = LEFT_DIR;
	}
	// キノコの左側
	else if(isObjectMap(map[this.downMapY][this.leftMapX]) || isObjectMap(map[this.upMapY][this.leftMapX])){
		// (加算される前の)中心点からの距離を取る
		var vecX = Math.abs((this.posX + HALF_MAP_SIZE) - ((this.leftMapX * MAP_SIZE) + HALF_MAP_SIZE));
		this.addPosX = -Math.abs(MAP_SIZE - vecX);
		this.direction = RIGHT_DIR;
	}
}

/**
	オブジェクトとの当たり判定Y
*/
Kinoko.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;
	}
}

/*
  マリオとの当たり判定

  map:マップチップ配列
  mario:Marioクラス
*/
Kinoko.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)){
          // マリオを大きくする処理を書く
          this.state = INACTIVE;
        }
      }
    }
  }
}

/*
	chapter27
	キノコの更新処理
*/
Kinoko.prototype.update = function(map,mario){
	if(this.state != INACTIVE){
		this.move(map,2);
		this.collisionWithMario(map,mario);
		this.gravityAction(map);
	}
}

/**
  キノコを出現させる処理

  posX:出現X座標
  posY:出現Y座標
*/
Kinoko.prototype.activate = function(posX,posY,dir){
  this.posX = posX;
  this.posY = posY;
  this.state = NORMAL_STATE;
  this.dir = dir;
}