エンティティコンポーネントシステム
A-Frameは、entity-component-system (opens new window)(ECS)アーキテクチャを採用したthree.jsベースのフレームワークです。 ECSアーキテクチャは、継承と階層の上に構成するという原則に従っており、3Dやゲーム開発において一般的で多く受け入れられています。
ECSの利点は以下の通りです。
- 再利用可能なパーツを組み合わせてオブジェクトを定義する際の柔軟性が高い。
- 機能が複雑に絡み合った長い継承の連鎖の問題が解消される。
- デカップリングやカプセル化、モジュール化、再利用性によってクリーンな設計を促進する。
- VRアプリケーションの作成時、複雑さの点から最もスケーラブルに構築することが可能。
- 3DやVRの開発で実績のあるアーキテクチャ。
- 新機能の拡張が可能(コミュニティコンポーネントとしても共有可能)。
2DのWebでは、階層に従って振る舞いが固定された要素を配置していきます。 3DやVRはそうではありません。無数のオブジェクトが存在し、そのオブジェクトの動作にも制限がありません。 ECSは、オブジェクトを管理できるパターンを提供します。
以下は、ECSアーキテクチャの入門に最適な資料です。 これらの資料にざっと目を通して、その利点をよりよく理解することをお勧めします。ECSはVRの開発に向いており、A-Frameはその上に A-Frameは、この作法に基づいています。
- エンティティコンポーネントシステム on Wikipedia(英) (opens new window)
- エンティティシステムとは何か by Adam Martin (opens new window)
- *コンポーネントパターンのデカップリング * on Game Programming Patterns (opens new window)
- 階層構造を進化させる by Mick West (opens new window)
ECSを実装した著名なゲームエンジンとしては、Unityがあります。このエンジンでは エンティティ間のコミュニケーションにおいて、A-Frame、DOM、そして宣言的なHTMLがECSの魅力を際立たせます。
# コンセプト
ECSの基本的な定義は以下になります。
- エンティティ それぞれのコンポーネントを格納することができるコンテナオブジェクトです。
エンティティはシーンの中で用いることの出来る基本的なオブジェクトとなります。
コンポーネントなしには、エンティティは何も描画することができません。
これは
<div>
タグに似ています。 - コンポーネント はエンティティに格納できる、再利用可能なモジュール、もしくはデータコンテナーです。コンポーネントは見た目を提供し、ビヘイビア(振る舞い)や機能を提供します。 コンポーネントはオブジェクトに対してプラグアンドプレイのように動作します。全てのロジックはコンポーネントの中に組み込まれており、異なるタイプのオブジェクトを混ぜ合わせたり突き合わせたり、コンポーネントを設定することで定義できます。錬金術のようですね!
- システム はコンポーネントのクラスに対してグローバルなスコープ、マネジメント、そしてサービスを付与します。システムは任意ですが、それらをロジックとデータに分けて使うことができます。システムはロジックを司り、コンポーネントはデータコンテナーとして振る舞います。
# 例
異なるコンポーネントを組み合わせることで作られた様々なタイプのエンティティの例をいくつか挙げてみます。
- ボックス = 位置 + ジオメトリ + マテリアル`。
- 電球 = 位置 + 光 + ジオメトリ + マテリアル + 影。
- サイン = 位置 + ジオメトリ + マテリアル + テキスト
- VRコントローラー = 位置 + 回転 + 入力 + モデル + グラブ + ジェスチャー
- ボール = 位置 + 速度 + 物理 + ジオメトリ + マテリアル
- プレイヤー = 位置 + カメラ + 入力 + アバター + アイデンティティ
別の抽象的な例として、コンポーネントを組み合わせることで車のエンティティを作ることを想像してみましょう。
- 車の外観に影響を与える「色」や「輝度」と言ったプロパティを
material
コンポーネントとして付与することができます。 - 車の機能に影響を与える「馬力や「重量」というプロパティを
engine
コンポーネントとして付与することができます - 車の振る舞いに影響を与える「タイヤの数」や「ステアリング角」というプロパティを
tire
コンポーネントとして付与することができます
このmaterial
、engine
、tire
といったコンポーネントのプロパティを変えることで様々なタイプの車を作ることができます。
このmaterial
、engine
、tire
といったコンポーネントはお互いに他の要素がどんな状態かを知る必要はなく、別のケースでは単独で使用されることもあります。
これらを混ぜたり組み合わせることで様々な乗り物を作ることができます。
- ボートのエンティティを作成するには、
tire
コンポーネントを削除します。 - バイク エンティティを作成するには:
tire
コンポーネントのタイヤの数を 2 に変更し、engine
コンポーネントを小さくします。 - 飛行機の実体を作るには、
wing
とjet
コンポーネントをくっつけます。
従来の継承式の方法では、オブジェクトを拡張したい場合、全てをコントロールして継承構造を利用するための大きなクラスを作成する必要がありました。
# A-FrameにおけるECS
A-Frameは、ECSの各要素を代表するAPIを持っています。
- エンティティは、
<a-entity>
要素で表されます。 - コンポーネントは、
<a-entity>
のHTML属性で表され、その下で、スキーマとライフサイクルハンドラーと、メソッドを持ったオブジェクトとなります。 コンポーネントはAFRAME.registerComponent (name, definition)
APIを用いて予約されます。 - システムは、
<a-scene>
のHTML属性で表されます。システムはその定義においてコンポーネントと似ています。 システムはAFRAME.registerSystem (name, definition)
APIを用いて予約されます。
# 構文
We create <a-entity>
and attach components as HTML attributes. Most
components have multiple properties that are represented by a syntax similar to
HTMLElement.style
CSS (opens new window). This syntax takes the form with a colon
(:
) separating property names from property values, and a semicolon (;
)
separating different property declarations:
<a-entity ${componentName}="${propertyName1}: ${propertyValue1}; ${propertyName2}: ${propertyValue2}">
例えば、<a-entity>
にgeometry、 material、light、 そして positionコンポーネントなどを追加してプロパティ値を設定します。
<a-entity geometry="primitive: sphere; radius: 1.5"
light="type: point; color: white; intensity: 2"
material="color: white; shader: flat; src: glow.jpg"
position="0 0 -5"></a-entity>
# 構成
ここからさらにコンポーネントを追加して、外観や振る舞いや機能を追加していくことができます。
(例:物理など)。あるいは、コンポーネントの値を更新して、使ってエンティティを設定します(宣言的に、もしくはまたは
.setAttribute
を用います)
複数のコンポーネントから構成されるエンティティの共通項はプレイヤーの手の中にあります。 以下のようなものです。
外見、ジェスチャー、振る舞い、他のオブジェクトとのインタラクションなど
手のエンティティにコンポーネントを装着することで、あたかも手のエンティティに 超能力がやどったかのような力を追加することができます。
以下の各コンポーネントはお互いのことを全く知らないが、組み合わせることで複雑な エンティティを作ります。
<a-entity
tracked-controls <!-- Hook into the Gamepad API for pose. -->
vive-controls <!-- Vive button mappings. -->
oculus-touch-controls <!-- Oculus button mappings. -->
hand-controls <!-- Appearance (model), gestures, and events. -->
laser-controls <!-- Laser to interact with menus and UI. -->
sphere-collider <!-- Listen when hand is in contact with an object. -->
grab <!-- Provide ability to grab objects. -->
throw <!-- Provide ability to throw objects. -->
event-set="_event: grabstart; visible: false" <!-- Hide hand when grabbing object. -->
event-set="_event: grabend; visible: true" <!-- Show hand when no longer grabbing object. -->
>
# 宣言的な DOMベースの ECS
A-Frameは、ECSを宣言型にし、DOMベースとすることで、ひとつ上の次元に進めています。 従来、ECSベースのエンジンは、エンティティを作成し、コンポーネントを付与して、コンポーネントを更新し、削除します。 これはコードを通じて行います。しかし、A-FrameはHTMLとDOM通じて、ECSを人間工学的に利用でき、その弱点の多くを解決してくれます。 以下は、DOMがECSに提供する能力の例です。
クエリセレクタによる他のエンティティの参照 DOMは強力なクエリセレクタシステムを提供し、シーングラフにクエリを発行し、条件にあったエンティティを参照することができます。 エンティティはIDやクラスやデータ属性を用いて参照することができます。 A-FrameはHTMLをベースにしているため、外部でクエリセレクタを使用することができます。
document.querySelector('#player')
.イベントを使った非連携型クロスエンティティの通信 DOM はイベントを探知し、発信する機能を持ちます。 これは、エンティティ間の発行/登録 コミュニケーションシステムを提供します。 コンポーネントは互いのことを知る必要はありません。 それぞれがイベント(これが膨張する可能性があります)を発信し、他のコンポーネントは、コールすることなしに、イベントを聞くことができます。
ball.emit('collided')
.DOMのAPIを使ったライフサイクル管理 DOMはHTMLの要素やツリーを更新する以下のようなAPIを提供しています。
.removeAttribute
,.createElement
, そして.removeChild
. これらは通常のWeb開発と同様に、A-Frameでそのまま使用することができます。属性セレクタによるエンティティのフィルタリング DOMは属性セレクタを提供しています。 これを使用することで、あるHTML属性を持っていたり、逆にあるHTML属性を持っていないエンティティを検索することができます。 は、特定のHTML属性を持っています。つまり、以下のような属性を持つエンティティを求めることができます。 は、特定のコンポーネントのセットを持った、もしくは特定のコンポーネントのセットを持たないエンティティを呼び出すことができます。
document.querySelector('[enemy]:not([alive])')
.宣言性 最後に、DOMはHTMLそのものを提供します。A-FrameはECSとHTMLの間の橋渡しをします。 きれいなパターンに沿った、宣言的で、読みやすく、コピー&ペーストが可能な形でです。
# 拡張性
A-Frameのコンポーネントは、何でもできます。開発者には許可をもらうことなく、あらゆる機能を拡張するコンポーネントを自由に開発することができます。 コンポーネントはJavaScript、three.js、そしてWeb API(例:WebRTC、Speech Recognition)を通じてフルアクセスが可能です。
後ほど We will later go over in detail how to [A-Frameコンポーネントの書き方][writecomponent]について詳しく見ていきます。 前段階として、基本的なコンポーネントの構造は以下のようになることを覚えておいてください。
AFRAME.registerComponent('foo', {
schema: {
bar: {type: 'number'},
baz: {type: 'string'}
},
init: function () {
// コンポーネントが最初に付与された時に行う何かを書く
},
update: function () {
// コンポーネントのデータが更新された場合に行う何かを書く
},
remove: function () {
// エンティティが分離された時に行う何かを書く
},
tick: function (time, timeDelta) {
// シーンが動いた時、もしくはフレームが動いた時に行う何かを書く
}
});
宣言型ECSは、JavaScriptのモジュールを書き、HTMLを通じてそれを抽象化する機能を提供します。
をHTMLで表現しています。コンポーネントが登録されるとこのコードモジュールをHTML属性を通じてエンティティに接続することができます。
このコードとHTMLを繋ぐ抽象化はECSを強力かつ簡単に推論できるようにしています。
foo
は私達が登録したコンポーネント名で、データとしてbar
と baz
のプロパティを持ちます。
<a-entity foo="bar: 5; baz: bazValue"></a-entity>
# コンポーネントベースの開発
VRアプリケーションの構築には、すべてのアプリケーションコードをコンポーネント(及びシステム)内に配置することをお勧めします。 理想的なA-Frameのコードベースは、モジュール化され、カプセル化され、各コンポーネントは分離しております。 これらのコンポーネントは、ユニット単独でテストすることができますし、または他のコンポーネントと一緒にテストすることもできます。
アプリケーションをコンポーネントのみで生成すると、そのコードベースの全てのパーツは再利用可能になります。 コンポーネントは、他の開発者が使えるように共有することができ、また私たちの他のプロジェクトで再利用することができます。 また、コンポーネントをフォークして修正することで他のユースケースに適応することもできます。
単純なECSコードベースは以下のように構築できます。
index.html
components/
ball.js
collidable.js
grabbable.js
enemy.js
scoreboard.js
throwable.js
# 高次のコンポーネント
コンポーネントはエンティティを通じて他のコンポーネントにセットすることができます。これによって抽象化内で高次のコンポーネントにすることができます。
例を出すと、 カーソルコンポーネント はraycaster componentの上にセットされます。 もしくはハンドコントロールコンポーネント は viveコントロールコンポーネント の上にセットされます。 そしてoculus タッチコントロールコンポーネントはトラックドコントロールコンポーネントの上に順にセットされます.
# コミュニティコンポーネントエコシステム
コンポーネントは、A-Frameのエコシステム内で共有され、そのコミュニティで使用することができます。
A-Frame ECSの素晴らしいところは、拡張性です。
経験豊富な開発者が物理システムやグラフィックシェーダーコンポーネントを開発することができ、新人開発者はHTML内に<script>
を書き込むだけでそのコンポーネントを自分のシーンに取り入れることができます。
JavaScriptに触れることなく強力な公開コンポーネントを利用できるのです。
# どこでコンポーネントを探すか
コンポーネントは様々な場所に存在します。 それらが発見できるよう最善を尽くしております。もしあなたがコンポーネントを開発した場合、このチャネルを通じて投稿して、共有してください!
# npm
A-Frameのほとんどのコンポーネントはnpmを通じて出力されます。Githubでも同様です。
npm's searchを使ってaframe-components
を検索する (opens new window)ことができます。
npmは品質や人気度、更新頻度によってソートされます。
ここでは完璧なコンポーネントのリストを見ることができます。
# GitHub プロジェクト
多くのA-Frameアプリは純粋にコンポーネントを用いて作られています。そしてそれらの多くはGitHub (opens new window)上でオープンソースとなっています。 このコードベースは私達が直接使うことのでき、参照することができ、複製することの出来るコンポーネントを含みます。以下は一度目を通しておくべきプロジェクトです。
- BeatSaver Viewer (opens new window)
- Super Says (opens new window)
- A-Painter (opens new window)
- A-Blast (opens new window)
# A-Frameの1週間
ブログでは毎週 (opens new window), A-Frameのコミュニティおよびエコシステム内の全ての活動をまとめております。 これには、新しく公開されたり更新された注目すべきコンポーネントを含みます。 A-Frame ホームページ (opens new window) には A-Frameの1週間 ポストへのリンクがあります。
# コミュニティコンポーネントを利用する
利用したいコンポーネントが見つかったら、それを使いたいHTMLの中に<script>
タグを用いて使うことができます。
例としてIdeaSpaceVRの[particle system component][particlesystem]を使用してみましょう。
# unpkg を使ってみる
最初にコンポーネントJSファイルへのCDNリンクを取得する必要があります。 このコンポーネントのドキュメントではCDNへのリンクが書かれており、使用方法も書かれております。しかし最新のCDNリンクを手に入れたいならunpkg.com (opens new window)を使うことです。
unpkg は npmに公開された全てを自動的にホストするCDNです。 unpkgはセマンティックバージョニングを読み解き、あなたが必要とするバージョンを提供します。 URLは以下のような形式をとります。
https://unpkg.com/<npm package name>@<version>/<path to file>
もし最新バージョンが欲しいのであれば version
は除外できます。
https://unpkg.com/<npm package name>/<path to file>
JSファイルへのパスを打ち込む代わりに、コンポーネントパッケージのディレクトリをブラウズすることでpath to file
を除外することができます。
このJSファイル群は通常 dist/
か build/
というディレクトリに入っており、.min.js
という文字がついています。
[particle system component][particlesystem]については
https://unpkg.com/aframe-particle-system-component/
のようになります。
注)スラッシュ(/
)で終えるようにしてください。必要なファイルが見つかったら右クリックで「リンクをアドレスにコピーする」を選択してクリップボードに格納してください。
# コンポーネントJSファイルを組み込む
HTMLへ移動し、<head>
の中, A-Frame JSの <script>
タグ の後で かつ<a-scene>
タグの前に, <script>
タグを用いてコンポーネントJSファイルを読み込みます。
これを執筆している時点での[particle system component][particlesystem]のCDN リンクは以下になります。
https://unpkg.com/aframe-particle-system-component@1.0.9/dist/aframe-particle-system-component.min.js
これをHTMLの中に組み込みます。
<html>
<head>
<script2 src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script2 src="https://unpkg.com/aframe-particle-system-component@1.0.9/dist/aframe-particle-system-component.min.js"></script>
</head>
<body>
<a-scene>
</a-scene>
</body>
</html>
# コンポーネントを使用する
コンポーネントのドキュメントに従って実装を行いましょう。 基本的には、使用法にはコンポーネントをエンティティに付与し、調整を行います。 パーティクルシステムコンポーネントの場合はこのようにHTMLに組み込みます。
<html>
<head>
<script2 src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script2 src="https://unpkg.com/aframe-particle-system-component@1.0.9/dist/aframe-particle-system-component.min.js"></script>
</head>
<body>
<a-scene>
<a-entity particle-system="preset: snow" position="0 0 -10"></a-entity>
</a-scene>
</body>
</html>
# 例
以下に示すのは、レジストリ内の様々なコミュニティコンポーネントをunpkg CDNを用いて使用する例です。 Glitch (opens new window)でリミックするなどして確かめることができます。
<html>
<head>
<script2 src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script2 src="https://unpkg.com/aframe-animation-component@3.2.1/dist/aframe-animation-component.min.js"></script>
<script2 src="https://unpkg.com/aframe-particle-system-component@1.0.x/dist/aframe-particle-system-component.min.js"></script>
<script2 src="https://unpkg.com/aframe-extras.ocean@%5E3.5.x/dist/aframe-extras.ocean.min.js"></script>
<script2 src="https://unpkg.com/aframe-gradient-sky@1.2.0/dist/gradientsky.min.js"></script>
</head>
<body>
<a-scene>
<a-entity id="rain" particle-system="preset: rain; color: #24CAFF; particleCount: 5000"></a-entity>
<a-entity id="sphere" geometry="primitive: sphere"
material="color: #EFEFEF; shader: flat"
position="0 0.15 -5"
light="type: point; intensity: 5"
animation="property: position; easing: easeInOutQuad; dir: alternate; dur: 1000; to: 0 -0.10 -5; loop: true"></a-entity>
<a-entity id="ocean" ocean="density: 20; width: 50; depth: 50; speed: 4"
material="color: #9CE3F9; opacity: 0.75; metalness: 0; roughness: 1"
rotation="-90 0 0"></a-entity>
<a-entity id="sky" geometry="primitive: sphere; radius: 5000"
material="shader: gradient; topColor: 235 235 245; bottomColor: 185 185 210"
scale="-1 1 1"></a-entity>
<a-entity id="light" light="type: ambient; color: #888"></a-entity>
</a-scene>
</body>
</html>