metalwork /robots/graphics/animation.py

Language Python Lines 243
MD5 Hash 7267e32a68f2314c5d6e30b092636280 Estimated Cost $4,907 (why?)
Repository https://bitbucket.org/lordmauve/metalwork View Raw File View Project SPDX
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
import os
import os.path
import re
import pygame

from pygame.rect import Rect


ANIMATIONS_ROOT = 'assets/anims'
IMAGES_ROOT = 'assets/images'

def load_sprite(fname):
	return pygame.image.load(os.path.join(IMAGES_ROOT, fname)).convert_alpha()

class AnimationLayer(object):
	def __init__(self, fname, num_sprites=1, offs=None):
		self.base = fname
		self.num_sprites = num_sprites
		if offs is not None and len(offs) > num_sprites:
			self.frames = len(offs)
		else:
			self.frames = num_sprites
		if self.frames < 1:
			raise ValueError("Invalid number of frames for animation layer %s" % fname)

		sprites = [self.load_frame(f) for f in range(self.num_sprites)]
		self.sprites = []
		for i in range(self.frames):
			s = sprites[i % self.num_sprites]
			if offs is None:
				self.sprites.append((0, 0, s))
			elif offs[i] is None:
				self.sprites.append((0, 0, None))
			else:
				ox, oy = offs[i]
				self.sprites.append((ox, oy, s))

	def load_frame(self, frame):
		if self.num_sprites < 2:
			return load_sprite('%s.png' % self.base)
		else:
			return load_sprite('%s-%d.png' % (self.base, frame))

	def sprite(self, frame):
		s = frame % self.frames
		return self.sprites[s]

	def translate(self, frame, dx, dy):
		s = frame % self.frames
		x, y, sprite = self.sprites[s]
		if sprite is None:
			return
		self.sprites[s] = x + dx, y + dy, sprite

	def draw(self, screen, frame, pos):
		x, y, s = self.sprite(frame)
		if s is None:
			return
		screen.blit(s, (x + pos[0], y + pos[1]))

	def bounds(self, frame):
		x, y, s = self.sprite(frame)
		if s is None:
			return Rect(0,0,0,0)
		return Rect((x, y), s.get_size())


class AnimatedSprite(object):
	class Instance(object):
		"""An instance of an animated sprite.
	
		The frame timings, layer visibility, etc, are here.
		"""
		def __init__(self, a, framedelay, loop):
			self.a = a
			self.ftime = 0
			self.currentframe = 0
			self.loop = loop
			self.framedelay = framedelay
			self.paused = False
			self.finished = False
			self.layer_vis = [True] * len(self.a.layers)

			self.listeners = []

		def pause(self):
			self.paused = True

		def play(self):
			self.paused = False

		def go(self, frame):
			if frame < 0:
				self.currentframe = (self.a.frames + frame) % self.a.frames
			else:
				self.currentframe = frame % self.a.frames
			
			fin = not self.loop and self.currentframe == (self.a.frames - 1)
			if fin and not self.finished:
				self.finished = fin
				self.fire_finish_event()

		def set_layer_visible(self, layer, vis):
			self.layer_vis[self.a._get_layer_id(layer)] = vis

		def get_layer_offset(self, layer):
			layer = self.a.layers[self.a._get_layer_id(layer)]
			x, y, s = layer.sprite(self.currentframe)
			return x, y

		def bounds(self, frame=None):
			if frame is None:
				frame = self.currentframe
			r = Rect(0, 0, 0, 0)
			for i, l in enumerate(self.a.layers):
				if not self.layer_vis[i]:
					continue
				b = l.bounds(frame)
				r.union_ip(b)
			return r

		def draw(self, screen, x, y):
			framedelay = self.framedelay

			if self.finished:
				return

			if not self.paused:
				self.ftime += 1

			if self.ftime == framedelay:
				self.ftime = 0
				if self.currentframe == (self.a.frames - 1):
					if self.loop:
						self.currentframe = 0
					else:
						if not self.finished:
							self.finished = True
							self.fire_finish_event()
				else:
					self.currentframe += 1

			for i, l in enumerate(self.a.layers):
				if self.layer_vis[i]:
					l.draw(screen, self.currentframe, (x, y))

		def add_finish_listener(self, listener):
			self.listeners.append(listener)

		def fire_finish_event(self):
			for l in self.listeners:
				l.animation_finished(self)

		def is_finished(self):
			return self.finished

	def __init__(self, layers=[], layer_names={}, name=''):
		self.layers = layers
		self.frames = max([l.frames for l in layers])
		self.layer_names = layer_names
		self.name = name

	def new(self, framedelay=3, loop=True, start_paused=False):
		inst = AnimatedSprite.Instance(self, framedelay=framedelay, loop=loop)
		if start_paused:
			inst.pause()
		return inst

	def draw(self, screen, frame, pos):
		for l in self.layers:
			l.draw(screen, frame, pos)

	def bounds(self, frame):
		r = None
		for l in self.layers:
			b = l.bounds(frame)
			if r is None:
				r = b
			else:
				r.union_ip(b)
		return r
				

	def __str__(self):
		return self.name

	def _get_layer_id(self, layer):
		if isinstance(layer, int):
			return layer
		try:
			return self.layer_names[layer]
		except KeyError:
			raise KeyError('Invalid layer id %s in animation %s' % (layer, self))

	@staticmethod
	def load(fname):
		layers = []
		base = None
		frames = 0
		offs = None
		layer_names = {}
		name = None

		for l in open(os.path.join(ANIMATIONS_ROOT, fname), 'r'):
			mo = re.match(r'^(base|name|frames|offsets):\s*(.*)', l)
			if not mo:
				continue
			k = mo.group(1)
			v = mo.group(2)
			if k == 'base':
				if base and offs:
					if name:
						layer_names[name] = len(layers) 
					layers.append(AnimationLayer(base, frames, offs))
				base = v
				offs = None
				angles = 0
				name = None
			elif k == 'frames':
				frames = int(v)
			elif k == 'name':
				name = v
			elif k == 'offsets':
				tuples = re.split(r'\s+', v.strip())
				offs = []
				for t in tuples:
					if t == '-':
						offs.append(None)
					else:
						x, y = t.split(',')
						x, y = int(x), int(y)
						offs.append((x, y))
		if base and offs:
			if name:
				layer_names[name] = len(layers) 
			layers.append(AnimationLayer(base, frames, offs))

		if not layers:
			raise ValueError('No layers found')

		return AnimatedSprite(layers, layer_names, name=fname)
Back to Top