ecs-entity
License: CC0 1.0 Universal
A minimal Entity type for the entity-component-system (ECS)
pattern. It is common in game development, but useful wherever a value is a bag of typed parts.
Unity users will recognize GameObject/MonoBehavior: entity.find(Transform) is GetComponent<Transform>(), entity.has(Transform) is TryGetComponent, and entity.get(Transform).
I think it is helpful to reimagine the Entity type in more generalized settings too:
- An
Entityis a poor man's HaskellData.Dependent.Map. - An
Entitycan be used as a service locator, likepython-injectorbut without the automatic dependency injection part. - An
Entityis a dictionary of components with type-safe access.
The library is minimal. There is no built-in entity ID logic, no entity manager, no game-world engine, or anything like it. You can build your own game architecture and use this library's Entity as a supporting type.
Install
uv add ecs-entity
# or
pip install ecs-entity
You may also directly copy src/ecs/entity.py into your
project.
Library Usage
Read the code documentation in src/ecs/entity.py. It is under 200 lines.
Here is a Minecraft example:
from dataclasses import dataclass
from ecs import Entity
Vec3 = tuple[float, float, float]
Quat = tuple[float, float, float, float]
MinecraftEntity = Entity # a player, a cow, a dropped item: all just component bags
@dataclass
class Transform:
"""Where the entity is, and which way it faces."""
position: Vec3
rotation: Quat
@dataclass
class Physics:
"""How the entity moves next tick."""
velocity: Vec3
acceleration: Vec3
@dataclass
class InventoryData:
items: tuple[str, ...]
@dataclass
class LivingBase:
"""A health bar."""
health: float
max_health: float
@dataclass
class PlayerBase:
"""Only players carry these."""
client_ip: str
gamemode: str
is_admin: bool
steve = MinecraftEntity.of(
Transform(position=(0.5, 64.0, 0.5), rotation=(0.0, 0.0, 0.0, 1.0)),
Physics(velocity=(0.0, 0.0, 0.0), acceleration=(0.0, -0.08, 0.0)),
LivingBase(health=20.0, max_health=20.0),
InventoryData(items=("diamond_sword", "bread")),
PlayerBase(client_ip="203.0.113.7", gamemode="survival", is_admin=False),
)
cow = MinecraftEntity.of(
Transform(position=(3.0, 64.0, 1.5), rotation=(0.0, 0.0, 0.0, 1.0)),
Physics(velocity=(0.0, 0.0, 0.0), acceleration=(0.0, -0.08, 0.0)),
LivingBase(health=10.0, max_health=10.0),
)
def is_player(entity: MinecraftEntity) -> bool:
"""A player is an entity that carries PlayerBase."""
return entity.has(PlayerBase)
assert is_player(steve) # true because steve carries PlayerBase
assert not is_player(cow)
assert steve.get(Transform).position == (0.5, 64.0, 0.5)
assert cow.find(InventoryData) is None
def tick(entity: MinecraftEntity) -> None:
"""One physics step: acceleration moves velocity, velocity moves position."""
physics = entity.find(Physics)
transform = entity.find(Transform)
if physics is None or transform is None:
raise RuntimeError("Input entity does not support physics!")
vx, vy, vz = physics.velocity
ax, ay, az = physics.acceleration
px, py, pz = transform.position
velocity = (vx + ax, vy + ay, vz + az)
position = (px + velocity[0], py + velocity[1], pz + velocity[2])
physics.velocity = velocity
transform.position = position
tick(cow)
assert cow.get(Physics).velocity == (0.0, -0.08, 0.0)
assert cow.get(Transform).position == (3.0, 63.92, 1.5)
def damage(entity: MinecraftEntity, amount: float) -> None:
"""Anything with a health bar can be hurt; this never asks what kind of entity it is."""
bar = entity.find(LivingBase)
if bar is None:
raise RuntimeError("Input entity does not support damage!")
bar.health = max(bar.health - amount, 0.0)
damage(cow, 4.0)
assert cow.get(LivingBase).health == 6.0
assert cow.get(LivingBase).max_health == 10.0
For the rest of the API, read src/ecs/entity.py.
Metadata
Release files for ecs-entity 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| ecs_entity-1.0.0.tar.gz | 7.6 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ecs_entity-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 15.8 kB
Release files / ecs_entity-1.0.0.tar.gz
| Download URL | ecs_entity-1.0.0.tar.gz |
|---|---|
| Size | 7.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f7e5d94ade3bccb91a8b907c83e25af5ea0578afed345879916a422dc10e1a44
|
|
BLAKE2b-256 checksum How to use checksums |
d1ec30e24b35649d651bd8c473c7e9f362b89553db1dc7f9a7f84fe8c5d9c420
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.15
|
Release files / ecs_entity-1.0.0-py3-none-any.whl
| Download URL | ecs_entity-1.0.0-py3-none-any.whl |
|---|---|
| Size | 8.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fae738e7ada63652c615dd4b69de291adc793b196a5963b1f4b3f875f9de0c16
|
|
BLAKE2b-256 checksum How to use checksums |
fa63baabb36530376809f16abab112a05269568a4b6d997a0329c6f6fc752d55
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.15
|
Security for ecs-entity 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.