Grease logo

Source code for grease.entity

#############################################################################
#
# Copyright (c) 2010 by Casey Duncan and contributors
# All Rights Reserved.
#
# This software is subject to the provisions of the MIT License
# A copy of the license should accompany this distribution.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
#
#############################################################################
"""Grease entities are useful as actionable, interactive
game elements that are often visible to the player.

You might use entities to represent:

- Characters
- Bullets
- Particles
- Pick-ups
- Space Ships
- Weapons
- Trees
- Planets
- Explosions

See :ref:`an example entity class in the tutorial <tut-entity-example>`.
"""

__version__ = '$Id$'

__all__ = ('Entity', 'EntityComponentAccessor', 'ComponentEntitySet')


class EntityMeta(type):
	"""The entity metaclass enforces fixed slots of `entity_id` and `world`
	for all subclasses. This prevents accidental use of other entity instance 
	attributes, which may not be saved. 
	
	Class attributes are not affected by this restriction, but subclasses
	should be careful not to cause name collisions with world components,
	which are exposed as entity attributes. Using a naming convention for
	class attributes, such as UPPER_CASE_WITH_UNDERSCORES is recommended to
	avoid name clashes.

	Note as a result of this, entity subclasses are not allowed to define
	`__slots__`, and doing so will cause a `TypeError` to be raised.
	"""

	def __new__(cls, name, bases, clsdict):
		if '__slots__' in clsdict:
			raise TypeError('__slots__ may not be defined in Entity subclasses')
		clsdict['__slots__'] = ('world', 'entity_id')
		return type.__new__(cls, name, bases, clsdict)


[docs]class Entity(object): """Base class for grease entities. Entity objects themselves are merely identifiers within a :class:`grease.world.World`. They also provide a facade for convenient entity-wise access of component data. However, they do not contain any data themselves other than an entity id. Entities must be instantiated in the context of a world. To instantiate an entity, you must pass the world as the first argument to the constructor. Subclasses that implement the :meth:`__init__()` method, must accept the world as their first argument (after ``self``). Other constructor arguments can be specified arbitarily by the subclass. """ __metaclass__ = EntityMeta def __new__(cls, world, *args, **kw): """Create a new entity and add it to the world""" entity = object.__new__(cls) entity.world = world entity.entity_id = world.new_entity_id() world.entities.add(entity) return entity
[docs] def __getattr__(self, name): """Return an :class:`EntityComponentAccessor` for this entity for the component named. Example:: my_entity.movement """ component = getattr(self.world.components, name) return EntityComponentAccessor(component, self)
[docs] def __setattr__(self, name, value): """Set the entity data in the named component for this entity. This sets the values of the component fields to the values of the matching attributes of the value provided. This value must have attributes for each of the component fields. This allows you to easily copy component data from one entity to another. Example:: my_entity.position = other_entity.position """ if name in self.__class__.__slots__: super(Entity, self).__setattr__(name, value) else: component = getattr(self.world.components, name) component.set(self, value)
[docs] def __delattr__(self, name): """Remove this entity and its data from the component. Example:: del my_entity.renderable """ component = getattr(self.world.components, name) del component[self]
def __hash__(self): return self.entity_id def __eq__(self, other): return self.world is other.world and self.entity_id == other.entity_id def __repr__(self): return "<%s id: %s of %s %x>" % ( self.__class__.__name__, self.entity_id, self.world.__class__.__name__, id(self.world))
[docs] def delete(self): """Delete the entity from its world. This removes all of its component data. If then entity has already been deleted, this call does nothing. """ self.world.entities.discard(self)
@property
[docs] def exists(self): """True if the entity still exists in the world""" return self in self.world.entities
[docs]class EntityComponentAccessor(object): """A facade for accessing specific component data for a single entity. The implementation is lazy and does not actually access the component data until needed. If an attribute is set for a component that the entity is not yet a member of, it is automatically added to the component first. :param component: The :class:`grease.Component` being accessed :param entity: The :class:`Entity` being accessed """ # beware, name mangling ahead. We want to avoid clashing with any # user-configured component field names __data = None def __init__(self, component, entity): clsname = self.__class__.__name__ self.__dict__['_%s__component' % clsname] = component self.__dict__['_%s__entity' % clsname] = entity
[docs] def __nonzero__(self): """The accessor is True if the entity is in the component, False if not, for convenient membership tests """ return self.__entity in self.__component
[docs] def __getattr__(self, name): """Return the data for the specified field of the entity's component""" if self.__data is None: try: data = self.__component[self.__entity] except KeyError: raise AttributeError(name) clsname = self.__class__.__name__ self.__dict__['_%s__data' % clsname] = data return getattr(self.__data, name)
[docs] def __setattr__(self, name, value): """Set the data for the specified field of the entity's component""" if self.__data is None: clsname = self.__class__.__name__ if self.__entity in self.__component: self.__dict__['_%s__data' % clsname] = self.__component[self.__entity] else: self.__dict__['_%s__data' % clsname] = self.__component.set(self.__entity) setattr(self.__data, name, value)
[docs]class ComponentEntitySet(set): """Set of entities in a component, can be queried by component fields""" _component = None def __init__(self, component, entities=()): self.__dict__['_component'] = component super(ComponentEntitySet, self).__init__(entities) def __getattr__(self, name): if self._component is not None and name in self._component.fields: return self._component.fields[name].accessor(self) raise AttributeError(name) def __setattr__(self, name, value): if self._component is not None and name in self._component.fields: self._component.fields[name].accessor(self).__set__(value) raise AttributeError(name)