source: nagslang/render.py

Last change on this file was 647:aeb366d97774, checked in by Stefano Rivera <stefano@…>, 7 years ago

Show splash image on startup

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