Mercurial > parpg-core
comparison src/parpg/grease/entity.py @ 27:09b581087d68
Added base files for grease
author | KarstenBock@gmx.net |
---|---|
date | Tue, 12 Jul 2011 10:16:48 +0200 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
26:5529dd5644b8 | 27:09b581087d68 |
---|---|
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 |