source: nagslang/game_object.py@ 185:dfacd08b8566

Last change on this file since 185:dfacd08b8566 was 185:dfacd08b8566, checked in by Stefano Rivera <stefano@…>, 7 years ago

Make FloorSwitchPuzzler are more generic CollidePuzzler

File size: 11.9 KB
Line 
1import math
2
3import pygame
4import pymunk
5import pymunk.pygame_util
6
7from nagslang.constants import (
8 SWITCH_PUSHERS, COLLISION_TYPE_SWITCH, COLLISION_TYPE_BOX, ZORDER_LOW,
9 ZORDER_FLOOR, COLLISION_TYPE_DOOR, COLLISION_TYPE_PLAYER)
10from nagslang.options import options
11from nagslang.resources import resources
12from nagslang.events import DoorEvent
13
14
15class PuzzleGlue(object):
16 """Glue that holds bits of a puzzle together.
17 """
18 def __init__(self):
19 self._components = {}
20
21 def add_component(self, name, puzzler):
22 if not isinstance(puzzler, Puzzler):
23 puzzler = puzzler.puzzler
24 self._components[name] = puzzler
25 puzzler.set_glue(self)
26
27 def get_state_of(self, name):
28 return self._components[name].get_state()
29
30
31class Puzzler(object):
32 """Behaviour specific to a puzzle component.
33 """
34 def set_glue(self, glue):
35 self.glue = glue
36
37 def set_game_object(self, game_object):
38 self.game_object = game_object
39
40 def get_state(self):
41 raise NotImplementedError()
42
43
44class CollidePuzzler(Puzzler):
45 def __init__(self, *collision_types):
46 if not collision_types:
47 collision_types = (COLLISION_TYPE_PLAYER,)
48 self._collision_types = collision_types
49
50 def get_state(self):
51 space = self.game_object.get_space()
52 for shape in space.shape_query(self.game_object.get_shape()):
53 if shape.collision_type in self._collision_types:
54 return True
55 return False
56
57
58class StateProxyPuzzler(Puzzler):
59 def __init__(self, state_source):
60 self._state_source = state_source
61
62 def get_state(self):
63 return self.glue.get_state_of(self._state_source)
64
65
66class StateLogicalAndPuzzler(Puzzler):
67 def __init__(self, *state_sources):
68 self._state_sources = state_sources
69
70 def get_state(self):
71 for state_source in self._state_sources:
72 if not self.glue.get_state_of(state_source):
73 return False
74 return True
75
76
77class Physicser(object):
78 def __init__(self, space):
79 self._space = space
80
81 def get_space(self):
82 return self._space
83
84 def set_game_object(self, game_object):
85 self.game_object = game_object
86
87 def get_shape(self):
88 raise NotImplementedError()
89
90 def add_to_space(self):
91 raise NotImplementedError()
92
93 def remove_from_space(self):
94 raise NotImplementedError()
95
96 def get_render_position(self, surface):
97 raise NotImplementedError()
98
99 def get_angle(self):
100 raise NotImplementedError()
101
102 def apply_impulse(self, j, r=(0, 0)):
103 raise NotImplementedError()
104
105
106class SingleShapePhysicser(Physicser):
107 def __init__(self, space, shape):
108 super(SingleShapePhysicser, self).__init__(space)
109 self._shape = shape
110
111 def get_shape(self):
112 return self._shape
113
114 def add_to_space(self):
115 self.get_space().add(self._shape)
116 if not self._shape.body.is_static:
117 self.get_space().add(self._shape.body)
118
119 def remove_from_space(self):
120 self.get_space().remove(self._shape)
121 if not self._shape.body.is_static:
122 self.get_space().remove(self._shape.body)
123
124 def get_render_position(self, surface):
125 pos = self._shape.body.position
126 return pymunk.pygame_util.to_pygame(pos, surface)
127
128 def get_angle(self):
129 return self._shape.body.angle
130
131 def apply_impulse(self, j, r=(0, 0)):
132 return self._shape.body.apply_impulse(j, r)
133
134
135class Renderer(object):
136 def set_game_object(self, game_object):
137 self.game_object = game_object
138
139 def _render_shape(self, surface):
140 shape = self.game_object.get_shape()
141 # Less general that pymunk.pygame_util.draw, but also a lot less noisy.
142 color = getattr(shape, 'color', pygame.color.THECOLORS['lightblue'])
143 # We only explicitly draw Circle and Poly shapes. Everything else we
144 # forward to pymunk.
145 if isinstance(shape, pymunk.Circle):
146 centre = pymunk.pygame_util.to_pygame(shape.body.position, surface)
147 radius = int(shape.radius)
148 pygame.draw.circle(surface, color, centre, radius, 2)
149 elif isinstance(shape, pymunk.Poly):
150 # polygon bounding box
151 points = [pymunk.pygame_util.to_pygame(p, surface)
152 for p in shape.get_vertices()]
153 pygame.draw.lines(surface, color, True, points, 2)
154 else:
155 pymunk.pygame_util.draw(surface, shape)
156
157 def render(self, surface):
158 if options.debug:
159 self._render_shape(surface)
160
161 def animate(self):
162 # Used by time animatations to advance the clock
163 pass
164
165
166def image_pos(image, pos):
167 return (pos[0] - image.get_width() / 2,
168 pos[1] - image.get_height() / 2)
169
170
171class ImageRenderer(Renderer):
172 def __init__(self, image):
173 self._image = image
174
175 def get_image(self):
176 return self._image
177
178 def rotate_image(self, image):
179 angle = self.game_object.get_render_angle() * 180 / math.pi
180 return pygame.transform.rotate(image, angle)
181
182 def render_image(self, surface, image):
183 image = self.rotate_image(image)
184 pos = self.game_object.get_render_position(surface)
185 surface.blit(image, image_pos(image, pos))
186
187 def render(self, surface):
188 self.render_image(surface, self.get_image())
189 super(ImageRenderer, self).render(surface)
190
191
192class ImageStateRenderer(ImageRenderer):
193 def __init__(self, state_images):
194 self._state_images = state_images
195
196 def get_image(self):
197 return self._state_images[self.game_object.puzzler.get_state()]
198
199
200class FacingImageRenderer(ImageRenderer):
201 def __init__(self, left_image, right_image):
202 self._images = {
203 'left': left_image,
204 'right': right_image,
205 }
206 self._face = 'left'
207
208 def _update_facing(self, angle):
209 if abs(angle) < math.pi / 2:
210 self._face = 'right'
211 elif abs(angle) > math.pi / 2:
212 self._face = 'left'
213
214 def rotate_image(self, image):
215 # Facing images don't get rotated.
216 return image
217
218 def get_facing_image(self):
219 return self._images[self._face]
220
221 def get_image(self):
222 angle = self.game_object.get_render_angle()
223 self._update_facing(angle)
224 return self.get_facing_image()
225
226
227class AnimatedFacingImageRenderer(FacingImageRenderer):
228 def __init__(self, left_images, right_images):
229 self._images = {
230 'left': left_images,
231 'right': right_images,
232 }
233 self._frame = 0
234 self._moving = False
235 self._face = 'left'
236
237 def get_facing_image(self):
238 if self._frame >= len(self._images[self._face]):
239 self._frame = 0
240 return self._images[self._face][self._frame]
241
242 def animate(self):
243 if self._moving:
244 self._frame += 1
245 else:
246 self._frame = 0
247
248 def start(self):
249 self._moving = True
250
251 def stop(self):
252 self._moving = False
253
254
255class TimedAnimatedRenderer(ImageRenderer):
256
257 def __init__(self, images):
258 self._images = images
259 self._frame = 0
260 self._image = None
261
262 def get_image(self):
263 if self._frame > len(self._imaages):
264 self._frame = 0
265 return self._images[self._frame]
266
267 def animate(self):
268 self._frame += 1
269
270
271class ShapeRenderer(Renderer):
272 def render(self, surface):
273 self._render_shape(surface)
274 super(ShapeRenderer, self).render(surface)
275
276
277class ShapeStateRenderer(ShapeRenderer):
278 """Renders the shape in a different colour depending on the state.
279
280 Requires the game object it's attached to to have a puzzler.
281 """
282 def render(self, surface):
283 if self.game_object.puzzler.get_state():
284 color = pygame.color.THECOLORS['green']
285 else:
286 color = pygame.color.THECOLORS['red']
287
288 self.game_object.get_shape().color = color
289 super(ShapeStateRenderer, self).render(surface)
290
291
292def damping_velocity_func(body, gravity, damping, dt):
293 """Apply custom damping to this body's velocity.
294 """
295 damping = getattr(body, 'damping', damping)
296 return pymunk.Body.update_velocity(body, gravity, damping, dt)
297
298
299def make_body(mass, moment, position, damping=None):
300 body = pymunk.Body(mass, moment)
301 body.position = tuple(position)
302 if damping is not None:
303 body.damping = damping
304 body.velocity_func = damping_velocity_func
305 return body
306
307
308class GameObject(object):
309 """A representation of a thing in the game world.
310
311 This has a rendery thing, physicsy things and maybe some other things.
312 """
313
314 zorder = ZORDER_LOW
315
316 def __init__(self, physicser, renderer, puzzler=None):
317 self.physicser = physicser
318 physicser.set_game_object(self)
319 self.physicser.add_to_space()
320 self.renderer = renderer
321 renderer.set_game_object(self)
322 self.puzzler = puzzler
323 if puzzler is not None:
324 puzzler.set_game_object(self)
325
326 def get_space(self):
327 return self.physicser.get_space()
328
329 def get_shape(self):
330 return self.physicser.get_shape()
331
332 def get_render_position(self, surface):
333 return self.physicser.get_render_position(surface)
334
335 def get_render_angle(self):
336 return self.physicser.get_angle()
337
338 def render(self, surface):
339 return self.renderer.render(surface)
340
341 def animate(self):
342 self.renderer.animate()
343
344
345class FloorSwitch(GameObject):
346 zorder = ZORDER_FLOOR
347
348 def __init__(self, space, position):
349 body = make_body(None, None, position)
350 self.shape = pymunk.Circle(body, 30)
351 self.shape.collision_type = COLLISION_TYPE_SWITCH
352 self.shape.sensor = True
353 super(FloorSwitch, self).__init__(
354 SingleShapePhysicser(space, self.shape),
355 ImageStateRenderer({
356 True: resources.get_image('objects', 'sensor_on.png'),
357 False: resources.get_image('objects', 'sensor_off.png'),
358 }),
359 CollidePuzzler(*SWITCH_PUSHERS),
360 )
361
362
363class FloorLight(GameObject):
364 zorder = ZORDER_FLOOR
365
366 def __init__(self, space, position, state_source):
367 body = make_body(None, None, position)
368 self.shape = pymunk.Circle(body, 10)
369 self.shape.collision_type = COLLISION_TYPE_SWITCH
370 self.shape.sensor = True
371 super(FloorLight, self).__init__(
372 SingleShapePhysicser(space, self.shape),
373 ImageStateRenderer({
374 True: resources.get_image('objects', 'light_on.png'),
375 False: resources.get_image('objects', 'light_off.png'),
376 }),
377 StateProxyPuzzler(state_source),
378 )
379
380
381class Box(GameObject):
382 def __init__(self, space, position):
383 body = make_body(10, 10000, position, damping=0.5)
384 self.shape = pymunk.Poly(
385 body, [(-20, -20), (20, -20), (20, 20), (-20, 20)])
386 self.shape.collision_type = COLLISION_TYPE_BOX
387 super(Box, self).__init__(
388 SingleShapePhysicser(space, self.shape),
389 ImageRenderer(resources.get_image('objects', 'crate.png')),
390 )
391
392
393class Door(GameObject):
394 zorder = ZORDER_FLOOR
395
396 def __init__(self, space, position, destination, dest_pos):
397 body = make_body(pymunk.inf, pymunk.inf, position, damping=0.5)
398 self.shape = pymunk.Poly(
399 body, [(-32, -32), (32, -32), (32, 32), (-32, 32)])
400 self.shape.collision_type = COLLISION_TYPE_DOOR
401 self.shape.sensor = True
402 self.destination = destination
403 self.dest_pos = tuple(dest_pos)
404 super(Door, self).__init__(
405 SingleShapePhysicser(space, self.shape),
406 ImageRenderer(resources.get_image('objects', 'door.png')),
407 )
408
409 def animate(self):
410 space = self.get_space()
411 for shape in space.shape_query(self.get_shape()):
412 if shape.collision_type == COLLISION_TYPE_PLAYER:
413 # Force to new position
414 DoorEvent.post(self.destination, self.dest_pos)
Note: See TracBrowser for help on using the repository browser.