source: nagslang/game_object.py@ 185:dfacd08b8566

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

Make FloorSwitchPuzzler are more generic CollidePuzzler

File size: 11.9 KB
RevLine 
[63]1import math
[62]2
[81]3import pygame
4import pymunk
[93]5import pymunk.pygame_util
[81]6
[107]7from nagslang.constants import (
[162]8 SWITCH_PUSHERS, COLLISION_TYPE_SWITCH, COLLISION_TYPE_BOX, ZORDER_LOW,
[176]9 ZORDER_FLOOR, COLLISION_TYPE_DOOR, COLLISION_TYPE_PLAYER)
[104]10from nagslang.options import options
[155]11from nagslang.resources import resources
[180]12from nagslang.events import DoorEvent
[81]13
[82]14
[106]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):
[145]22 if not isinstance(puzzler, Puzzler):
23 puzzler = puzzler.puzzler
[106]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
[81]31class Puzzler(object):
[106]32 """Behaviour specific to a puzzle component.
33 """
34 def set_glue(self, glue):
35 self.glue = glue
36
[123]37 def set_game_object(self, game_object):
38 self.game_object = game_object
39
[106]40 def get_state(self):
[81]41 raise NotImplementedError()
42
43
[185]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
[93]50 def get_state(self):
[123]51 space = self.game_object.get_space()
52 for shape in space.shape_query(self.game_object.get_shape()):
[185]53 if shape.collision_type in self._collision_types:
[81]54 return True
55 return False
56
[59]57
[106]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
[140]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
[59]77class Physicser(object):
[93]78 def __init__(self, space):
[123]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()
[93]89
90 def add_to_space(self):
[59]91 raise NotImplementedError()
92
[93]93 def remove_from_space(self):
[59]94 raise NotImplementedError()
95
[93]96 def get_render_position(self, surface):
[63]97 raise NotImplementedError()
98
[93]99 def get_angle(self):
100 raise NotImplementedError()
101
102 def apply_impulse(self, j, r=(0, 0)):
[59]103 raise NotImplementedError()
104
105
106class SingleShapePhysicser(Physicser):
[93]107 def __init__(self, space, shape):
108 super(SingleShapePhysicser, self).__init__(space)
[59]109 self._shape = shape
110
[123]111 def get_shape(self):
112 return self._shape
113
[93]114 def add_to_space(self):
[123]115 self.get_space().add(self._shape)
[93]116 if not self._shape.body.is_static:
[123]117 self.get_space().add(self._shape.body)
[59]118
[93]119 def remove_from_space(self):
[123]120 self.get_space().remove(self._shape)
[93]121 if not self._shape.body.is_static:
[123]122 self.get_space().remove(self._shape.body)
[59]123
[93]124 def get_render_position(self, surface):
[59]125 pos = self._shape.body.position
126 return pymunk.pygame_util.to_pygame(pos, surface)
[63]127
[93]128 def get_angle(self):
[63]129 return self._shape.body.angle
[59]130
[93]131 def apply_impulse(self, j, r=(0, 0)):
132 return self._shape.body.apply_impulse(j, r)
133
[59]134
135class Renderer(object):
[123]136 def set_game_object(self, game_object):
137 self.game_object = game_object
[104]138
[123]139 def _render_shape(self, surface):
140 shape = self.game_object.get_shape()
[104]141 # Less general that pymunk.pygame_util.draw, but also a lot less noisy.
[123]142 color = getattr(shape, 'color', pygame.color.THECOLORS['lightblue'])
[104]143 # We only explicitly draw Circle and Poly shapes. Everything else we
144 # forward to pymunk.
[123]145 if isinstance(shape, pymunk.Circle):
146 centre = pymunk.pygame_util.to_pygame(shape.body.position, surface)
147 radius = int(shape.radius)
[104]148 pygame.draw.circle(surface, color, centre, radius, 2)
[123]149 elif isinstance(shape, pymunk.Poly):
[104]150 # polygon bounding box
151 points = [pymunk.pygame_util.to_pygame(p, surface)
[123]152 for p in shape.get_vertices()]
[104]153 pygame.draw.lines(surface, color, True, points, 2)
154 else:
[123]155 pymunk.pygame_util.draw(surface, shape)
[104]156
[123]157 def render(self, surface):
[104]158 if options.debug:
[155]159 self._render_shape(surface)
[59]160
[143]161 def animate(self):
162 # Used by time animatations to advance the clock
163 pass
164
[59]165
[63]166def image_pos(image, pos):
167 return (pos[0] - image.get_width() / 2,
168 pos[1] - image.get_height() / 2)
169
170
[59]171class ImageRenderer(Renderer):
[123]172 def __init__(self, image):
[59]173 self._image = image
174
[160]175 def get_image(self):
176 return self._image
177
178 def rotate_image(self, image):
[155]179 angle = self.game_object.get_render_angle() * 180 / math.pi
[160]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)
[155]185 surface.blit(image, image_pos(image, pos))
[160]186
187 def render(self, surface):
188 self.render_image(surface, self.get_image())
[123]189 super(ImageRenderer, self).render(surface)
[63]190
191
[162]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
[160]200class FacingImageRenderer(ImageRenderer):
[123]201 def __init__(self, left_image, right_image):
[63]202 self._images = {
203 'left': left_image,
204 'right': right_image,
205 }
[159]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'
[63]213
[160]214 def rotate_image(self, image):
215 # Facing images don't get rotated.
216 return image
217
218 def get_facing_image(self):
[159]219 return self._images[self._face]
[63]220
[160]221 def get_image(self):
222 angle = self.game_object.get_render_angle()
223 self._update_facing(angle)
224 return self.get_facing_image()
[59]225
226
[143]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
[159]235 self._face = 'left'
[143]236
[160]237 def get_facing_image(self):
[159]238 if self._frame >= len(self._images[self._face]):
[143]239 self._frame = 0
[159]240 return self._images[self._face][self._frame]
[143]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
[160]262 def get_image(self):
[143]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
[133]271class ShapeRenderer(Renderer):
272 def render(self, surface):
273 self._render_shape(surface)
274 super(ShapeRenderer, self).render(surface)
275
276
277class ShapeStateRenderer(ShapeRenderer):
[126]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 """
[123]282 def render(self, surface):
[126]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)
[59]290
291
[133]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)
[145]301 body.position = tuple(position)
[133]302 if damping is not None:
303 body.damping = damping
304 body.velocity_func = damping_velocity_func
305 return body
306
307
[59]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
[162]314 zorder = ZORDER_LOW
315
[93]316 def __init__(self, physicser, renderer, puzzler=None):
317 self.physicser = physicser
[123]318 physicser.set_game_object(self)
[93]319 self.physicser.add_to_space()
[59]320 self.renderer = renderer
[123]321 renderer.set_game_object(self)
[81]322 self.puzzler = puzzler
[123]323 if puzzler is not None:
324 puzzler.set_game_object(self)
[59]325
[123]326 def get_space(self):
327 return self.physicser.get_space()
328
329 def get_shape(self):
330 return self.physicser.get_shape()
331
[93]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()
[59]337
338 def render(self, surface):
[123]339 return self.renderer.render(surface)
[81]340
[143]341 def animate(self):
342 self.renderer.animate()
343
[81]344
345class FloorSwitch(GameObject):
[162]346 zorder = ZORDER_FLOOR
347
[93]348 def __init__(self, space, position):
[145]349 body = make_body(None, None, position)
[81]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__(
[93]354 SingleShapePhysicser(space, self.shape),
[162]355 ImageStateRenderer({
356 True: resources.get_image('objects', 'sensor_on.png'),
357 False: resources.get_image('objects', 'sensor_off.png'),
358 }),
[185]359 CollidePuzzler(*SWITCH_PUSHERS),
[81]360 )
361
[106]362
363class FloorLight(GameObject):
[162]364 zorder = ZORDER_FLOOR
365
[106]366 def __init__(self, space, position, state_source):
[145]367 body = make_body(None, None, position)
[106]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),
[162]373 ImageStateRenderer({
374 True: resources.get_image('objects', 'light_on.png'),
375 False: resources.get_image('objects', 'light_off.png'),
376 }),
[106]377 StateProxyPuzzler(state_source),
378 )
[133]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),
[155]389 ImageRenderer(resources.get_image('objects', 'crate.png')),
[133]390 )
[176]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
[180]414 DoorEvent.post(self.destination, self.dest_pos)
Note: See TracBrowser for help on using the repository browser.