| | | 1 | | using UnityEngine; |
| | | 2 | | |
| | | 3 | | // Missile movement. |
| | | 4 | | // |
| | | 5 | | // A missile is defined by having multiple phases during its flight: ready phases (before it is |
| | | 6 | | // launched), boost phases (when its burner is on), midcourse phases (after the burner has completed |
| | | 7 | | // burning), and terminal phases (when the agent is homing in on the target). |
| | | 8 | | // |
| | | 9 | | // We define three additional states for our simulator: initialized state (when the missile has been |
| | | 10 | | // created), ballistic state (when the missile is subject to only drag and gravity), and terminated |
| | | 11 | | // state (when the missile has finished its flight). |
| | | 12 | | // |
| | | 13 | | // The missile is responsible for determining when it enters the boost phase. |
| | | 14 | | public class MissileMovement : AerialMovement { |
| | | 15 | | // Flight phase of the agent. |
| | | 16 | | private Simulation.FlightPhase _flightPhase; |
| | | 17 | | |
| | | 18 | | // Boost time in seconds relative to the agent's creation time. |
| | | 19 | | private float _boostTime; |
| | | 20 | | |
| | | 21 | | public Simulation.FlightPhase FlightPhase { |
| | 0 | 22 | | get { return _flightPhase; } |
| | 0 | 23 | | set { |
| | 0 | 24 | | _flightPhase = value; |
| | 0 | 25 | | if (FlightPhase == Simulation.FlightPhase.Boost) { |
| | 0 | 26 | | _boostTime = Agent.ElapsedTime; |
| | 0 | 27 | | } |
| | 0 | 28 | | } |
| | | 29 | | } |
| | | 30 | | |
| | 0 | 31 | | public MissileMovement(IAgent agent) : base(agent) { |
| | 0 | 32 | | FlightPhase = Simulation.FlightPhase.Initialized; |
| | 0 | 33 | | } |
| | | 34 | | |
| | | 35 | | // Determine the agent's actual acceleration input given its intended acceleration input by |
| | | 36 | | // applying physics and other constraints. |
| | 0 | 37 | | public override Vector3 Act(in Vector3 accelerationInput) { |
| | | 38 | | // Step through the flight phases. |
| | 0 | 39 | | if (FlightPhase == Simulation.FlightPhase.Boost && |
| | 0 | 40 | | Agent.ElapsedTime > _boostTime + (Agent.StaticConfig?.BoostConfig?.BoostTime ?? 0)) { |
| | 0 | 41 | | FlightPhase = Simulation.FlightPhase.Midcourse; |
| | 0 | 42 | | } |
| | | 43 | | |
| | | 44 | | // Act according to the flight phase. |
| | 0 | 45 | | switch (FlightPhase) { |
| | 0 | 46 | | case Simulation.FlightPhase.Initialized: { |
| | | 47 | | // In the initialized phase, the agent is not subject to any acceleration. |
| | 0 | 48 | | return Vector3.zero; |
| | | 49 | | } |
| | 0 | 50 | | case Simulation.FlightPhase.Ready: { |
| | 0 | 51 | | return ActReady(accelerationInput); |
| | | 52 | | } |
| | 0 | 53 | | case Simulation.FlightPhase.Boost: { |
| | 0 | 54 | | return ActBoost(accelerationInput); |
| | | 55 | | } |
| | 0 | 56 | | case Simulation.FlightPhase.Midcourse: { |
| | 0 | 57 | | return ActMidCourse(accelerationInput); |
| | | 58 | | } |
| | 0 | 59 | | case Simulation.FlightPhase.Terminal: { |
| | 0 | 60 | | return ActTerminal(accelerationInput); |
| | | 61 | | } |
| | 0 | 62 | | case Simulation.FlightPhase.Ballistic: { |
| | 0 | 63 | | return ActBallistic(accelerationInput); |
| | | 64 | | } |
| | 0 | 65 | | case Simulation.FlightPhase.Terminated: { |
| | | 66 | | // In the terminated phase, the agent is not subject to any acceleration. |
| | 0 | 67 | | return Vector3.zero; |
| | | 68 | | } |
| | 0 | 69 | | default: { |
| | 0 | 70 | | return Vector3.zero; |
| | | 71 | | } |
| | | 72 | | } |
| | 0 | 73 | | } |
| | | 74 | | |
| | | 75 | | // In the ready phase, the agent is subject to drag and gravity but has not boosted yet. |
| | 0 | 76 | | private Vector3 ActReady(in Vector3 accelerationInput) { |
| | 0 | 77 | | Vector3 limitedAccelerationInput = LimitAccelerationInput(accelerationInput); |
| | 0 | 78 | | return CalculateNetAccelerationInput(limitedAccelerationInput); |
| | 0 | 79 | | } |
| | | 80 | | |
| | | 81 | | // In the boost phase, the boost acceleration is added to the control acceleration input. |
| | 0 | 82 | | private Vector3 ActBoost(in Vector3 accelerationInput) { |
| | 0 | 83 | | Vector3 accelerationInputWithGroundAvoidance = AvoidGround(accelerationInput); |
| | 0 | 84 | | Vector3 limitedAccelerationInput = LimitAccelerationInput(accelerationInputWithGroundAvoidance); |
| | | 85 | | |
| | | 86 | | // Determine the boost acceleration. |
| | 0 | 87 | | float boostAcceleration = |
| | | 88 | | (Agent.StaticConfig?.BoostConfig?.BoostAcceleration ?? 0) * Constants.kGravity; |
| | 0 | 89 | | Vector3 totalAccelerationInput = boostAcceleration * Agent.Forward + limitedAccelerationInput; |
| | 0 | 90 | | return CalculateNetAccelerationInput(totalAccelerationInput); |
| | 0 | 91 | | } |
| | | 92 | | |
| | | 93 | | // In the midcourse phase, the agent accelerates according to the control acceleration input but |
| | | 94 | | // is subject to drag and gravity. |
| | 0 | 95 | | private Vector3 ActMidCourse(in Vector3 accelerationInput) { |
| | 0 | 96 | | Vector3 accelerationInputWithGroundAvoidance = AvoidGround(accelerationInput); |
| | 0 | 97 | | Vector3 limitedAccelerationInput = LimitAccelerationInput(accelerationInputWithGroundAvoidance); |
| | 0 | 98 | | return CalculateNetAccelerationInput(limitedAccelerationInput); |
| | 0 | 99 | | } |
| | | 100 | | |
| | | 101 | | // In the terminal phase, the agent is homing in on the target and is still subject to drag and |
| | | 102 | | // gravity. |
| | 0 | 103 | | private Vector3 ActTerminal(in Vector3 accelerationInput) { |
| | | 104 | | // Currently, the agent acts the same in the terminal phase as in the midcourse phase. |
| | 0 | 105 | | return ActMidCourse(accelerationInput); |
| | 0 | 106 | | } |
| | | 107 | | |
| | | 108 | | // In the ballistic phase, the agent is subject to only drag and gravity. |
| | 0 | 109 | | private Vector3 ActBallistic(in Vector3 accelerationInput) { |
| | 0 | 110 | | return ActMidCourse(accelerationInput: Vector3.zero); |
| | 0 | 111 | | } |
| | | 112 | | |
| | | 113 | | // Adjust the acceleration input to avoid the ground. |
| | 0 | 114 | | private Vector3 AvoidGround(in Vector3 accelerationInput) { |
| | | 115 | | const float groundProximityThresholdFactor = 5f; |
| | | 116 | | |
| | 0 | 117 | | Vector3 agentPosition = Agent.Position; |
| | 0 | 118 | | Vector3 agentVelocity = Agent.Velocity; |
| | 0 | 119 | | float altitude = agentPosition.y; |
| | 0 | 120 | | float groundProximityThreshold = |
| | | 121 | | Mathf.Abs(agentVelocity.y) * groundProximityThresholdFactor + |
| | | 122 | | 0.5f * Constants.kGravity * groundProximityThresholdFactor * groundProximityThresholdFactor; |
| | 0 | 123 | | if (agentVelocity.y < 0 && altitude < groundProximityThreshold) { |
| | | 124 | | // Add some upward acceleration to avoid the ground. |
| | 0 | 125 | | float blendFactor = 1 - (altitude / groundProximityThreshold); |
| | 0 | 126 | | return accelerationInput + blendFactor * Agent.MaxNormalAcceleration() * Agent.Up; |
| | | 127 | | } |
| | 0 | 128 | | return accelerationInput; |
| | 0 | 129 | | } |
| | | 130 | | |
| | | 131 | | // Calculate the acceleration input with drag and gravity. |
| | 0 | 132 | | private Vector3 CalculateNetAccelerationInput(in Vector3 accelerationInput) { |
| | 0 | 133 | | Vector3 gravity = Physics.gravity; |
| | 0 | 134 | | float airDrag = CalculateDrag(); |
| | 0 | 135 | | float liftInducedDrag = CalculateLiftInducedDrag(accelerationInput + gravity); |
| | 0 | 136 | | float dragAcceleration = -(airDrag + liftInducedDrag); |
| | 0 | 137 | | return accelerationInput + gravity + dragAcceleration * Agent.Forward; |
| | 0 | 138 | | } |
| | | 139 | | } |