PageRenderTime 41ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/robots/animations.py

https://bitbucket.org/lordmauve/metalwork
Python | 317 lines | 316 code | 1 blank | 0 comment | 0 complexity | 8d350dcecfc522d7a091208ff388f6f9 MD5 | raw file
  1. import math
  2. from compass import *
  3. """This module contains "Animations", which are animated controllers for world state.
  4. Each animation runs for a certain number of frames after which they will automatically be
  5. removed from the animation queue. They should leave the world in a consistent state.
  6. """
  7. def linear_interpolation(currentframe, frames):
  8. """Returns a value between 0.0 and 1.0 that varies linearly with currentframe."""
  9. return float(currentframe) / float(frames)
  10. def cosine_interpolation(currentframe, frames):
  11. """Returns a value between 0.0 and 1.0 that varies in a cosine function with currentframe."""
  12. return math.sin(0.5 * math.pi * (float(currentframe) / frames)) ** 2
  13. class Animation(object):
  14. def update(self):
  15. """Update state for next frame of the animation"""
  16. def is_finished(self):
  17. return True
  18. class Interpolation(Animation):
  19. def __init__(self, frames=30, interpolation=cosine_interpolation):
  20. self.currentframe = 0
  21. self.frames = frames
  22. self.interp = interpolation
  23. def is_finished(self):
  24. return self.currentframe >= self.frames
  25. def update(self):
  26. self.currentframe += 1
  27. if self.is_finished():
  28. self.finish()
  29. else:
  30. frac = self.interp(self.currentframe, self.frames)
  31. self.step(frac)
  32. def step(self, frac):
  33. """Called each frame; update the animation for value frac."""
  34. def finish(self):
  35. """Called when the animation finishes."""
  36. class MoveAnimation(Interpolation):
  37. def __init__(self, actor, dx, dy, frames=50, interpolation=cosine_interpolation):
  38. super(MoveAnimation, self).__init__(frames, interpolation)
  39. self.dx = dx
  40. self.dy = dy
  41. self.actor = actor
  42. def finish(self):
  43. x, y = self.actor.pos
  44. self.actor.pos = x + self.dx, y + self.dy
  45. self.actor.display_pos = None
  46. def step(self, frac):
  47. x, y = self.actor.pos
  48. nx = x + frac * self.dx
  49. ny = y + frac * self.dy
  50. self.actor.display_pos = nx, ny
  51. class Pause(Animation):
  52. def __init__(self, frames=30):
  53. self.currentframe = 0
  54. self.frames = frames
  55. def is_finished(self):
  56. return self.currentframe >= self.frames
  57. def update(self):
  58. self.currentframe += 1
  59. class AnimationSequence(Animation):
  60. """Plays the given animations in sequence"""
  61. def __init__(self, animations):
  62. self.animations = animations
  63. self.current_anim = animations.pop(0)
  64. def is_finished(self):
  65. return self.current_anim is None
  66. def finish(self):
  67. pass
  68. def update(self):
  69. if self.current_anim.is_finished():
  70. try:
  71. self.current_anim = self.animations.pop(0)
  72. except IndexError:
  73. self.current_anim = None
  74. self.finish()
  75. return
  76. self.current_anim.update()
  77. class AnimationList(Animation):
  78. """Plays the given animations in parallel"""
  79. def __init__(self, animations):
  80. self.animations = animations
  81. def is_finished(self):
  82. return not self.animations
  83. def finish(self):
  84. pass
  85. def update(self):
  86. for a in self.animations:
  87. a.update()
  88. self.animations = [a for a in self.animations if not a.is_finished()]
  89. class TurnAnimation(Animation):
  90. """Turns the robot then runs the next animation"""
  91. def __init__(self, actor, direction):
  92. self.actor = actor
  93. self.direction = direction
  94. current_orient = Compass.direction_for_id(actor.orient).frame
  95. target_orient = direction.frame
  96. fsp = []
  97. fsn = []
  98. for i in range(7):
  99. f = (current_orient + i) % 12
  100. fsp += [f]
  101. if f == target_orient:
  102. self.fs = fsp
  103. break
  104. f = (current_orient - i) % 12
  105. fsn += [f]
  106. if f == target_orient:
  107. self.fs = fsn
  108. break
  109. self.frame = 0
  110. self.framedelay = 4
  111. self.f = 0
  112. def is_finished(self):
  113. return self.frame >= len(self.fs)
  114. def finish(self):
  115. self.actor.display_orient = None
  116. self.actor.orient = self.direction.id
  117. def update(self):
  118. if self.f == self.framedelay:
  119. self.frame += 1
  120. self.f = 0
  121. else:
  122. self.f += 1
  123. if self.frame < len(self.fs):
  124. self.actor.display_orient = self.fs[self.frame]
  125. else:
  126. self.finish()
  127. class PushAnimation(Animation):
  128. def __init__(self, actor, target, dx, dy, frames=90):
  129. self.actor_anim = MoveAnimation(actor, dx, dy, frames)
  130. self.target_anim = MoveAnimation(target, dx, dy, frames-30, interpolation=linear_interpolation)
  131. def update(self):
  132. if not self.actor_anim.is_finished():
  133. self.actor_anim.update()
  134. if self.actor_anim.currentframe >= 20 and not self.target_anim.is_finished():
  135. self.target_anim.update()
  136. def finish(self):
  137. pass
  138. def is_finished(self):
  139. return self.actor_anim.is_finished() and self.target_anim.is_finished()
  140. class LiftAnimation(Interpolation):
  141. def __init__(self, tile, actors, target):
  142. super(LiftAnimation, self).__init__()
  143. self.tile = tile
  144. self.actors = actors
  145. self.target = target
  146. self.initial = tile.frac
  147. def step(self, frac):
  148. self.tile.frac = frac * self.target + (1 - frac) * self.initial
  149. for a in self.actors:
  150. a.display_alt = a.alt + (self.tile.frac - self.initial)
  151. def finish(self):
  152. self.tile.frac = self.target
  153. for a in self.actors:
  154. a.alt = a.alt + (self.target - self.initial)
  155. a.display_alt = None
  156. class BoxSinkAnimation(Animation):
  157. def __init__(self, scene, box):
  158. self.box = box
  159. self.scene = scene
  160. self.box.play('drop')
  161. self.finished = False
  162. def is_finished(self):
  163. f = self.box.anim.is_finished()
  164. if f and not self.finished:
  165. self.finish()
  166. return f
  167. def finish(self):
  168. self.scene.world.remove(self.box)
  169. self.scene.world[self.box.pos].fill(self.box)
  170. class AdjacentInteraction(AnimationSequence):
  171. """This is an animation where a robot moves partway towards an adjacent tile to
  172. interact with it, pauses, then moves back."""
  173. MOVE_FRAC = 0.35 # The fraction of the way to the next tile to move
  174. def __init__(self, actor, direction, interaction_animation=None):
  175. x, y = direction.vector
  176. x *= self.MOVE_FRAC
  177. y *= self.MOVE_FRAC
  178. self.pos = actor.pos # store pos to avoid slight errors from the MoveAnimations
  179. if interaction_animation is None:
  180. interaction_animation = Pause(45)
  181. anims = [MoveAnimation(actor, x, y), interaction_animation, MoveAnimation(actor, -x, -y)]
  182. super(AdjacentInteraction, self).__init__(anims)
  183. def update(self):
  184. super(AdjacentInteraction, self).update()
  185. if self.current_anim and self.current_anim.is_finished():
  186. if len(self.animations) == 2:
  187. self.on_interaction_start()
  188. elif len(self.animations) == 1:
  189. self.on_interaction_finish()
  190. def on_interaction_start(self):
  191. """Subclasses may implement this method to specify what happens when the interaction starts"""
  192. def on_interaction_finish(self):
  193. """Subclasses may implement this method to specify what happens when the interaction finishes"""
  194. def finish(self):
  195. self.actor.pos = self.pos # restore pos
  196. class PumpAnimation(AdjacentInteraction):
  197. MOVE_FRAC = 0.35
  198. def __init__(self, actor, direction, target):
  199. self.actor = actor
  200. self.target = target
  201. super(PumpAnimation, self).__init__(actor, direction)
  202. def on_interaction_start(self):
  203. fill = self.actor.get_fill()
  204. if not fill and hasattr(self.target, 'start_draw_up_fluid'):
  205. self.target.start_draw_up_fluid()
  206. elif fill and hasattr(self.target, 'start_fill_fluid'):
  207. self.target.start_fill_fluid()
  208. def on_interaction_finish(self):
  209. fill = self.actor.get_fill()
  210. if fill:
  211. self.target.fill_fluid(fill)
  212. self.actor.set_fill(None)
  213. else:
  214. self.actor.set_fill(self.target.draw_up_fluid())
  215. class FreezerAnimation(AdjacentInteraction):
  216. MOVE_FRAC = 0.2
  217. def __init__(self, actor, direction, freezer, world):
  218. self.actor = actor
  219. self.freezer = freezer
  220. self.world = world
  221. super(FreezerAnimation, self).__init__(actor, direction)
  222. def on_interaction_start(self):
  223. self.freezer.play('freeze')
  224. def on_interaction_finish(self):
  225. self.actor.set_fill(None)
  226. self.freezer.cube = True
  227. class FreezerEjectAnimation(Animation):
  228. def __init__(self, scene, freezer):
  229. self.freezer = freezer
  230. self.scene = scene
  231. self.freezer.play('eject')
  232. self.finished = False
  233. # move the freezer south one unit until the animation finishes, to hack z-order
  234. # this is compensated for in the animation loading
  235. self.freezer.display_pos = self.freezer.eject_pos()[:2]
  236. def is_finished(self):
  237. f = self.freezer.anim.is_finished()
  238. if f and not self.finished:
  239. self.finish()
  240. return f
  241. def finish(self):
  242. from robots.objects import Ice
  243. self.freezer.display_pos = None
  244. ice = Ice(*self.freezer.eject_pos())
  245. self.scene.world.add(ice)
  246. ice.play(None)
  247. self.freezer.cube = False