#region File Description //----------------------------------------------------------------------------- // PlayerPosition.cs // // Microsoft XNA Community Game Platform // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #endregion #region Using Statements using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using RolePlayingGameData; #endregion namespace RolePlaying { /// /// Static class for a tileable map /// static class TileEngine { #region Map /// /// The map being used by the tile engine. /// private static Map map = null; /// /// The map being used by the tile engine. /// public static Map Map { get { return map; } } /// /// The position of the outside 0,0 corner of the map, in pixels. /// private static Vector2 mapOriginPosition; /// /// Calculate the screen position of a given map location (in tiles). /// /// A map location, in tiles. /// The current screen position of that location. public static Vector2 GetScreenPosition(Point mapPosition) { return new Vector2( mapOriginPosition.X + mapPosition.X * map.TileSize.X, mapOriginPosition.Y + mapPosition.Y * map.TileSize.Y); } /// /// Set the map in use by the tile engine. /// /// The new map for the tile engine. /// The portal the party is entering on, if any. public static void SetMap(Map newMap, MapEntry portalEntry) { // check the parameter if (newMap == null) { throw new ArgumentNullException("newMap"); } // assign the new map map = newMap; // reset the map origin, which will be recalculate on the first update mapOriginPosition = Vector2.Zero; // move the party to its initial position if (portalEntry == null) { // no portal - use the spawn position partyLeaderPosition.TilePosition = map.SpawnMapPosition; partyLeaderPosition.TileOffset = Vector2.Zero; partyLeaderPosition.Direction = Direction.South; } else { // use the portal provided, which may include automatic movement partyLeaderPosition.TilePosition = portalEntry.MapPosition; partyLeaderPosition.TileOffset = Vector2.Zero; partyLeaderPosition.Direction = portalEntry.Direction; autoPartyLeaderMovement = Vector2.Multiply( new Vector2(map.TileSize.X, map.TileSize.Y), new Vector2( portalEntry.Content.LandingMapPosition.X - partyLeaderPosition.TilePosition.X, portalEntry.Content.LandingMapPosition.Y - partyLeaderPosition.TilePosition.Y)); } } #endregion #region Graphics Data /// /// The viewport that the tile engine is rendering within. /// private static Viewport viewport; /// /// The viewport that the tile engine is rendering within. /// public static Viewport Viewport { get { return viewport; } set { viewport = value; viewportCenter = new Vector2( viewport.X + viewport.Width / 2f, viewport.Y + viewport.Height / 2f); } } /// /// The center of the current viewport. /// private static Vector2 viewportCenter; #endregion #region Party /// /// The speed of the party leader, in units per second. /// /// /// The movementCollisionTolerance constant should be a multiple of this number. /// private const float partyLeaderMovementSpeed = 3f; /// /// The current position of the party leader. /// private static PlayerPosition partyLeaderPosition = new PlayerPosition(); public static PlayerPosition PartyLeaderPosition { get { return partyLeaderPosition; } set { partyLeaderPosition = value; } } /// /// The automatic movement remaining for the party leader. /// /// /// This is typically used for automatic movement when spawning on a map. /// private static Vector2 autoPartyLeaderMovement = Vector2.Zero; /// /// Updates the automatic movement of the party. /// /// The automatic movement for this update. private static Vector2 UpdatePartyLeaderAutoMovement(GameTime gameTime) { // check for any remaining auto-movement if (autoPartyLeaderMovement == Vector2.Zero) { return Vector2.Zero; } // get the remaining-movement direction Vector2 autoMovementDirection = Vector2.Normalize(autoPartyLeaderMovement); // calculate the potential movement vector Vector2 movement = Vector2.Multiply(autoMovementDirection, partyLeaderMovementSpeed); // limit the potential movement vector by the remaining auto-movement movement.X = Math.Sign(movement.X) * MathHelper.Min(Math.Abs(movement.X), Math.Abs(autoPartyLeaderMovement.X)); movement.Y = Math.Sign(movement.Y) * MathHelper.Min(Math.Abs(movement.Y), Math.Abs(autoPartyLeaderMovement.Y)); // remove the movement from the total remaining auto-movement autoPartyLeaderMovement -= movement; return movement; } /// /// Update the user-controlled movement of the party. /// /// The controlled movement for this update. private static Vector2 UpdateUserMovement(GameTime gameTime) { Vector2 desiredMovement = Vector2.Zero; // accumulate the desired direction from user input if (InputManager.IsActionPressed(InputManager.Action.MoveCharacterUp)) { if (CanPartyLeaderMoveUp()) { desiredMovement.Y -= partyLeaderMovementSpeed; } } if (InputManager.IsActionPressed(InputManager.Action.MoveCharacterDown)) { if (CanPartyLeaderMoveDown()) { desiredMovement.Y += partyLeaderMovementSpeed; } } if (InputManager.IsActionPressed(InputManager.Action.MoveCharacterLeft)) { if (CanPartyLeaderMoveLeft()) { desiredMovement.X -= partyLeaderMovementSpeed; } } if (InputManager.IsActionPressed(InputManager.Action.MoveCharacterRight)) { if (CanPartyLeaderMoveRight()) { desiredMovement.X += partyLeaderMovementSpeed; } } // if there is no desired movement, then we can't determine a direction if (desiredMovement == Vector2.Zero) { return Vector2.Zero; } return desiredMovement; } #endregion #region Collision /// /// The number of pixels that characters should be allowed to move into /// blocking tiles. /// /// /// The partyMovementSpeed constant should cleanly divide this number. /// const int movementCollisionTolerance = 12; /// /// Returns true if the player can move up from their current position. /// private static bool CanPartyLeaderMoveUp() { // if they're not within the tolerance of the next tile, then this is moot if (partyLeaderPosition.TileOffset.Y > -movementCollisionTolerance) { return true; } // if the player is at the outside left and right edges, // then check the diagonal tiles if (partyLeaderPosition.TileOffset.X < -movementCollisionTolerance) { if (map.IsBlocked(new Point( partyLeaderPosition.TilePosition.X - 1, partyLeaderPosition.TilePosition.Y - 1))) { return false; } } else if (partyLeaderPosition.TileOffset.X > movementCollisionTolerance) { if (map.IsBlocked(new Point( partyLeaderPosition.TilePosition.X + 1, partyLeaderPosition.TilePosition.Y - 1))) { return false; } } // check the tile above the current one return !map.IsBlocked(new Point( partyLeaderPosition.TilePosition.X, partyLeaderPosition.TilePosition.Y - 1)); } /// /// Returns true if the player can move down from their current position. /// private static bool CanPartyLeaderMoveDown() { // if they're not within the tolerance of the next tile, then this is moot if (partyLeaderPosition.TileOffset.Y < movementCollisionTolerance) { return true; } // if the player is at the outside left and right edges, // then check the diagonal tiles if (partyLeaderPosition.TileOffset.X < -movementCollisionTolerance) { if (map.IsBlocked(new Point( partyLeaderPosition.TilePosition.X - 1, partyLeaderPosition.TilePosition.Y + 1))) { return false; } } else if (partyLeaderPosition.TileOffset.X > movementCollisionTolerance) { if (map.IsBlocked(new Point( partyLeaderPosition.TilePosition.X + 1, partyLeaderPosition.TilePosition.Y + 1))) { return false; } } // check the tile below the current one return !map.IsBlocked(new Point( partyLeaderPosition.TilePosition.X, partyLeaderPosition.TilePosition.Y + 1)); } /// /// Returns true if the player can move left from their current position. /// private static bool CanPartyLeaderMoveLeft() { // if they're not within the tolerance of the next tile, then this is moot if (partyLeaderPosition.TileOffset.X > -movementCollisionTolerance) { return true; } // if the player is at the outside left and right edges, // then check the diagonal tiles if (partyLeaderPosition.TileOffset.Y < -movementCollisionTolerance) { if (map.IsBlocked(new Point( partyLeaderPosition.TilePosition.X - 1, partyLeaderPosition.TilePosition.Y - 1))) { return false; } } else if (partyLeaderPosition.TileOffset.Y > movementCollisionTolerance) { if (map.IsBlocked(new Point( partyLeaderPosition.TilePosition.X - 1, partyLeaderPosition.TilePosition.Y + 1))) { return false; } } // check the tile to the left of the current one return !map.IsBlocked(new Point( partyLeaderPosition.TilePosition.X - 1, partyLeaderPosition.TilePosition.Y)); } /// /// Returns true if the player can move right from their current position. /// private static bool CanPartyLeaderMoveRight() { // if they're not within the tolerance of the next tile, then this is moot if (partyLeaderPosition.TileOffset.X < movementCollisionTolerance) { return true; } // if the player is at the outside left and right edges, // then check the diagonal tiles if (partyLeaderPosition.TileOffset.Y < -movementCollisionTolerance) { if (map.IsBlocked(new Point( partyLeaderPosition.TilePosition.X + 1, partyLeaderPosition.TilePosition.Y - 1))) { return false; } } else if (partyLeaderPosition.TileOffset.Y > movementCollisionTolerance) { if (map.IsBlocked(new Point( partyLeaderPosition.TilePosition.X + 1, partyLeaderPosition.TilePosition.Y + 1))) { return false; } } // check the tile to the right of the current one return !map.IsBlocked(new Point( partyLeaderPosition.TilePosition.X + 1, partyLeaderPosition.TilePosition.Y)); } #endregion #region Updating /// /// Update the tile engine. /// public static void Update(GameTime gameTime) { // check for auto-movement Vector2 autoMovement = UpdatePartyLeaderAutoMovement(gameTime); // if there is no auto-movement, handle user controls Vector2 userMovement = Vector2.Zero; if (autoMovement == Vector2.Zero) { userMovement = UpdateUserMovement(gameTime); // calculate the desired position if (userMovement != Vector2.Zero) { Point desiredTilePosition = partyLeaderPosition.TilePosition; Vector2 desiredTileOffset = partyLeaderPosition.TileOffset; PlayerPosition.CalculateMovement( Vector2.Multiply(userMovement, 15f), ref desiredTilePosition, ref desiredTileOffset); // check for collisions or encounters in the new tile if ((partyLeaderPosition.TilePosition != desiredTilePosition) && !MoveIntoTile(desiredTilePosition)) { userMovement = Vector2.Zero; } } } // move the party Point oldPartyLeaderTilePosition = partyLeaderPosition.TilePosition; partyLeaderPosition.Move(autoMovement + userMovement); // if the tile position has changed, check for random combat if ((autoMovement == Vector2.Zero) && (partyLeaderPosition.TilePosition != oldPartyLeaderTilePosition)) { Session.CheckForRandomCombat(Map.RandomCombat); } // adjust the map origin so that the party is at the center of the viewport mapOriginPosition += viewportCenter - (partyLeaderPosition.ScreenPosition + Session.Party.Players[0].MapSprite.SourceOffset); // make sure the boundaries of the map are never inside the viewport mapOriginPosition.X = MathHelper.Min(mapOriginPosition.X, viewport.X); mapOriginPosition.Y = MathHelper.Min(mapOriginPosition.Y, viewport.Y); mapOriginPosition.X += MathHelper.Max( (viewport.X + viewport.Width) - (mapOriginPosition.X + map.MapDimensions.X * map.TileSize.X), 0f); mapOriginPosition.Y += MathHelper.Max( (viewport.Y + viewport.Height - Hud.HudHeight) - (mapOriginPosition.Y + map.MapDimensions.Y * map.TileSize.Y), 0f); } /// /// Performs any actions associated with moving into a new tile. /// /// True if the character can move into the tile. private static bool MoveIntoTile(Point mapPosition) { // if the tile is blocked, then this is simple if (map.IsBlocked(mapPosition)) { return false; } // check for anything that might be in the tile if (Session.EncounterTile(mapPosition)) { return false; } // nothing stops the party from moving into the tile return true; } #endregion #region Drawing /// /// Draw the visible tiles in the given map layers. /// public static void DrawLayers(SpriteBatch spriteBatch, bool drawBase, bool drawFringe, bool drawObject) { // check the parameters if (spriteBatch == null) { throw new ArgumentNullException("spriteBatch"); } if (!drawBase && !drawFringe && !drawObject) { return; } Rectangle destinationRectangle = new Rectangle(0, 0, map.TileSize.X, map.TileSize.Y); for (int y = 0; y < map.MapDimensions.Y; y++) { for (int x = 0; x < map.MapDimensions.X; x++) { destinationRectangle.X = (int)mapOriginPosition.X + x * map.TileSize.X; destinationRectangle.Y = (int)mapOriginPosition.Y + y * map.TileSize.Y; // If the tile is inside the screen if (CheckVisibility(destinationRectangle)) { Point mapPosition = new Point(x, y); if (drawBase) { Rectangle sourceRectangle = map.GetBaseLayerSourceRectangle(mapPosition); if (sourceRectangle != Rectangle.Empty) { spriteBatch.Draw(map.Texture, destinationRectangle, sourceRectangle, Color.White); } } if (drawFringe) { Rectangle sourceRectangle = map.GetFringeLayerSourceRectangle(mapPosition); if (sourceRectangle != Rectangle.Empty) { spriteBatch.Draw(map.Texture, destinationRectangle, sourceRectangle, Color.White); } } if (drawObject) { Rectangle sourceRectangle = map.GetObjectLayerSourceRectangle(mapPosition); if (sourceRectangle != Rectangle.Empty) { spriteBatch.Draw(map.Texture, destinationRectangle, sourceRectangle, Color.White); } } } } } } /// /// Returns true if the given rectangle is within the viewport. /// public static bool CheckVisibility(Rectangle screenRectangle) { return ((screenRectangle.X > viewport.X - screenRectangle.Width) && (screenRectangle.Y > viewport.Y - screenRectangle.Height) && (screenRectangle.X < viewport.X + viewport.Width) && (screenRectangle.Y < viewport.Y + viewport.Height)); } #endregion } }