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 GetNeighborCell(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 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 = GetNeighborCell(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
}
}