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

Last change on this file since 400:4523b1ff17ae was 400:4523b1ff17ae, checked in by Neil Muller <drnlmuller@…>, 8 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.