TileEngine.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  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. // Process mouse input for actions
  160. InputManager.HandleMouseDown(PartyLeaderPosition.ScreenPosition.ToPoint());
  161. Vector2 desiredMovement = Vector2.Zero;
  162. // accumulate the desired direction from user input
  163. if (InputManager.IsActionPressed(InputManager.InputAction.MoveCharacterUp))
  164. {
  165. if (CanPartyLeaderMoveUp())
  166. {
  167. desiredMovement.Y -= partyLeaderMovementSpeed;
  168. }
  169. }
  170. if (InputManager.IsActionPressed(InputManager.InputAction.MoveCharacterDown))
  171. {
  172. if (CanPartyLeaderMoveDown())
  173. {
  174. desiredMovement.Y += partyLeaderMovementSpeed;
  175. }
  176. }
  177. if (InputManager.IsActionPressed(InputManager.InputAction.MoveCharacterLeft))
  178. {
  179. if (CanPartyLeaderMoveLeft())
  180. {
  181. desiredMovement.X -= partyLeaderMovementSpeed;
  182. }
  183. }
  184. if (InputManager.IsActionPressed(InputManager.InputAction.MoveCharacterRight))
  185. {
  186. if (CanPartyLeaderMoveRight())
  187. {
  188. desiredMovement.X += partyLeaderMovementSpeed;
  189. }
  190. }
  191. // if there is no desired movement, then we can't determine a direction
  192. if (desiredMovement == Vector2.Zero)
  193. {
  194. return Vector2.Zero;
  195. }
  196. return desiredMovement;
  197. }
  198. /// <summary>
  199. /// The number of pixels that characters should be allowed to move into
  200. /// blocking tiles.
  201. /// </summary>
  202. /// <remarks>
  203. /// The partyMovementSpeed constant should cleanly divide this number.
  204. /// </remarks>
  205. const int movementCollisionTolerance = 12;
  206. /// <summary>
  207. /// Returns true if the player can move up from their current position.
  208. /// </summary>
  209. private static bool CanPartyLeaderMoveUp()
  210. {
  211. // if they're not within the tolerance of the next tile, then this is moot
  212. if (partyLeaderPosition.TileOffset.Y > -movementCollisionTolerance)
  213. {
  214. return true;
  215. }
  216. // if the player is at the outside left and right edges,
  217. // then check the diagonal tiles
  218. if (partyLeaderPosition.TileOffset.X < -movementCollisionTolerance)
  219. {
  220. if (map.IsBlocked(new Point(
  221. partyLeaderPosition.TilePosition.X - 1,
  222. partyLeaderPosition.TilePosition.Y - 1)))
  223. {
  224. return false;
  225. }
  226. }
  227. else 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. // check the tile above the current one
  237. return !map.IsBlocked(new Point(
  238. partyLeaderPosition.TilePosition.X,
  239. partyLeaderPosition.TilePosition.Y - 1));
  240. }
  241. /// <summary>
  242. /// Returns true if the player can move down from their current position.
  243. /// </summary>
  244. private static bool CanPartyLeaderMoveDown()
  245. {
  246. // if they're not within the tolerance of the next tile, then this is moot
  247. if (partyLeaderPosition.TileOffset.Y < movementCollisionTolerance)
  248. {
  249. return true;
  250. }
  251. // if the player is at the outside left and right edges,
  252. // then check the diagonal tiles
  253. if (partyLeaderPosition.TileOffset.X < -movementCollisionTolerance)
  254. {
  255. if (map.IsBlocked(new Point(
  256. partyLeaderPosition.TilePosition.X - 1,
  257. partyLeaderPosition.TilePosition.Y + 1)))
  258. {
  259. return false;
  260. }
  261. }
  262. else 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. // check the tile below the current one
  272. return !map.IsBlocked(new Point(
  273. partyLeaderPosition.TilePosition.X,
  274. partyLeaderPosition.TilePosition.Y + 1));
  275. }
  276. /// <summary>
  277. /// Returns true if the player can move left from their current position.
  278. /// </summary>
  279. private static bool CanPartyLeaderMoveLeft()
  280. {
  281. // if they're not within the tolerance of the next tile, then this is moot
  282. if (partyLeaderPosition.TileOffset.X > -movementCollisionTolerance)
  283. {
  284. return true;
  285. }
  286. // if the player is at the outside left and right edges,
  287. // then check the diagonal tiles
  288. if (partyLeaderPosition.TileOffset.Y < -movementCollisionTolerance)
  289. {
  290. if (map.IsBlocked(new Point(
  291. partyLeaderPosition.TilePosition.X - 1,
  292. partyLeaderPosition.TilePosition.Y - 1)))
  293. {
  294. return false;
  295. }
  296. }
  297. else 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. // check the tile to the left of the current one
  307. return !map.IsBlocked(new Point(
  308. partyLeaderPosition.TilePosition.X - 1,
  309. partyLeaderPosition.TilePosition.Y));
  310. }
  311. /// <summary>
  312. /// Returns true if the player can move right from their current position.
  313. /// </summary>
  314. private static bool CanPartyLeaderMoveRight()
  315. {
  316. // if they're not within the tolerance of the next tile, then this is moot
  317. if (partyLeaderPosition.TileOffset.X < movementCollisionTolerance)
  318. {
  319. return true;
  320. }
  321. // if the player is at the outside left and right edges,
  322. // then check the diagonal tiles
  323. if (partyLeaderPosition.TileOffset.Y < -movementCollisionTolerance)
  324. {
  325. if (map.IsBlocked(new Point(
  326. partyLeaderPosition.TilePosition.X + 1,
  327. partyLeaderPosition.TilePosition.Y - 1)))
  328. {
  329. return false;
  330. }
  331. }
  332. else 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. // check the tile to the right of the current one
  342. return !map.IsBlocked(new Point(
  343. partyLeaderPosition.TilePosition.X + 1,
  344. partyLeaderPosition.TilePosition.Y));
  345. }
  346. /// <summary>
  347. /// Update the tile engine.
  348. /// </summary>
  349. public static void Update(GameTime gameTime)
  350. {
  351. // check for auto-movement
  352. Vector2 autoMovement = UpdatePartyLeaderAutoMovement(gameTime);
  353. // if there is no auto-movement, handle user controls
  354. Vector2 userMovement = Vector2.Zero;
  355. if (autoMovement == Vector2.Zero)
  356. {
  357. userMovement = UpdateUserMovement(gameTime);
  358. // calculate the desired position
  359. if (userMovement != Vector2.Zero)
  360. {
  361. Point desiredTilePosition = partyLeaderPosition.TilePosition;
  362. Vector2 desiredTileOffset = partyLeaderPosition.TileOffset;
  363. PlayerPosition.CalculateMovement(
  364. Vector2.Multiply(userMovement, 15f),
  365. ref desiredTilePosition, ref desiredTileOffset);
  366. // check for collisions or encounters in the new tile
  367. if ((partyLeaderPosition.TilePosition != desiredTilePosition) &&
  368. !MoveIntoTile(desiredTilePosition))
  369. {
  370. userMovement = Vector2.Zero;
  371. }
  372. }
  373. }
  374. // move the party
  375. Point oldPartyLeaderTilePosition = partyLeaderPosition.TilePosition;
  376. partyLeaderPosition.Move(autoMovement + userMovement);
  377. // if the tile position has changed, check for random combat
  378. if ((autoMovement == Vector2.Zero) &&
  379. (partyLeaderPosition.TilePosition != oldPartyLeaderTilePosition))
  380. {
  381. Session.CheckForRandomCombat(Map.RandomCombat);
  382. }
  383. // adjust the map origin so that the party is at the center of the viewport
  384. mapOriginPosition += viewportCenter - (partyLeaderPosition.ScreenPosition +
  385. Session.Party.Players[0].MapSprite.SourceOffset);
  386. // make sure the boundaries of the map are never inside the viewport
  387. mapOriginPosition.X = MathHelper.Min(mapOriginPosition.X, viewport.X);
  388. mapOriginPosition.Y = MathHelper.Min(mapOriginPosition.Y, viewport.Y);
  389. mapOriginPosition.X += MathHelper.Max(
  390. (viewport.X + viewport.Width) -
  391. (mapOriginPosition.X + map.MapDimensions.X * map.TileSize.X), 0f);
  392. mapOriginPosition.Y += MathHelper.Max(
  393. (viewport.Y + viewport.Height - Hud.HudHeight) -
  394. (mapOriginPosition.Y + map.MapDimensions.Y * map.TileSize.Y), 0f);
  395. }
  396. /// <summary>
  397. /// Performs any actions associated with moving into a new tile.
  398. /// </summary>
  399. /// <returns>True if the character can move into the tile.</returns>
  400. private static bool MoveIntoTile(Point mapPosition)
  401. {
  402. // if the tile is blocked, then this is simple
  403. if (map.IsBlocked(mapPosition))
  404. {
  405. return false;
  406. }
  407. // check for anything that might be in the tile
  408. if (Session.EncounterTile(mapPosition))
  409. {
  410. return false;
  411. }
  412. // nothing stops the party from moving into the tile
  413. return true;
  414. }
  415. /// <summary>
  416. /// Draw the visible tiles in the given map layers.
  417. /// </summary>
  418. public static void DrawLayers(SpriteBatch spriteBatch, bool drawBase,
  419. bool drawFringe, bool drawObject)
  420. {
  421. // check the parameters
  422. if (spriteBatch == null)
  423. {
  424. throw new ArgumentNullException("spriteBatch");
  425. }
  426. if (!drawBase && !drawFringe && !drawObject)
  427. {
  428. return;
  429. }
  430. Rectangle destinationRectangle =
  431. new Rectangle(0, 0, map.TileSize.X, map.TileSize.Y);
  432. for (int y = 0; y < map.MapDimensions.Y; y++)
  433. {
  434. for (int x = 0; x < map.MapDimensions.X; x++)
  435. {
  436. destinationRectangle.X =
  437. (int)mapOriginPosition.X + x * map.TileSize.X;
  438. destinationRectangle.Y =
  439. (int)mapOriginPosition.Y + y * map.TileSize.Y;
  440. // If the tile is inside the screen
  441. if (CheckVisibility(destinationRectangle))
  442. {
  443. Point mapPosition = new Point(x, y);
  444. if (drawBase)
  445. {
  446. Rectangle sourceRectangle =
  447. map.GetBaseLayerSourceRectangle(mapPosition);
  448. if (sourceRectangle != Rectangle.Empty)
  449. {
  450. spriteBatch.Draw(map.Texture, destinationRectangle,
  451. sourceRectangle, Color.White);
  452. }
  453. }
  454. if (drawFringe)
  455. {
  456. Rectangle sourceRectangle =
  457. map.GetFringeLayerSourceRectangle(mapPosition);
  458. if (sourceRectangle != Rectangle.Empty)
  459. {
  460. spriteBatch.Draw(map.Texture, destinationRectangle,
  461. sourceRectangle, Color.White);
  462. }
  463. }
  464. if (drawObject)
  465. {
  466. Rectangle sourceRectangle =
  467. map.GetObjectLayerSourceRectangle(mapPosition);
  468. if (sourceRectangle != Rectangle.Empty)
  469. {
  470. spriteBatch.Draw(map.Texture, destinationRectangle,
  471. sourceRectangle, Color.White);
  472. }
  473. }
  474. }
  475. }
  476. }
  477. }
  478. /// <summary>
  479. /// Returns true if the given rectangle is within the viewport.
  480. /// </summary>
  481. public static bool CheckVisibility(Rectangle screenRectangle)
  482. {
  483. return ((screenRectangle.X > viewport.X - screenRectangle.Width) &&
  484. (screenRectangle.Y > viewport.Y - screenRectangle.Height) &&
  485. (screenRectangle.X < viewport.X + viewport.Width) &&
  486. (screenRectangle.Y < viewport.Y + viewport.Height));
  487. }
  488. }
  489. }