source: nagslang/render.py @ 400:4523b1ff17ae

Last change on this file since 400:4523b1ff17ae was 400:4523b1ff17ae, checked in by Neil Muller <drnlmuller@…>, 7 years ago

Hack together tile animation

File size: 8.3 KB
Line 
1import math
2import textwrap
3
4import pygame
5import pygame.locals as pgl
6import pymunk
7
8from nagslang.options import options
9from nagslang.utils import tile_surface
10from nagslang.widgets.text import LabelWidget, MultiLineWidget
11
12
13class Renderer(object):
14    def set_game_object(self, game_object):
15        self.game_object = game_object
16
17    def _render_shape(self, surface):
18        shape = self.game_object.get_shape()
19        # Less general that pymunk.pygame_util.draw, but also a lot less noisy.
20        color = getattr(shape, 'color', pygame.color.THECOLORS['lightblue'])
21        # We only explicitly draw Circle and Poly shapes. Everything else we
22        # forward to pymunk.
23        if isinstance(shape, pymunk.Circle):
24            centre = pymunk.pygame_util.to_pygame(shape.body.position, surface)
25            radius = int(shape.radius)
26            pygame.draw.circle(surface, color, centre, radius, 2)
27        elif isinstance(shape, pymunk.Poly):
28            # polygon bounding box
29            points = [pymunk.pygame_util.to_pygame(p, surface)
30                      for p in shape.get_vertices()]
31            pygame.draw.lines(surface, color, True, points, 2)
32        else:
33            pymunk.pygame_util.draw(surface, shape)
34
35    def render(self, surface):
36        if options.debug:
37            self._render_shape(surface)
38
39    def update(self, seconds):
40        # Used by time animatations to advance the clock
41        pass
42
43
44class NullRenderer(Renderer):
45    def render(self, surface):
46        pass
47
48
49def image_pos(image, pos):
50    return (pos[0] - image.get_width() / 2,
51            pos[1] - image.get_height() / 2)
52
53
54class ImageRenderer(Renderer):
55    def __init__(self, image):
56        self._image = image
57
58    def get_image(self):
59        return self._image
60
61    def rotate_image(self, image):
62        angle = self.game_object.get_render_angle() * 180 / math.pi
63        return pygame.transform.rotate(image, angle)
64
65    def render_image(self, surface, image):
66        image = self.rotate_image(image)
67        pos = self.game_object.get_render_position(surface)
68        surface.blit(image, image_pos(image, pos))
69
70    def render(self, surface):
71        self.render_image(surface, self.get_image())
72        super(ImageRenderer, self).render(surface)
73
74
75class ImageStateRenderer(ImageRenderer):
76    def __init__(self, state_images):
77        self._state_images = state_images
78
79    def get_image(self):
80        return self._state_images[self.game_object.puzzler.get_state()]
81
82
83class TimedAnimatedRenderer(ImageRenderer):
84    def __init__(self, images, frame_ticks=1):
85        self._images = images
86        self._frame_ticks = frame_ticks
87        self._frame_tick = 0
88        self._frame = 0
89
90    def advance_tick(self):
91        self._frame_tick += 1
92        if self._frame_tick > self._frame_ticks:
93            self._frame_tick = 0
94            self._frame += 1
95        if self._frame >= len(self._images):
96            self._frame = 0
97
98    def reset(self):
99        self._frame_tick = 0
100        self._frame = 0
101
102    def get_image(self):
103        return self._images[self._frame]
104
105    def update(self, seconds):
106        self.advance_tick()
107
108
109class MovementAnimatedRenderer(TimedAnimatedRenderer):
110    def update(self, seconds):
111        if self.game_object.is_moving:
112            self.advance_tick()
113        else:
114            self.reset()
115
116
117class RendererSelectionRenderer(Renderer):
118    def __init__(self, renderers):
119        self._renderers = renderers
120
121    def set_game_object(self, game_object):
122        self.game_object = game_object
123        for renderer in self._renderers.values():
124            renderer.set_game_object(game_object)
125
126    @property
127    def renderer(self):
128        return self._renderers[self.select_renderer()]
129
130    def render(self, surface):
131        return self.renderer.render(surface)
132
133    def update(self, seconds):
134        return self.renderer.update(seconds)
135
136    def select_renderer(self):
137        raise NotImplementedError()
138
139
140class FacingSelectionRenderer(RendererSelectionRenderer):
141    def select_renderer(self):
142        return self.game_object.get_facing_direction()
143
144
145class ShapeRenderer(Renderer):
146    def render(self, surface):
147        self._render_shape(surface)
148        super(ShapeRenderer, self).render(surface)
149
150
151class ShapeStateRenderer(ShapeRenderer):
152    """Renders the shape in a different colour depending on the state.
153
154    Requires the game object it's attached to to have a puzzler.
155    """
156    def render(self, surface):
157        if self.game_object.puzzler.get_state():
158            color = pygame.color.THECOLORS['green']
159        else:
160            color = pygame.color.THECOLORS['red']
161
162        self.game_object.get_shape().color = color
163        super(ShapeStateRenderer, self).render(surface)
164
165
166class Overlay(object):
167    def set_game_object(self, game_object):
168        self.game_object = game_object
169
170    def render(self, surface, display_offset, max_width):
171        pass
172
173    def is_visible(self):
174        return self.game_object.puzzler.get_state()
175
176
177class TextOverlay(Overlay):
178    def __init__(self, text):
179        self.text = text
180        self.widget = LabelWidget((20, 20), self.text)
181
182    def render(self, surface, display_offset, max_width):
183        x, y = 20, 20
184        if display_offset[0] < 0:
185            x += abs(display_offset[0])
186        if display_offset[1] < 0:
187            y += abs(display_offset[1])
188        if self.widget.rect.width > max_width - 40:
189            # Need to relayout the widget
190            factor = 2
191            while self.widget.rect.width > max_width - 40:
192                wrapped = '\n'.join(textwrap.wrap(self.text,
193                                                  len(self.text) // factor))
194                factor *= 2
195                self.widget = MultiLineWidget((20, 20), wrapped)
196                if self.widget.rect.width < 100:
197                    # safety valve
198                    break
199            self.widget.rect.topleft = (x, y)
200            self.widget.draw(surface)
201            # TODO: undo the mad folding
202        else:
203            self.widget.rect.topleft = (x, y)
204            self.widget.draw(surface)
205
206
207class TiledRenderer(Renderer):
208    """Tile the given image to fit the given outline
209
210       Outline is assumed to be in pymunk coordinates"""
211
212    def __init__(self, outline, tile_image, alpha=255):
213        self._tile_image = tile_image
214        self.outline = outline
215        self._tiled = None
216        self._offset = None
217        self._alpha = alpha
218
219    def _make_surface(self, surface, image):
220        size = surface.get_size()
221        mask = pygame.surface.Surface(size, pgl.SRCALPHA)
222        pointlist = [pymunk.pygame_util.to_pygame(p, surface)
223                     for p in self.outline]
224        rect = pygame.draw.polygon(mask,
225                                   pygame.color.Color(
226                                       255, 255, 255, self._alpha),
227                                   pointlist, 0)
228        self._offset = (rect.x, rect.y)
229        tiled = tile_surface((rect.w, rect.h), image)
230        tiled.blit(mask, (0, 0), rect,
231                   special_flags=pgl.BLEND_RGBA_MULT)
232        return tiled
233
234    def render(self, surface):
235        if not self._tiled:
236            self._tiled = self._make_surface(surface, self._tile_image)
237        surface.blit(self._tiled, self._offset)
238        super(TiledRenderer, self).render(surface)
239
240
241class TimedTiledRenderer(TiledRenderer):
242    """Animate tiles"""
243
244    # Should make this a mixin with TimeAnimate, but not right now
245
246    def __init__(self, outline, images, frame_ticks=1, alpha=255):
247        self._images = images
248        self._frame_ticks = frame_ticks
249        self.outline = outline
250        self._frames = [None] * len(images)
251        self._offset = None
252        self._alpha = alpha
253        self._frame = 0
254        self._frame_tick = 0
255
256    def advance_tick(self):
257        self._frame_tick += 1
258        if self._frame_tick > self._frame_ticks:
259            self._frame_tick = 0
260            self._frame += 1
261        if self._frame >= len(self._images):
262            self._frame = 0
263
264    def reset(self):
265        self._frame_tick = 0
266        self._frame = 0
267
268    def update(self, seconds):
269        self.advance_tick()
270
271    def render(self, surface):
272        if not self._frames[self._frame]:
273            self._frames[self._frame] = self._make_surface(
274                surface, self._images[self._frame])
275        self._tiled = self._frames[self._frame]
276        super(TimedTiledRenderer, self).render(surface)
Note: See TracBrowser for help on using the repository browser.