forge3d.ecs — Entity-Component System¶
The ECS (Entity-Component System) provides a data-driven architecture for building scenes with reusable, composable behaviours.
Core concepts¶
| Concept | Description |
|---|---|
| Entity | An integer ID with no data of its own. |
| Component | A data class attached to an entity (e.g. Transform, Rigidbody). |
| System | Logic that processes all entities with a specific set of components. |
| EntityWorld | The container that holds all entities, components, and systems. |
EntityWorld¶
EntityWorld ¶
ECS 월드 — 엔티티 생명주기와 컴포넌트 저장소.
query ¶
모든 지정 타입을 가진 엔티티와 컴포넌트 튜플을 순회한다.
반환: (entity, comp_type1_inst, comp_type2_inst, ...)
Built-in components¶
Transform
dataclass
¶
Rigidbody
dataclass
¶
Bases: Component
Collider
dataclass
¶
Bases: Component
MeshRenderer ¶
Bases: Component
Visual mesh + material component for an ECS entity.
Two equivalent ways to construct:
.. code-block:: python
# Convenience: shape + size derive mesh_id automatically
f3d.MeshRenderer(shape="box", size=(1, 1, 1))
f3d.MeshRenderer(shape="sphere", size=(0.5,))
# Explicit mesh_id (advanced)
f3d.MeshRenderer(mesh_id="box_2x1x0.5", material_id="metal")
CameraComponent
dataclass
¶
Bases: Component
LightComponent
dataclass
¶
Bases: Component
Script
dataclass
¶
Bases: Component
Built-in systems¶
PhysicsSystem ¶
Bases: System
Rigidbody 엔티티를 v1 World에 동기화하고 물리 스텝을 실행한다.
동작
- ECS Transform → v1 Body 위치 동기화
- v1 World.step()
- v1 Body 위치 → ECS Transform 역동기화
RenderSystem ¶
Bases: System
ECS Transform + MeshRenderer → SceneSnapshot 생성.
last_snapshot 속성에 가장 최근 스냅샷을 저장한다.
ScriptSystem ¶
Bases: System
Script 컴포넌트의 on_start/on_update 콜백을 실행한다.
Utilities¶
body_to_entity ¶
기존 v1 Body를 ECS 엔티티로 래핑한다 (파괴적 변환 없음).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
world
|
Any
|
forge3d.World (v1 퍼사드) |
required |
body
|
Any
|
forge3d.Body 인스턴스 |
required |
ew
|
EntityWorld
|
대상 EntityWorld |
required |
Returns:
| Type | Description |
|---|---|
Entity
|
생성된 Entity ID |
Usage examples¶
Creating entities¶
import forge3d as f3d
ew = f3d.EntityWorld()
# A dynamic box at height 5 — pass components directly to create_entity()
box = ew.create_entity(
f3d.Transform(position=[0, 0, 5]),
f3d.Rigidbody(mass=1.0),
f3d.MeshRenderer(shape="box", size=(1, 1, 1)),
f3d.Collider(shape="box", size=(1, 1, 1)),
)
# A static ground plane — or add components after creation
ground = ew.create_entity()
ew.add_component(ground, f3d.Transform(position=[0, 0, 0]))
ew.add_component(ground, f3d.Rigidbody(mass=0)) # mass=0 → static
ew.add_component(ground, f3d.Collider(shape="box", size=(100, 100, 0.1)))
Stepping the world¶
for _ in range(600): # 10 seconds at 60 Hz
ew.step(dt=1/60)
tf = ew.get_component(box, f3d.Transform)
print(f"Box z = {tf.position[2]:.3f}")
Custom script component¶
class Spinner(f3d.Script):
speed: float = 2.0
def update(self, entity, ew, dt):
tf = ew.get_component(entity, f3d.Transform)
tf.rotation[2] += self.speed * dt # yaw in place
spinner_e = ew.create_entity(f3d.Transform(), Spinner(speed=3.0))
Scripts are called by ScriptSystem every ew.step().
Query entities by components¶
# Find all entities that have both Rigidbody and MeshRenderer
for entity, (rb, mr) in ew.query(f3d.Rigidbody, f3d.MeshRenderer):
print(f"Entity {entity}: mass={rb.mass}, shape={mr.shape}")
Bridge to physics World¶
world = f3d.World()
body = world.add_box(size=(1, 1, 1), position=(0, 0, 5), mass=1.0)
# Wrap an existing Body as an ECS entity
entity = f3d.body_to_entity(ew, body)
tf = ew.get_component(entity, f3d.Transform)
print(tf.position) # synced from Body.position