Package: ecstremity
ECStremityECStremityis an Entity-Component library. It is a Python port of the JavaScript librarygeoticby Dalton Mills.entity: a unique id and a collection of componentscomponent: a data containerquery: a way to gather collections of entities that match some criteria, for use in systemsevent: a message to an entity and its componentsInstallationpip install ecstremityUsageTo start usingECStremity, import the library and make some components.fromecstremityimport(Engine,Component)ecs=Engine()classPosition(Component):def__init__(self,x:int,y:int)->None:self.x=xself.y=yclassVelocity(Component):def__init__(self,x:int,y:int)->None:self.x=xself.y=yclassFrozen(Component):"""Tag component denoting a frozen character."""All components must be registered with the engine. Component registration must use the class symbol (i.e. do not use the component name attribute).ecs.register_component(Position)ecs.register_component(Velocity)ecs.register_component(Frozen)Instruct the engine to make a new entity, then add components to it.
Once a component is registered, it can be accessed using the class symbol or a string representing the class. The name attribute is not case-sensitive.entity=ecs.create_entity()entity.add(Position)entity.add("Velocity")The ecstremity library has no actual "system" class. Instead, instruct the engine to produce a query. For example, make a query that tracks all components that have both aPositionandVelocitycomponent, but not aFrozencomponent. A query can have any combination of theall_of,any_of, andnone_ofquantifiers.kinematics=ecs.create_query(all_of=['Position','Velocity'],none_of=['Frozen'])Loop over the result set to update the position for all entities in the query. The query will always return an up-to-date list containing entities that match.defloop(dt):forentityinkinematics.result:entity['Position'].x+=entity['Velocity'].x*dtentity['Position'].y+=entity['Velocity'].y*dtChangelogv.1.0.1Initial releasev.1.0.2Changed how component names are handled. Previously creating a component required setting a class variablenamewith a string in all-caps that is identical to the class name, e.g. if a component was created asclass Position,
the class required a variablename = "POSITION". Now all components inherit fromcomponentmetawhich handles
this automatically. All references to component names inside the engine also convert the name string to the required
casing.Added the ability to make use of theEntityEventsystem. Useentity.fire_event('event_name', data)where data can
be any object (typically a dict) that you want to pass to an entity's components. The'event_name'should have a
correspondingon_event_namemethod on one or more components of the entity, which will have the event passed to it.Added a prefab system. This is a work-in-progress addition, but essentially you can now define component structures
that can be applied all at once to an entity, allowing for templating of entity types.v.1.0.3Miscellaneous fixes and performance updates.Fixed an issue with queries not updating their cache when components are added/removed from an entity.v.1.0.4Added anEngineAdapterclass that allows for passing in a reference to the game client.Added entity cloning. Useentity.clone()to make a copy of an entity with all attached components.Added anEventDataclass to pass in as the data argument ofentity.fire_event. This base class is meant to be
extensible, but by default it has five optional parameters:instigator: EntityUsed to pass reference to the entity that fired the event.target: Union[Tuple[int, int], Entity]Used to pass reference to an entity or position that can be used for various things, like forwarding an event or querying for data.interactions: List[Dict[str, str]]Used to get back a list of interactions from a component. Typical format is{'name': 'event_name', 'event': 'on_event_method'}.callback: Callable[[Any], Any]A callback that can be executed inside a component.cost: floatAn event cost, for use with energy-based action systems.AddedEntityEvent.routeto trigger forwarding of an event to a target entity. For example, in my project gameAnathema,
I use this to query a target entity for interactions, say when bumping into it:classLegs(Component):# ...defon_try_move(self,evt:EntityEvent)->None:ifself.area.is_blocked(*evt.data.target):ifself.area.is_interactable(*evt.data.target):self.entity.fire_event('try_interact',evt.data)and then in a separate component:classBrain(Component):# ...defon_try_interact(self,evt:EntityEvent)->None:evt.data.instigator=self.entityevt.data.interactions=[]target:Entity=self.client.interaction_system.get(*evt.dat.target)routed_evt:EntityEvent=evt.route(new_event='get_interactions',target=target)routed_evt.handle()Finally, on a component attached to the target entity, I might have:classContainer(Component):# ...defon_get_interactions(self,evt)->None:ifself._is_open:evt.data.interactions.append({"name":"Close","event":"try_close_container"})# ...Which requires a correspondingContainer.on_try_close_container, and so forth.