source: nagslang/game_object.py@ 176:054944c6472b

Last change on this file since 176:054944c6472b was 176:054944c6472b, checked in by Neil Muller <drnlmuller@…>, 8 years ago

Initial door object

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