Ecs Overview
Π§ΡΠΎ ΡΠ°ΠΊΠΎΠ΅ ECS?
Entity-Component-System (ECS) β ΡΡΠΎ Π°ΡΡ ΠΈΡΠ΅ΠΊΡΡΡΠ½ΡΠΉ ΠΏΠ°ΡΡΠ΅ΡΠ½, ΠΊΠΎΡΠΎΡΡΠΉ ΡΠ°Π·Π΄Π΅Π»ΡΠ΅Ρ Π»ΠΎΠ³ΠΈΠΊΡ ΠΈ Π΄Π°Π½Π½ΡΠ΅ ΠΈΠ³ΡΡ Π½Π° ΡΡΠΈ ΠΎΡΠ½ΠΎΠ²Π½ΡΡ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ°:
Entity (Π‘ΡΡΠ½ΠΎΡΡΡ)
Π£Π½ΠΈΠΊΠ°Π»ΡΠ½ΡΠΉ ΠΈΠ΄Π΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΎΡ, ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»ΡΡΡΠΈΠΉ ΠΎΠ±ΡΠ΅ΠΊΡ Π² ΠΈΠ³ΡΠΎΠ²ΠΎΠΌ ΠΌΠΈΡΠ΅. Π‘ΡΡΠ½ΠΎΡΡΡ ΡΠ°ΠΌΠ° ΠΏΠΎ ΡΠ΅Π±Π΅ Π½Π΅ ΠΈΠΌΠ΅Π΅Ρ Π΄Π°Π½Π½ΡΡ ΠΈΠ»ΠΈ ΠΏΠΎΠ²Π΅Π΄Π΅Π½ΠΈΡ.
Component (ΠΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ)
ΠΠΎΠ½ΡΠ΅ΠΉΠ½Π΅Ρ Π΄Π°Π½Π½ΡΡ , ΠΊΠΎΡΠΎΡΡΠΉ ΠΏΡΠΈΠΊΡΠ΅ΠΏΠ»ΡΠ΅ΡΡΡ ΠΊ ΡΡΡΠ½ΠΎΡΡΡΠΌ. ΠΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ ΡΠΎΠ΄Π΅ΡΠΆΠ°Ρ ΡΠΎΠ»ΡΠΊΠΎ Π΄Π°Π½Π½ΡΠ΅, Π½ΠΎ Π½Π΅ Π»ΠΎΠ³ΠΈΠΊΡ.
System (Π‘ΠΈΡΡΠ΅ΠΌΠ°)
ΠΠΎΠ³ΠΈΠΊΠ°, ΠΊΠΎΡΠΎΡΠ°Ρ ΠΎΠ±ΡΠ°Π±Π°ΡΡΠ²Π°Π΅Ρ ΡΡΡΠ½ΠΎΡΡΠΈ Ρ ΠΎΠΏΡΠ΅Π΄Π΅Π»Π΅Π½Π½ΡΠΌΠΈ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°ΠΌΠΈ. Π‘ΠΈΡΡΠ΅ΠΌΡ ΡΠΎΠ΄Π΅ΡΠΆΠ°Ρ ΡΠΎΠ»ΡΠΊΠΎ ΠΏΠΎΠ²Π΅Π΄Π΅Π½ΠΈΠ΅, Π½ΠΎ Π½Π΅ Π΄Π°Π½Π½ΡΠ΅.
ΠΡΠ΅ΠΈΠΌΡΡΠ΅ΡΡΠ²Π° ECS
- ΠΠΎΠΌΠΏΠΎΠ·ΠΈΡΠΈΡ Π²ΠΌΠ΅ΡΡΠΎ Π½Π°ΡΠ»Π΅Π΄ΠΎΠ²Π°Π½ΠΈΡ: ΠΠ±ΡΠ΅ΠΊΡΡ ΡΠΎΠ·Π΄Π°ΡΡΡΡ ΠΏΡΡΠ΅ΠΌ ΠΊΠΎΠΌΠ±ΠΈΠ½ΠΈΡΠΎΠ²Π°Π½ΠΈΡ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠ²
- Π Π°Π·Π΄Π΅Π»Π΅Π½ΠΈΠ΅ Π΄Π°Π½Π½ΡΡ ΠΈ Π»ΠΎΠ³ΠΈΠΊΠΈ: Π£Π»ΡΡΡΠ°Π΅Ρ ΠΎΡΠ³Π°Π½ΠΈΠ·Π°ΡΠΈΡ ΠΊΠΎΠ΄Π° ΠΈ ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡΠ΅Π»ΡΠ½ΠΎΡΡΡ
- ΠΠ°ΡΠ°Π»Π»Π΅Π»ΡΠ½Π°Ρ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠ°: Π‘ΠΈΡΡΠ΅ΠΌΡ ΠΌΠΎΠ³ΡΡ ΡΠ°Π±ΠΎΡΠ°ΡΡ ΠΏΠ°ΡΠ°Π»Π»Π΅Π»ΡΠ½ΠΎ Ρ ΡΠ°Π·Π½ΡΠΌΠΈ Π³ΡΡΠΏΠΏΠ°ΠΌΠΈ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠ²
- ΠΡΡ-ΡΡΡΠ΅ΠΊΡΠΈΠ²Π½ΠΎΡΡΡ: ΠΠ°Π½Π½ΡΠ΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠ² Ρ ΡΠ°Π½ΡΡΡΡ Π² Π½Π΅ΠΏΡΠ΅ΡΡΠ²Π½ΡΡ ΠΌΠ°ΡΡΠΈΠ²Π°Ρ
- ΠΠΈΠ±ΠΊΠΎΡΡΡ: ΠΠ΅Π³ΠΊΠΎ Π΄ΠΎΠ±Π°Π²Π»ΡΡΡ, ΡΠ΄Π°Π»ΡΡΡ ΠΈ ΠΈΠ·ΠΌΠ΅Π½ΡΡΡ ΠΏΠΎΠ²Π΅Π΄Π΅Π½ΠΈΠ΅ ΠΎΠ±ΡΠ΅ΠΊΡΠΎΠ²
ΠΡΡ ΠΈΡΠ΅ΠΊΡΡΡΠ° ECS Π² Wudgine
Π‘ΡΡΠ½ΠΎΡΡΠΈ (Entities)
Π Wudgine ΡΡΡΠ½ΠΎΡΡΠΈ ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»Π΅Π½Ρ ΠΊΠ°ΠΊ ΠΏΡΠΎΡΡΡΠ΅ ΠΈΠ΄Π΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΎΡΡ:
// Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΡΡΡΠ½ΠΎΡΡΠΈ
Entity player = world.createEntity("Player");
// ΠΡΠΎΠ²Π΅ΡΠΊΠ° Π²Π°Π»ΠΈΠ΄Π½ΠΎΡΡΠΈ ΡΡΡΠ½ΠΎΡΡΠΈ
if (player.isValid()) {
// Π Π°Π±ΠΎΡΠ° Ρ ΡΡΡΠ½ΠΎΡΡΡΡ
}
// Π£Π½ΠΈΡΡΠΎΠΆΠ΅Π½ΠΈΠ΅ ΡΡΡΠ½ΠΎΡΡΠΈ
world.destroyEntity(player);
ΠΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ (Components)
ΠΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ β ΡΡΠΎ ΡΡΡΡΠΊΡΡΡΡ Π΄Π°Π½Π½ΡΡ , ΠΊΠΎΡΠΎΡΡΠ΅ ΠΌΠΎΠΆΠ½ΠΎ ΠΏΡΠΈΠΊΡΠ΅ΠΏΠ»ΡΡΡ ΠΊ ΡΡΡΠ½ΠΎΡΡΡΠΌ:
// ΠΠΏΡΠ΅Π΄Π΅Π»Π΅Π½ΠΈΠ΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°
struct TransformComponent {
Vector3 position = Vector3(0.0f);
Quaternion rotation = Quaternion::identity();
Vector3 scale = Vector3(1.0f);
};
// ΠΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ° ΠΊ ΡΡΡΠ½ΠΎΡΡΠΈ
auto& transform = player.addComponent<TransformComponent>();
transform.position = Vector3(0.0f, 1.0f, 0.0f);
// ΠΠΎΠ»ΡΡΠ΅Π½ΠΈΠ΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°
if (player.hasComponent<TransformComponent>()) {
auto& transform = player.getComponent<TransformComponent>();
transform.position.y += 1.0f;
}
// Π£Π΄Π°Π»Π΅Π½ΠΈΠ΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°
player.removeComponent<TransformComponent>();
Π‘ΠΈΡΡΠ΅ΠΌΡ (Systems)
Π‘ΠΈΡΡΠ΅ΠΌΡ ΠΎΠ±ΡΠ°Π±Π°ΡΡΠ²Π°ΡΡ ΡΡΡΠ½ΠΎΡΡΠΈ Ρ ΠΎΠΏΡΠ΅Π΄Π΅Π»Π΅Π½Π½ΡΠΌΠΈ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°ΠΌΠΈ:
// ΠΠΏΡΠ΅Π΄Π΅Π»Π΅Π½ΠΈΠ΅ ΡΠΈΡΡΠ΅ΠΌΡ
class MovementSystem : public System {
public:
void update(float deltaTime) override {
// ΠΠ±ΡΠ°Π±ΠΎΡΠΊΠ° Π²ΡΠ΅Ρ
ΡΡΡΠ½ΠΎΡΡΠ΅ΠΉ Ρ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°ΠΌΠΈ Transform ΠΈ Velocity
world.each<TransformComponent, VelocityComponent>(
[deltaTime](Entity entity, TransformComponent& transform, VelocityComponent& velocity) {
transform.position += velocity.value * deltaTime;
}
);
}
};
// Π Π΅Π³ΠΈΡΡΡΠ°ΡΠΈΡ ΡΠΈΡΡΠ΅ΠΌΡ
world.registerSystem<MovementSystem>();
ΠΠΈΠ·Π½Π΅Π½Π½ΡΠΉ ΡΠΈΠΊΠ» ECS
- ΠΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·Π°ΡΠΈΡ: Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΌΠΈΡΠ°, ΡΠ΅Π³ΠΈΡΡΡΠ°ΡΠΈΡ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠ² ΠΈ ΡΠΈΡΡΠ΅ΠΌ
- ΠΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅: ΠΡΠΏΠΎΠ»Π½Π΅Π½ΠΈΠ΅ ΡΠΈΡΡΠ΅ΠΌ Π² ΠΎΠΏΡΠ΅Π΄Π΅Π»Π΅Π½Π½ΠΎΠΌ ΠΏΠΎΡΡΠ΄ΠΊΠ΅
- ΠΠ°Π²Π΅ΡΡΠ΅Π½ΠΈΠ΅: ΠΡΠ²ΠΎΠ±ΠΎΠΆΠ΄Π΅Π½ΠΈΠ΅ ΡΠ΅ΡΡΡΡΠΎΠ² ΠΈ ΡΠ½ΠΈΡΡΠΎΠΆΠ΅Π½ΠΈΠ΅ ΠΌΠΈΡΠ°
// ΠΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·Π°ΡΠΈΡ
World world;
world.registerComponent<TransformComponent>();
world.registerComponent<VelocityComponent>();
world.registerSystem<MovementSystem>();
world.registerSystem<RenderSystem>();
// ΠΠ³ΡΠΎΠ²ΠΎΠΉ ΡΠΈΠΊΠ»
while (game.isRunning()) {
float deltaTime = timer.getDeltaTime();
// ΠΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ Π²ΡΠ΅Ρ
ΡΠΈΡΡΠ΅ΠΌ
world.update(deltaTime);
// Π Π΅Π½Π΄Π΅ΡΠΈΠ½Π³
renderer.render();
}
// ΠΠ°Π²Π΅ΡΡΠ΅Π½ΠΈΠ΅
world.destroy();
ΠΠ°ΠΏΡΠΎΡΡ ΠΈ ΡΠΈΠ»ΡΡΡΡ
Wudgine ΠΏΡΠ΅Π΄ΠΎΡΡΠ°Π²Π»ΡΠ΅Ρ ΠΌΠΎΡΠ½ΡΠΉ API Π΄Π»Ρ Π·Π°ΠΏΡΠΎΡΠΎΠ² ΡΡΡΠ½ΠΎΡΡΠ΅ΠΉ Ρ ΠΎΠΏΡΠ΅Π΄Π΅Π»Π΅Π½Π½ΡΠΌΠΈ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°ΠΌΠΈ:
// ΠΠΎΠ»ΡΡΠ΅Π½ΠΈΠ΅ Π²ΡΠ΅Ρ
ΡΡΡΠ½ΠΎΡΡΠ΅ΠΉ Ρ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°ΠΌΠΈ Transform ΠΈ Renderer
world.each<TransformComponent, MeshRendererComponent>(
[](Entity entity, TransformComponent& transform, MeshRendererComponent& renderer) {
// ΠΠ±ΡΠ°Π±ΠΎΡΠΊΠ° ΡΡΡΠ½ΠΎΡΡΠΈ
}
);
// ΠΠΎΠ»ΡΡΠ΅Π½ΠΈΠ΅ ΡΡΡΠ½ΠΎΡΡΠ΅ΠΉ Ρ Transform ΠΈ Renderer, Π½ΠΎ Π±Π΅Π· Physics
world.each<TransformComponent, MeshRendererComponent>()
.exclude<PhysicsComponent>()
.forEach([](Entity entity, TransformComponent& transform, MeshRendererComponent& renderer) {
// ΠΠ±ΡΠ°Π±ΠΎΡΠΊΠ° ΡΡΡΠ½ΠΎΡΡΠΈ
});
// Π‘ΠΎΡΡΠΈΡΠΎΠ²ΠΊΠ° ΡΡΡΠ½ΠΎΡΡΠ΅ΠΉ ΠΏΠΎ ΡΠ°ΡΡΡΠΎΡΠ½ΠΈΡ ΠΎΡ ΠΊΠ°ΠΌΠ΅ΡΡ
world.each<TransformComponent, MeshRendererComponent>()
.sort([cameraPos](const TransformComponent& a, const TransformComponent& b) {
float distA = Vector3::distance(a.position, cameraPos);
float distB = Vector3::distance(b.position, cameraPos);
return distA < distB;
})
.forEach([](Entity entity, TransformComponent& transform, MeshRendererComponent& renderer) {
// ΠΠ±ΡΠ°Π±ΠΎΡΠΊΠ° ΡΡΡΠ½ΠΎΡΡΠ΅ΠΉ Π² ΠΎΡΡΠΎΡΡΠΈΡΠΎΠ²Π°Π½Π½ΠΎΠΌ ΠΏΠΎΡΡΠ΄ΠΊΠ΅
});
Π‘ΠΎΠ±ΡΡΠΈΡ ΠΈ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡ
Wudgine Π²ΠΊΠ»ΡΡΠ°Π΅Ρ ΡΠΈΡΡΠ΅ΠΌΡ ΡΠΎΠ±ΡΡΠΈΠΉ Π΄Π»Ρ ΠΊΠΎΠΌΠΌΡΠ½ΠΈΠΊΠ°ΡΠΈΠΈ ΠΌΠ΅ΠΆΠ΄Ρ ΡΠΈΡΡΠ΅ΠΌΠ°ΠΌΠΈ:
// ΠΠΏΡΠ΅Π΄Π΅Π»Π΅Π½ΠΈΠ΅ ΡΠΎΠ±ΡΡΠΈΡ
struct CollisionEvent {
Entity entityA;
Entity entityB;
Vector3 contactPoint;
Vector3 contactNormal;
};
// ΠΡΠΏΡΠ°Π²ΠΊΠ° ΡΠΎΠ±ΡΡΠΈΡ
world.emit<CollisionEvent>({player, enemy, contactPoint, contactNormal});
// ΠΠΎΠ΄ΠΏΠΈΡΠΊΠ° Π½Π° ΡΠΎΠ±ΡΡΠΈΠ΅
world.subscribe<CollisionEvent>([](const CollisionEvent& event) {
// ΠΠ±ΡΠ°Π±ΠΎΡΠΊΠ° ΡΠΎΠ±ΡΡΠΈΡ ΡΡΠΎΠ»ΠΊΠ½ΠΎΠ²Π΅Π½ΠΈΡ
Debug::log("Collision between {} and {}",
event.entityA.getName(),
event.entityB.getName());
});
Π‘Π΅ΡΠΈΠ°Π»ΠΈΠ·Π°ΡΠΈΡ
Wudgine ΠΏΠΎΠ΄Π΄Π΅ΡΠΆΠΈΠ²Π°Π΅Ρ ΡΠ΅ΡΠΈΠ°Π»ΠΈΠ·Π°ΡΠΈΡ ΠΈ Π΄Π΅ΡΠ΅ΡΠΈΠ°Π»ΠΈΠ·Π°ΡΠΈΡ ΡΡΡΠ½ΠΎΡΡΠ΅ΠΉ ΠΈ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠ²:
// Π‘Π΅ΡΠΈΠ°Π»ΠΈΠ·Π°ΡΠΈΡ ΡΡΡΠ½ΠΎΡΡΠΈ Π² JSON
Entity player = world.createEntity("Player");
player.addComponent<TransformComponent>();
player.addComponent<PlayerComponent>();
json playerJson = EntitySerializer::toJson(player);
// ΠΠ΅ΡΠ΅ΡΠΈΠ°Π»ΠΈΠ·Π°ΡΠΈΡ ΡΡΡΠ½ΠΎΡΡΠΈ ΠΈΠ· JSON
Entity loadedPlayer = EntitySerializer::fromJson(world, playerJson);
Π‘Π»Π΅Π΄ΡΡΡΠΈΠ΅ ΡΠ°Π³ΠΈ
Π’Π΅ΠΏΠ΅ΡΡ, ΠΊΠΎΠ³Π΄Π° Π²Ρ ΠΎΠ·Π½Π°ΠΊΠΎΠΌΠΈΠ»ΠΈΡΡ Ρ ΠΎΡΠ½ΠΎΠ²Π°ΠΌΠΈ ECS Π² Wudgine, ΡΠ΅ΠΊΠΎΠΌΠ΅Π½Π΄ΡΠ΅ΠΌ: