source: nagslang/render.py@ 558:4abf8cf717e9

Last change on this file since 558:4abf8cf717e9 was 558:4abf8cf717e9, checked in by Jeremy Thurgood <firxen@…>, 8 years ago

Rename "bulkhead" to "hatch".

File size: 9.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 (
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 HatchRenderer(Renderer):
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(self, surface):
60 shape = self.game_object.get_shape()
61 if self.game_object.puzzler.get_state():
62 offset = vec_from_angle((shape.b - shape.a).angle, 10)
63 ai = shape.a + offset
64 bi = shape.b - offset
65 self.draw_hatch_line(surface, shape.a, ai)
66 self.draw_hatch_line(surface, bi, shape.b)
67 else:
68 mid = shape.a + (shape.b - shape.a) / 2
69 self.draw_hatch_line(surface, shape.a, mid)
70 self.draw_hatch_line(surface, mid, shape.b)
71
72
73def image_pos(image, pos):
74 return (pos[0] - image.get_width() / 2,
75 pos[1] - image.get_height() / 2)
76
77
78class ImageRenderer(Renderer):
79 def __init__(self, image):
80 self._image = image
81
82 def get_image(self):
83 return self._image
84
85 def rotate_image(self, image):
86 angle = self.game_object.get_render_angle() * 180 / math.pi
87 return pygame.transform.rotate(image, angle)
88
89 def render_image(self, surface, image):
90 image = self.rotate_image(image)
91 pos = self.game_object.get_render_position(surface)
92 surface.blit(image, image_pos(image, pos))
93
94 def render(self, surface):
95 self.render_image(surface, self.get_image())
96 super(ImageRenderer, self).render(surface)
97
98
99class ImageStateRenderer(ImageRenderer):
100 def __init__(self, state_images):
101 self._state_images = state_images
102
103 def get_image(self):
104 return self._state_images[self.game_object.puzzler.get_state()]
105
106
107class TimedAnimatedRenderer(ImageRenderer):
108 def __init__(self, images, frame_ticks=1):
109 self._images = images
110 self._frame_ticks = frame_ticks
111 self._frame_tick = 0
112 self._frame = 0
113
114 def advance_tick(self):
115 self._frame_tick += 1
116 if self._frame_tick > self._frame_ticks:
117 self._frame_tick = 0
118 self._frame += 1
119 if self._frame >= len(self._images):
120 self._frame = 0
121
122 def reset(self):
123 self._frame_tick = 0
124 self._frame = 0
125
126 def get_image(self):
127 return self._images[self._frame]
128
129 def update(self, seconds):
130 self.advance_tick()
131
132
133class MovementAnimatedRenderer(TimedAnimatedRenderer):
134 def update(self, seconds):
135 if self.game_object.is_moving:
136 self.advance_tick()
137 else:
138 self.reset()
139
140
141class RendererSelectionRenderer(Renderer):
142 def __init__(self, renderers):
143 self._renderers = renderers
144
145 def set_game_object(self, game_object):
146 self.game_object = game_object
147 for renderer in self._renderers.values():
148 renderer.set_game_object(game_object)
149
150 @property
151 def renderer(self):
152 return self._renderers[self.select_renderer()]
153
154 def render(self, surface):
155 return self.renderer.render(surface)
156
157 def update(self, seconds):
158 return self.renderer.update(seconds)
159
160 def select_renderer(self):
161 raise NotImplementedError()
162
163
164class FacingSelectionRenderer(RendererSelectionRenderer):
165 def select_renderer(self):
166 return self.game_object.get_facing_direction()
167
168
169class ShapeRenderer(Renderer):
170 def render(self, surface):
171 self._render_shape(surface)
172 super(ShapeRenderer, self).render(surface)
173
174
175class ShapeStateRenderer(ShapeRenderer):
176 """Renders the shape in a different colour depending on the state.
177
178 Requires the game object it's attached to to have a puzzler.
179 """
180 def render(self, surface):
181 if self.game_object.puzzler.get_state():
182 color = pygame.color.THECOLORS['green']
183 else:
184 color = pygame.color.THECOLORS['red']
185
186 self.game_object.get_shape().color = color
187 super(ShapeStateRenderer, self).render(surface)
188
189
190class Overlay(object):
191 def set_game_object(self, game_object):
192 self.game_object = game_object
193
194 def render(self, surface, display_offset, max_width):
195 pass
196
197 def is_visible(self):
198 return self.game_object.puzzler.get_state()
199
200
201class TextOverlay(Overlay):
202 def __init__(self, text, **kwargs):
203 self.text = text
204 self.widget = LabelWidget((20, 20), self.text, **kwargs)
205
206 def render(self, surface, display_offset, max_width):
207 x, y = 20, 20
208 if display_offset[0] < 0:
209 x += abs(display_offset[0])
210 if display_offset[1] < 0:
211 y += abs(display_offset[1])
212 if self.widget.rect.width > max_width - 40:
213 # Need to relayout the widget
214 factor = 2
215 while self.widget.rect.width > max_width - 40:
216 wrapped = '\n'.join(textwrap.wrap(self.text,
217 len(self.text) // factor))
218 factor *= 2
219 self.widget = MultiLineWidget((20, 20), wrapped)
220 if self.widget.rect.width < 100:
221 # safety valve
222 break
223 self.widget.rect.topleft = (x, y)
224 self.widget.draw(surface)
225 # TODO: undo the mad folding
226 else:
227 self.widget.rect.topleft = (x, y)
228 self.widget.draw(surface)
229
230
231class TiledRenderer(Renderer):
232 """Tile the given image to fit the given outline
233
234 Outline is assumed to be in pymunk coordinates"""
235
236 def __init__(self, outline, tile_image, alpha=255):
237 self._tile_image = tile_image
238 self.outline = outline
239 self._tiled = None
240 self._offset = None
241 self._alpha = alpha
242
243 def _make_surface(self, surface, image):
244 size = surface.get_size()
245 mask = pygame.surface.Surface(size, pgl.SRCALPHA)
246 pointlist = [pymunk.pygame_util.to_pygame(p, surface)
247 for p in self.outline]
248 rect = pygame.draw.polygon(mask,
249 pygame.color.Color(
250 255, 255, 255, self._alpha),
251 pointlist, 0)
252 self._offset = (rect.x, rect.y)
253 tiled = tile_surface((rect.w, rect.h), image)
254 tiled.blit(mask, (0, 0), rect,
255 special_flags=pgl.BLEND_RGBA_MULT)
256 return tiled
257
258 def render(self, surface):
259 if not self._tiled:
260 self._tiled = self._make_surface(surface, self._tile_image)
261 surface.blit(self._tiled, self._offset)
262 super(TiledRenderer, self).render(surface)
263
264
265class TimedTiledRenderer(TiledRenderer):
266 """Animate tiles"""
267
268 # Should make this a mixin with TimeAnimate, but not right now
269
270 def __init__(self, outline, images, frame_ticks=1, alpha=255):
271 self._images = images
272 self._frame_ticks = frame_ticks
273 self.outline = outline
274 self._frames = [None] * len(images)
275 self._offset = None
276 self._alpha = alpha
277 self._frame = 0
278 self._frame_tick = 0
279
280 def advance_tick(self):
281 self._frame_tick += 1
282 if self._frame_tick > self._frame_ticks:
283 self._frame_tick = 0
284 self._frame += 1
285 if self._frame >= len(self._images):
286 self._frame = 0
287
288 def reset(self):
289 self._frame_tick = 0
290 self._frame = 0
291
292 def update(self, seconds):
293 self.advance_tick()
294
295 def render(self, surface):
296 if not self._frames[self._frame]:
297 self._frames[self._frame] = self._make_surface(
298 surface, self._images[self._frame])
299 self._tiled = self._frames[self._frame]
300 super(TimedTiledRenderer, self).render(surface)
Note: See TracBrowser for help on using the repository browser.