AnimatingSprite.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. //-----------------------------------------------------------------------------
  2. // AnimatingSprite.cs
  3. //
  4. // Microsoft XNA Community Game Platform
  5. // Copyright (C) Microsoft Corporation. All rights reserved.
  6. //-----------------------------------------------------------------------------
  7. using System;
  8. using System.Collections.Generic;
  9. using System.IO;
  10. using System.Linq;
  11. using System.Xml.Linq;
  12. using Microsoft.Xna.Framework;
  13. using Microsoft.Xna.Framework.Content;
  14. using Microsoft.Xna.Framework.Graphics;
  15. namespace RolePlaying.Data
  16. {
  17. /// <summary>
  18. /// A sprite sheet with flipbook-style animations.
  19. /// </summary>
  20. public class AnimatingSprite : ContentObject
  21. #if WINDOWS
  22. , ICloneable
  23. #endif
  24. {
  25. /// <summary>
  26. /// The content path and name of the texture for this spell animation.
  27. /// </summary>
  28. private string textureName;
  29. /// <summary>
  30. /// The content path and name of the texture for this spell animation.
  31. /// </summary>
  32. public string TextureName
  33. {
  34. get { return textureName; }
  35. set { textureName = value; }
  36. }
  37. /// <summary>
  38. /// The texture for this spell animation.
  39. /// </summary>
  40. private Texture2D texture;
  41. /// <summary>
  42. /// The texture for this spell animation.
  43. /// </summary>
  44. [ContentSerializerIgnore]
  45. public Texture2D Texture
  46. {
  47. get { return texture; }
  48. set { texture = value; }
  49. }
  50. /// <summary>
  51. /// The dimensions of a single frame of animation.
  52. /// </summary>
  53. private Point frameDimensions;
  54. /// <summary>
  55. /// The width of a single frame of animation.
  56. /// </summary>
  57. public Point FrameDimensions
  58. {
  59. get { return frameDimensions; }
  60. set
  61. {
  62. frameDimensions = value;
  63. frameOrigin.X = frameDimensions.X / 2;
  64. frameOrigin.Y = frameDimensions.Y / 2;
  65. }
  66. }
  67. /// <summary>
  68. /// The origin of the sprite, within a frame.
  69. /// </summary>
  70. private Point frameOrigin;
  71. /// <summary>
  72. /// The number of frames in a row in this sprite.
  73. /// </summary>
  74. private int framesPerRow;
  75. /// <summary>
  76. /// The number of frames in a row in this sprite.
  77. /// </summary>
  78. public int FramesPerRow
  79. {
  80. get { return framesPerRow; }
  81. set { framesPerRow = value; }
  82. }
  83. /// <summary>
  84. /// The offset of this sprite from the position it's drawn at.
  85. /// </summary>
  86. private Vector2 sourceOffset;
  87. /// <summary>
  88. /// The offset of this sprite from the position it's drawn at.
  89. /// </summary>
  90. [ContentSerializer(Optional = true)]
  91. public Vector2 SourceOffset
  92. {
  93. get { return sourceOffset; }
  94. set { sourceOffset = value; }
  95. }
  96. /// <summary>
  97. /// The animations defined for this sprite.
  98. /// </summary>
  99. private List<Animation> animations = new List<Animation>();
  100. /// <summary>
  101. /// The animations defined for this sprite.
  102. /// </summary>
  103. public List<Animation> Animations
  104. {
  105. get { return animations; }
  106. set { animations = value; }
  107. }
  108. /// <summary>
  109. /// Enumerate the animations on this animated sprite.
  110. /// </summary>
  111. /// <param name="animationName">The name of the animation.</param>
  112. /// <returns>The animation if found; null otherwise.</returns>
  113. public Animation this[string animationName]
  114. {
  115. get
  116. {
  117. if (String.IsNullOrEmpty(animationName))
  118. {
  119. return null;
  120. }
  121. foreach (Animation animation in animations)
  122. {
  123. if (String.Compare(animation.Name, animationName, StringComparison.OrdinalIgnoreCase) == 0)
  124. {
  125. return animation;
  126. }
  127. }
  128. return null;
  129. }
  130. }
  131. /// <summary>
  132. /// Add the animation to the list, checking for name collisions.
  133. /// </summary>
  134. /// <returns>True if the animation was added to the list.</returns>
  135. public bool AddAnimation(Animation animation)
  136. {
  137. if ((animation != null) && (this[animation.Name] == null))
  138. {
  139. animations.Add(animation);
  140. return true;
  141. }
  142. return false;
  143. }
  144. /// <summary>
  145. /// The animation currently playing back on this sprite.
  146. /// </summary>
  147. private Animation currentAnimation = null;
  148. /// <summary>
  149. /// The current frame in the current animation.
  150. /// </summary>
  151. private int currentFrame;
  152. /// <summary>
  153. /// The elapsed time since the last frame switch.
  154. /// </summary>
  155. private float elapsedTime;
  156. /// <summary>
  157. /// The source rectangle of the current frame of animation.
  158. /// </summary>
  159. private Rectangle sourceRectangle;
  160. /// <summary>
  161. /// The source rectangle of the current frame of animation.
  162. /// </summary>
  163. public Rectangle SourceRectangle
  164. {
  165. get { return sourceRectangle; }
  166. }
  167. /// <summary>
  168. /// Play the given animation on the sprite.
  169. /// </summary>
  170. /// <remarks>The given animation may be null, to clear any animation.</remarks>
  171. public void PlayAnimation(Animation animation)
  172. {
  173. // start the new animation, ignoring redundant Plays
  174. if (animation != currentAnimation)
  175. {
  176. currentAnimation = animation;
  177. ResetAnimation();
  178. }
  179. }
  180. /// <summary>
  181. /// Play an animation given by index.
  182. /// </summary>
  183. public void PlayAnimation(int index)
  184. {
  185. // check the parameter
  186. if ((index < 0) || (index >= animations.Count))
  187. {
  188. throw new ArgumentOutOfRangeException("index");
  189. }
  190. PlayAnimation(this.animations[index]);
  191. }
  192. /// <summary>
  193. /// Play an animation given by name.
  194. /// </summary>
  195. public void PlayAnimation(string name)
  196. {
  197. // check the parameter
  198. if (String.IsNullOrEmpty(name))
  199. {
  200. throw new ArgumentNullException("name");
  201. }
  202. PlayAnimation(this[name]);
  203. }
  204. /// <summary>
  205. /// Play a given animation name, with the given direction suffix.
  206. /// </summary>
  207. /// <example>
  208. /// For example, passing "Walk" and Direction.South will play the animation
  209. /// named "WalkSouth".
  210. /// </example>
  211. public void PlayAnimation(string name, Direction direction)
  212. {
  213. // check the parameter
  214. if (String.IsNullOrEmpty(name))
  215. {
  216. throw new ArgumentNullException("name");
  217. }
  218. PlayAnimation(name + direction.ToString());
  219. }
  220. /// <summary>
  221. /// Reset the animation back to its starting position.
  222. /// </summary>
  223. public void ResetAnimation()
  224. {
  225. elapsedTime = 0f;
  226. if (currentAnimation != null)
  227. {
  228. currentFrame = currentAnimation.StartingFrame;
  229. // calculate the source rectangle by updating the animation
  230. UpdateAnimation(0f);
  231. }
  232. }
  233. /// <summary>
  234. /// Advance the current animation to the final sprite.
  235. /// </summary>
  236. public void AdvanceToEnd()
  237. {
  238. if (currentAnimation != null)
  239. {
  240. currentFrame = currentAnimation.EndingFrame;
  241. // calculate the source rectangle by updating the animation
  242. UpdateAnimation(0f);
  243. }
  244. }
  245. /// <summary>
  246. /// Stop any animation playing on the sprite.
  247. /// </summary>
  248. public void StopAnimation()
  249. {
  250. currentAnimation = null;
  251. }
  252. /// <summary>
  253. /// Returns true if playback on the current animation is complete, or if
  254. /// there is no animation at all.
  255. /// </summary>
  256. public bool IsPlaybackComplete
  257. {
  258. get
  259. {
  260. return ((currentAnimation == null) ||
  261. (!currentAnimation.IsLoop &&
  262. (currentFrame > currentAnimation.EndingFrame)));
  263. }
  264. }
  265. /// <summary>
  266. /// Update the current animation.
  267. /// </summary>
  268. public void UpdateAnimation(float elapsedSeconds)
  269. {
  270. if (IsPlaybackComplete)
  271. {
  272. return;
  273. }
  274. // loop the animation if needed
  275. if (currentAnimation.IsLoop && (currentFrame > currentAnimation.EndingFrame))
  276. {
  277. currentFrame = currentAnimation.StartingFrame;
  278. }
  279. // update the source rectangle
  280. int column = (currentFrame - 1) / framesPerRow;
  281. sourceRectangle = new Rectangle(
  282. (currentFrame - 1 - (column * framesPerRow)) * frameDimensions.X,
  283. column * frameDimensions.Y,
  284. frameDimensions.X, frameDimensions.Y);
  285. // update the elapsed time
  286. elapsedTime += elapsedSeconds;
  287. // advance to the next frame if ready
  288. while (elapsedTime * 1000f > (float)currentAnimation.Interval)
  289. {
  290. currentFrame++;
  291. elapsedTime -= (float)currentAnimation.Interval / 1000f;
  292. }
  293. }
  294. /// <summary>
  295. /// Draw the sprite at the given position.
  296. /// </summary>
  297. /// <param name="spriteBatch">The SpriteBatch object used to draw.</param>
  298. /// <param name="position">The position of the sprite on-screen.</param>
  299. /// <param name="layerDepth">The depth at which the sprite is drawn.</param>
  300. public void Draw(SpriteBatch spriteBatch, Vector2 position, float layerDepth)
  301. {
  302. Draw(spriteBatch, position, layerDepth, SpriteEffects.None);
  303. }
  304. /// <summary>
  305. /// Draw the sprite at the given position.
  306. /// </summary>
  307. /// <param name="spriteBatch">The SpriteBatch object used to draw.</param>
  308. /// <param name="position">The position of the sprite on-screen.</param>
  309. /// <param name="layerDepth">The depth at which the sprite is drawn.</param>
  310. /// <param name="spriteEffect">The sprite-effect applied.</param>
  311. public void Draw(SpriteBatch spriteBatch, Vector2 position, float layerDepth,
  312. SpriteEffects spriteEffect)
  313. {
  314. // check the parameters
  315. if (spriteBatch == null)
  316. {
  317. throw new ArgumentNullException("spriteBatch");
  318. }
  319. if (texture != null)
  320. {
  321. spriteBatch.Draw(texture, position, sourceRectangle, Color.White, 0f,
  322. sourceOffset, 1f, spriteEffect,
  323. MathHelper.Clamp(layerDepth, 0f, 1f));
  324. }
  325. }
  326. /// <summary>
  327. /// Read an AnimatingSprite object from the content pipeline.
  328. /// </summary>
  329. public class AnimatingSpriteReader : ContentTypeReader<AnimatingSprite>
  330. {
  331. /// <summary>
  332. /// Read an AnimatingSprite object from the content pipeline.
  333. /// </summary>
  334. protected override AnimatingSprite Read(ContentReader input,
  335. AnimatingSprite existingInstance)
  336. {
  337. AnimatingSprite animatingSprite = existingInstance;
  338. if (animatingSprite == null)
  339. {
  340. animatingSprite = new AnimatingSprite();
  341. }
  342. animatingSprite.AssetName = input.AssetName;
  343. animatingSprite.TextureName = input.ReadString();
  344. animatingSprite.Texture =
  345. input.ContentManager.Load<Texture2D>(Path.Combine("Textures", animatingSprite.TextureName));
  346. animatingSprite.FrameDimensions = input.ReadObject<Point>();
  347. animatingSprite.FramesPerRow = input.ReadInt32();
  348. animatingSprite.SourceOffset = input.ReadObject<Vector2>();
  349. animatingSprite.Animations.AddRange(
  350. input.ReadObject<List<Animation>>());
  351. return animatingSprite;
  352. }
  353. }
  354. /// <summary>
  355. /// Creates a clone of this object.
  356. /// </summary>
  357. public object Clone()
  358. {
  359. AnimatingSprite animatingSprite = new AnimatingSprite();
  360. animatingSprite.animations.AddRange(animations);
  361. animatingSprite.currentAnimation = currentAnimation;
  362. animatingSprite.currentFrame = currentFrame;
  363. animatingSprite.elapsedTime = elapsedTime;
  364. animatingSprite.frameDimensions = frameDimensions;
  365. animatingSprite.frameOrigin = frameOrigin;
  366. animatingSprite.framesPerRow = framesPerRow;
  367. animatingSprite.sourceOffset = sourceOffset;
  368. animatingSprite.sourceRectangle = sourceRectangle;
  369. animatingSprite.texture = texture;
  370. animatingSprite.textureName = textureName;
  371. return animatingSprite;
  372. }
  373. internal static AnimatingSprite Load(XElement xElement, ContentManager contentManager)
  374. {
  375. // Create the AnimatingSprite object
  376. var animatingSprite = new AnimatingSprite
  377. {
  378. TextureName = (string)xElement.Element("TextureName"),
  379. Texture = contentManager.Load<Texture2D>(Path.Combine("Textures", (string)xElement.Element("TextureName"))),
  380. FrameDimensions = new Point(
  381. int.Parse(xElement.Element("FrameDimensions").Value.Split(' ')[0]),
  382. int.Parse(xElement.Element("FrameDimensions").Value.Split(' ')[1])),
  383. FramesPerRow = (int)xElement.Element("FramesPerRow"),
  384. SourceOffset = xElement.Element("SourceOffset") != null ? new Vector2(
  385. int.Parse(xElement.Element("SourceOffset").Value.Split(' ')[0]),
  386. int.Parse(xElement.Element("SourceOffset").Value.Split(' ')[1])) : default,
  387. Animations = xElement.Element("Animations")?.Elements("Item").Aggregate(
  388. new List<Animation>(),
  389. (list, animationElement) =>
  390. {
  391. var animation = Animation.Load(animationElement, contentManager);
  392. list.Add(animation);
  393. return list;
  394. }) ?? new List<Animation>()
  395. // Handle Animations if needed
  396. };
  397. return animatingSprite;
  398. }
  399. }
  400. }