マリオマップチップを定義する

マップチップ座標の更新を行う(プログラムでマリオを作る第18回目)

マップチップとの当たり判定を行うためにマップチップ座標を定義して、マップチップ座標を
更新するプログラムを書きます。

解説

今回はマリオのマップチップ座標を定義したいと思います。

mario.js

まず、クラスにマップチップ座標を定義します。

// マップチップ座標(chapter18)
this.rightMapX = 0;
this.leftMapX = 0;
this.upMapY = 0;
this.downMapY = 0;

続いてconst.jsにマップサイズの定数を定義します。

// chapter18
var MAP_SIZE = 32;

mario.jsに戻って
マップ座標を更新する関数を書きます。

/**
	chapter18
	マップチップ座標を更新する
*/
Mario.prototype.updateMapPosition = function(){
	// x座標
	this.leftMapX = Math.floor(this.posX / MAP_SIZE);
	this.rightMapX = Math.floor((this.posX + MAP_SIZE - 1) / MAP_SIZE);

	// 配列外チェック
	if(this.leftMapX >= MAX_MAP_X){
		this.leftMapX = MAX_MAP_X;
	}
	if(this.leftMapX < 0){
		this.leftMapX = 0;
	}
	if(this.rightMapX >= MAX_MAP_X){
		this.rightMapX = MAX_MAP_X;
	}
	if(this.rightMapX < 0){
		this.rightMapX = 0;
	}

	// y座標
	this.upMapY = Math.floor(this.posY / MAP_SIZE);
	this.downMapY = Math.floor((this.posY + MAP_SIZE - 1) / MAP_SIZE);

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

	// log
	console.log("rightMapX = " + this.rightMapX + ", leftMapX = " + this.leftMapX + ",upMapY = " + this.upMapY + ",this.downMapY = "
		+ this.downMapY);

	console.log("mario posX = " + this.posX + ",mario posY = " + this.posY);
}

マップチップの更新を実行するために、main.jsで
updateMapPosition関数を呼び出します。

main.js

とりあえずmove関数の一番上に追加します。

gMario.updateMapPosition();

追記

マップ座標の用途としては、マリオがどのマップチップと接触しているかをすぐに判定する
ために使います。

マップが32×32で定義されているので、マリオが0~32までの間にいる場合は、
マップチップ座標は0になり、マリオが33~64までにいる場合はマップチップ座標は1になります。

これを実現するための式を定義して、マリオのマップチップ座標に代入しています。

次回は、マップチップ座標を利用して、当たり判定をおこなっていきたいと思います。

github

ソースコード

mario.js

function Mario(posX,posY){
	// 定数
	this.NORMAL_JUMP_POWER = 10;
	this.DASH_JUMP_POWER = 13;

	this.posX = posX;
	this.posY = posY;
	// どのタイミングでアニメーションを切り替えるか
	this.animCnt = 0;
	// 切り出す始点のX座標
	this.animX = 0;
	this.animOffsetX = 0;
	// 方向を切り替えるための変数
	this.direction = RIGHT_DIR;
	// ダッシュフラグ
	this.isDash = false;
	// ジャンプ
	this.isJump = false;
	this.jumpCnt = 0;
	this.jumpPower = 0;
	// マップチップ座標
	this.rightMapX = 0;
	this.leftMapX = 0;
	this.upMapY = 0;
	this.downMapY = 0;
}

/*
	描画関数
	ctx:context
	texture:img class
*/
Mario.prototype.draw = function(ctx,texture){
	ctx.drawImage(texture, (this.animX * 32) + this.animOffsetX,this.direction * 32,32,32,this.posX,this.posY,32,32);
}

Mario.prototype.moveX = function(moveX){
	// 移動方向変える
	if(moveX > 0){
		this.direction = RIGHT_DIR;
	}
	else{
		this.direction = LEFT_DIR;
	}
	this.posX += moveX;
	// ダッシュ時のアニメーションは早くする
	var cnt = this.isDash ? 2 : 1;
	this.animCnt += cnt;

	// animation
	if(this.animCnt >= 12){
		this.animCnt = 0;
		// 一定以上に達したらアニメーションを更新する
		if(++this.animX > 3){
			this.animX = 0;
		}
	}

}

Mario.prototype.setIsDash = function(isDash){
	this.isDash = isDash;
}

/**
	ジャンプ動作ボタンが押された時にジャンプフラグを立てる
*/
Mario.prototype.setJumpSettings = function(isDash){
	if(!this.isJump){
		this.isJump = true;
		this.animOffsetX = 128;
		var jumpNum = isDash ? this.DASH_JUMP_POWER : this.NORMAL_JUMP_POWER;
		this.jumpPower = jumpNum;
	}
}

