🐕

[A-Frame]A-Frameでオブジェクトプールを使用する

2022/01/14に公開

A-Frameでオブジェクトプールを使用するメモです。

前提条件

  • A-Frame 1.2.0

オブジェクトプールとは

ゲーム等で大量の敵や弾を出現させる場合、弾を発射する度に新しく弾を生成&破棄すると動作が重くなります。
オブジェクトプールは、事前に必要十分な量の敵や弾などのオブジェクトを生成(プール)しておき、そのオブジェクトを使いまわします。これにより、生成&破棄が無くなるため、動作速度の向上が見込めます。

A-Frameには標準でオブジェクトプールの仕組みが用意されています。

https://aframe.io/docs/1.2.0/components/pool.html

本記事では、上記の機能を使用して、以下のようにひたすらボールを発射するだけのプログラムを作成してみます。

HTML側

HTML側ではボールとオブジェクトプールの定義を記述します。

Mixinについて

A-Frameでは、オブジェクトプールで管理するオブジェクトをMixinとして定義します。
Mixinは、<a-mixin>で定義したコンポーネントの設定を再利用できる仕組みです。

https://aframe.io/docs/1.2.0/core/mixins.html

以下の例では、それぞれredbluecubeという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