エンティティとTransformコンポーネントを作る

前回でTransformクラスを作り、姿勢を表すことができるようになりました。
この姿勢の情報を持たせる実体が必要ですね。

Spinelでは、実体を表現するものとしてエンティティとコンポーネントを導入します。

エンティティは3D空間上に存在するオブジェクトを指します。このエンティティはそのままでは何の機能も持ちません。
このエンティティに様々な種類のコンポーネントを搭載させることで、機能を付け加えていきます。

まずは、Entityクラスを作りましょう。

Entity.ts

srcフォルダの下にecフォルダを作ります。そしてsrc/ecフォルダの下にEntity.tsファイルを作成してください。以下のように記述します。

export class Entity { private _name: string; private _id: number; private static _entities: Entity[] = []; private constructor(id: number) { this._id = id; this._name = "Entity_" + id; } getId() { return this._id; } getName() { return this._name.concat(); } setName(name: string) { this._name = name; } static create(): Entity { const entity = new Entity(this._entities.length); this._entities.push(entity); return entity; } static get(id: number): Entity | undefined { if (id < 0 || id >= this._entities.length) { return undefined; } return this._entities[id]; } static reset() { this._entities = []; } }

同階層に、テストケースであるEntity.test.tsファイルも作ります。

import { Entity } from "./Entity.js"; afterEach(() => { Entity.reset(); }); test("Entity.create", () => { const entity = Entity.create(); expect(entity.getId()).toBe(0); expect(entity.getName()).toBe("Entity_0"); const entity2 = Entity.create(); expect(entity2.getId()).toBe(1); expect(entity2.getName()).toBe("Entity_1"); }); test("Entity.get", () => { const entity = Entity.create(); expect(Entity.get(0)).toBe(entity); expect(Entity.get(1)).toBe(undefined); expect(Entity.get(-1)).toBe(undefined); });

Entityクラス使い方

Entityを新たに作成する際には、Entity.createメソッドを使います。

const entity = Entity.create();

Entityにはそれぞれ作られた順にIDを持っており、getId()メソッドでIDを取得することができます。

const entity = Entity.create(); const id = entity.getId(); // 0 const entity2 = Entity.create(); const id2 = entity2.getId(); // 1

そのIDを使って、Entityクラスからentityの実体をいつでも取り出すことができます。存在しないIDを渡した場合はundefinedが返ります。

const entity0 = Entity.get(0);

Transformコンポーネントを作る

さて、いよいよ姿勢を保持するコンポーネントである、Transformコンポーネントを作りましょう。

まずは、今後作るすべての種類のコンポーネントで共通の処理を扱うComponentクラスを作ります。

import type { Entity } from "./Entity.js"; export abstract class Component { private _entity: Entity; constructor(entity: Entity) { this._entity = entity; } get entity() { return this._entity; } }

次に、TransformComponentクラスを作ります。内部に前回作ったTransformオブジェクトを保持していますね。

import { Matrix4 } from "../../index2.js"; import { Quaternion } from "../../math/Quaternion.js"; import { Transform } from "../../math/Transform.js"; import { Vector3 } from "../../math/Vector3.js"; import { Component } from "../Component.js"; import type { Entity } from "../Entity.js"; export class TransformComponent extends Component { private _transform: Transform; private constructor(entity: Entity) { super(entity); this._transform = new Transform(Vector3.zero(), Quaternion.identity(), Vector3.one()); } setTransform(transform: Transform) { this._transform = transform; } getTransform() { return this._transform.clone(); } setLocalPosition(value: Vector3) { this._transform.setPosition(value); } getLocalPosition() { return this._transform.getPosition(); } setLocalRotation(value: Quaternion) { this._transform.setRotation(value); } getLocalRotation() { return this._transform.getRotation(); } setLocalEulerAngles(value: Vector3) { this._transform.setEulerAngles(value); } getLocalEulerAngles() { return this._transform.getEulerAngles(); } setLocalScale(value: Vector3) { this._transform.setScale(value); } getLocalScale() { return this._transform.getScale(); } getLocalMatrix() { return this._transform.getMatrix(); } setLocalMatrix(value: Matrix4) { this._transform.setMatrix(value); } /** * @private * @param entity */ static _create(entity: Entity) { return new TransformComponent(entity); } }

細かい話ですが、Vector3とVector4にもそれぞれ以下の関数を追加しています。

// Vector3.ts static zero() { return new Vector3(0, 0, 0); } static one() { return new Vector3(1, 1, 1); } // Vector4.ts static zero() { return new Vector4(0, 0, 0, 1); } static one() { return new Vector4(1, 1, 1, 1); }

さて、Transformコンポーネントができたところで、これをEntityクラスに搭載させてみましょう。このTransformコンポーネントは3D空間上の位置を示す非常に大事なコンポーネントなので、Entityクラスに標準で持たせることにします。
Entityクラスを以下のように書き換えてください。

import { TransformComponent } from "./components/TransformComponent.js"; // 追記 export class Entity { private _name: string; private _id: number; private static _entities: Entity[] = []; private _transform: TransformComponent; // 追記 private constructor(id: number) { this._id = id; this._name = "Entity_" + id; this._transform = TransformComponent._create(this); // 追記 } getId() { return this._id; } getName() { return this._name.concat(); } setName(name: string) { this._name = name; } getTransform(): TransformComponent { // 追記 return this._transform; } static create(): Entity { const entity = new Entity(this._entities.length); this._entities.push(entity); return entity; } static get(id: number): Entity | undefined { if (id < 0 || id >= this._entities.length) { return undefined; } return this._entities[id]; } static reset() { this._entities = []; } }

最後に

これで、Entityという実体に姿勢情報を持たせることができました。

15日目:トランスフォームクラスを作る

17日目:SceneGraphコンポーネントを作る