/**
	ジャンプ動作
	isPush : 対象のキーが押されているか
*/
Mario.prototype.jumpAction = function(isPush){
	if(this.isJump){
		this.posY -= this.jumpPower;
		// 落下量調整
		if(this.jumpPower > -MAX_GRAVITY){
			// 上昇中かつキーが押されている場合は下降量を減らす
			if(isPush && this.jumpPower > 0){
				this.jumpPower -= (GRAVITY_POWER - (GRAVITY_POWER / 2));
			}else{
				this.jumpPower -= GRAVITY_POWER;
			}
		}
		console.log("jumpPower = " + this.jumpPower)
		// 地面についた時
		if(this.posY >= 384){
			this.posY = 384;
			this.isJump = false;
			this.animOffsetX = 0;
		}

	}
}

/**
	chapter17
	マップチップ座標を更新する
*/
Mario.prototype.updateMapPosition = function(){
	// x座標
	this.leftMapX = Math.floor(this.posX / MAP_SIZE);
	this.rightMapX = Math.floor((this.posX + MAP_SIZE - 1) / MAP_SIZE);

	// 配列外チェック
	if(this.leftMapX >= MAX_MAP_X){
		this.leftMapX = MAX_MAP_X;
	}
	if(this.leftMapX < 0){
		this.leftMapX = 0;
	}
	if(this.rightMapX >= MAX_MAP_X){
		this.rightMapX = MAX_MAP_X;
	}
	if(this.rightMapX < 0){
		this.rightMapX = 0;
	}

	// y座標
	this.upMapY = Math.floor(this.posY / MAP_SIZE);
	this.downMapY = Math.floor((this.posY + MAP_SIZE - 1) / MAP_SIZE);

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

	// log
	console.log("rightMapX = " + this.rightMapX + ", leftMapX = " + this.leftMapX + ",upMapY = " + this.upMapY + ",this.downMapY = "
		+ this.downMapY);

	console.log("mario posX = " + this.posX + ",mario posY = " + this.posY);
}

const.js

var LEFT_DIR = 0;
var RIGHT_DIR = 1;
// chapter10
var MAX_MAP_X = 20;
var MAX_MAP_Y = 15;
// chapter11
var DASH_SPEED = 5;
var NORMAL_SPPED = 3;
// chapter12
var GRAVITY_POWER = 1;
var MAX_GRAVITY = 8;
// chapter17
var MAP_SIZE = 32;

main.js

var g_Canvas;
var g_Ctx;
// fps
var g_LastAnimationFrameTime = 0;
var g_LastFpsUpdateTime = 0;
var g_FpsElement;

// chapter7
// texture
var gMarioTex;
var gMapTex;

// key
var gSpacePush = false;	// space
var gLeftPush = false;  // 左
var gRightPush = false;	// 右
var gUpPush = false;	// 上
var gDownPush = false; 	// 下

// keyの定義
var SPACE_KEY = 32;
var LEFT_KEY = 37;
var RIGHT_KEY = 39;
var UP_KEY = 38;
var DOWN_KEY = 40;

// マリオ
var gMario;

// chapter7
/**
	マップ
*/
var gMapChip = [
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,80,80,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,64,64,64,64,64,64,64,64,64,64,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [112,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,130]
];

/**
	onload
	最初に呼び出される関数
*/
onload = function () {
    // キャンバスに代入
    g_Canvas = document.getElementById('id_canvas');
    g_FpsElement = document.getElementById("fps");
    // cavasに対応していない
    if (!g_Canvas || !g_Canvas.getContext) {
        alert("html5に対応していないので、実行できません");
        return false;
    }

    g_Ctx = g_Canvas.getContext('2d');          // cox
    loadTexture();
    gMario = new Mario(0,384);
    // キーの登録
    window.addEventListener("keydown",keyDown, true);
    window.addEventListener("keyup",keyUp, true);

    requestNextAnimationFrame(animate);		// loopスタート
};

/*
	テクスチャのロード
*/
function loadTexture(){
	gMarioTex = new Image();
	gMarioTex.src = "resource/main.png";
  // chapter7
  gMapTex = new Image();
	gMapTex.src = "resource/map512.png";
}


function animate(now) {
    // fps
   calculateFps(now);
    move();
    // 描画
    Draw();
    requestNextAnimationFrame(animate);
}

