PyBevy is in an early and experimental stage. The API is incomplete, subject to breaking changes without notice, and you should expect bugs. Many features are still under development.
Why an Entity Component System?
Understanding the philosophy behind pybevy's architecture.
Introduction
If you're coming from other Python game libraries like Pygame, you might be used to an Object-Oriented Programming (OOP) approach. You might create a Player class
that inherits from a GameObject class, and that Player class would have methods like update(), draw(), and handle_input().
pybevy uses a different pattern: the Entity Component System (ECS). This file explains why.
The Problem with Inheritance
Let's imagine building a game the traditional OOP way. class GameObject: def init(self, x, y): self.x = x self.y = y def update(self): pass def draw(self): pass
class Player(GameObject): def update(self): handle keyboard input to move ...
class Enemy(GameObject): def update(self): run AI logic to chase player ...
This seems simple enough. But what happens when we want to add more features?
- What if we want a stationary enemy? Does it inherit from
Enemybut we just don't move it? - What if we want a moving tree that sways in the wind? Does it inherit from
GameObjectand get anupdatemethod? - What if we want an enemy that can be controlled by a second player? Does it inherit from both
PlayerandEnemy? Python supports multiple inheritance, but it can get very messy (the "Diamond Problem").
This leads to deep, rigid inheritance hierarchies that are hard to change and reuse. This is the classic "composition over inheritance" problem.
The ECS Solution: Composition
ECS solves this by favoring composition. Instead of creating a "is-a" relationship (a Player is a GameObject), we create a "has-a" relationship (an entity has a Position, it has a Velocity).
- Entity: A simple ID. A "thing".
- Component: A piece of data. A property of a thing. (e.g.,
Position,Velocity,Health,PlayerInput). Components are just data structs/classes. - System: The logic. A function that operates on all entities that have a certain set of components.
Let's re-imagine our game in ECS:
Components: @Component class Position: ...
@Component class Velocity: ...
@Component class PlayerControlled: ... # A "marker" component with no data
@Component class EnemyAI: ...
Systems: def movement_system(query: Query[(Mut[Position], Velocity)]): for mut position, velocity in query: position.x += velocity.x position.y += velocity.y
def player_input_system(query: Query[Mut[Velocity], With[PlayerControlled]]): read keyboard and set velocity for player-controlled entities ...
def enemy_ai_system(query: Query[Mut[Velocity], With[EnemyAI]]): run AI logic and set velocity for AI-controlled entities ...
Now, our game objects are defined by the components they have:
- Player: An entity with
(Position, Velocity, PlayerControlled) - Moving Enemy: An entity with
(Position, Velocity, EnemyAI) - Stationary Enemy: An entity with
(Position, EnemyAI)(themovement_systemwon't run on it!) - Moving Tree: An entity with
(Position, Velocity)(and maybe aSwayingcomponent for a different system)
This is incredibly flexible. You can mix and match components to create any kind of object you can imagine without changing your inheritance structure.
The Benefits
- Flexibility: As shown above, it's trivial to create new types of game objects by just changing which components an entity has.
- Performance: ECS is very "data-oriented". Grouping all
Positioncomponents together in memory is very friendly to modern CPUs. It avoids jumping around in memory to find data, which is a major performance bottleneck. - Parallelism: Because systems declare what data they read and write, Bevy can automatically run non-conflicting systems in parallel across multiple CPU cores, giving you a significant performance boost for free.
While it can take a moment to get used to, the ECS pattern leads to cleaner, more scalable, and higher-performance code in the long run.
# This file is for explanation only and does not contain runnable code.
pass