Catapult.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // Catapult.cs
  4. //
  5. // Microsoft XNA Community Game Platform
  6. // Copyright (C) Microsoft Corporation. All rights reserved.
  7. //-----------------------------------------------------------------------------
  8. #endregion
  9. #region File Information
  10. //-----------------------------------------------------------------------------
  11. // Animation.cs
  12. //
  13. // Microsoft XNA Community Game Platform
  14. // Copyright (C) Microsoft Corporation. All rights reserved.
  15. //-----------------------------------------------------------------------------
  16. #endregion
  17. #region Using Statements
  18. using System;
  19. using System.Collections.Generic;
  20. using System.Linq;
  21. using System.Text;
  22. using Microsoft.Xna.Framework;
  23. using Microsoft.Xna.Framework.Graphics;
  24. using Microsoft.Xna.Framework.Input.Touch;
  25. //using Microsoft.Devices;
  26. using System.Xml.Linq;
  27. #endregion
  28. namespace CatapultGame
  29. {
  30. #region Catapult states definition enum
  31. [Flags]
  32. public enum CatapultState
  33. {
  34. Idle = 0x0,
  35. Aiming = 0x1,
  36. Firing = 0x2,
  37. ProjectileFlying = 0x4,
  38. ProjectileHit = 0x8,
  39. Hit = 0x10,
  40. Reset = 0x20,
  41. Stalling = 0x40
  42. }
  43. #endregion
  44. public class Catapult : DrawableGameComponent
  45. {
  46. #region Variables/Fields and Properties
  47. // Hold what the game to which the catapult belongs
  48. CatapultGame curGame = null;
  49. SpriteBatch spriteBatch;
  50. Random random;
  51. public bool AnimationRunning { get; set; }
  52. public string Name { get; set; }
  53. public bool IsActive { get; set; }
  54. // In some cases the game need to start second animation while first animation is still running;
  55. // this variable define at which frame the second animation should start
  56. Dictionary<string, int> splitFrames;
  57. Texture2D idleTexture;
  58. Dictionary<string, Animation> animations;
  59. SpriteEffects spriteEffects;
  60. // Projectile
  61. Projectile projectile;
  62. string idleTextureName;
  63. bool isAI;
  64. // Game constants
  65. const float gravity = 500f;
  66. // State of the catapult during its last update
  67. CatapultState lastUpdateState = CatapultState.Idle;
  68. // Used to stall animations
  69. int stallUpdateCycles;
  70. // Current state of the Catapult
  71. CatapultState currentState;
  72. public CatapultState CurrentState
  73. {
  74. get { return currentState; }
  75. set { currentState = value; }
  76. }
  77. float wind;
  78. public float Wind
  79. {
  80. set
  81. {
  82. wind = value;
  83. }
  84. }
  85. Player enemy;
  86. internal Player Enemy
  87. {
  88. set
  89. {
  90. enemy = value;
  91. }
  92. }
  93. Player self;
  94. internal Player Self
  95. {
  96. set
  97. {
  98. self = value;
  99. }
  100. }
  101. Vector2 catapultPosition;
  102. public Vector2 Position
  103. {
  104. get
  105. {
  106. return catapultPosition;
  107. }
  108. }
  109. /// <summary>
  110. /// Describes how powerful the current shot being fired is. The more powerful
  111. /// the shot, the further it goes. 0 is the weakest, 1 is the strongest.
  112. /// </summary>
  113. public float ShotStrength { get; set; }
  114. public float ShotVelocity { get; set; }
  115. /// <summary>
  116. /// Used to determine whether or not the game is over
  117. /// </summary>
  118. public bool GameOver { get; set; }
  119. const int winScore = 5;
  120. #endregion
  121. #region Initialization
  122. public Catapult(Game game)
  123. : base(game)
  124. {
  125. curGame = (CatapultGame)game;
  126. }
  127. public Catapult(Game game, SpriteBatch screenSpriteBatch,
  128. string IdleTexture,
  129. Vector2 CatapultPosition, SpriteEffects SpriteEffect, bool IsAI)
  130. : this(game)
  131. {
  132. idleTextureName = IdleTexture;
  133. catapultPosition = CatapultPosition;
  134. spriteEffects = SpriteEffect;
  135. spriteBatch = screenSpriteBatch;
  136. isAI = IsAI;
  137. splitFrames = new Dictionary<string, int>();
  138. animations = new Dictionary<string, Animation>();
  139. }
  140. /// <summary>
  141. /// Function initializes the catapult instance and loads the animations from XML definition sheet
  142. /// </summary>
  143. public override void Initialize()
  144. {
  145. // Define initial state of the catapult
  146. IsActive = true;
  147. AnimationRunning = false;
  148. currentState = CatapultState.Idle;
  149. stallUpdateCycles = 0;
  150. // Load multiple animations form XML definition
  151. XDocument doc = null;
  152. #if ANDROID
  153. using(var stream = Game.Activity.Assets.Open(@"Content/Textures/Catapults/AnimationsDef.xml"))
  154. {
  155. doc = XDocument.Load(stream);
  156. }
  157. #else
  158. doc = XDocument.Load("Content/Textures/Catapults/AnimationsDef.xml");
  159. #endif
  160. XName name = XName.Get("Definition");
  161. var definitions = doc.Document.Descendants(name);
  162. // Loop over all definitions in XML
  163. foreach (var animationDefinition in definitions)
  164. {
  165. bool? toLoad = null;
  166. bool val;
  167. if (bool.TryParse(animationDefinition.Attribute("IsAI").Value, out val))
  168. toLoad = val;
  169. // Check if the animation definition need to be loaded for current catapult
  170. if (toLoad == isAI || null == toLoad)
  171. {
  172. // Get a name of the animation
  173. string animatonAlias = animationDefinition.Attribute("Alias").Value;
  174. Texture2D texture =
  175. curGame.Content.Load<Texture2D>(animationDefinition.Attribute("SheetName").Value);
  176. // Get the frame size (width & height)
  177. Point frameSize = new Point();
  178. frameSize.X = int.Parse(animationDefinition.Attribute("FrameWidth").Value);
  179. frameSize.Y = int.Parse(animationDefinition.Attribute("FrameHeight").Value);
  180. // Get the frames sheet dimensions
  181. Point sheetSize = new Point();
  182. sheetSize.X = int.Parse(animationDefinition.Attribute("SheetColumns").Value);
  183. sheetSize.Y = int.Parse(animationDefinition.Attribute("SheetRows").Value);
  184. // If definition has a "SplitFrame" - means that other animation should start here - load it
  185. if (null != animationDefinition.Attribute("SplitFrame"))
  186. splitFrames.Add(animatonAlias,
  187. int.Parse(animationDefinition.Attribute("SplitFrame").Value));
  188. // Defing animation speed
  189. TimeSpan frameInterval = TimeSpan.FromSeconds((float)1 /
  190. int.Parse(animationDefinition.Attribute("Speed").Value));
  191. Animation animation = new Animation(texture, frameSize, sheetSize);
  192. // If definition has an offset defined - means that it should be rendered relatively
  193. // to some element/other animation - load it
  194. if (null != animationDefinition.Attribute("OffsetX") &&
  195. null != animationDefinition.Attribute("OffsetY"))
  196. {
  197. animation.Offset = new Vector2(int.Parse(animationDefinition.Attribute("OffsetX").Value),
  198. int.Parse(animationDefinition.Attribute("OffsetY").Value));
  199. }
  200. animations.Add(animatonAlias, animation);
  201. }
  202. }
  203. // Load the textures
  204. idleTexture = curGame.Content.Load<Texture2D>(idleTextureName);
  205. // Initialize the projectile
  206. Vector2 projectileStartPosition;
  207. if (isAI)
  208. projectileStartPosition = new Vector2(630, 340);
  209. else
  210. projectileStartPosition = new Vector2(175, 340);
  211. projectile = new Projectile(curGame, spriteBatch, "Textures/Ammo/rock_ammo",
  212. projectileStartPosition, animations["Fire"].FrameSize.Y, isAI, gravity);
  213. projectile.Initialize();
  214. // Initialize randomizer
  215. random = new Random();
  216. base.Initialize();
  217. }
  218. #endregion
  219. #region Update and Render
  220. public override void Update(GameTime gameTime)
  221. {
  222. bool isGroundHit;
  223. bool startStall;
  224. CatapultState postUpdateStateChange = 0;
  225. if (gameTime == null)
  226. throw new ArgumentNullException("gameTime");
  227. // The catapult is inactive, so there is nothing to update
  228. if (!IsActive)
  229. {
  230. base.Update(gameTime);
  231. return;
  232. }
  233. switch (currentState)
  234. {
  235. case CatapultState.Idle:
  236. // Nothing to do
  237. break;
  238. case CatapultState.Aiming:
  239. if (lastUpdateState != CatapultState.Aiming)
  240. {
  241. AudioManager.PlaySound("ropeStretch", true);
  242. AnimationRunning = true;
  243. if (isAI == true)
  244. {
  245. animations["Aim"].PlayFromFrameIndex(0);
  246. stallUpdateCycles = 20;
  247. startStall = false;
  248. }
  249. }
  250. // Progress Aiming "animation"
  251. if (isAI == false)
  252. {
  253. UpdateAimAccordingToShotStrength();
  254. }
  255. else
  256. {
  257. animations["Aim"].Update();
  258. startStall = AimReachedShotStrength();
  259. currentState = (startStall) ?
  260. CatapultState.Stalling : CatapultState.Aiming;
  261. }
  262. break;
  263. case CatapultState.Stalling:
  264. if (stallUpdateCycles-- <= 0)
  265. {
  266. // We've finished stalling, fire the projectile
  267. Fire(ShotVelocity);
  268. postUpdateStateChange = CatapultState.Firing;
  269. }
  270. break;
  271. case CatapultState.Firing:
  272. // Progress Fire animation
  273. if (lastUpdateState != CatapultState.Firing)
  274. {
  275. AudioManager.StopSound("ropeStretch");
  276. AudioManager.PlaySound("catapultFire");
  277. StartFiringFromLastAimPosition();
  278. }
  279. animations["Fire"].Update();
  280. // If in the "split" point of the animation start
  281. // projectile fire sequence
  282. if (animations["Fire"].FrameIndex == splitFrames["Fire"])
  283. {
  284. postUpdateStateChange =
  285. currentState | CatapultState.ProjectileFlying;
  286. projectile.ProjectilePosition =
  287. projectile.ProjectileStartPosition;
  288. }
  289. break;
  290. case CatapultState.Firing | CatapultState.ProjectileFlying:
  291. // Progress Fire animation
  292. animations["Fire"].Update();
  293. // Update projectile velocity & position in flight
  294. projectile.UpdateProjectileFlightData(gameTime, wind,
  295. gravity, out isGroundHit);
  296. if (isGroundHit)
  297. {
  298. // Start hit sequence
  299. postUpdateStateChange = CatapultState.ProjectileHit;
  300. animations["fireMiss"].PlayFromFrameIndex(0);
  301. }
  302. break;
  303. case CatapultState.ProjectileFlying:
  304. // Update projectile velocity & position in flight
  305. projectile.UpdateProjectileFlightData(gameTime, wind,
  306. gravity, out isGroundHit);
  307. if (isGroundHit)
  308. {
  309. // Start hit sequence
  310. postUpdateStateChange = CatapultState.ProjectileHit;
  311. animations["fireMiss"].PlayFromFrameIndex(0);
  312. }
  313. break;
  314. case CatapultState.ProjectileHit:
  315. // Check hit on ground impact
  316. if (!CheckHit())
  317. {
  318. if (lastUpdateState != CatapultState.ProjectileHit)
  319. {
  320. // VibrateController.Default.Start(
  321. // TimeSpan.FromMilliseconds(100));
  322. // Play hit sound only on a missed hit,
  323. // a direct hit will trigger the explosion sound
  324. AudioManager.PlaySound("boulderHit");
  325. }
  326. // Hit animation finished playing
  327. if (animations["fireMiss"].IsActive == false)
  328. {
  329. postUpdateStateChange = CatapultState.Reset;
  330. }
  331. animations["fireMiss"].Update();
  332. }
  333. else
  334. {
  335. // Catapult hit - start longer vibration on any catapult hit
  336. // Remember that the call to "CheckHit" updates the catapult's
  337. // state to "Hit"
  338. // VibrateController.Default.Start(
  339. // TimeSpan.FromMilliseconds(500));
  340. }
  341. break;
  342. case CatapultState.Hit:
  343. // Progress hit animation
  344. if ((animations["Destroyed"].IsActive == false) &&
  345. (animations["hitSmoke"].IsActive == false))
  346. {
  347. if (enemy.Score >= winScore)
  348. {
  349. GameOver = true;
  350. break;
  351. }
  352. postUpdateStateChange = CatapultState.Reset;
  353. }
  354. animations["Destroyed"].Update();
  355. animations["hitSmoke"].Update();
  356. break;
  357. case CatapultState.Reset:
  358. AnimationRunning = false;
  359. break;
  360. default:
  361. break;
  362. }
  363. lastUpdateState = currentState;
  364. if (postUpdateStateChange != 0)
  365. {
  366. currentState = postUpdateStateChange;
  367. }
  368. base.Update(gameTime);
  369. }
  370. /// <summary>
  371. /// Used to check if the current aim animation frame represents the shot
  372. /// strength set for the catapult.
  373. /// </summary>
  374. /// <returns>True if the current frame represents the shot strength,
  375. /// false otherwise.</returns>
  376. private bool AimReachedShotStrength()
  377. {
  378. return (animations["Aim"].FrameIndex ==
  379. (Convert.ToInt32(animations["Aim"].FrameCount * ShotStrength) - 1));
  380. }
  381. private void UpdateAimAccordingToShotStrength()
  382. {
  383. var aimAnimation = animations["Aim"];
  384. int frameToDisplay =
  385. Convert.ToInt32(aimAnimation.FrameCount * ShotStrength);
  386. aimAnimation.FrameIndex = Math.Min(aimAnimation.FrameCount, frameToDisplay);
  387. }
  388. /// <summary>
  389. /// Calculates the frame from which to start the firing animation,
  390. /// and activates it.
  391. /// </summary>
  392. private void StartFiringFromLastAimPosition()
  393. {
  394. int startFrame = animations["Aim"].FrameCount -
  395. animations["Aim"].FrameIndex;
  396. animations["Fire"].PlayFromFrameIndex(startFrame);
  397. }
  398. public override void Draw(GameTime gameTime)
  399. {
  400. if (gameTime == null)
  401. throw new ArgumentNullException("gameTime");
  402. // Using the last update state makes sure we do not draw
  403. // before updating animations properly
  404. switch (lastUpdateState)
  405. {
  406. case CatapultState.Idle:
  407. DrawIdleCatapult();
  408. break;
  409. case CatapultState.Aiming:
  410. case CatapultState.Stalling:
  411. animations["Aim"].Draw(spriteBatch, catapultPosition,
  412. spriteEffects);
  413. break;
  414. case CatapultState.Firing:
  415. animations["Fire"].Draw(spriteBatch, catapultPosition,
  416. spriteEffects);
  417. break;
  418. case CatapultState.Firing | CatapultState.ProjectileFlying:
  419. case CatapultState.ProjectileFlying:
  420. animations["Fire"].Draw(spriteBatch, catapultPosition,
  421. spriteEffects);
  422. projectile.Draw(gameTime);
  423. break;
  424. case CatapultState.ProjectileHit:
  425. // Draw the catapult
  426. DrawIdleCatapult();
  427. // Projectile Hit animation
  428. animations["fireMiss"].Draw(spriteBatch,
  429. projectile.ProjectileHitPosition, spriteEffects);
  430. break;
  431. case CatapultState.Hit:
  432. // Catapult hit animation
  433. animations["Destroyed"].Draw(spriteBatch, catapultPosition,
  434. spriteEffects);
  435. // Projectile smoke animation
  436. animations["hitSmoke"].Draw(spriteBatch, catapultPosition,
  437. spriteEffects);
  438. break;
  439. case CatapultState.Reset:
  440. DrawIdleCatapult();
  441. break;
  442. default:
  443. break;
  444. }
  445. base.Draw(gameTime);
  446. }
  447. #endregion
  448. #region Hit
  449. /// <summary>
  450. /// Start Hit sequence on catapult - could be executed on self or from enemy in case of hit
  451. /// </summary>
  452. public void Hit()
  453. {
  454. AnimationRunning = true;
  455. animations["Destroyed"].PlayFromFrameIndex(0);
  456. animations["hitSmoke"].PlayFromFrameIndex(0);
  457. currentState = CatapultState.Hit;
  458. }
  459. #endregion
  460. public void Fire(float velocity)
  461. {
  462. projectile.Fire(velocity, velocity);
  463. }
  464. #region Helper Functions
  465. /// <summary>
  466. /// Check if projectile hit some catapult. The possibilities are:
  467. /// Nothing hit, Hit enemy, Hit self
  468. /// </summary>
  469. /// <returns></returns>
  470. private bool CheckHit()
  471. {
  472. bool bRes = false;
  473. // Build a sphere around a projectile
  474. Vector3 center = new Vector3(projectile.ProjectilePosition, 0);
  475. BoundingSphere sphere = new BoundingSphere(center,
  476. Math.Max(projectile.ProjectileTexture.Width / 2,
  477. projectile.ProjectileTexture.Height / 2));
  478. // Check Self-Hit - create a bounding box around self
  479. Vector3 min = new Vector3(catapultPosition, 0);
  480. Vector3 max = new Vector3(catapultPosition +
  481. new Vector2(animations["Fire"].FrameSize.X,
  482. animations["Fire"].FrameSize.Y), 0);
  483. BoundingBox selfBox = new BoundingBox(min, max);
  484. // Check enemy - create a bounding box around the enemy
  485. min = new Vector3(enemy.Catapult.Position, 0);
  486. max = new Vector3(enemy.Catapult.Position +
  487. new Vector2(animations["Fire"].FrameSize.X,
  488. animations["Fire"].FrameSize.Y), 0);
  489. BoundingBox enemyBox = new BoundingBox(min, max);
  490. // Check self hit
  491. if (sphere.Intersects(selfBox) && currentState != CatapultState.Hit)
  492. {
  493. AudioManager.PlaySound("catapultExplosion");
  494. // Launch hit animation sequence on self
  495. Hit();
  496. enemy.Score++;
  497. bRes = true;
  498. }
  499. // Check if enemy was hit
  500. else if (sphere.Intersects(enemyBox)
  501. && enemy.Catapult.CurrentState != CatapultState.Hit
  502. && enemy.Catapult.CurrentState != CatapultState.Reset)
  503. {
  504. AudioManager.PlaySound("catapultExplosion");
  505. // Launch enemy hit animaton
  506. enemy.Catapult.Hit();
  507. self.Score++;
  508. bRes = true;
  509. currentState = CatapultState.Reset;
  510. }
  511. return bRes;
  512. }
  513. /// <summary>
  514. /// Draw catapult in Idle state
  515. /// </summary>
  516. private void DrawIdleCatapult()
  517. {
  518. spriteBatch.Draw(idleTexture, catapultPosition, null, Color.White,
  519. 0.0f, Vector2.Zero, 1.0f,
  520. spriteEffects, 0);
  521. }
  522. #endregion
  523. }
  524. }