source: nagslang/render.py

Last change on this file was 647:aeb366d97774, checked in by Stefano Rivera <stefano@…>, 8 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.