SteeringBehavior.cs 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // SteeringBehavior.cs
  4. //
  5. // Microsoft XNA Community Game Platform
  6. // Copyright (C) Microsoft Corporation. All rights reserved.
  7. //-----------------------------------------------------------------------------
  8. #endregion
  9. #region Using Statements
  10. using System;
  11. #if IPHONE
  12. using Microsoft.Xna.Framework;
  13. #else
  14. using Microsoft.Xna.Framework;
  15. #endif
  16. #endregion
  17. namespace Waypoint
  18. {
  19. /// <summary>
  20. /// This Behavior causes the tank to steer and change direction smoothly
  21. /// to its current destination. This Behavior uses logic from the Aiming sample
  22. /// </summary>
  23. class SteeringBehavior : Behavior
  24. {
  25. #region Initialization
  26. public SteeringBehavior(Tank tank)
  27. : base(tank)
  28. {
  29. }
  30. #endregion
  31. #region Update
  32. /// <summary>
  33. /// Update adjusts the tanks’ movement speed as necessary to ensure that
  34. /// the current waypoint is inside its’ turning radius and steers the tank
  35. /// towards the waypoint based on its’ maximum angular velocity.
  36. /// </summary>
  37. public override void Update(GameTime gameTime)
  38. {
  39. float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
  40. // This code causes the tank to change its speed gradually while it
  41. // moves toward the waypoint previousMoveSpeed tracks how fast the
  42. // tank was going, desiredMoveSpeed finds how fast the tank want to
  43. // go and Math.Clamp keeps the tank from accelerating or decelerating
  44. // too fast.
  45. float previousMoveSpeed = tank.MoveSpeed;
  46. float desiredMoveSpeed = FindMaxMoveSpeed(tank.Waypoints.Peek());
  47. tank.MoveSpeed = MathHelper.Clamp(desiredMoveSpeed,
  48. previousMoveSpeed - Tank.MaxMoveSpeedDelta * elapsedTime,
  49. previousMoveSpeed + Tank.MaxMoveSpeedDelta * elapsedTime);
  50. // This code causes the tank to turn towards the waypoint. First we
  51. // take the vector that represents the tanks’ current heading,
  52. // Tank.Direction, and convert it into an angle in radians. Then we
  53. // use TurnToFace to make the tank turn towards it’s waypoint based
  54. // on it’s turning speed, Tank,MaxAngualrVelocity. After we have the
  55. // new direction in radian we convert it back into a vector.
  56. float facingDirection = (float)Math.Atan2(
  57. tank.Direction.Y, tank.Direction.X);
  58. facingDirection = TurnToFace(tank.Location, tank.Waypoints.Peek(),
  59. facingDirection, Tank.MaxAngularVelocity * elapsedTime);
  60. tank.Direction = new Vector2(
  61. (float)Math.Cos(facingDirection),
  62. (float)Math.Sin(facingDirection));
  63. }
  64. #endregion
  65. #region Methods
  66. /// <summary>
  67. /// Estimate the Tank's best possible movement speed to it's destination
  68. /// </summary>
  69. /// <param name="newDestination">The Tank's target location</param>
  70. /// <returns>Maximum estimated movement speed for the Tank
  71. /// up to Tank.MaxMoveSpeed</returns>
  72. private float FindMaxMoveSpeed(Vector2 waypoint)
  73. {
  74. float finalSpeed = Tank.MaxMoveSpeed;
  75. // Given a velocity v (Tank.MaxMoveSpeed) and an angular velocity
  76. // w(Tank.MaxAngularVelocity), the smallest turning radius
  77. // r(turningRadius) ofthe tank is the velocity divided by the turning
  78. // speed: r = v/w
  79. float turningRadius = Tank.MaxMoveSpeed / Tank.MaxAngularVelocity;
  80. // This code figures out if the tank can move to its waypoint from its
  81. // current location based on its turning circle(turningRadius) when its
  82. // moving as fast as possible(Tank.MaxMoveSpeed). For any given turning
  83. // circle there is an area to either side of the tank that it cannot
  84. // move into that can be represented by 2 circles of radius turningRadius
  85. // on either side of the tank. If the waypoint is inside one of these
  86. // 2 circles the tank will have to slow down before it can move to it
  87. // This creates a vector that’s orthogonal to the tank in the direction
  88. // it's facing. This means that the vector is at a right angle to the
  89. // direction the tank is pointing in.
  90. Vector2 orth = new Vector2(tank.Direction.Y, -tank.Direction.X);
  91. // In this code we can combine the tanks’ location, the orthogonal
  92. // vector and the tanks’ turning radius to find the 2 points that
  93. // describe the centers of the circles the tanks cannot move into.
  94. // Then we use Vector2.Distance to find the distances from each circle
  95. // center to the waypoint. Afterwards Math.Min return the distance from
  96. // the waypoint to whichever circle was closest.
  97. float closestDistance = Math.Min(
  98. Vector2.Distance(waypoint, tank.Location + (orth * turningRadius)),
  99. Vector2.Distance(waypoint, tank.Location - (orth * turningRadius)));
  100. // If closestDistance is less than turningRadius, then the waypoint is
  101. // inside one of the 2 circles the Tank cannot turn into when moving at
  102. // Tank.MaxMoveSpeed, instead we need to estimate a speed that the tank
  103. // can move at.
  104. if (closestDistance < turningRadius)
  105. {
  106. // This finds the radius of a circle where the Tank's location and
  107. // the waypoint are 2 points on opposite sides of the circle.
  108. float radius = Vector2.Distance(tank.Location, waypoint) / 2;
  109. // Now we use the radius from above to and Tank.MaxAngularVelocity
  110. // to find out how fast we can move towards the waypoint by taking
  111. // r = v/w and turning it into v = r*w
  112. finalSpeed = Tank.MaxAngularVelocity * radius;
  113. }
  114. return finalSpeed;
  115. }
  116. /// <summary>
  117. /// Calculates the angle that an object should face, given its position, its
  118. /// target's position, its current angle, and its maximum turning speed.
  119. /// </summary>
  120. private static float TurnToFace(Vector2 position, Vector2 faceThis,
  121. float currentAngle, float turnSpeed)
  122. {
  123. // consider this diagram:
  124. // C
  125. // /|
  126. // / |
  127. // / | y
  128. // / o |
  129. // S--------
  130. // x
  131. //
  132. // where S is the position of the spot light, C is the position of the cat,
  133. // and "o" is the angle that the spot light should be facing in order to
  134. // point at the cat. we need to know what o is. using trig, we know that
  135. // tan(theta) = opposite / adjacent
  136. // tan(o) = y / x
  137. // if we take the arctan of both sides of this equation...
  138. // arctan( tan(o) ) = arctan( y / x )
  139. // o = arctan( y / x )
  140. // so, we can use x and y to find o, our "desiredAngle."
  141. // x and y are just the differences in position between the two objects.
  142. float x = faceThis.X - position.X;
  143. float y = faceThis.Y - position.Y;
  144. // we'll use the Atan2 function. Atan will calculates the arc tangent of
  145. // y / x for us, and has the added benefit that it will use the signs of x
  146. // and y to determine what cartesian quadrant to put the result in.
  147. // http://msdn2.microsoft.com/en-us/library/system.math.atan2.aspx
  148. float desiredAngle = (float)Math.Atan2(y, x);
  149. // so now we know where we WANT to be facing, and where we ARE facing...
  150. // if we weren't constrained by turnSpeed, this would be easy: we'd just
  151. // return desiredAngle.
  152. // instead, we have to calculate how much we WANT to turn, and then make
  153. // sure that's not more than turnSpeed.
  154. // first, figure out how much we want to turn, using WrapAngle to get our
  155. // result from -Pi to Pi ( -180 degrees to 180 degrees )
  156. float difference = WrapAngle(desiredAngle - currentAngle);
  157. // clamp that between -turnSpeed and turnSpeed.
  158. difference = MathHelper.Clamp(difference, -turnSpeed, turnSpeed);
  159. // so, the closest we can get to our target is currentAngle + difference.
  160. // return that, using WrapAngle again.
  161. return WrapAngle(currentAngle + difference);
  162. }
  163. /// <summary>
  164. /// Returns the angle expressed in radians between -Pi and Pi.
  165. /// </summary>
  166. private static float WrapAngle(float radians)
  167. {
  168. while (radians < -MathHelper.Pi)
  169. {
  170. radians += MathHelper.TwoPi;
  171. }
  172. while (radians > MathHelper.Pi)
  173. {
  174. radians -= MathHelper.TwoPi;
  175. }
  176. return radians;
  177. }
  178. #endregion
  179. }
  180. }