bear_hug.ecs module

Entity-component system.

Entities are just an ID and the container of components. The major way for them to do something useful should be components calling something like self.owner.that_other_component.do_stuff() or emitting events.

The creation of a new Entity is announced by the following event: BearEvent(event_type='ecs_create', event_value=entity)

It is an only event type that uses the actual entity object, not its ID, as event_value. When this event is emitted, the entity should be ready to work; in particular, all its components should be subscribed to the appropriate events.

Both Entities and Components can be serialized to JSON using repr(object) and then deserialized.

class bear_hug.ecs.Entity(id='Default ID', components=[])

Bases: object

A root entity class.

This is basically a container of components, and an ID.

Entity ID not checked for uniqueness during Entity creation, because it’s possible that the Entity object will be created before the queue is turned on (and, therefore, before EntityTracker knows anything about any entities), but having non-unique IDs is practically guaranteed to cause some entertaining glitches.

When the component is added to the Entity, its name (a component.name attribute) is added to entity.__dict__. This way, other components can then address it as self.owner.position or self.owner.widget or whatever. Names thus serve as something like slots, so that an entity couldn’t have multiple components for the same function. Possible names are not restricted in any way, but it is strongly recommended not to change them during inheritance between Component subclasses, and especially not to use the same name for any two components that could ever possibly be used within a single entity.

Parameters:
  • id – a string used as an Entity ID.
  • components – an iterable of Component instances that can will be added to this entity.
add_component(component)

Add a single component to the Entity.

Raises exception if Component.name is already in self.__dict__ and not in self.components. This allows overwriting components (should you want to change eg the entity’s widget), while protecting the non-Component properties.

Parameters:component – A Component instance.
remove_component(component_name)

Remove a single component from this entity.

Uses the Component.name, not an actual instance, as an argument. If the Entity doesn’t have such a component, raises BearECSException

Parameters:component_name – The name of a component to remove
Returns:
class bear_hug.ecs.Component(dispatcher, name='Root', owner=None)

Bases: bear_hug.widgets.Listener

A root component class.

Component name is expected to be the same between all components of the same general type (normally, base class for a given role, like position component, AI/input controller or a widget interface) and all its subclasses). Component inherits from Listener and is therefore able to receive and return BearEvent. Of course, it needs the correct subscriptions to actually get them.

repr(component) is used for serialization and should generate a valid JSON-encoded dict. It should always include a ‘class’ key which should equal the class name for that component and will be used by a deserializer to determine what to create. All other keys will be deserialized and treated as kwargs to a newly-created object. To define the deserialization protocol, JSON dict may also contain keys formatted as {kwarg_name}_dtype which should be a string and will be eval-ed during deserialization. Only Python’s builtin converters (eg str, int or float) are allowed; custom ones are currently unsupported.

For example, the following is a valid JSON:

` {"class": "TestComponent", "x": 5, "y": 5, "direction": "r", "former_owners": ["asd", "zxc", "qwe"], "former_owners_type": "set"} `

Its deserialization is equivalent to the following call:

x = TestComponent(x=5, y=5, direction='r', former_owners=set(['asd', 'zxc', 'qwe']))

The following keys are forbidden: ‘name’, ‘owner’, ‘dispatcher’. Kwarg validity is not controlled except by Component.__init__().

Parameters:
  • dispatcher – A queue that the component should subscribe to. Component.__init__() may use this to subscribe to whatever events it needs.
  • name – A name that will be added to Entity.__dict__. Should be hardcoded in all Component subclasses.
  • owner – the Entity (actual object, not ID) to which this object should attach.
set_owner(owner)

Register a component owner.

This is only useful if the component is passed from one owner to another, or if the component is created with the owner argument (thus attaching it immediately upon creation). This method calls owner’s add_component

Parameters:owner – an Entity to attach to.
on_event(event)

Component’s event callback. Should be overridden if subclasses want to process events.

Parameters:event – BearEvent instance
class bear_hug.ecs.EntityTracker

Bases: bear_hug.widgets.Listener

A singleton Listener that keeps track of all existing entities.

Listens to the ecs_add and ecs_destroy events, updating self.entities accordingly.

Can be used to look up an entity by its ID:

entity_called_id = EntityTracker.entities['entity_id']

Can also be used to get all entities that correspond to some criterion:

entity_iter = EntityTracker().filter_entities(lambda x: 'part_of_id' in x.id)

filter_entities(key=<function EntityTracker.<lambda>>)

Return all entities for which key evaluates to True.

Note that this method returns entity objects themselves, not the IDs.

Parameters:key – A single-arg callable
Returns:iterator of Entities
class bear_hug.ecs.WidgetComponent(dispatcher, widget, owner=None)

Bases: bear_hug.ecs.Component

Widget as a component.

This component is an ECS wrapper around the Widget object. Since Widgets can accept events and it is sometimes reasonable to keep some event logic in the Widget instead of Components (ie to keep animation running), its on_event method simply passes the events to the Widget. It also supports height, width and size properties, also by calling widget’s ones.

Parameters:widget – A Widget instance.
height

Height of the widget

width

Width of the widget

size

A (width, height) tuple

class bear_hug.ecs.SwitchWidgetComponent(*args, **kwargs)

Bases: bear_hug.ecs.WidgetComponent

A widget component that supports SwitchingWidget.

Provides methods to use its widget-switching abilities without other components having to call Widget object directly.

switch_to_image(image_id)

Switch widget to a necessary image.

If image ID is incorrect, the widget will raise BearException.

