source: nagslang/game_object.py@ 276:3153196517fc

Last change on this file since 276:3153196517fc was 276:3153196517fc, checked in by Neil Muller <drnlmuller@…>, 8 years ago

Move protagonist to the world

File size: 9.7 KB
Line 
1import pymunk
2import pymunk.pygame_util
3
4import math
5
6from nagslang import puzzle
7from nagslang import render
8from nagslang.constants import (
9 SWITCH_PUSHERS, COLLISION_TYPE_SWITCH, COLLISION_TYPE_BOX, ZORDER_LOW,
10 ZORDER_FLOOR, COLLISION_TYPE_DOOR, COLLISION_TYPE_PROJECTILE)
11from nagslang.resources import resources
12from nagslang.events import DoorEvent
13
14
15def get_editable_game_objects():
16 classes = []
17 for cls_name, cls in globals().iteritems():
18 if isinstance(cls, type) and hasattr(cls, 'requires'):
19 classes.append((cls_name, cls))
20 return classes
21
22
23class Physicser(object):
24 def __init__(self, space):
25 self._space = space
26
27 def get_space(self):
28 return self._space
29
30 def set_space(self, new_space):
31 self._space = new_space
32
33 def set_game_object(self, game_object):
34 self.game_object = game_object
35
36 def get_shape(self):
37 raise NotImplementedError()
38
39 def add_to_space(self):
40 shape = self.get_shape()
41 self.get_space().add(shape)
42 if not shape.body.is_static:
43 self.get_space().add(shape.body)
44
45 def remove_from_space(self):
46 shape = self.get_shape()
47 self.get_space().remove(shape)
48 if not shape.body.is_static:
49 self.get_space().remove(shape.body)
50
51 def get_render_position(self, surface):
52 pos = self.get_shape().body.position
53 return pymunk.pygame_util.to_pygame(pos, surface)
54
55 def get_angle(self):
56 return self.get_shape().body.angle
57
58 def get_velocity(self):
59 return self.get_shape().body.velocity
60
61 def _get_position(self):
62 return self.get_shape().body.position
63
64 def _set_position(self, position):
65 self.get_shape().body.position = position
66
67 position = property(_get_position, _set_position)
68
69 def apply_impulse(self, j, r=(0, 0)):
70 return self.get_shape().body.apply_impulse(j, r)
71
72
73class SingleShapePhysicser(Physicser):
74 def __init__(self, space, shape):
75 super(SingleShapePhysicser, self).__init__(space)
76 self._shape = shape
77 shape.physicser = self
78
79 def get_shape(self):
80 return self._shape
81
82
83def damping_velocity_func(body, gravity, damping, dt):
84 """Apply custom damping to this body's velocity.
85 """
86 damping = getattr(body, 'damping', damping)
87 return pymunk.Body.update_velocity(body, gravity, damping, dt)
88
89
90def make_body(mass, moment, position, damping=None):
91 body = pymunk.Body(mass, moment)
92 body.position = tuple(position)
93 if damping is not None:
94 body.damping = damping
95 body.velocity_func = damping_velocity_func
96 return body
97
98
99class GameObject(object):
100 """A representation of a thing in the game world.
101
102 This has a rendery thing, physicsy things and maybe some other things.
103 """
104
105 zorder = ZORDER_LOW
106 is_moving = False # `True` if a movement animation should play.
107
108 def __init__(self, physicser, renderer, puzzler=None, overlay=None):
109 self.physicser = physicser
110 physicser.set_game_object(self)
111 self.physicser.add_to_space()
112 self.renderer = renderer
113 renderer.set_game_object(self)
114 self.puzzler = puzzler
115 if puzzler is not None:
116 puzzler.set_game_object(self)
117 self.overlay = overlay
118 if overlay is not None:
119 self.overlay.set_game_object(self)
120
121 def get_space(self):
122 return self.physicser.get_space()
123
124 def get_shape(self):
125 return self.physicser.get_shape()
126
127 def get_render_position(self, surface):
128 return self.physicser.get_render_position(surface)
129
130 def get_render_angle(self):
131 return self.physicser.get_angle()
132
133 def get_facing_direction(self):
134 """Used by rendererd that care what direction an object is facing.
135 """
136 return None
137
138 def render(self, surface):
139 return self.renderer.render(surface)
140
141 def animate(self):
142 self.renderer.animate()
143
144 def collide_with_protagonist(self, protagonist):
145 """Called as a `pre_solve` collision callback with the protagonist.
146
147 You can return `False` to ignore the collision, anything else
148 (including `None`) to process the collision as normal.
149 """
150 return True
151
152 @classmethod
153 def requires(cls):
154 """Hints for the level editor"""
155 return [("name", "string")]
156
157
158class FloorSwitch(GameObject):
159 zorder = ZORDER_FLOOR
160
161 def __init__(self, space, position):
162 body = make_body(None, None, position)
163 self.shape = pymunk.Circle(body, 30)
164 self.shape.collision_type = COLLISION_TYPE_SWITCH
165 self.shape.sensor = True
166 super(FloorSwitch, self).__init__(
167 SingleShapePhysicser(space, self.shape),
168 render.ImageStateRenderer({
169 True: resources.get_image('objects', 'sensor_on.png'),
170 False: resources.get_image('objects', 'sensor_off.png'),
171 }),
172 puzzle.CollidePuzzler(*SWITCH_PUSHERS),
173 )
174
175 @classmethod
176 def requires(cls):
177 return [("name", "string"), ("position", "coordinates")]
178
179
180class Note(GameObject):
181 zorder = ZORDER_FLOOR
182
183 def __init__(self, space, position, message):
184 body = make_body(None, None, position)
185 self.shape = pymunk.Circle(body, 30)
186 self.shape.sensor = True
187 super(Note, self).__init__(
188 SingleShapePhysicser(space, self.shape),
189 render.ImageRenderer(resources.get_image('objects', 'note.png')),
190 puzzle.CollidePuzzler(),
191 render.TextOverlay(message),
192 )
193
194 @classmethod
195 def requires(cls):
196 return [("name", "string"), ("position", "coordinates"),
197 ("message", "text")]
198
199
200class FloorLight(GameObject):
201 zorder = ZORDER_FLOOR
202
203 def __init__(self, space, position, state_source):
204 body = make_body(None, None, position)
205 self.shape = pymunk.Circle(body, 10)
206 self.shape.collision_type = COLLISION_TYPE_SWITCH
207 self.shape.sensor = True
208 super(FloorLight, self).__init__(
209 SingleShapePhysicser(space, self.shape),
210 render.ImageStateRenderer({
211 True: resources.get_image('objects', 'light_on.png'),
212 False: resources.get_image('objects', 'light_off.png'),
213 }),
214 puzzle.StateProxyPuzzler(state_source),
215 )
216
217 @classmethod
218 def requires(cls):
219 return [("name", "string"), ("position", "coordinates"),
220 ("state_source", "puzzler")]
221
222
223class Box(GameObject):
224 def __init__(self, space, position):
225 body = make_body(10, 10000, position, damping=0.5)
226 self.shape = pymunk.Poly(
227 body, [(-20, -20), (20, -20), (20, 20), (-20, 20)])
228 self.shape.friction = 0.5
229 self.shape.collision_type = COLLISION_TYPE_BOX
230 super(Box, self).__init__(
231 SingleShapePhysicser(space, self.shape),
232 render.ImageRenderer(resources.get_image('objects', 'crate.png')),
233 )
234
235 @classmethod
236 def requires(cls):
237 return [("name", "string"), ("position", "coordinates"),
238 ("state_source", "puzzler")]
239
240
241class Door(GameObject):
242 zorder = ZORDER_FLOOR
243
244 def __init__(self, space, position, destination, dest_pos, angle,
245 key_state=None):
246 body = make_body(pymunk.inf, pymunk.inf, position, damping=0.5)
247 self.shape = pymunk.Poly(
248 body, [(-4, -30), (4, -30), (4, 30), (-4, 30)])
249 self.shape.collision_type = COLLISION_TYPE_DOOR
250 self.shape.body.angle = float(angle) / 180 * math.pi
251 self.shape.sensor = True
252 self.destination = destination
253 self.dest_pos = tuple(dest_pos)
254 if key_state is None:
255 puzzler = puzzle.YesPuzzler()
256 else:
257 puzzler = puzzle.StateProxyPuzzler(key_state)
258 super(Door, self).__init__(
259 SingleShapePhysicser(space, self.shape),
260 render.ImageRenderer(resources.get_image('objects', 'door.png')),
261 puzzler,
262 )
263
264 def collide_with_protagonist(self, protagonist):
265 if self.puzzler.get_state():
266 DoorEvent.post(self.destination, self.dest_pos)
267
268 @classmethod
269 def requires(cls):
270 return [("name", "string"), ("position", "coordinates"),
271 ("destination", "level name"), ("dest_pos", "coordinate"),
272 ("angle", "degrees"),
273 ("key_state", "puzzler (optional)")]
274
275
276class Bulkhead(GameObject):
277 zorder = ZORDER_FLOOR
278
279 def __init__(self, space, end1, end2, key_state=None):
280 body = make_body(None, None, (0, 0))
281 self.shape = pymunk.Segment(body, tuple(end1), tuple(end2), 3)
282 self.shape.collision_type = COLLISION_TYPE_DOOR
283 if key_state is None:
284 puzzler = puzzle.YesPuzzler()
285 else:
286 puzzler = puzzle.StateProxyPuzzler(key_state)
287 super(Bulkhead, self).__init__(
288 SingleShapePhysicser(space, self.shape),
289 render.ShapeStateRenderer(),
290 puzzler,
291 )
292
293 def collide_with_protagonist(self, protagonist):
294 if self.puzzler.get_state():
295 # Reject the collision, we can walk through.
296 return False
297 return True
298
299 @classmethod
300 def requires(cls):
301 return [("name", "string"), ("end1", "coordinates"),
302 ("end2", "coordinates"), ("key_state", "puzzler")]
303
304
305class Bullet(GameObject):
306 def __init__(self, space, position, impulse):
307 body = make_body(1, pymunk.inf, position)
308 self.shape = pymunk.Circle(body, 1)
309 self.shape.collision_type = COLLISION_TYPE_PROJECTILE
310 super(Bullet, self).__init__(
311 SingleShapePhysicser(space, self.shape),
312 render.ImageRenderer(resources.get_image('objects', 'bullet.png')),
313 )
314 self.physicser.apply_impulse(impulse)
Note: See TracBrowser for help on using the repository browser.