X軸との当たり判定を行いたいと思います。
解説
今回はX軸方向のマップチップとの当たり判定を行います。
まずは、mario.jsを開いてください
mario.js
次にX軸方向の移動量を管理する変数を定義します。
this.addPosX = 0;
続いてX軸方向の当たり判定の関数を作りたいんですが、
まず、マップ座標を更新する関数を分散化して、X方向のみの
マップチップ座標を変える関数を作ります。
/**
chapter19
x軸方向のマップチップ座標の更新
pos:マップチップ更新対象となるx座標
*/
Mario.prototype.updateMapPositionX = function(posX){
this.leftMapX = Math.floor(posX / MAP_SIZE);
this.rightMapX = Math.floor((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;
}
}
マップチップ更新関数を分散化したので、
updateMapPosition関数でもupdateMapPositionX関数を使うようにします。
/**
chapter17
マップチップ座標を更新する
*/
Mario.prototype.updateMapPosition = function(){
this.updateMapPositionX(this.posX);
// 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);
}
続いて、移動できないと判定させるマップチップかどうかを返す
関数を定義します。
この関数は他のところでも使う可能性があるので、
collisionUtilsを作成して、その中に定義します。
collisionUtils.js
/**
引数のマップチップ数が通れないと判定される
objectマップかどうか
*/
function isObjectMap(mapNumber){
if(mapNumber >= 48 && mapNumber <= 95){
return true;
}
return false;
}
続いて、作ったファイルをhtmlファイルに読み込ませましょう
index.html
<script lang="JavaScript" src=collisionUtils.js></script>
続いて、const.jsにマップチップの中間点を表す定数の定義をします。
const.js
var HALF_MAP_SIZE = 16;
続いて、maio.jsに戻りx軸方向との当たり判定を行う関数を作ります。
mario.js
/*
chapter19
オブジェクトとの当たり判定X
*/
Mario.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));
// Xの加算量調整
this.addPosX = Math.abs(MAP_SIZE - vecX);
}
// マリオの左側
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));
// Xの加算量調整
this.addPosX = -Math.abs(MAP_SIZE - vecX);
}
}
続いて、collisionXを適用させるために、
X方向の移動関数の修正をします。
collisionX関数により、x軸方向の加算移動量を調整しています。
Mario.prototype.moveX = function(mapChip,moveX){
this.addPosX = moveX;
// 移動後の加算量を渡す
this.collisionX(mapChip,this.posX + this.addPosX);
// 移動方向変える
if(moveX > 0){
this.direction = RIGHT_DIR;
}
else{
this.direction = LEFT_DIR;
}
this.posX += this.addPosX;
// 2と1だと中間位置が存在しなくなる
var cnt = this.isDash ? 2 : 1;
this.animCnt += cnt;
// animation
if(this.animCnt >= 12){
this.animCnt = 0;
// 一定以上に達したらアニメーションを更新する
if(++this.animX > 3){
this.animX = 0;
}
}
}
最後にmain.jsを修正します。
移動関数に対象のマップチップを引数に入れます。
main.js
function move(){
gMario.updateMapPosition();
// 左キーが押されている状態
if(gLeftPush){
if(gSpacePush){
gMario.setIsDash(true);
gMario.moveX(gMapChip,-DASH_SPEED);
}
else{
gMario.setIsDash(false);
gMario.moveX(gMapChip,-NORMAL_SPPED);
}
}
// 右キーが押されている状態
if(gRightPush){
if(gSpacePush){
gMario.setIsDash(true);
gMario.moveX(gMapChip,DASH_SPEED);
}
else{
gMario.setIsDash(false);
gMario.moveX(gMapChip,NORMAL_SPPED);
}
}
// ジャンプ動作
if(gUpPush){
gMario.setJumpSettings(gSpacePush);
}
gMario.jumpAction(gUpPush);
}
全体のソースコード
mario.js
function Mario(posX,posY){
// 定数
this.NORMAL_JUMP_POWER = 10;
this.DASH_JUMP_POWER = 13;
this.addPosX = 0;
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(mapChip,moveX){
// 加算量を代入する
this.addPosX = moveX;
// 移動後の加算量を渡すマップチップの状況に応じてx方向の加算量を決める
this.collisionX(mapChip,this.posX + this.addPosX);
// 移動方向変える
if(moveX > 0){
this.direction = RIGHT_DIR;
}
else{
this.direction = LEFT_DIR;
}
this.posX += this.addPosX;
// ダッシュ時のアニメーションは早くする
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;
}
}
}
/**
x軸方向のマップチップ座標の更新
posX : マップチップ更新対象となるx座標
*/
Mario.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_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;
}
}
/**
chapter17
マップチップ座標を更新する
*/
Mario.prototype.updateMapPosition = function(){
this.updateMapPositionX(this.posX);
// 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);
}
/**
chapter19
オブジェクトとの当たり判定X
*/
Mario.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);
}
// マリオの左側
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);
}
}
collisionUtils.js
/**
引数のマップチップ数が通れないと判定される
objectマップかどうか
*/
function isObjectMap(mapNumber){
if(mapNumber >= 48 && mapNumber <= 95){
return true;
}
return false;
}
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="main.css">
<script lang="JavaScript" src=const.js></script>
<script lang="JavaScript" src=collisionUtils.js></script>
<script lang="JavaScript" src=mario.js></script>
<script lang="JavaScript" src=main.js></script>
</head>
<body>
<div id="fps"></div>
<div id="canvas_wrapper">
<canvas id="id_canvas" width="640" height="480"></canvas>
</div>
</body>
</html>
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;
// chapter18
var MAP_SIZE = 32;
// chapter19
var HALF_MAP_SIZE = MAP_SIZE / 2;
main.js
var g_Canvas;
var g_Ctx;
// texture
var gMarioTex;
var gMapTex;
var gMario;
// key
var gSpacePush = false; // space
var gLeftPush = false; // left
var gRightPush = false; // right
var gUpPush = false; // up
var gDownPush = false; // down
// keyの定義
var SPACE_KEY = 32;
var LEFT_KEY = 37;
var RIGHT_KEY = 39;
var UP_KEY = 38;
var DOWN_KEY = 40;
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,64,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');
// cavasに対応していない
if (!g_Canvas || !g_Canvas.getContext) {
alert("html5に対応していないので、実行できません");
return false;
}
g_Ctx = g_Canvas.getContext('2d'); // ctx
loadTexture();
// mario
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";
gMapTex = new Image();
gMapTex.src = "resource/map512.png";
}
function animate(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);
};
}
)
();
/*
Draw
描画
*/
function Draw(){
drawMap(gMapChip);
//g_Ctx.drawImage(gMarioTex,0,0,24,24,gMarioPosX,gMarioPosY,24,24);
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);
}
}
}
function move(){
// マップ座標の更新
gMario.updateMapPosition();
// 左キーが押されている状態
if(gLeftPush){
if(gSpacePush){
gMario.setIsDash(true);
gMario.moveX(gMapChip,-DASH_SPEED);
}
else{
gMario.setIsDash(false);
gMario.moveX(gMapChip,-NORMAL_SPPED);
}
}
// →キーが押されている状態
if(gRightPush){
if(gSpacePush){
gMario.setIsDash(true);
gMario.moveX(gMapChip,DASH_SPEED);
}
else{
gMario.setIsDash(false);
gMario.moveX(gMapChip,NORMAL_SPPED);
}
}
// ジャンプ動作
if(gUpPush){
// ジャンプ設定をオンにする
gMario.setJumpSettings(gSpacePush);
}
// ジャンプ処理
gMario.jumpAction(gUpPush);
}
/*
キーを押した時の操作
*/
function keyDown(event) {
var code = event.keyCode; // どのキーが押されたか
switch(code) {
// スペースキー
case SPACE_KEY:
// スクロールさせないため
event.returnValue = false; // ie
event.preventDefault(); // firefox
gSpacePush = true;
break;
// ←キー
case LEFT_KEY:
gLeftPush = true;
break;
// →キー
case RIGHT_KEY:
gRightPush = true;
break;
// ↑キー
case UP_KEY:
event.returnValue = false; // ie
event.preventDefault(); // firefox
gUpPush = true;
break;
// ↓キー
case DOWN_KEY:
event.returnValue = false; // ie
event.preventDefault(); // firefox
gDownPush = true;
break;
}
}
/*
キーを離した時のイベント
*/
function keyUp(event) {
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:
// ↓キー
gDownPush = false;
break;
}
}