Parameters:image_id – image ID (str)
validate_image(image_id)

Return True if image_id is a valid ID for its widget

Parameters:image_id – image ID (str)
class bear_hug.ecs.PositionComponent(dispatcher, x=0, y=0, vx=0, vy=0, last_move=(1, 0), affect_z=True, owner=None)

Bases: bear_hug.ecs.Component

A component responsible for positioning an Entity on ECSLayout.

Parameters:
  • x – A position of top left corner along X axis.
  • y – A position of top left corner along Y axis
  • vx – Horizontal speed (chars per second)
  • vy – Vertical speed (chars per second)
  • affect_z – Set Z-level for widgets when placing. Default True
move(x, y, emit_event=True)

Move the Entity to a specified position.

Parameters:
  • x – x
  • y – y
  • emit_event – If True, emit an ‘esc_move’ event. There are a few cases (ie setting the coordinates after the component is created, but before the entity is added to the terminal) where this is undesirable.
relative_move(dx, dy, emit_event=True)

Move the Entity to a specified position relative to its current position.

Parameters:
  • dx – Movement along X axis, in chars
  • dy – Movement along Y axis, in chars
  • emit_event – gets passed to self.move() under the hood.
on_event(event)

Process tick, if dx != 0 or dy != 0

Parameters:event – A BearEvent instance
class bear_hug.ecs.DestructorComponent(*args, is_destroying=False, **kwargs)

Bases: bear_hug.ecs.Component

A component responsible for cleanly destroying its entity and everything that has to do with it.

When used, all owner’s components except this one are unsubscribed from all events. The deletion does not happen until tick end, to let any running interactions involving the owner finish cleanly.

destroy()

Destroy this component’s owner.

Unsubscribes owner and all its components from the queue and sends ‘ecs_remove’. Then all components are deleted. Entity itself is left at the mercy of garbage collector.

class bear_hug.ecs.CollisionComponent(*args, depth=0, z_shift=(0, 0), face_position=(0, 0), face_size=(0, 0), passable=False, **kwargs)

Bases: bear_hug.ecs.Component

A component responsible for processing collisions of this object.

Stores the following data:

depth: Int, a number of additional Z-levels over which collision is possible. Additional collisions are detected on lower Z-levels, ie the level where the object is displayed is always considered to be the front. Defaults to 0, ie collides only to the objects within its own Z-level.

z_shift: A 2-tuple of ints. Every next Z-level is offset from the previous one by this much, to create perspective. Defaults to (0, 0), ie no offset.

face_position: A tuple of ints describing upper left corner of the collidable part of the entity on the top Z-level. Defaults to (0, 0), ie the upper left corner of the widget is where the hitbox begins. This is a suitable default for flat items, but not for something drawn in perspective.

face_size: A tuple of ints describing the size of the collidable part of the entity on the top Z-level. If set to (0, 0), entire entity widget is considered collidable. Defaults to (0, 0). There is no method for making uncollidable entities via setting zero face size; for that, just create your entities without any CollisionComponent at all.

passable: whether collisions with this item should be blocking. This class by itself does nothing with this knowledge, but child classes may need it to make distinction between collisions where further movement is impossible (eg walls) and collisions that should be detected, but do not prevent movement (eg walking through fire). Defaults to False, ie blocking collision.

This is a base class, so its event processing just calls self.collided_into(other_entity) when owner moves into something, and self.collided_by(other_entity) when something else moves into the owner. Both methods do nothing by themselves;actual collision processing logic should be provided by subclasses.

Creating entities with the CollisionComponent but without either PositionComponent or WidgetComponent is just asking for trouble.

class bear_hug.ecs.WalkerCollisionComponent(*args, **kwargs)

Bases: bear_hug.ecs.CollisionComponent

A collision component that, upon colliding into something impassable (or screen edges), moves the entity back to where it came from.

Expects both entities involved to have a PositionComponent and a PassabilityComponent.

class bear_hug.ecs.DecayComponent(*args, destroy_condition='keypress', lifetime=1.0, age=0, **kwargs)

Bases: bear_hug.ecs.Component

Attaches to an entity and destroys it when conditions are met.

Expects the owner to have DestructorComponent.

Parameters:
  • destroy_condition – either ‘keypress’ or ‘timeout’
  • lifetime – time between entity creation and its destruction. Does nothing if destroy_condition is set to ‘keypress’. Defaults to 1 second.
  • age – the age of a given entity. Not meant to be set explicitly, except during deserialization.
class bear_hug.ecs.CollisionListener(*args, **kwargs)

Bases: bear_hug.widgets.Listener

A listener responsible for detecting collision

bear_hug.ecs.deserialize_component(serial, dispatcher)

Load the component from a JSON string or dict.

Does not subscribe a component to anything (which can be done either by a caller or in the ComponentClass.__init__) or assign it to any Entity (which is probably done within deserialize_entity). The class of a deserialized Component should be imported by the code that calls this function, or someone within its call stack.

If there is a risk that

Parameters:
  • serial – A valid JSON string or a dict produced by deserializing such a string.
  • dispatcher – A queue passed to the Component.__init__
Returns:

a Component instance.

bear_hug.ecs.deserialize_entity(serial, dispatcher)

Load the entity from JSON string or dict.

Does not subscribe a new entity to anything or emit bear_create events; this should be done by a caller. All components within the entity are deserialized by calls to deserialize_component

Parameters:serial – A valid JSON string or a dict produced by deserializing such a string.
Returns:an Entity instance