comparison bGrease/entity.py @ 41:ff3e395abf91

Renamed grease to bGrease (Basic Grease) to get rid of conflicts with an already installed grease.
author KarstenBock@gmx.net
date Mon, 05 Sep 2011 15:00:34 +0200
parents grease/entity.py@bc88f7d5ca8b
children a9cc5559ec2a
comparison
equal deleted inserted replaced
40:2e3ab06a2f47 41:ff3e395abf91
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