[A-Frame]A-Frameでオブジェクトプールを使用する
A-Frameでオブジェクトプールを使用するメモです。
前提条件
- A-Frame 1.2.0
オブジェクトプールとは
ゲーム等で大量の敵や弾を出現させる場合、弾を発射する度に新しく弾を生成&破棄すると動作が重くなります。
オブジェクトプールは、事前に必要十分な量の敵や弾などのオブジェクトを生成(プール)しておき、そのオブジェクトを使いまわします。これにより、生成&破棄が無くなるため、動作速度の向上が見込めます。
A-Frameには標準でオブジェクトプールの仕組みが用意されています。
本記事では、上記の機能を使用して、以下のようにひたすらボールを発射するだけのプログラムを作成してみます。
HTML側
HTML側ではボールとオブジェクトプールの定義を記述します。
Mixinについて
A-Frameでは、オブジェクトプールで管理するオブジェクトをMixinとして定義します。
Mixinは、<a-mixin>
で定義したコンポーネントの設定を再利用できる仕組みです。
以下の例では、それぞれred
、blue
、cube
というid
を持つ3つの<a-mixin>
を定義しています。
そして、定義した<a-mixin>
のid
を下部の<a-entity>
の中でmixin="red cube"
の形式で指定しています。
<a-scene>
<a-assets>
<a-mixin id="red" material="color: red"></a-mixin>
<a-mixin id="blue" material="color: blue"></a-mixin>
<a-mixin id="cube" geometry="primitive: box"></a-mixin>
</a-assets>
<a-entity mixin="red cube"></a-entity>
<a-entity mixin="blue cube"></a-entity>
</a-scene>
このとき、a-entity
は以下の内容に展開されます。
<!-- mixin="red cube" -->
<a-entity material="color: red" geometry="primitive: box"></a-entity>
<!-- mixin="blue cube" -->
<a-entity material="color: blue" geometry="primitive: box"></a-entity>
ボールを定義する
今回は、ボールをオブジェクトプールで管理するため、ボールの設定をMixinとして定義します。
また、ボールの移動用に自作のコンポーネントであるball-movement
を追加しています。
<a-mixin id="ball-mixin"
geometry="primitive:sphere; radius:0.1;"
material="color:red" position="0 0 -5" ball-movement>
</a-mixin>
オブジェクトプールを追加する
オブジェクトプールのシステムを<a-scene>
に対して追加します。
オブジェクトプールはa-scene
に対してpool__任意の名称
を追加することで定義します。
<a-scene pool__ball="mixin: ball-mixin; size: 10; dynamic:true">
mixin: ball-mixin
で、先ほど作成したボール用のmixinを指定しています。
size: 10
はプールの初期サイズで、ここでは10を指定しています。
dynamic: true
は、size
で指定した数を超えてボールが要求された際に、自動的にプールを拡張するかどうかです。
なお、dynamic: false
を指定した場合、size
で指定した数を超えてボールが要求された場合は、undefined
が返ってきます。
A-Scene全体の記載内容
上記で説明したMixinとPoolの記載に加えて、オブジェクト生成用のコンポーネントとして<a-entity ball-spawner></a-entity>
を追加しています。
また、背景用に<a-sky color="#3F51B5"></a-sky>
も追加しています。
<a-scene pool__ball="mixin: ball-mixin; size: 10; dynamic:true">
<a-assets>
<a-mixin id="ball-mixin" geometry="primitive:sphere; radius:0.1;" material="color:red" ball-movement position="0 0 -5"></a-mixin>
</a-assets>
<a-entity ball-spawner></a-entity>
<a-sky color="#3F51B5"></a-sky>
</a-scene>
JavaScript側
JavaScript側でボールの生成処理とボールの移動処理を記載します。
ボールの生成処理
ボール生成用のball-spawner
コンポーネントです。
以下のコンポーネントは、500msごとにpool__ball
からEntityを取得し、実行しています。
AFRAME.registerComponent('ball-spawner', {
init: function() {
this.interval = 500;
this.previousTime = 0;
},
tick: function(time, timeDelta) {
const elapsedTime = time - this.previousTime;
if (elapsedTime < this.interval) return;
// プールからEntityを取得する
let entity = this.el.sceneEl.components.pool__ball.requestEntity();
// play()でEntityの動作を開始する
entity.play();
this.previousTime = time;
}
});
プールからオブジェクトを取得しているのは以下の部分です。
let entity = this.el.sceneEl.components.pool__ball.requestEntity();
また、オブジェクトプールに保存されているEntityはデフォルトではポーズの状態になっているため、play()
を呼んで動作を開始する必要があります。
entity.play();
ボールの動作
ボールを動かすball-movement
コンポーネントの処理内容です。
ボールのY座標を上に移動していき、一定以上の値になったら、ボールをプールに返却して消去します。
AFRAME.registerComponent('ball-movement', {
schema: {
speed: { type: 'number', default: 1 }
},
play: function() {
console.log('play');
this.el.object3D.position.set(0, 0, -5);
},
pause: function() {
console.log('pause');
},
tick: function(time, timeDelta) {
let y = this.el.object3D.position.y + this.data.speed * timeDelta * 0.001;
this.el.object3D.position.setY(y);
if (y > 5) {
// poolにEntityを返却する
this.el.sceneEl.components.pool__ball.returnEntity(this.el);
}
}
});
プールにEntityを返却しているのは以下の部分です。
プールにEntityを返却すると、自動的にpause()が呼び出されます。
this.el.sceneEl.components.pool__ball.returnEntity(this.el);
オブジェクトプールの場合、同じEntityが何度も使いまわされます。そのため、使いまわされても大丈夫なように、play
メソッドでオブジェクトの開始処理、pause
メソッドでオブジェクトの停止処理を行います。
今回は、play
メソッド内で、ボールの位置を元に戻す処理を行っています。
最終的なコード
<!DOCTYPE html>
<html>
<head>
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
</head>
<body>
<script>
// ボール生成コンポーネント
AFRAME.registerComponent('ball-spawner', {
init: function() {
this.interval = 500;
this.previousTime = 0;
},
tick: function(time, timeDelta) {
const elapsedTime = time - this.previousTime;
if (elapsedTime < this.interval) return;
// プールからEntityを取得する
let entity = this.el.sceneEl.components.pool__ball.requestEntity();
// play()でEntityの動作を開始する
entity.play();
this.previousTime = time;
}
});
// ボール移動コンポーネント
AFRAME.registerComponent('ball-movement', {
schema: {
speed: { type: 'number', default: 1 }
},
play: function() {
console.log('play');
this.el.object3D.position.set(0, 0, -5);
},
pause: function() {
console.log('pause');
},
tick: function(time, timeDelta) {
let y = this.el.object3D.position.y + this.data.speed * timeDelta * 0.001;
this.el.object3D.position.setY(y);
if (y > 5) {
// poolにEntityを返却する
this.el.sceneEl.components.pool__ball.returnEntity(this.el);
}
}
});
</script>
<a-scene pool__ball="mixin: ball-mixin; size: 10; dynamic:true">
<a-assets>
<a-mixin id="ball-mixin"
geometry="primitive:sphere; radius:0.1;" material="color:red"
ball-movement position="0 0 -5"></a-mixin>
</a-assets>
<a-entity ball-spawner></a-entity>
<a-sky color="#3F51B5"></a-sky>
</a-scene>
</body>
</html>
Discussion