27
|
1 #############################################################################
|
|
2 #
|
|
3 # Copyright (c) 2010 by Casey Duncan and contributors
|
|
4 # All Rights Reserved.
|
|
5 #
|
|
6 # This software is subject to the provisions of the MIT License
|
|
7 # A copy of the license should accompany this distribution.
|
|
8 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
9 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
10 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
11 #
|
|
12 #############################################################################
|
|
13 """Grease entities are useful as actionable, interactive
|
|
14 game elements that are often visible to the player.
|
|
15
|
|
16 You might use entities to represent:
|
|
17
|
|
18 - Characters
|
|
19 - Bullets
|
|
20 - Particles
|
|
21 - Pick-ups
|
|
22 - Space Ships
|
|
23 - Weapons
|
|
24 - Trees
|
|
25 - Planets
|
|
26 - Explosions
|
|
27
|
|
28 See :ref:`an example entity class in the tutorial <tut-entity-example>`.
|
|
29 """
|
|
30
|
|
31 __version__ = '$Id$'
|
|
32
|
|
33 __all__ = ('Entity', 'EntityComponentAccessor', 'ComponentEntitySet')
|
|
34
|
|
35
|
|
36 class EntityMeta(type):
|
|
37 """The entity metaclass enforces fixed slots of `entity_id` and `world`
|
|
38 for all subclasses. This prevents accidental use of other entity instance
|
|
39 attributes, which may not be saved.
|
|
40
|
|
41 Class attributes are not affected by this restriction, but subclasses
|
|
42 should be careful not to cause name collisions with world components,
|
|
43 which are exposed as entity attributes. Using a naming convention for
|
|
44 class attributes, such as UPPER_CASE_WITH_UNDERSCORES is recommended to
|
|
45 avoid name clashes.
|
|
46
|
|
47 Note as a result of this, entity subclasses are not allowed to define
|
|
48 `__slots__`, and doing so will cause a `TypeError` to be raised.
|
|
49 """
|
|
50
|
|
51 def __new__(cls, name, bases, clsdict):
|
|
52 if '__slots__' in clsdict:
|
|
53 raise TypeError('__slots__ may not be defined in Entity subclasses')
|
|
54 clsdict['__slots__'] = ('world', 'entity_id')
|
|
55 return type.__new__(cls, name, bases, clsdict)
|
|
56
|
|
57
|
|
58 class Entity(object):
|
|
59 """Base class for grease entities.
|
|
60
|
|
61 Entity objects themselves are merely identifiers within a :class:`grease.world.World`.
|
|
62 They also provide a facade for convenient entity-wise access of component
|
|
63 data. However, they do not contain any data themselves other than an
|
|
64 entity id.
|
|
65
|
|
66 Entities must be instantiated in the context of a world. To instantiate an
|
|
67 entity, you must pass the world as the first argument to the constructor.
|
|
68 Subclasses that implement the :meth:`__init__()` method, must accept the world
|
|
69 as their first argument (after ``self``). Other constructor arguments can be
|
|
70 specified arbitarily by the subclass.
|
|
71 """
|
|
72 __metaclass__ = EntityMeta
|
|
73
|
|
74 def __new__(cls, world, *args, **kw):
|
|
75 """Create a new entity and add it to the world"""
|
|
76 entity = object.__new__(cls)
|
|
77 entity.world = world
|
|
78 entity.entity_id = world.new_entity_id()
|
|
79 world.entities.add(entity)
|
|
80 return entity
|
|
81
|
|
82 def __getattr__(self, name):
|
|
83 """Return an :class:`EntityComponentAccessor` for this entity
|
|
84 for the component named.
|
|
85
|
|
86 Example::
|
|
87
|
|
88 my_entity.movement
|
|
89 """
|
|
90 component = getattr(self.world.components, name)
|
|
91 return EntityComponentAccessor(component, self)
|
|
92
|
|
93 def __setattr__(self, name, value):
|
|
94 """Set the entity data in the named component for this entity.
|
|
95 This sets the values of the component fields to the values of
|
|
96 the matching attributes of the value provided. This value must
|
|
97 have attributes for each of the component fields.
|
|
98
|
|
99 This allows you to easily copy component data from one entity
|
|
100 to another.
|
|
101
|
|
102 Example::
|
|
103
|
|
104 my_entity.position = other_entity.position
|
|
105 """
|
|
106 if name in self.__class__.__slots__:
|
|
107 super(Entity, self).__setattr__(name, value)
|
|
108 else:
|
|
109 component = getattr(self.world.components, name)
|
|
110 component.set(self, value)
|
|
111
|
|
112 def __delattr__(self, name):
|
|
113 """Remove this entity and its data from the component.
|
|
114
|
|
115 Example::
|
|
116
|
|
117 del my_entity.renderable
|
|
118 """
|
|
119 component = getattr(self.world.components, name)
|
|
120 del component[self]
|
|
121
|
|
122 def __hash__(self):
|
|
123 return self.entity_id
|
|
124
|
|
125 def __eq__(self, other):
|
|
126 return self.world is other.world and self.entity_id == other.entity_id
|
|
127
|
|
128 def __repr__(self):
|
|
129 return "<%s id: %s of %s %x>" % (
|
|
130 self.__class__.__name__, self.entity_id,
|
|
131 self.world.__class__.__name__, id(self.world))
|
|
132
|
|
133 def delete(self):
|
|
134 """Delete the entity from its world. This removes all of its
|
|
135 component data. If then entity has already been deleted,
|
|
136 this call does nothing.
|
|
137 """
|
|
138 self.world.entities.discard(self)
|
|
139
|
|
140 @property
|
|
141 def exists(self):
|
|
142 """True if the entity still exists in the world"""
|
|
143 return self in self.world.entities
|
|
144
|
|
145
|
|
146 class EntityComponentAccessor(object):
|
|
147 """A facade for accessing specific component data for a single entity.
|
|
148 The implementation is lazy and does not actually access the component
|
|
149 data until needed. If an attribute is set for a component that the
|
|
150 entity is not yet a member of, it is automatically added to the
|
|
151 component first.
|
|
152
|
|
153 :param component: The :class:`grease.Component` being accessed
|
|
154 :param entity: The :class:`Entity` being accessed
|
|
155 """
|
|
156
|
|
157 # beware, name mangling ahead. We want to avoid clashing with any
|
|
158 # user-configured component field names
|
|
159 __data = None
|
|
160
|
|
161 def __init__(self, component, entity):
|
|
162 clsname = self.__class__.__name__
|
|
163 self.__dict__['_%s__component' % clsname] = component
|
|
164 self.__dict__['_%s__entity' % clsname] = entity
|
|
165
|
|
166 def __nonzero__(self):
|
|
167 """The accessor is True if the entity is in the component,
|
|
168 False if not, for convenient membership tests
|
|
169 """
|
|
170 return self.__entity in self.__component
|
|
171
|
|
172 def __getattr__(self, name):
|
|
173 """Return the data for the specified field of the entity's component"""
|
|
174 if self.__data is None:
|
|
175 try:
|
|
176 data = self.__component[self.__entity]
|
|
177 except KeyError:
|
|
178 raise AttributeError(name)
|
|
179 clsname = self.__class__.__name__
|
|
180 self.__dict__['_%s__data' % clsname] = data
|
|
181 return getattr(self.__data, name)
|
|
182
|
|
183 def __setattr__(self, name, value):
|
|
184 """Set the data for the specified field of the entity's component"""
|
|
185 if self.__data is None:
|
|
186 clsname = self.__class__.__name__
|
|
187 if self.__entity in self.__component:
|
|
188 self.__dict__['_%s__data' % clsname] = self.__component[self.__entity]
|
|
189 else:
|
|
190 self.__dict__['_%s__data' % clsname] = self.__component.set(self.__entity)
|
|
191 setattr(self.__data, name, value)
|
|
192
|
|
193
|
|
194 class ComponentEntitySet(set):
|
|
195 """Set of entities in a component, can be queried by component fields"""
|
|
196
|
|
197 _component = None
|
|
198
|
|
199 def __init__(self, component, entities=()):
|
|
200 self.__dict__['_component'] = component
|
|
201 super(ComponentEntitySet, self).__init__(entities)
|
|
202
|
|
203 def __getattr__(self, name):
|
|
204 if self._component is not None and name in self._component.fields:
|
|
205 return self._component.fields[name].accessor(self)
|
|
206 raise AttributeError(name)
|
|
207
|
|
208 def __setattr__(self, name, value):
|
|
209 if self._component is not None and name in self._component.fields:
|
|
210 self._component.fields[name].accessor(self).__set__(value)
|
|
211 raise AttributeError(name)
|
|
212
|