using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Xna.Framework; namespace CarFire { /// /// A type for a direction, including diagonals. /// public enum Direction { Down, // Default direction is down. Left, UpperLeft, Up, UpperRight, Right, LowerRight, LowerLeft, None } /// /// A class to manage the motion of objects on a grid of cells. /// Each update you can pass a direction and the manager will move /// the position to that point while also enforcing a speed limit. /// This class does not detect collisions, so care must be taken /// to only pass directions to a walkable cell during an update. /// public class MovementManager { #region Public Properties /// /// Get the current position in map coordinates. This is the /// smooth, interpolated set of coordinates. /// public Vector2 Position { get { return mPosition; } } /// /// Get the grid coordinates where the object is at or /// is moving to. /// public Point Coordinates { get { return mCoordinates; } } /// /// Get and set the speed of movement in grid cells / second. /// public float Speed; /// /// Get whether or not the object is moving. /// public bool IsMoving { get { return mIsMoving; } } /// /// Get the direction the object is facing. /// public Direction Direction { get { return mDirection; } } #endregion #region Public Methods /// /// Construct a movement manager with the initial position of /// the thing you want to track. /// /// Grid coordinates. public MovementManager(Point position) { mPosition = new Vector2((float)position.X, (float)position.Y); mCoordinates = position; mLastCoordinates = position; Speed = 1.0f; } /// /// Construct a movement manager with the initial position of /// the thing you want to track and its speed. /// /// Grid coordinates. /// Speed: Grid cells per second. public MovementManager(Point position, float speed) { mPosition = new Vector2((float)position.X, (float)position.Y); mCoordinates = position; mLastCoordinates = position; Speed = speed; } /// /// Update the movement manager with the timeslice and no directions. /// /// The timeslice. public void Update(TimeSpan timeSpan) { Update(timeSpan, false, false, false, false); } /// /// Update the movement manager with the timeslice and a direction. /// /// The timeslice. /// Direction you want to move. public void Update(TimeSpan timeSpan, Direction direction) { if (direction == Direction.Left) Update(timeSpan, true, false, false, false); else if (direction == Direction.UpperLeft) Update(timeSpan, true, false, true, false); else if (direction == Direction.Up) Update(timeSpan, false, false, true, false); else if (direction == Direction.UpperRight) Update(timeSpan, false, true, true, false); else if (direction == Direction.Right) Update(timeSpan, false, true, false, false); else if (direction == Direction.LowerRight) Update(timeSpan, false, true, false, true); else if (direction == Direction.Down) Update(timeSpan, false, false, false, true); else if (direction == Direction.LowerLeft) Update(timeSpan, true, false, false, true); else Update(timeSpan); } /// /// Update the movement manager with the timeslice and the directions /// the object is supposed to go. The directions will be ignored if the /// object is currently in transit from one cell to another. /// /// The timeslice. /// Want to move left. /// Want to move right. /// Want to move up. /// Want to move down. public void Update(TimeSpan timeSpan, bool moveLeft, bool moveRight, bool moveUp, bool moveDown) { float passedTime = (float)timeSpan.TotalSeconds; bool requestMove = (moveLeft ^ moveRight) || (moveUp ^ moveDown); if (!mIsMoving && requestMove) { mTimeAccumulator = passedTime; mIsMoving = true; UpdateCoordinates(moveLeft, moveRight, moveUp, moveDown); mDirection = GetDirection(moveLeft, moveRight, moveUp, moveDown); RecalculatePosition(mTimeAccumulator / mInverseSpeed); } else if (mIsMoving) { mTimeAccumulator += passedTime; float alpha = mTimeAccumulator / mInverseSpeed; if (alpha >= 1.0f) { if (requestMove) { mTimeAccumulator = mTimeAccumulator - mInverseSpeed; alpha = mTimeAccumulator / mInverseSpeed; UpdateCoordinates(moveLeft, moveRight, moveUp, moveDown); mDirection = GetDirection(moveLeft, moveRight, moveUp, moveDown); } else { mIsMoving = false; alpha = 1.0f; } } RecalculatePosition(alpha); } } public void LockUpdate(TimeSpan timeSpan, bool moveLeft, bool moveRight, bool moveUp, bool moveDown) { float passedTime = (float)timeSpan.TotalSeconds; if (moveLeft == true || moveRight == true || moveUp == true || moveDown == true) { mDirection = GetDirection(moveLeft, moveRight, moveUp, moveDown); } if (mIsMoving) { mTimeAccumulator += passedTime; float alpha = mTimeAccumulator / mInverseSpeed; if (alpha >= 1.0f) { mIsMoving = false; alpha = 1.0f; } RecalculatePosition(alpha); } } /// /// Helper method to get neighbor cells from a point and directions. /// /// The point. /// To the left. /// To the right. /// Above. /// Below. /// The neighbor cell coordinates. public static Point GetNeighbor(Point point, bool left, bool right, bool up, bool down) { if (left) point.X--; if (right) point.X++; if (up) point.Y--; if (down) point.Y++; return point; } /// /// Helper method to get the two neighbor cells of two nearby cells. /// /// A point. /// Another point. /// An array of two points representing the neighbor cells. public static Point[] GetNeighbors(Point a, Point b) { Point[] neighbors = new Point[2]; neighbors[0] = new Point(a.X, b.Y); neighbors[1] = new Point(b.X, a.Y); return neighbors; } /// /// Helper method to get a Direction type from directions. /// /// Left. /// Right. /// Up. /// Down. /// The direction. public static Direction GetDirection(bool left, bool right, bool up, bool down) { if (left && !right) { if (up) return Direction.UpperLeft; else if (down) return Direction.LowerLeft; else return Direction.Left; } else if (right && !left) { if (up) return Direction.UpperRight; else if (down) return Direction.LowerRight; else return Direction.Right; } else if (up) return Direction.Up; else if (down) return Direction.Down; else return Direction.None; } /// /// Helper method to get the general Direction type if you want to move /// from one cell to another. /// Starting point. /// Destination point. /// The direction toward the cell. public static Direction GetDirection(Point a, Point b) { int dx = b.X - a.X; int dy = b.Y - a.Y; if (dx < 0) { if (dy < 0) return Direction.UpperLeft; else if (dy > 0) return Direction.LowerLeft; else return Direction.Left; } else if (dx > 0) { if (dy < 0) return Direction.UpperRight; else if (dy > 0) return Direction.LowerRight; else return Direction.Right; } else if (dy < 0) return Direction.Up; else if (dy > 0) return Direction.Down; else return Direction.None; } #endregion #region Private Methods void RecalculatePosition(float alpha) { //Console.WriteLine("last: " + mLastCoordinates + ", now: " + mCoordinates + ", alpha: " + alpha); mPosition.X = (float)mLastCoordinates.X + alpha * ((float)mCoordinates.X - (float)mLastCoordinates.X); mPosition.Y = (float)mLastCoordinates.Y + alpha * ((float)mCoordinates.Y - (float)mLastCoordinates.Y); } void UpdateCoordinates(bool moveLeft, bool moveRight, bool moveUp, bool moveDown) { mLastCoordinates = mCoordinates; mCoordinates = GetNeighbor(mCoordinates, moveLeft, moveRight, moveUp, moveDown); if ((moveLeft && moveUp) || (moveUp && moveRight) || (moveRight && moveDown) || (moveDown && moveLeft)) { mInverseSpeed = 1.4f / Speed; } else { mInverseSpeed = 1.0f / Speed; } } #endregion #region Private Variables Vector2 mPosition; // Position on the viewable map. Point mCoordinates; // Position on the grid. Point mLastCoordinates; // Last position on the grid. float mInverseSpeed; // The time it takes to move from one cell to another. float mTimeAccumulator; // Amount of time passed since last move. bool mIsMoving; // Whether or not it is currently in the process of moving. Direction mDirection; // The direction the object is facing. #endregion } }