PageRenderTime 49ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/robots/graphics/animation.py

https://bitbucket.org/lordmauve/metalwork
Python | 242 lines | 208 code | 30 blank | 4 comment | 33 complexity | 7267e32a68f2314c5d6e30b092636280 MD5 | raw file
  1. import os
  2. import os.path
  3. import re
  4. import pygame
  5. from pygame.rect import Rect
  6. ANIMATIONS_ROOT = 'assets/anims'
  7. IMAGES_ROOT = 'assets/images'
  8. def load_sprite(fname):
  9. return pygame.image.load(os.path.join(IMAGES_ROOT, fname)).convert_alpha()
  10. class AnimationLayer(object):
  11. def __init__(self, fname, num_sprites=1, offs=None):
  12. self.base = fname
  13. self.num_sprites = num_sprites
  14. if offs is not None and len(offs) > num_sprites:
  15. self.frames = len(offs)
  16. else:
  17. self.frames = num_sprites
  18. if self.frames < 1:
  19. raise ValueError("Invalid number of frames for animation layer %s" % fname)
  20. sprites = [self.load_frame(f) for f in range(self.num_sprites)]
  21. self.sprites = []
  22. for i in range(self.frames):
  23. s = sprites[i % self.num_sprites]
  24. if offs is None:
  25. self.sprites.append((0, 0, s))
  26. elif offs[i] is None:
  27. self.sprites.append((0, 0, None))
  28. else:
  29. ox, oy = offs[i]
  30. self.sprites.append((ox, oy, s))
  31. def load_frame(self, frame):
  32. if self.num_sprites < 2:
  33. return load_sprite('%s.png' % self.base)
  34. else:
  35. return load_sprite('%s-%d.png' % (self.base, frame))
  36. def sprite(self, frame):
  37. s = frame % self.frames
  38. return self.sprites[s]
  39. def translate(self, frame, dx, dy):
  40. s = frame % self.frames
  41. x, y, sprite = self.sprites[s]
  42. if sprite is None:
  43. return
  44. self.sprites[s] = x + dx, y + dy, sprite
  45. def draw(self, screen, frame, pos):
  46. x, y, s = self.sprite(frame)
  47. if s is None:
  48. return
  49. screen.blit(s, (x + pos[0], y + pos[1]))
  50. def bounds(self, frame):
  51. x, y, s = self.sprite(frame)
  52. if s is None:
  53. return Rect(0,0,0,0)
  54. return Rect((x, y), s.get_size())
  55. class AnimatedSprite(object):
  56. class Instance(object):
  57. """An instance of an animated sprite.
  58. The frame timings, layer visibility, etc, are here.
  59. """
  60. def __init__(self, a, framedelay, loop):
  61. self.a = a
  62. self.ftime = 0
  63. self.currentframe = 0
  64. self.loop = loop
  65. self.framedelay = framedelay
  66. self.paused = False
  67. self.finished = False
  68. self.layer_vis = [True] * len(self.a.layers)
  69. self.listeners = []
  70. def pause(self):
  71. self.paused = True
  72. def play(self):
  73. self.paused = False
  74. def go(self, frame):
  75. if frame < 0:
  76. self.currentframe = (self.a.frames + frame) % self.a.frames
  77. else:
  78. self.currentframe = frame % self.a.frames
  79. fin = not self.loop and self.currentframe == (self.a.frames - 1)
  80. if fin and not self.finished:
  81. self.finished = fin
  82. self.fire_finish_event()
  83. def set_layer_visible(self, layer, vis):
  84. self.layer_vis[self.a._get_layer_id(layer)] = vis
  85. def get_layer_offset(self, layer):
  86. layer = self.a.layers[self.a._get_layer_id(layer)]
  87. x, y, s = layer.sprite(self.currentframe)
  88. return x, y
  89. def bounds(self, frame=None):
  90. if frame is None:
  91. frame = self.currentframe
  92. r = Rect(0, 0, 0, 0)
  93. for i, l in enumerate(self.a.layers):
  94. if not self.layer_vis[i]:
  95. continue
  96. b = l.bounds(frame)
  97. r.union_ip(b)
  98. return r
  99. def draw(self, screen, x, y):
  100. framedelay = self.framedelay
  101. if self.finished:
  102. return
  103. if not self.paused:
  104. self.ftime += 1
  105. if self.ftime == framedelay:
  106. self.ftime = 0
  107. if self.currentframe == (self.a.frames - 1):
  108. if self.loop:
  109. self.currentframe = 0
  110. else:
  111. if not self.finished:
  112. self.finished = True
  113. self.fire_finish_event()
  114. else:
  115. self.currentframe += 1
  116. for i, l in enumerate(self.a.layers):
  117. if self.layer_vis[i]:
  118. l.draw(screen, self.currentframe, (x, y))
  119. def add_finish_listener(self, listener):
  120. self.listeners.append(listener)
  121. def fire_finish_event(self):
  122. for l in self.listeners:
  123. l.animation_finished(self)
  124. def is_finished(self):
  125. return self.finished
  126. def __init__(self, layers=[], layer_names={}, name=''):
  127. self.layers = layers
  128. self.frames = max([l.frames for l in layers])
  129. self.layer_names = layer_names
  130. self.name = name
  131. def new(self, framedelay=3, loop=True, start_paused=False):
  132. inst = AnimatedSprite.Instance(self, framedelay=framedelay, loop=loop)
  133. if start_paused:
  134. inst.pause()
  135. return inst
  136. def draw(self, screen, frame, pos):
  137. for l in self.layers:
  138. l.draw(screen, frame, pos)
  139. def bounds(self, frame):
  140. r = None
  141. for l in self.layers:
  142. b = l.bounds(frame)
  143. if r is None:
  144. r = b
  145. else:
  146. r.union_ip(b)
  147. return r
  148. def __str__(self):
  149. return self.name
  150. def _get_layer_id(self, layer):
  151. if isinstance(layer, int):
  152. return layer
  153. try:
  154. return self.layer_names[layer]
  155. except KeyError:
  156. raise KeyError('Invalid layer id %s in animation %s' % (layer, self))
  157. @staticmethod
  158. def load(fname):
  159. layers = []
  160. base = None
  161. frames = 0
  162. offs = None
  163. layer_names = {}
  164. name = None
  165. for l in open(os.path.join(ANIMATIONS_ROOT, fname), 'r'):
  166. mo = re.match(r'^(base|name|frames|offsets):\s*(.*)', l)
  167. if not mo:
  168. continue
  169. k = mo.group(1)
  170. v = mo.group(2)
  171. if k == 'base':
  172. if base and offs:
  173. if name:
  174. layer_names[name] = len(layers)
  175. layers.append(AnimationLayer(base, frames, offs))
  176. base = v
  177. offs = None
  178. angles = 0
  179. name = None
  180. elif k == 'frames':
  181. frames = int(v)
  182. elif k == 'name':
  183. name = v
  184. elif k == 'offsets':
  185. tuples = re.split(r'\s+', v.strip())
  186. offs = []
  187. for t in tuples:
  188. if t == '-':
  189. offs.append(None)
  190. else:
  191. x, y = t.split(',')
  192. x, y = int(x), int(y)
  193. offs.append((x, y))
  194. if base and offs:
  195. if name:
  196. layer_names[name] = len(layers)
  197. layers.append(AnimationLayer(base, frames, offs))
  198. if not layers:
  199. raise ValueError('No layers found')
  200. return AnimatedSprite(layers, layer_names, name=fname)