1 ﻿using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using Microsoft.Xna.Framework;
7 namespace CarFire
8 {
9 /// <summary>
10 /// A type for a direction, including diagonals.
11 /// </summary>
12 public enum Direction
13 {
14 Down, // Default direction is down.
15 Left,
16 UpperLeft,
17 Up,
18 UpperRight,
19 Right,
20 LowerRight,
21 LowerLeft,
22 None
23 }
26 /// <summary>
27 /// A class to manage the motion of objects on a grid of cells.
28 /// Each update you can pass a direction and the manager will move
29 /// the position to that point while also enforcing a speed limit.
30 /// This class does not detect collisions, so care must be taken
31 /// to only pass directions to a walkable cell during an update.
32 /// </summary>
33 public class MovementManager
34 {
35 #region Public Properties
37 /// <summary>
38 /// Get the current position in map coordinates. This is the
39 /// smooth, interpolated set of coordinates.
40 /// </summary>
41 public Vector2 Position { get { return mPosition; } }
43 /// <summary>
44 /// Get the grid coordinates where the object is at or
45 /// is moving to.
46 /// </summary>
47 public Point Coordinates { get { return mCoordinates; } }
49 /// <summary>
50 /// Get and set the speed of movement in grid cells / second.
51 /// </summary>
52 public float Speed;
54 /// <summary>
55 /// Get whether or not the object is moving.
56 /// </summary>
57 public bool IsMoving { get { return mIsMoving; } }
59 /// <summary>
60 /// Get the direction the object is facing.
61 /// </summary>
62 public Direction Direction { get { return mDirection; } }
64 #endregion
67 #region Public Methods
69 /// <summary>
70 /// Construct a movement manager with the initial position of
71 /// the thing you want to track.
72 /// </summary>
73 /// <param name="position">Grid coordinates.</param>
74 public MovementManager(Point position)
75 {
76 mPosition = new Vector2((float)position.X, (float)position.Y);
77 mCoordinates = position;
78 mLastCoordinates = position;
79 Speed = 1.0f;
80 }
82 /// <summary>
83 /// Construct a movement manager with the initial position of
84 /// the thing you want to track and its speed.
85 /// </summary>
86 /// <param name="position">Grid coordinates.</param>
87 /// <param name="speed">Speed: Grid cells per second.</param>
88 public MovementManager(Point position, float speed)
89 {
90 mPosition = new Vector2((float)position.X, (float)position.Y);
91 mCoordinates = position;
92 mLastCoordinates = position;
93 Speed = speed;
94 }
97 /// <summary>
98 /// Update the movement manager with the timeslice and no directions.
99 /// </summary>
100 /// <param name="timeSpan">The timeslice.</param>
101 public void Update(TimeSpan timeSpan)
102 {
103 Update(timeSpan, false, false, false, false);
104 }
106 /// <summary>
107 /// Update the movement manager with the timeslice and a direction.
108 /// </summary>
109 /// <param name="timeSpan">The timeslice.</param>
110 /// <param name="direction">Direction you want to move.</param>
111 public void Update(TimeSpan timeSpan, Direction direction)
112 {
113 if (direction == Direction.Left) Update(timeSpan, true, false, false, false);
114 else if (direction == Direction.UpperLeft) Update(timeSpan, true, false, true, false);
115 else if (direction == Direction.Up) Update(timeSpan, false, false, true, false);
116 else if (direction == Direction.UpperRight) Update(timeSpan, false, true, true, false);
117 else if (direction == Direction.Right) Update(timeSpan, false, true, false, false);
118 else if (direction == Direction.LowerRight) Update(timeSpan, false, true, false, true);
119 else if (direction == Direction.Down) Update(timeSpan, false, false, false, true);
120 else if (direction == Direction.LowerLeft) Update(timeSpan, true, false, false, true);
121 else Update(timeSpan);
122 }
124 /// <summary>
125 /// Update the movement manager with the timeslice and the directions
126 /// the object is supposed to go. The directions will be ignored if the
127 /// object is currently in transit from one cell to another.
128 /// </summary>
129 /// <param name="timeSpan">The timeslice.</param>
130 /// <param name="moveLeft">Want to move left.</param>
131 /// <param name="moveRight">Want to move right.</param>
132 /// <param name="moveUp">Want to move up.</param>
133 /// <param name="moveDown">Want to move down.</param>
134 public void Update(TimeSpan timeSpan, bool moveLeft, bool moveRight, bool moveUp, bool moveDown)
135 {
136 float passedTime = (float)timeSpan.TotalSeconds;
138 bool requestMove = (moveLeft ^ moveRight) || (moveUp ^ moveDown);
139 if (!mIsMoving && requestMove)
140 {
141 mTimeAccumulator = passedTime;
143 mIsMoving = true;
144 UpdateCoordinates(moveLeft, moveRight, moveUp, moveDown);
145 mDirection = GetDirection(moveLeft, moveRight, moveUp, moveDown);
147 RecalculatePosition(mTimeAccumulator / mInverseSpeed);
148 }
149 else if (mIsMoving)
150 {
151 mTimeAccumulator += passedTime;
153 float alpha = mTimeAccumulator / mInverseSpeed;
154 if (alpha >= 1.0f)
155 {
156 if (requestMove)
157 {
158 mTimeAccumulator = mTimeAccumulator - mInverseSpeed;
159 alpha = mTimeAccumulator / mInverseSpeed;
161 UpdateCoordinates(moveLeft, moveRight, moveUp, moveDown);
162 mDirection = GetDirection(moveLeft, moveRight, moveUp, moveDown);
163 }
164 else
165 {
166 mIsMoving = false;
167 alpha = 1.0f;
168 }
169 }
171 RecalculatePosition(alpha);
172 }
173 }
174 public void LockUpdate(TimeSpan timeSpan, bool moveLeft, bool moveRight, bool moveUp, bool moveDown)
175 {
176 float passedTime = (float)timeSpan.TotalSeconds;
177 if (moveLeft == true || moveRight == true || moveUp == true || moveDown == true)
178 {
179 mDirection = GetDirection(moveLeft, moveRight, moveUp, moveDown);
180 }
181 if (mIsMoving)
182 {
183 mTimeAccumulator += passedTime;
185 float alpha = mTimeAccumulator / mInverseSpeed;
186 if (alpha >= 1.0f)
187 {
188 mIsMoving = false;
189 alpha = 1.0f;
190 }
192 RecalculatePosition(alpha);
193 }
194 }
197 /// <summary>
198 /// Helper method to get a neighbor cell from a point and directions.
199 /// </summary>
200 /// <param name="point">The point.</param>
201 /// <param name="left">To the left.</param>
202 /// <param name="right">To the right.</param>
203 /// <param name="up">Above.</param>
204 /// <param name="down">Below.</param>
205 /// <returns>The neighbor cell coordinates.</returns>
206 public static Point GetNeighbor(Point point, bool left, bool right, bool up, bool down)
207 {
208 if (left) point.X--;
209 if (right) point.X++;
210 if (up) point.Y--;
211 if (down) point.Y++;
212 return point;
213 }
215 /// <summary>
216 /// Helper method to get a neighbor cell from a point and a direction.
217 /// </summary>
218 /// <param name="point">The point.</param>
219 /// <param name="direction">The direction.</param>
220 /// <returns>The neighbor cell coordinates.</returns>
221 public static Point GetNeighbor(Point point, Direction direction)
222 {
223 switch (direction)
224 {
225 case Direction.Left: return new Point(point.X - 1, point.Y);
226 case Direction.UpperLeft: return new Point(point.X - 1, point.Y - 1);
227 case Direction.Up: return new Point(point.X, point.Y - 1);
228 case Direction.UpperRight: return new Point(point.X + 1, point.Y - 1);
229 case Direction.Right: return new Point(point.X + 1, point.Y);
230 case Direction.LowerRight: return new Point(point.X + 1, point.Y + 1);
231 case Direction.Down: return new Point(point.X, point.Y + 1);
232 case Direction.LowerLeft: return new Point(point.X - 1, point.Y + 1);
233 }
234 return point;
235 }
237 /// <summary>
238 /// Helper method to get the two neighbor cells of two nearby cells.
239 /// </summary>
240 /// <param name="a">A point.</param>
241 /// <param name="b">Another point.</param>
242 /// <returns>An array of two points representing the neighbor cells.</returns>
243 public static Point[] GetNeighbors(Point a, Point b)
244 {
245 Point[] neighbors = new Point[2];
246 neighbors[0] = new Point(a.X, b.Y);
247 neighbors[1] = new Point(b.X, a.Y);
248 return neighbors;
249 }
252 /// <summary>
253 /// Helper method to get a Direction type from directions.
254 /// </summary>
255 /// <param name="left">Left.</param>
256 /// <param name="right">Right.</param>
257 /// <param name="up">Up.</param>
258 /// <param name="down">Down.</param>
259 /// <returns>The direction.</returns>
260 public static Direction GetDirection(bool left, bool right, bool up, bool down)
261 {
262 if (left && !right)
263 {
264 if (up) return Direction.UpperLeft;
265 else if (down) return Direction.LowerLeft;
266 else return Direction.Left;
267 }
268 else if (right && !left)
269 {
270 if (up) return Direction.UpperRight;
271 else if (down) return Direction.LowerRight;
272 else return Direction.Right;
273 }
274 else if (up) return Direction.Up;
275 else if (down) return Direction.Down;
276 else return Direction.None;
277 }
279 /// <summary>
280 /// Helper method to get the general Direction type if you want to move
281 /// from one cell to another.
282 /// <param name="a">Starting point.</param>
283 /// <param name="b">Destination point.</param>
284 /// <returns>The direction toward the cell.</returns>
285 public static Direction GetDirection(Point a, Point b)
286 {
287 int dx = b.X - a.X;
288 int dy = b.Y - a.Y;
290 if (dx < 0)
291 {
292 if (dy < 0) return Direction.UpperLeft;
293 else if (dy > 0) return Direction.LowerLeft;
294 else return Direction.Left;
295 }
296 else if (dx > 0)
297 {
298 if (dy < 0) return Direction.UpperRight;
299 else if (dy > 0) return Direction.LowerRight;
300 else return Direction.Right;
301 }
302 else if (dy < 0) return Direction.Up;
303 else if (dy > 0) return Direction.Down;
304 else return Direction.None;
305 }
307 #endregion
310 #region Private Methods
312 void RecalculatePosition(float alpha)
313 {
314 //Console.WriteLine("last: " + mLastCoordinates + ", now: " + mCoordinates + ", alpha: " + alpha);
315 mPosition.X = (float)mLastCoordinates.X + alpha * ((float)mCoordinates.X - (float)mLastCoordinates.X);
316 mPosition.Y = (float)mLastCoordinates.Y + alpha * ((float)mCoordinates.Y - (float)mLastCoordinates.Y);
317 }
319 void UpdateCoordinates(bool moveLeft, bool moveRight, bool moveUp, bool moveDown)
320 {
321 mLastCoordinates = mCoordinates;
322 mCoordinates = GetNeighbor(mCoordinates, moveLeft, moveRight, moveUp, moveDown);
324 if ((moveLeft && moveUp) || (moveUp && moveRight) || (moveRight && moveDown) || (moveDown && moveLeft))
325 {
326 mInverseSpeed = 1.4f / Speed;
327 }
328 else
329 {
330 mInverseSpeed = 1.0f / Speed;
331 }
332 }
334 #endregion
337 #region Private Variables
339 Vector2 mPosition; // Position on the viewable map.
340 Point mCoordinates; // Position on the grid.
341 Point mLastCoordinates; // Last position on the grid.
342 float mInverseSpeed; // The time it takes to move from one cell to another.
343 float mTimeAccumulator; // Amount of time passed since last move.
344 bool mIsMoving; // Whether or not it is currently in the process of moving.
345 Direction mDirection; // The direction the object is facing.
347 #endregion
348 }
349 }