jaynewey / stup

A Simple Python Entity Component System Framework.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

stup

GitHub Travis (.com)

A tiny, lightweight Entity Component System Framework for Python.

Fully functioning and usable for small practice projects. In development. Not recommended for production code.

Contents

Usage

Entity

Entities are are Universally Unique Identifiers (UUIDs) and nothing else. Entities can be instantiated directly and then added to the manager:

entity_manager = EntityManager()
entity = Entity()
entity_manager.add_entity(entity)

or both in one step with EntityManager.create_entity():

entity = entity_manager.create_entity()

EntityManager

The entity manager is responsible for handling entities, their components and systems. It is essentially a database of all entities and systems and is the link between entities and their components.
You will usually want to create an instance of an EntityManager:

entity_manager = EntityManager()

...And call its update() function every tick. This updates all of the Systems registered with the Entity Manager. Entities are not registered in the entity manager database at all unless they have components attached to them.

Because components aren't directly attached to entities (only through the entity manager), components become detached from an entity the moment it is removed from the manager. Thus, removed entities that are then re-added to the manager will no longer have their components unless you handle this yourself.

deltatime

EntityManager.update() takes a parameter deltatime which is a measurement of time between frames. You can utilise this however you like for framerate independence.

Component

Components are simply data holders and nothing more. You should inherit the Component class when writing components. Components should not contain logic and should only contain data.
An example position component class in a 2d game could be:

class PositionComponent(Component):  
	def __init__(self):
		self.x = 0
		self.y = 0

However you might prefer to use private attributes with getter/setter functions.

Adding and Removing Components

You can add and remove components from entities dynamically through the Entity Manager:

entity = Entity()
entity_manager = EntityManager()
entity_manager.add_component_to_entity(entity, PositionComponent())
entity_manager.remove_component_from_entity(entity, PositionComponent)

Notice that you must provide a Component instance when adding a component but only need provide a Component type when removing one.

System

Systems are for processing specific sets of entities. You should inherit the System class when writing systems. The update() method of your system is called every tick and should perform your logic, usually iterating through a Family. A Family is ensentially a set of Entity instances and should be assigned in the constructor.
Usually in a System you want to only perform logic on entities that have a specific Component type or set of Component types. For example, a movement system might only affect entities that have a position component and a velocity component, we use families to hold these for us, and we can obtain a Family of entities by using get_family from an EntityManager instance. Then, you would want to work with the components of the entities, so use EntityManager's get_component_map() to get all components of a type so you can access a Component by using an Entity as the key.
Here's how it might look:

class MovementSystem(System):
	def __init__(self, entity_manager):
		super().__init__()
		# Get the Family of entities with position and velocity components:
		self.family = entity_manager.get_family(PositionComponent, VelocityComponent)
		# Get the component maps so we can access the components using the entity as a key:
		self.movement_component_map = entity_manager.get_component_map(MovementComponent)
		self.position_component_map = entity_manager.get_component_map(PositionComponent)
		
	def update(self, deltatime):
		for entity in self.family:
			position = self.position_component_map[entity]
			velocity = self.velocity_component_map[entity]
			position.x += velocity.x * deltatime
			position.y += velocity.y * deltatime

Then, you can add an instance of this system to the Entity Manager:

movement_system = MovementSystem()
entity_manager.add_system(movement_system)

Systems can also be removed dynamically:

entity_manager.remove_system(movement_system)

System Priority

You may want to run systems in a given order, or prioritise some systems over others.

You can enable this behaviour when overriding the System constructor. Systems have a priority of 0 by default. This means that when not specified, systems have the lowest possible priority, and are executed in the order they're added to the manager.

Interact with this how you choose. But, for example, if you want to mimic the default constructor, and take a dynamic priority parameter:

class MovementSystem(System):
	def __init__(self, entity_manager, priority=0):
        super().__init__(priority=priority)
        # ...

You can then instantiate your system with a given priority, movement_system = MovementSystem(priority=1)

Systems with a higher priority will be executed first by the EntityManager.

IteratorSystem

Most systems will involve iterating over a family. To avoid redundant code, stup provides a handy utility class which will do this for you, called IteratorSystem.

In the constructor of your System, get the Family of entities as you normally would:

self.family = entity_manager.get_family(PositionComponent, VelocityComponent)

Rather than overriding the Update() function, override Iterator System's Process()function to do your logic and it will be applied to all entities in the family. The MovementSystem from before would look like this:

class MovementSystem(IteratorSystem):
	def __init__(self, entity_manager):
		super().__init__()
		# Get the Family of entities with position and velocity components:
		self.family = entity_manager.get_family(PositionComponent, VelocityComponent)
		# Get the component maps so we can access the components using the entity as a key:
		self.movement_component_map = entity_manager.get_component_map(MovementComponent)
		self.position_component_map = entity_manager.get_component_map(PositionComponent)		

	def process(self, deltatime, entity):
		position = self.position_component_map[entity]
		velocity = self.velocity_component_map[entity]
		position.x += velocity.x * deltatime
		position.y += velocity.y * deltatime

EntityListener

Entity listeners can be registered with an EntityManager instance. Listeners get notified whenever an entity is added or removed from the manager. This is useful if you want to do something upon one of those events.

To write your own listener, inherit the EntityListener class. You must override the abstract methods entity_added and entity_removed but any that aren't required can just pass.

For the purpose of example the following class will print the entity object when it is removed from the manager.

class PrintListener(EntityListener):
    def entity_added(self, entity):
        pass

    def entity_removed(self, entity, components):
        print(entity)

entity_added takes the added entity as a parameter, whereas entity_removed additionally takes the components that were attached to the entity in the system so that you can utilise them in your listener.

You must register a listener with an ÈntityManager instance for it to function. You can do so like this:

entity_manager = EntityManager()
print_listener = PrintListener()
entity_manager.add_listener(print_listener)

You can removed listeners from the manager, too:

entity_manager.remove_listener(print_listener)

About

A Simple Python Entity Component System Framework.

License:MIT License


Languages

Language:Python 100.0%