これは恐ろしいオーバーヘッドのように聞こえるかもしれませんが、シーン グラフを使用して、すべての衝突検出、描画、さらには画面上のクリック イベントを処理することを検討する必要があります。
シーン グラフは基本的に、1 つの親から n の子への関係を表すツリー データ構造です (注意: すべての HTML ページの DOM もシーン グラフです)。
したがって、これに近づくには、「ノード」などと呼ばれる基本的なインターフェイスまたは抽象クラスがあり、シーングラフ内のすべてのノードが実装する必要があるインターフェイスを表します。DOM の要素と同じように、すべての要素に CSS プロパティ、イベント処理用のメソッド、および位置修飾子があります。
ノード:
{
children: [],
update: function() {
for(var i = 0; i < this.children.length; i++) {
this.children[i].update();
}
},
draw: function() {
for(var i = 0; i < this.children.length; i++) {
this.children[i].draw();
}
}
}
さて、ご存じかもしれませんが、DOM 内の 1 つの要素をマーリング、位置などごとに移動すると、すべての子要素が自動的に親とともに新しい位置に移動します。この動作は、簡単にするために、変換の継承によって実現されます。 m は 3D 変換マトリックスを表示するのではなく、単なる平行移動 (x、y オフセット) を表示します。
ノード:
{
children: [],
translation: new Translation(),
update: function(worldTranslation) {
worldTranslation.addTranslation(this.translation);
for(var i = 0; i < this.children.length; i++) {
this.children[i].update(worldTranslation);
}
worldTranslation.removeTranslation(this.translation);
},
draw: function() {
for(var i = 0; i < this.children.length; i++) {
this.children[i].draw();
}
}
}
変身:
{
x: 0,
y: 0,
addTranslation: function(translation) {
this.x += translation.x;
this.y += translation.y;
},
removeTranslation: function(translation) {
this.x -= translation.x;
this.y -= translation.y;
}
}
(グローバルな翻訳で値を追加/削除するだけの方が安価であるため、新しい翻訳オブジェクトをインスタンス化しないことに注意してください)
これで、worldTranslation (または globalTranslation) は、ノードが親から継承できるすべてのオフセットを備えています。
衝突検出に入る前に、この手法でスプライトを描画する方法を示します。基本的に、描画ループの配列に位置と画像のペアを入力します
ノード:
{
children: [],
translation: new Translation(),
image: null, //assume this value is a valid htmlImage or htmlCanvas element, ready to be drawn onto a canvas
screenPosition: null, //assume this is an object with x and y values like {x: 0, y: 0}
update: function(worldTranslation) {
worldTranslation.addTranslation(this.translation);
this.screenPosition = {x: worldTranslation.x, y: worldTranslation.y};
for(var i = 0; i < this.children.length; i++) {
this.children[i].update(worldTranslation);
}
worldTranslation.removeTranslation(this.translation);
},
draw: function(spriteBatch) {
spriteBatch.push({
x: this.screenPosition.x,
y: this.screenPosition.y,
});
for(var i = 0; i < this.children.length; i++) {
this.children[i].draw(spriteBatch);
}
}
}
レンダリング機能:
function drawScene(rootNode, context) {
//clear context here
var spriteBatch = [];
rootNode.draw(spriteBatch);
//if you have to, do sorting according to position.x, position.y or some z-value you can set in the draw function
for(var i = 0; i < spriteBatch.length; i++) {
var sprite = spriteBatch[i];
context.drawImage(sprite.image, sprite.position.x, sprite.position.y);
}
}
SceneGraph から画像を描画する基本的な理解ができたので、衝突検出に進みます。
まず、Node に BoundryBox が必要です。
箱:
{
x: 0,
y: 0,
width: 0,
height: 0,
collides: function(box) {
return !(
((this.y + this.height) < (box.y)) ||
(this.y > (box.y + box.height)) ||
((this.x + this.width) < box.x) ||
(this.x > (box.x + box.width))
);
}
}
2D ゲームでは、境界ボックスをノードの座標系ではなく、その絶対値を知ることを好みます。CSS による説明: css ではマージンとパディングを設定できます。これらの値はページに依存せず、それらの値が設定されている要素の「座標系」にのみ存在します。position: absolute と left: 10px と margin-left: 10px; のようなものです。2D の利点は、検出を見つけるために SceneGraph をずっとバブリングする必要がないことと、現在の座標系からボックスを計算する必要がないことです -> ワールド座標系に -> 衝突検出のために各ノードに戻す必要があります。
ノード:
{
children: [],
translation: new Translation(),
image: null,
screenPosition: null,
box: null, //the boundry box
update: function(worldTranslation) {
worldTranslation.addTranslation(this.translation);
this.screenPosition = {x: worldTranslation.x, y: worldTranslation.y};
this.box.x = worldTranslation.x;
this.box.y = worldTranslation.y;
this.box.width = this.image.width;
this.box.height = this.image.height;
for(var i = 0; i < this.children.length; i++) {
this.children[i].update(worldTranslation);
}
worldTranslation.removeTranslation(this.translation);
},
collide: function(box, collisions) {
if(this.box.collides(box)) {
collisions.push(this);
}
//we will optimize this later, in normal sceneGraphs a boundry box asures that it contains ALL children
//so we only will go further down the tree if this node collides with the box
for(var i = 0; i < this.children.length; i++) {
this.children[i].collide(box, collisions);
}
},
draw: function(spriteBatch) {
spriteBatch.push({
x: this.screenPosition.x,
y: this.screenPosition.y,
image: this.image,
});
for(var i = 0; i < this.children.length; i++) {
this.children[i].draw(spriteBatch);
}
}
}
衝突機能:
function collideScene(rootNode, box) {
var hits = [];
rootNode.collide(box, hits);
for(var i = 0; i < hits.length; i++) {
var hit = hits[i];
//your code for every hit
}
}
注:
すべてのノードがイメージを表す必要はありません。ノードを作成して、その子に翻訳を追加することができます。たとえば、1 つの位置を編集するだけで、多数のオブジェクトを配置するためのビジュアルを持たない DIV コンテナーを作成することができます。キャラクターは次のもので構成できます。
CharacterRoot (Translation Only)
CharacterSprite (Image)
Weapon (Image)
Shield (Image)
これらは、ゲームで使用されるシーン管理に関するいくつかの基本にすぎません。ここで使用した多くのサームをググって、さらに最適化を行って、お気軽に質問してください。