This tutorial describes an implementation of self-guided missile.
Note
This is not a complete mutator. This example assumes that you should implement your own class (probably derived from RocketProj) and insert the code below in it. For details on how implement the complete mutator, see Weapon Mutator Tutorial for example.
What we will do
We will implement a 'fire-and-forget' missile. Original UT2004 rocket launcher may fire with player-guided missile, i.e. player (bot or human) should select the target and 'point' to it with crosshair before launching the missile. Our missile will be self-guied - it will pick the target and adjust the trajectory without any player intervention.
Implementation overview
Our missile will 'think' at each game tick. This means that the logic and algorithms should be very fast. On the other side, we would like to get a quite 'intelligent' and physicaly realistic missile. To achieve this, we will guide with the following assumptions:
- Flying missile observes the semisphere in front of itself .
- Missile treats all targets as motionless and doesn't try to predict the target location in the near future.
- Missile should't adjust it's trajectory too much at once. For example, it shouldn't turn the direction with 90 (and even 10 or 5) degrees during one tick - it should do a few tens small adjustments with 0.5 degree each instead. This gives a smooth, arch-like and realistic resulting trajectory.
Code and comments
The following two new variables controls the behaviour of missile:
var float MaxAngleDelta;
This is a maximum angle (in radians) to rotate the direction vector at each tick. For example, if V1 = Normal(Vector(Rotation)) at tick N and V2 = Normal(Vector(Rotation)) at tick N+1, then Acos(Normal(V1) Dot Normal(V2)) <= MaxAngleDelta. Let's set it to 0.0015. This means that if missile speed is 1000 UUs/second and one game tick takes 0.01 second, it will require at least 104 ticks to turn the direction on 90 degrees. This is pleasantly smooth on my taste.
var float MaxTargetRadius;
This is a 'radius of visibility' (in UUs) - our missile M will 'see' the target T if VSize(T.Location - M.Location) <= MaxTargetRadius. Let's set it to 10000. On the most of maps, this means the distance 'from missile to the end of the world'. It's large, really.
End of well-documented stuff
This is a function which observes the semi-sphere in front of the flying missile and picks the target which may be reached with the best probability.
simulated function Pawn PickTarget(float DeltaTime, out float OutDistance, out float OutAngle) { local Pawn Candidate, ReachableTarget, UnreachableTarget; local vector direction, V; local float D, distance, angle; Candidate = None; ReachableTarget = None; UnreachableTarget = None; OutDistance = 0.0; OutAngle = 0.0; direction = Normal(Vector(Rotation)); D = - (direction dot Location); foreach VisibleCollidingActors(class'Pawn', Candidate, MaxTargetRadius) { // Skip myself and dying pawns if ((Candidate == Instigator) || (Candidate.IsInState('Dying'))) continue; // Skip teammates in team games if (Level.Game.bTeamGame && (Instigator.GetTeamNum() == Candidate.GetTeamNum())) continue; if ((direction dot Candidate.Location + D) > 0.0) { // Candidate is in front semisphere of the missile V = Candidate.Location - Location; distance = VSize(V); angle = Acos(direction dot Normal(V)); if (((distance / Speed) / DeltaTime) * MaxAngleDelta >= angle) { if ((ReachableTarget == None) || (distance < OutDistance)) { ReachableTarget = Candidate; OutDistance = distance; OutAngle = angle; } } else { if ((UnreachableTarget == None) || (angle < OutAngle)) { UnreachableTarget = Candidate; OutDistance = distance; OutAngle = angle; } } } } if (ReachableTarget != None) return ReachableTarget; return UnreachableTarget; }
This is a function which is called each game tick and adjusts the missile's trajectory in attempt to reach the picked target.
simulated function Tick(float DeltaTime) { local Pawn Target; local float angle, distance, lambda; Target = PickTarget(DeltaTime, distance, angle); if ((Target == None) || (angle == 0.0)) return; if (angle <= MaxAngleDelta) Velocity = VSize(Velocity) * Normal(Target.Location - Location); else { lambda = MaxAngleDelta / (angle - MaxAngleDelta); Velocity = Normal(((Normal(Vector(Rotation)) * distance + Location) + lambda * Target.Location) * (1.0 / (1.0 + lambda)) - Location) * VSize(Velocity); } Speed = VSize(Velocity); SetRotation (rotator(Velocity)); }