[207] | 1 | import math |
---|
| 2 | |
---|
| 3 | import pygame |
---|
| 4 | import pymunk |
---|
| 5 | |
---|
| 6 | from nagslang.options import options |
---|
| 7 | |
---|
| 8 | |
---|
| 9 | class Renderer(object): |
---|
| 10 | def set_game_object(self, game_object): |
---|
| 11 | self.game_object = game_object |
---|
| 12 | |
---|
| 13 | def _render_shape(self, surface): |
---|
| 14 | shape = self.game_object.get_shape() |
---|
| 15 | # Less general that pymunk.pygame_util.draw, but also a lot less noisy. |
---|
| 16 | color = getattr(shape, 'color', pygame.color.THECOLORS['lightblue']) |
---|
| 17 | # We only explicitly draw Circle and Poly shapes. Everything else we |
---|
| 18 | # forward to pymunk. |
---|
| 19 | if isinstance(shape, pymunk.Circle): |
---|
| 20 | centre = pymunk.pygame_util.to_pygame(shape.body.position, surface) |
---|
| 21 | radius = int(shape.radius) |
---|
| 22 | pygame.draw.circle(surface, color, centre, radius, 2) |
---|
| 23 | elif isinstance(shape, pymunk.Poly): |
---|
| 24 | # polygon bounding box |
---|
| 25 | points = [pymunk.pygame_util.to_pygame(p, surface) |
---|
| 26 | for p in shape.get_vertices()] |
---|
| 27 | pygame.draw.lines(surface, color, True, points, 2) |
---|
| 28 | else: |
---|
| 29 | pymunk.pygame_util.draw(surface, shape) |
---|
| 30 | |
---|
| 31 | def render(self, surface): |
---|
| 32 | if options.debug: |
---|
| 33 | self._render_shape(surface) |
---|
| 34 | |
---|
| 35 | def animate(self): |
---|
| 36 | # Used by time animatations to advance the clock |
---|
| 37 | pass |
---|
| 38 | |
---|
| 39 | |
---|
| 40 | def image_pos(image, pos): |
---|
| 41 | return (pos[0] - image.get_width() / 2, |
---|
| 42 | pos[1] - image.get_height() / 2) |
---|
| 43 | |
---|
| 44 | |
---|
| 45 | class ImageRenderer(Renderer): |
---|
[219] | 46 | rotate = True # Set to `False` to suppress image rotation. |
---|
| 47 | |
---|
[207] | 48 | def __init__(self, image): |
---|
| 49 | self._image = image |
---|
| 50 | |
---|
| 51 | def get_image(self): |
---|
| 52 | return self._image |
---|
| 53 | |
---|
| 54 | def rotate_image(self, image): |
---|
[219] | 55 | if not self._rotate: |
---|
| 56 | return image |
---|
[207] | 57 | angle = self.game_object.get_render_angle() * 180 / math.pi |
---|
| 58 | return pygame.transform.rotate(image, angle) |
---|
| 59 | |
---|
| 60 | def render_image(self, surface, image): |
---|
| 61 | image = self.rotate_image(image) |
---|
| 62 | pos = self.game_object.get_render_position(surface) |
---|
| 63 | surface.blit(image, image_pos(image, pos)) |
---|
| 64 | |
---|
| 65 | def render(self, surface): |
---|
| 66 | self.render_image(surface, self.get_image()) |
---|
| 67 | super(ImageRenderer, self).render(surface) |
---|
| 68 | |
---|
| 69 | |
---|
| 70 | class ImageStateRenderer(ImageRenderer): |
---|
| 71 | def __init__(self, state_images): |
---|
| 72 | self._state_images = state_images |
---|
| 73 | |
---|
| 74 | def get_image(self): |
---|
| 75 | return self._state_images[self.game_object.puzzler.get_state()] |
---|
| 76 | |
---|
| 77 | |
---|
[217] | 78 | class TimedAnimatedRenderer(ImageRenderer): |
---|
| 79 | def __init__(self, images, frame_ticks=1): |
---|
| 80 | self._images = images |
---|
| 81 | self._frame_ticks = frame_ticks |
---|
| 82 | self._frame_tick = 0 |
---|
| 83 | self._frame = 0 |
---|
| 84 | |
---|
| 85 | def advance_tick(self): |
---|
| 86 | self._frame_tick += 1 |
---|
| 87 | if self._frame_tick > self._frame_ticks: |
---|
| 88 | self._frame_tick = 0 |
---|
| 89 | self._frame += 1 |
---|
| 90 | if self._frame >= len(self._images): |
---|
| 91 | self._frame = 0 |
---|
| 92 | |
---|
| 93 | def reset(self): |
---|
| 94 | self._frame_tick = 0 |
---|
| 95 | self._frame = 0 |
---|
| 96 | |
---|
| 97 | def get_image(self): |
---|
| 98 | return self._images[self._frame] |
---|
| 99 | |
---|
| 100 | def animate(self): |
---|
| 101 | self.advance_tick() |
---|
| 102 | |
---|
| 103 | |
---|
| 104 | class MovementAnimatedRenderer(TimedAnimatedRenderer): |
---|
| 105 | def animate(self): |
---|
[218] | 106 | if self.game_object.is_moving: |
---|
[217] | 107 | self.advance_tick() |
---|
| 108 | else: |
---|
| 109 | self.reset() |
---|
| 110 | |
---|
| 111 | |
---|
| 112 | class RendererSelectionRenderer(Renderer): |
---|
| 113 | def __init__(self, renderers): |
---|
| 114 | self._renderers = renderers |
---|
| 115 | |
---|
| 116 | def set_game_object(self, game_object): |
---|
| 117 | self.game_object = game_object |
---|
| 118 | for renderer in self._renderers.values(): |
---|
| 119 | renderer.set_game_object(game_object) |
---|
| 120 | |
---|
| 121 | @property |
---|
| 122 | def renderer(self): |
---|
| 123 | return self._renderers[self.select_renderer()] |
---|
| 124 | |
---|
| 125 | def render(self, surface): |
---|
| 126 | return self.renderer.render(surface) |
---|
| 127 | |
---|
| 128 | def animate(self): |
---|
| 129 | return self.renderer.animate() |
---|
| 130 | |
---|
| 131 | def select_renderer(self): |
---|
| 132 | raise NotImplementedError() |
---|
| 133 | |
---|
| 134 | |
---|
| 135 | class FacingSelectionRenderer(RendererSelectionRenderer): |
---|
| 136 | def __init__(self, renderers): |
---|
| 137 | for renderer in renderers.values(): |
---|
[219] | 138 | renderer.rotate = False |
---|
[217] | 139 | super(FacingSelectionRenderer, self).__init__(renderers) |
---|
[207] | 140 | self._face = 'left' |
---|
| 141 | |
---|
| 142 | def _update_facing(self, angle): |
---|
| 143 | if abs(angle) < math.pi / 2: |
---|
| 144 | self._face = 'right' |
---|
| 145 | elif abs(angle) > math.pi / 2: |
---|
| 146 | self._face = 'left' |
---|
| 147 | |
---|
[217] | 148 | def select_renderer(self): |
---|
[207] | 149 | angle = self.game_object.get_render_angle() |
---|
| 150 | self._update_facing(angle) |
---|
[217] | 151 | return self._face |
---|
[207] | 152 | |
---|
| 153 | |
---|
| 154 | class ShapeRenderer(Renderer): |
---|
| 155 | def render(self, surface): |
---|
| 156 | self._render_shape(surface) |
---|
| 157 | super(ShapeRenderer, self).render(surface) |
---|
| 158 | |
---|
| 159 | |
---|
| 160 | class ShapeStateRenderer(ShapeRenderer): |
---|
| 161 | """Renders the shape in a different colour depending on the state. |
---|
| 162 | |
---|
| 163 | Requires the game object it's attached to to have a puzzler. |
---|
| 164 | """ |
---|
| 165 | def render(self, surface): |
---|
| 166 | if self.game_object.puzzler.get_state(): |
---|
| 167 | color = pygame.color.THECOLORS['green'] |
---|
| 168 | else: |
---|
| 169 | color = pygame.color.THECOLORS['red'] |
---|
| 170 | |
---|
| 171 | self.game_object.get_shape().color = color |
---|
| 172 | super(ShapeStateRenderer, self).render(surface) |
---|