SteeringBehavior.cs 9.0 KB

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