Catapult.cs 21 KB

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