当たり判定処理 後編

2012 年 6 月 19 日 by 山平

前編ではキャラクタ同士の当たり判定について考えてみました。
後編ではキャラクタと壁の当たり判定について考えてみます。

当たり判定処理 前編

当たり判定の考え方

キャラクタが現在位置から次に移動しようしている位置について、移動できるかどうかを判定する必要があります。
マップは任意のサイズ(16×16など)のタイル縦横に敷き詰めて表現しています。
タイルとキャラクタのサイズは一致していない、と考えます。
すると、どのタイルをチェックすればいかが分かります。

hit_block

この図を例に移動をチェックするべきタイルに注目します。
図中のクマの位置は次に移動しようしている位置であることに注意してください。

このとき、クマの上側横2つぶん網掛けされたタイルをチェックすることで上方向の移動が可能かどうかが判定できます。
また、クマの右側縦3つぶんに網掛けされたタイルをチェックすることで右方向の移動が可能かどうかが判定できます。

スプライトとタイルの大きさが不定であっても汎用的に処理できるように、当たり判定領域(クマの赤枠)の右端から左端、上端から下端までにかかるタイルは全てチェックします。
ただし、右上方向に移動(ジャンプ)しようとしていることから、左方向と下方向へのチェックは省略できます。

各タイルをチェックし、障害物だった場合はめり込んでいるぶんだけ押し戻した後の座標が次に移動する(した)位置になります。

enchant.jsによる実装

enchant.jsには、タイルで構成されたマップを管理するオブジェクトがあり、その中に当たり判定用のメソッドが実装されています。
あとはどの座標をチェックすればよいかがわかればよいことになります。

enchant.Map#hitTest

ちなみに、以前に取り上げたアクションゲームにも壁との衝突判定は実装されています。
しかし、移動方向の直線を使って判定しているようで、処理が複雑であること、計算回数が多いことからこの処理は流用しませんでした。

enchant.jsのサンプルゲームを読む

まず、どれだけ壁にめり込んでいるかを取得できる当たり判定処理を縦方向、横方向それぞれに準備します。
長くなるので抜粋します。
なお、キャラクタの進行方向が左、もしくは上方向の場合、返ってきた値からタイルのサイズを引いたものが実際にめり込んだ幅になります。

hitTestX: function(candiX, _candiMinY, _candiMaxY, tileW, tileH){
var candiMinY = Math.floor((_candiMinY + 1) / tileH);
var candiMaxY = Math.floor((_candiMaxY - 1) / tileH);
for(var candiY = candiMinY; candiY < candiMaxY + 1; candiY++){
if(this._mapObj.hitTest(candiX, candiY * tileH)) return candiX % tileW;
}
return 0;
},
hitTestY: function(_candiMinX, _candiMaxX, candiY, tileW, tileH){
var candiMinX = Math.floor((_candiMinX + 1) / tileW);
var candiMaxX = Math.floor((_candiMaxX - 1) / tileW);
for(var candiX = candiMinX; candiX < candiMaxX + 1; candiX++){
if(this._mapObj.hitTest(candiX * tileW, candiY)) return candiY % tileH;
}
return 0;
},

次に実際のキャラクタの処理の中で、移動先が決まった後にマップとの当たり判定を行います。
実際に補正した移動量でキャラクタを移動させるのは縦方向、横方向それぞれの補正値が確定した後に行う必要がある、と言う点に注意が必要です。

var _nextX = this.nextX;//横方向移動増分
var _nextY = this.nextY;//縦方向移動増分
var gapX = 0;//横方向移動増分補正用
var gapY = 0;//縦方向移動増分補正用
//横方向
if(_nextX != 0){
if(_nextX > 0){
gapX = this.hitTestX(this.rx + _nextX, this.uy, this.by, tileW, tileH);
if(gapX != 0) { this.nextX = 0; }
}else{
gapX = this.hitTestX(this.lx + _nextX, this.uy, this.by, tileW, tileH);
if(gapX != 0) { gapX -= tileW; this.nextX = 0;}
}
}
//縦方向
if(_nextY != 0){
if(_nextY > 0){
gapY = this.hitTestY(this.lx, this.rx, this.by + _nextY, tileW, tileH);
}else{
gapY = this.hitTestY(this.lx, this.rx, this.uy + _nextY, tileW, tileH);
if(gapY != 0) gapY -= tileH;
}
}
//縦横の補正が終わってから実際の移動を行う!
this.x += (_nextX - gapX);
this.y += (_nextY - gapY);

以上です。

タグ: ,

TrackBack