TileEngine.cs 20 KB

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