/*
	60fps毎に処理を実行
*/
window.requestNextAnimationFrame =
(function () {
   var originalWebkitRequestAnimationFrame = undefined,
       wrapper = undefined,
       callback = undefined,
       geckoVersion = 0,
       userAgent = navigator.userAgent,
       index = 0,
       self = this;

   // Workaround for Chrome 10 bug where Chrome
   // does not pass the time to the animation function

   if (window.webkitRequestAnimationFrame) {
      // Define the wrapper

      wrapper = function (time) {
        if (time === undefined) {
           time = +new Date();
        }
        self.callback(time);
      };

      // Make the switch

      originalWebkitRequestAnimationFrame = window.webkitRequestAnimationFrame;

      window.webkitRequestAnimationFrame = function (callback, element) {
         self.callback = callback;

         // Browser calls the wrapper and wrapper calls the callback

         originalWebkitRequestAnimationFrame(wrapper, element);
      }
   }

   // Workaround for Gecko 2.0, which has a bug in
   // mozRequestAnimationFrame() that restricts animations
   // to 30-40 fps.

   if (window.mozRequestAnimationFrame) {
      // Check the Gecko version. Gecko is used by browsers
      // other than Firefox. Gecko 2.0 corresponds to
      // Firefox 4.0.

      index = userAgent.indexOf('rv:');

      if (userAgent.indexOf('Gecko') != -1) {
         geckoVersion = userAgent.substr(index + 3, 3);

         if (geckoVersion === '2.0') {
            // Forces the return statement to fall through
            // to the setTimeout() function.

            window.mozRequestAnimationFrame = undefined;
         }
      }
   }

   return window.requestAnimationFrame   ||
      window.webkitRequestAnimationFrame ||
      window.mozRequestAnimationFrame    ||
      window.oRequestAnimationFrame      ||
      window.msRequestAnimationFrame     ||

      function (callback, element) {
         var start,
             finish;


         window.setTimeout( function () {
            start = +new Date();
            callback(start);
            finish = +new Date();

            self.timeout = 1000 / 60 - (finish - start);

         }, self.timeout);
      };
   }
)
();


function move(){
  gMario.updateMapPosition();

	// 左キーが押されている状態
	if(gLeftPush){
    if(gSpacePush){
        gMario.setIsDash(true);
        gMario.moveX(-DASH_SPEED);
    }
    else{
      gMario.setIsDash(false);
      gMario.moveX(-NORMAL_SPPED);
    }
	}
	// 右キーが押されている状態
	if(gRightPush){
    if(gSpacePush){
        gMario.setIsDash(true);
        gMario.moveX(DASH_SPEED);
    }
    else{
      gMario.setIsDash(false);
      gMario.moveX(NORMAL_SPPED);
    }
	}
  // ジャンプ動作
  if(gUpPush){
    gMario.setJumpSettings(gSpacePush);
  }
  gMario.jumpAction(gUpPush);
}

/*
	Draw
	描画
*/
function Draw(){
	drawMap(gMapChip)
	// context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);
	gMario.draw(g_Ctx,gMarioTex);

}

/**
	マップチップの描画

  map:対象のマップチップ配列
*/
function drawMap(map){
  // y軸
	for(var y = 0;y < MAX_MAP_Y;++y){
    // x軸
		for(var x = 0;x < MAX_MAP_X;++x){
      var indexX = 32 * ((map[y][x] + 16) % 16);
      var indexY = 32 * Math.floor(map[y][x] / 16);
			g_Ctx.drawImage(gMapTex, indexX, indexY , 32, 32, x * 32, y * 32, 32, 32);
		}
	}
}


/*
	fpsの計算
*/
function calculateFps(now) {
        // 1秒間に何回実行されているか
	var fps = 1000 / (now - g_LastAnimationFrameTime);
	g_LastAnimationFrameTime = now;

        // 1秒経過
	if (now - g_LastFpsUpdateTime > 1000) {
  		g_LastFpsUpdateTime = now;
                // 要素にfps値を代入する
		g_FpsElement.innerHTML = fps.toFixed(0) + ' fps';
	}
}

/*
	キーを押した時の処理
*/
function keyDown(event){
	// どのキーが押されたか
	var code = event.keyCode;
	switch(code){
		case SPACE_KEY:
		// スクロールさせないため
		event.returnValue = false;
		event.preventDefault();
		gSpacePush = true;
		break;

		// 左キー
		case LEFT_KEY:
			gLeftPush = true;
			break;

		// 右キー
		case RIGHT_KEY:
			gRightPush = true;
			break;
		// 上キー
		case UP_KEY:
			// スクロールさせないため
			event.returnValue = false;
			event.preventDefault();
			gUpPush = true;
			break;
		// 下キー
		case DOWN_KEY:
			// スクロールさせないため
			event.returnValue = false;
			event.preventDefault();
			gDownKey = true;
			break;

	}

}

/*
	キーを離した時のイベント
*/
function keyUp(event){
	// どのキーが押されたか
	var code = event.keyCode;
	switch(code){
		case SPACE_KEY:
		gSpacePush = false;
		break;

		// 左キー
		case LEFT_KEY:
			gLeftPush = false;
			break;

		// 右キー
		case RIGHT_KEY:
			gRightPush = false;
			break;
		// 上キー
		case UP_KEY:
			gUpPush = false;
			break;
		// 下キー
		case DOWN_KEY:
			gDownKey = false;
			break;

	}
}