Mercurial > nagslang
view nagslang/game_object.py @ 160:0c9b0449485e
Consolidate image rendering stuff a bit.
author | Jeremy Thurgood <firxen@gmail.com> |
---|---|
date | Tue, 03 Sep 2013 00:50:11 +0200 |
parents | f80323140317 |
children | 507df17cfbaf |
line wrap: on
line source
import math import pygame import pymunk import pymunk.pygame_util from nagslang.constants import ( SWITCH_PUSHERS, COLLISION_TYPE_SWITCH, COLLISION_TYPE_BOX, ZORDER_LOW) from nagslang.options import options from nagslang.resources import resources class PuzzleGlue(object): """Glue that holds bits of a puzzle together. """ def __init__(self): self._components = {} def add_component(self, name, puzzler): if not isinstance(puzzler, Puzzler): puzzler = puzzler.puzzler self._components[name] = puzzler puzzler.set_glue(self) def get_state_of(self, name): return self._components[name].get_state() class Puzzler(object): """Behaviour specific to a puzzle component. """ def set_glue(self, glue): self.glue = glue def set_game_object(self, game_object): self.game_object = game_object def get_state(self): raise NotImplementedError() class FloorSwitchPuzzler(Puzzler): def get_state(self): space = self.game_object.get_space() for shape in space.shape_query(self.game_object.get_shape()): if shape.collision_type in SWITCH_PUSHERS: return True return False class StateProxyPuzzler(Puzzler): def __init__(self, state_source): self._state_source = state_source def get_state(self): return self.glue.get_state_of(self._state_source) class StateLogicalAndPuzzler(Puzzler): def __init__(self, *state_sources): self._state_sources = state_sources def get_state(self): for state_source in self._state_sources: if not self.glue.get_state_of(state_source): return False return True class Physicser(object): def __init__(self, space): self._space = space def get_space(self): return self._space def set_game_object(self, game_object): self.game_object = game_object def get_shape(self): raise NotImplementedError() def add_to_space(self): raise NotImplementedError() def remove_from_space(self): raise NotImplementedError() def get_render_position(self, surface): raise NotImplementedError() def get_angle(self): raise NotImplementedError() def apply_impulse(self, j, r=(0, 0)): raise NotImplementedError() class SingleShapePhysicser(Physicser): def __init__(self, space, shape): super(SingleShapePhysicser, self).__init__(space) self._shape = shape def get_shape(self): return self._shape def add_to_space(self): self.get_space().add(self._shape) if not self._shape.body.is_static: self.get_space().add(self._shape.body) def remove_from_space(self): self.get_space().remove(self._shape) if not self._shape.body.is_static: self.get_space().remove(self._shape.body) def get_render_position(self, surface): pos = self._shape.body.position return pymunk.pygame_util.to_pygame(pos, surface) def get_angle(self): return self._shape.body.angle def apply_impulse(self, j, r=(0, 0)): return self._shape.body.apply_impulse(j, r) class Renderer(object): def set_game_object(self, game_object): self.game_object = game_object def _render_shape(self, surface): shape = self.game_object.get_shape() # Less general that pymunk.pygame_util.draw, but also a lot less noisy. color = getattr(shape, 'color', pygame.color.THECOLORS['lightblue']) # We only explicitly draw Circle and Poly shapes. Everything else we # forward to pymunk. if isinstance(shape, pymunk.Circle): centre = pymunk.pygame_util.to_pygame(shape.body.position, surface) radius = int(shape.radius) pygame.draw.circle(surface, color, centre, radius, 2) elif isinstance(shape, pymunk.Poly): # polygon bounding box points = [pymunk.pygame_util.to_pygame(p, surface) for p in shape.get_vertices()] pygame.draw.lines(surface, color, True, points, 2) else: pymunk.pygame_util.draw(surface, shape) def render(self, surface): if options.debug: self._render_shape(surface) def animate(self): # Used by time animatations to advance the clock pass def image_pos(image, pos): return (pos[0] - image.get_width() / 2, pos[1] - image.get_height() / 2) class ImageRenderer(Renderer): def __init__(self, image): self._image = image def get_image(self): return self._image def rotate_image(self, image): angle = self.game_object.get_render_angle() * 180 / math.pi return pygame.transform.rotate(image, angle) def render_image(self, surface, image): image = self.rotate_image(image) pos = self.game_object.get_render_position(surface) surface.blit(image, image_pos(image, pos)) def render(self, surface): self.render_image(surface, self.get_image()) super(ImageRenderer, self).render(surface) class FacingImageRenderer(ImageRenderer): def __init__(self, left_image, right_image): self._images = { 'left': left_image, 'right': right_image, } self._face = 'left' def _update_facing(self, angle): if abs(angle) < math.pi / 2: self._face = 'right' elif abs(angle) > math.pi / 2: self._face = 'left' def rotate_image(self, image): # Facing images don't get rotated. return image def get_facing_image(self): return self._images[self._face] def get_image(self): angle = self.game_object.get_render_angle() self._update_facing(angle) return self.get_facing_image() class AnimatedFacingImageRenderer(FacingImageRenderer): def __init__(self, left_images, right_images): self._images = { 'left': left_images, 'right': right_images, } self._frame = 0 self._moving = False self._face = 'left' def get_facing_image(self): if self._frame >= len(self._images[self._face]): self._frame = 0 return self._images[self._face][self._frame] def animate(self): if self._moving: self._frame += 1 else: self._frame = 0 def start(self): self._moving = True def stop(self): self._moving = False class TimedAnimatedRenderer(ImageRenderer): def __init__(self, images): self._images = images self._frame = 0 self._image = None def get_image(self): if self._frame > len(self._imaages): self._frame = 0 return self._images[self._frame] def animate(self): self._frame += 1 class ShapeRenderer(Renderer): def render(self, surface): self._render_shape(surface) super(ShapeRenderer, self).render(surface) class ShapeStateRenderer(ShapeRenderer): """Renders the shape in a different colour depending on the state. Requires the game object it's attached to to have a puzzler. """ def render(self, surface): if self.game_object.puzzler.get_state(): color = pygame.color.THECOLORS['green'] else: color = pygame.color.THECOLORS['red'] self.game_object.get_shape().color = color super(ShapeStateRenderer, self).render(surface) def damping_velocity_func(body, gravity, damping, dt): """Apply custom damping to this body's velocity. """ damping = getattr(body, 'damping', damping) return pymunk.Body.update_velocity(body, gravity, damping, dt) def make_body(mass, moment, position, damping=None): body = pymunk.Body(mass, moment) body.position = tuple(position) if damping is not None: body.damping = damping body.velocity_func = damping_velocity_func return body class GameObject(object): """A representation of a thing in the game world. This has a rendery thing, physicsy things and maybe some other things. """ def __init__(self, physicser, renderer, puzzler=None): self.physicser = physicser physicser.set_game_object(self) self.physicser.add_to_space() self.renderer = renderer renderer.set_game_object(self) self.puzzler = puzzler if puzzler is not None: puzzler.set_game_object(self) self.zorder = ZORDER_LOW def get_space(self): return self.physicser.get_space() def get_shape(self): return self.physicser.get_shape() def get_render_position(self, surface): return self.physicser.get_render_position(surface) def get_render_angle(self): return self.physicser.get_angle() def render(self, surface): return self.renderer.render(surface) def animate(self): self.renderer.animate() class FloorSwitch(GameObject): def __init__(self, space, position): body = make_body(None, None, position) self.shape = pymunk.Circle(body, 30) self.shape.collision_type = COLLISION_TYPE_SWITCH self.shape.sensor = True super(FloorSwitch, self).__init__( SingleShapePhysicser(space, self.shape), ShapeStateRenderer(), FloorSwitchPuzzler(), ) class FloorLight(GameObject): def __init__(self, space, position, state_source): body = make_body(None, None, position) self.shape = pymunk.Circle(body, 10) self.shape.collision_type = COLLISION_TYPE_SWITCH self.shape.sensor = True super(FloorLight, self).__init__( SingleShapePhysicser(space, self.shape), ShapeStateRenderer(), StateProxyPuzzler(state_source), ) class Box(GameObject): def __init__(self, space, position): body = make_body(10, 10000, position, damping=0.5) self.shape = pymunk.Poly( body, [(-20, -20), (20, -20), (20, 20), (-20, 20)]) self.shape.collision_type = COLLISION_TYPE_BOX super(Box, self).__init__( SingleShapePhysicser(space, self.shape), ImageRenderer(resources.get_image('objects', 'crate.png')), )