2
0

TileEngine.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // PlayerPosition.cs
  4. //
  5. // Microsoft XNA Community Game Platform
  6. // Copyright (C) Microsoft Corporation. All rights reserved.
  7. //-----------------------------------------------------------------------------
  8. #endregion
  9. #region Using Statements
  10. using System;
  11. using System.Collections.Generic;
  12. using Microsoft.Xna.Framework;
  13. using Microsoft.Xna.Framework.Graphics;
  14. using RolePlayingGameData;
  15. #endregion
  16. namespace RolePlaying
  17. {
  18. /// <summary>
  19. /// Static class for a tileable map
  20. /// </summary>
  21. static class TileEngine
  22. {
  23. #region Map
  24. /// <summary>
  25. /// The map being used by the tile engine.
  26. /// </summary>
  27. private static Map map = null;
  28. /// <summary>
  29. /// The map being used by the tile engine.
  30. /// </summary>
  31. public static Map Map
  32. {
  33. get { return map; }
  34. }
  35. /// <summary>
  36. /// The position of the outside 0,0 corner of the map, in pixels.
  37. /// </summary>
  38. private static Vector2 mapOriginPosition;
  39. /// <summary>
  40. /// Calculate the screen position of a given map location (in tiles).
  41. /// </summary>
  42. /// <param name="mapPosition">A map location, in tiles.</param>
  43. /// <returns>The current screen position of that location.</returns>
  44. public static Vector2 GetScreenPosition(Point mapPosition)
  45. {
  46. return new Vector2(
  47. mapOriginPosition.X + mapPosition.X * map.TileSize.X,
  48. mapOriginPosition.Y + mapPosition.Y * map.TileSize.Y);
  49. }
  50. /// <summary>
  51. /// Set the map in use by the tile engine.
  52. /// </summary>
  53. /// <param name="map">The new map for the tile engine.</param>
  54. /// <param name="portal">The portal the party is entering on, if any.</param>
  55. public static void SetMap(Map newMap, MapEntry<Portal> portalEntry)
  56. {
  57. // check the parameter
  58. if (newMap == null)
  59. {
  60. throw new ArgumentNullException("newMap");
  61. }
  62. // assign the new map
  63. map = newMap;
  64. // reset the map origin, which will be recalculate on the first update
  65. mapOriginPosition = Vector2.Zero;
  66. // move the party to its initial position
  67. if (portalEntry == null)
  68. {
  69. // no portal - use the spawn position
  70. partyLeaderPosition.TilePosition = map.SpawnMapPosition;
  71. partyLeaderPosition.TileOffset = Vector2.Zero;
  72. partyLeaderPosition.Direction = Direction.South;
  73. }
  74. else
  75. {
  76. // use the portal provided, which may include automatic movement
  77. partyLeaderPosition.TilePosition = portalEntry.MapPosition;
  78. partyLeaderPosition.TileOffset = Vector2.Zero;
  79. partyLeaderPosition.Direction = portalEntry.Direction;
  80. autoPartyLeaderMovement = Vector2.Multiply(
  81. new Vector2(map.TileSize.X, map.TileSize.Y), new Vector2(
  82. portalEntry.Content.LandingMapPosition.X -
  83. partyLeaderPosition.TilePosition.X,
  84. portalEntry.Content.LandingMapPosition.Y -
  85. partyLeaderPosition.TilePosition.Y));
  86. }
  87. }
  88. #endregion
  89. #region Graphics Data
  90. /// <summary>
  91. /// The viewport that the tile engine is rendering within.
  92. /// </summary>
  93. private static Viewport viewport;
  94. /// <summary>
  95. /// The viewport that the tile engine is rendering within.
  96. /// </summary>
  97. public static Viewport Viewport
  98. {
  99. get { return viewport; }
  100. set
  101. {
  102. viewport = value;
  103. viewportCenter = new Vector2(
  104. viewport.X + viewport.Width / 2f,
  105. viewport.Y + viewport.Height / 2f);
  106. }
  107. }
  108. /// <summary>
  109. /// The center of the current viewport.
  110. /// </summary>
  111. private static Vector2 viewportCenter;
  112. #endregion
  113. #region Party
  114. /// <summary>
  115. /// The speed of the party leader, in units per second.
  116. /// </summary>
  117. /// <remarks>
  118. /// The movementCollisionTolerance constant should be a multiple of this number.
  119. /// </remarks>
  120. private const float partyLeaderMovementSpeed = 3f;
  121. /// <summary>
  122. /// The current position of the party leader.
  123. /// </summary>
  124. private static PlayerPosition partyLeaderPosition = new PlayerPosition();
  125. public static PlayerPosition PartyLeaderPosition
  126. {
  127. get { return partyLeaderPosition; }
  128. set { partyLeaderPosition = value; }
  129. }
  130. /// <summary>
  131. /// The automatic movement remaining for the party leader.
  132. /// </summary>
  133. /// <remarks>
  134. /// This is typically used for automatic movement when spawning on a map.
  135. /// </remarks>
  136. private static Vector2 autoPartyLeaderMovement = Vector2.Zero;
  137. /// <summary>
  138. /// Updates the automatic movement of the party.
  139. /// </summary>
  140. /// <returns>The automatic movement for this update.</returns>
  141. private static Vector2 UpdatePartyLeaderAutoMovement(GameTime gameTime)
  142. {
  143. // check for any remaining auto-movement
  144. if (autoPartyLeaderMovement == Vector2.Zero)
  145. {
  146. return Vector2.Zero;
  147. }
  148. // get the remaining-movement direction
  149. Vector2 autoMovementDirection = Vector2.Normalize(autoPartyLeaderMovement);
  150. // calculate the potential movement vector
  151. Vector2 movement = Vector2.Multiply(autoMovementDirection,
  152. partyLeaderMovementSpeed);
  153. // limit the potential movement vector by the remaining auto-movement
  154. movement.X = Math.Sign(movement.X) * MathHelper.Min(Math.Abs(movement.X),
  155. Math.Abs(autoPartyLeaderMovement.X));
  156. movement.Y = Math.Sign(movement.Y) * MathHelper.Min(Math.Abs(movement.Y),
  157. Math.Abs(autoPartyLeaderMovement.Y));
  158. // remove the movement from the total remaining auto-movement
  159. autoPartyLeaderMovement -= movement;
  160. return movement;
  161. }
  162. /// <summary>
  163. /// Update the user-controlled movement of the party.
  164. /// </summary>
  165. /// <returns>The controlled movement for this update.</returns>
  166. private static Vector2 UpdateUserMovement(GameTime gameTime)
  167. {
  168. Vector2 desiredMovement = Vector2.Zero;
  169. // accumulate the desired direction from user input
  170. if (InputManager.IsActionPressed(InputManager.Action.MoveCharacterUp))
  171. {
  172. if (CanPartyLeaderMoveUp())
  173. {
  174. desiredMovement.Y -= partyLeaderMovementSpeed;
  175. }
  176. }
  177. if (InputManager.IsActionPressed(InputManager.Action.MoveCharacterDown))
  178. {
  179. if (CanPartyLeaderMoveDown())
  180. {
  181. desiredMovement.Y += partyLeaderMovementSpeed;
  182. }
  183. }
  184. if (InputManager.IsActionPressed(InputManager.Action.MoveCharacterLeft))
  185. {
  186. if (CanPartyLeaderMoveLeft())
  187. {
  188. desiredMovement.X -= partyLeaderMovementSpeed;
  189. }
  190. }
  191. if (InputManager.IsActionPressed(InputManager.Action.MoveCharacterRight))
  192. {
  193. if (CanPartyLeaderMoveRight())
  194. {
  195. desiredMovement.X += partyLeaderMovementSpeed;
  196. }
  197. }
  198. // if there is no desired movement, then we can't determine a direction
  199. if (desiredMovement == Vector2.Zero)
  200. {
  201. return Vector2.Zero;
  202. }
  203. return desiredMovement;
  204. }
  205. #endregion
  206. #region Collision
  207. /// <summary>
  208. /// The number of pixels that characters should be allowed to move into
  209. /// blocking tiles.
  210. /// </summary>
  211. /// <remarks>
  212. /// The partyMovementSpeed constant should cleanly divide this number.
  213. /// </remarks>
  214. const int movementCollisionTolerance = 12;
  215. /// <summary>
  216. /// Returns true if the player can move up from their current position.
  217. /// </summary>
  218. private static bool CanPartyLeaderMoveUp()
  219. {
  220. // if they're not within the tolerance of the next tile, then this is moot
  221. if (partyLeaderPosition.TileOffset.Y > -movementCollisionTolerance)
  222. {
  223. return true;
  224. }
  225. // if the player is at the outside left and right edges,
  226. // then check the diagonal tiles
  227. if (partyLeaderPosition.TileOffset.X < -movementCollisionTolerance)
  228. {
  229. if (map.IsBlocked(new Point(
  230. partyLeaderPosition.TilePosition.X - 1,
  231. partyLeaderPosition.TilePosition.Y - 1)))
  232. {
  233. return false;
  234. }
  235. }
  236. else if (partyLeaderPosition.TileOffset.X > movementCollisionTolerance)
  237. {
  238. if (map.IsBlocked(new Point(
  239. partyLeaderPosition.TilePosition.X + 1,
  240. partyLeaderPosition.TilePosition.Y - 1)))
  241. {
  242. return false;
  243. }
  244. }
  245. // check the tile above the current one
  246. return !map.IsBlocked(new Point(
  247. partyLeaderPosition.TilePosition.X,
  248. partyLeaderPosition.TilePosition.Y - 1));
  249. }
  250. /// <summary>
  251. /// Returns true if the player can move down from their current position.
  252. /// </summary>
  253. private static bool CanPartyLeaderMoveDown()
  254. {
  255. // if they're not within the tolerance of the next tile, then this is moot
  256. if (partyLeaderPosition.TileOffset.Y < movementCollisionTolerance)
  257. {
  258. return true;
  259. }
  260. // if the player is at the outside left and right edges,
  261. // then check the diagonal tiles
  262. if (partyLeaderPosition.TileOffset.X < -movementCollisionTolerance)
  263. {
  264. if (map.IsBlocked(new Point(
  265. partyLeaderPosition.TilePosition.X - 1,
  266. partyLeaderPosition.TilePosition.Y + 1)))
  267. {
  268. return false;
  269. }
  270. }
  271. else if (partyLeaderPosition.TileOffset.X > movementCollisionTolerance)
  272. {
  273. if (map.IsBlocked(new Point(
  274. partyLeaderPosition.TilePosition.X + 1,
  275. partyLeaderPosition.TilePosition.Y + 1)))
  276. {
  277. return false;
  278. }
  279. }
  280. // check the tile below the current one
  281. return !map.IsBlocked(new Point(
  282. partyLeaderPosition.TilePosition.X,
  283. partyLeaderPosition.TilePosition.Y + 1));
  284. }
  285. /// <summary>
  286. /// Returns true if the player can move left from their current position.
  287. /// </summary>
  288. private static bool CanPartyLeaderMoveLeft()
  289. {
  290. // if they're not within the tolerance of the next tile, then this is moot
  291. if (partyLeaderPosition.TileOffset.X > -movementCollisionTolerance)
  292. {
  293. return true;
  294. }
  295. // if the player is at the outside left and right edges,
  296. // then check the diagonal tiles
  297. if (partyLeaderPosition.TileOffset.Y < -movementCollisionTolerance)
  298. {
  299. if (map.IsBlocked(new Point(
  300. partyLeaderPosition.TilePosition.X - 1,
  301. partyLeaderPosition.TilePosition.Y - 1)))
  302. {
  303. return false;
  304. }
  305. }
  306. else if (partyLeaderPosition.TileOffset.Y > movementCollisionTolerance)
  307. {
  308. if (map.IsBlocked(new Point(
  309. partyLeaderPosition.TilePosition.X - 1,
  310. partyLeaderPosition.TilePosition.Y + 1)))
  311. {
  312. return false;
  313. }
  314. }
  315. // check the tile to the left of the current one
  316. return !map.IsBlocked(new Point(
  317. partyLeaderPosition.TilePosition.X - 1,
  318. partyLeaderPosition.TilePosition.Y));
  319. }
  320. /// <summary>
  321. /// Returns true if the player can move right from their current position.
  322. /// </summary>
  323. private static bool CanPartyLeaderMoveRight()
  324. {
  325. // if they're not within the tolerance of the next tile, then this is moot
  326. if (partyLeaderPosition.TileOffset.X < movementCollisionTolerance)
  327. {
  328. return true;
  329. }
  330. // if the player is at the outside left and right edges,
  331. // then check the diagonal tiles
  332. if (partyLeaderPosition.TileOffset.Y < -movementCollisionTolerance)
  333. {
  334. if (map.IsBlocked(new Point(
  335. partyLeaderPosition.TilePosition.X + 1,
  336. partyLeaderPosition.TilePosition.Y - 1)))
  337. {
  338. return false;
  339. }
  340. }
  341. else if (partyLeaderPosition.TileOffset.Y > movementCollisionTolerance)
  342. {
  343. if (map.IsBlocked(new Point(
  344. partyLeaderPosition.TilePosition.X + 1,
  345. partyLeaderPosition.TilePosition.Y + 1)))
  346. {
  347. return false;
  348. }
  349. }
  350. // check the tile to the right of the current one
  351. return !map.IsBlocked(new Point(
  352. partyLeaderPosition.TilePosition.X + 1,
  353. partyLeaderPosition.TilePosition.Y));
  354. }
  355. #endregion
  356. #region Updating
  357. /// <summary>
  358. /// Update the tile engine.
  359. /// </summary>
  360. public static void Update(GameTime gameTime)
  361. {
  362. // check for auto-movement
  363. Vector2 autoMovement = UpdatePartyLeaderAutoMovement(gameTime);
  364. // if there is no auto-movement, handle user controls
  365. Vector2 userMovement = Vector2.Zero;
  366. if (autoMovement == Vector2.Zero)
  367. {
  368. userMovement = UpdateUserMovement(gameTime);
  369. // calculate the desired position
  370. if (userMovement != Vector2.Zero)
  371. {
  372. Point desiredTilePosition = partyLeaderPosition.TilePosition;
  373. Vector2 desiredTileOffset = partyLeaderPosition.TileOffset;
  374. PlayerPosition.CalculateMovement(
  375. Vector2.Multiply(userMovement, 15f),
  376. ref desiredTilePosition, ref desiredTileOffset);
  377. // check for collisions or encounters in the new tile
  378. if ((partyLeaderPosition.TilePosition != desiredTilePosition) &&
  379. !MoveIntoTile(desiredTilePosition))
  380. {
  381. userMovement = Vector2.Zero;
  382. }
  383. }
  384. }
  385. // move the party
  386. Point oldPartyLeaderTilePosition = partyLeaderPosition.TilePosition;
  387. partyLeaderPosition.Move(autoMovement + userMovement);
  388. // if the tile position has changed, check for random combat
  389. if ((autoMovement == Vector2.Zero) &&
  390. (partyLeaderPosition.TilePosition != oldPartyLeaderTilePosition))
  391. {
  392. Session.CheckForRandomCombat(Map.RandomCombat);
  393. }
  394. // adjust the map origin so that the party is at the center of the viewport
  395. mapOriginPosition += viewportCenter - (partyLeaderPosition.ScreenPosition +
  396. Session.Party.Players[0].MapSprite.SourceOffset);
  397. // make sure the boundaries of the map are never inside the viewport
  398. mapOriginPosition.X = MathHelper.Min(mapOriginPosition.X, viewport.X);
  399. mapOriginPosition.Y = MathHelper.Min(mapOriginPosition.Y, viewport.Y);
  400. mapOriginPosition.X += MathHelper.Max(
  401. (viewport.X + viewport.Width) -
  402. (mapOriginPosition.X + map.MapDimensions.X * map.TileSize.X), 0f);
  403. mapOriginPosition.Y += MathHelper.Max(
  404. (viewport.Y + viewport.Height - Hud.HudHeight) -
  405. (mapOriginPosition.Y + map.MapDimensions.Y * map.TileSize.Y), 0f);
  406. }
  407. /// <summary>
  408. /// Performs any actions associated with moving into a new tile.
  409. /// </summary>
  410. /// <returns>True if the character can move into the tile.</returns>
  411. private static bool MoveIntoTile(Point mapPosition)
  412. {
  413. // if the tile is blocked, then this is simple
  414. if (map.IsBlocked(mapPosition))
  415. {
  416. return false;
  417. }
  418. // check for anything that might be in the tile
  419. if (Session.EncounterTile(mapPosition))
  420. {
  421. return false;
  422. }
  423. // nothing stops the party from moving into the tile
  424. return true;
  425. }
  426. #endregion
  427. #region Drawing
  428. /// <summary>
  429. /// Draw the visible tiles in the given map layers.
  430. /// </summary>
  431. public static void DrawLayers(SpriteBatch spriteBatch, bool drawBase,
  432. bool drawFringe, bool drawObject)
  433. {
  434. // check the parameters
  435. if (spriteBatch == null)
  436. {
  437. throw new ArgumentNullException("spriteBatch");
  438. }
  439. if (!drawBase && !drawFringe && !drawObject)
  440. {
  441. return;
  442. }
  443. Rectangle destinationRectangle =
  444. new Rectangle(0, 0, map.TileSize.X, map.TileSize.Y);
  445. for (int y = 0; y < map.MapDimensions.Y; y++)
  446. {
  447. for (int x = 0; x < map.MapDimensions.X; x++)
  448. {
  449. destinationRectangle.X =
  450. (int)mapOriginPosition.X + x * map.TileSize.X;
  451. destinationRectangle.Y =
  452. (int)mapOriginPosition.Y + y * map.TileSize.Y;
  453. // If the tile is inside the screen
  454. if (CheckVisibility(destinationRectangle))
  455. {
  456. Point mapPosition = new Point(x, y);
  457. if (drawBase)
  458. {
  459. Rectangle sourceRectangle =
  460. map.GetBaseLayerSourceRectangle(mapPosition);
  461. if (sourceRectangle != Rectangle.Empty)
  462. {
  463. spriteBatch.Draw(map.Texture, destinationRectangle,
  464. sourceRectangle, Color.White);
  465. }
  466. }
  467. if (drawFringe)
  468. {
  469. Rectangle sourceRectangle =
  470. map.GetFringeLayerSourceRectangle(mapPosition);
  471. if (sourceRectangle != Rectangle.Empty)
  472. {
  473. spriteBatch.Draw(map.Texture, destinationRectangle,
  474. sourceRectangle, Color.White);
  475. }
  476. }
  477. if (drawObject)
  478. {
  479. Rectangle sourceRectangle =
  480. map.GetObjectLayerSourceRectangle(mapPosition);
  481. if (sourceRectangle != Rectangle.Empty)
  482. {
  483. spriteBatch.Draw(map.Texture, destinationRectangle,
  484. sourceRectangle, Color.White);
  485. }
  486. }
  487. }
  488. }
  489. }
  490. }
  491. /// <summary>
  492. /// Returns true if the given rectangle is within the viewport.
  493. /// </summary>
  494. public static bool CheckVisibility(Rectangle screenRectangle)
  495. {
  496. return ((screenRectangle.X > viewport.X - screenRectangle.Width) &&
  497. (screenRectangle.Y > viewport.Y - screenRectangle.Height) &&
  498. (screenRectangle.X < viewport.X + viewport.Width) &&
  499. (screenRectangle.Y < viewport.Y + viewport.Height));
  500. }
  501. #endregion
  502. }
  503. }