Trails

From Oldunreal-Wiki
Jump to navigation Jump to search
This tutorial outlines a generic class to create a trailing effect, for example a smoke trail from a rocket or a cool motion effect. Oh and thanks to TNSe for pointing out some silly mistakes I made :|

Theory The trail will consist of a number of Actors (we shall call them Trailer), so we shall store them in a linked list with the first Trailer in the list controlling the rest of the trail. This is somewhat similar to the way the PBolt beam in UT works, except with our trail the first item in the linked list isn't of a different class to the rest; there's no need when a few extra lines of code will allow us to determine which object is first. To do this, and to limit the maximum number of Trailers in a trail, we keep a counter in each Trailer. This counter gets incremented when a new Trailer is added to the linked list. The approach taken to trails in UT (for example in the rockets) is to continously spawn short lived Actors which gives the effect of a trail. The approach we're taking is quite different, as indicated earlier we're using a fixed number of Actors held in a linked list. Because new Actors aren't continously being spawned at the location of whatever the trail is attached to we need to devise a means of moving the individual Trailers in such a fashion as to create a trail effect. We simply move each Trailer to the location the one in preceeding it (in the linked list) occupied and move the first Trailer to the same location as the Actor the trail is set to follow. This is really quite simple to do, if we set the Owner of each Trailer to be the Trailer preceeding it and in the case of the first Trailer set the Owner to the Actor that the trail should follow. So now we can not only traverse down the linked list, but each item in the list has a means of referring to the item preceeding it. Now every time we update the trail each Trailer will set its location to be that of its Owner. This can be bundled into a function which initially is called on the first Trailer, this function will then call itself on the next item in the linked list (and so on), then set the current Trailer's location to that of its Owner and finally if it's the last item in the linked list and the trail contains less Trailers than we want, spawn a new Trailer. All that remains to get the whole thing working is to set up Tick() to periodically call the trail update function on the first Trailer in the linked list. Implementation So we've gone through a basic explaination of how the trail system works, so here's the code for the generic Trailer class. Note that it expands considerably on the basic concept, adding a number of useful features such as the ability to delay trail updating and offset the trail from the Actor it follows.

// Generic trailer effect
class Trailer extends Effects;
// Position in trail & maximum number of trails
var int Pos, MaxTrails;
// Next part of the trail, Owner is used to keep track of previous part of trail
var Trailer Next;
// Time delay to lag the trail updating by
var float lagBy;
var float cntr;
// What to spawn as trails
var class<Trailer>TrailerClass;
// Location on a cylinder around the object being trailed that trail should appear from
// X = radius, Y = elevation, Z = angle, e.g. 10UUs directly behind at same level would be vector(10,0,180)
var vector TrailOffset;
replication
{
 unreliable if (ROLE == ROLE_Authority)
   Pos;
}
// Make sure the whole trail gets destroyed at once
simulated function Destroyed()
{
 Super.Destroyed();
 if (Next != None)
   Next.Destroy();
}
// Update the trailer
simulated function Update()
{
 local vector Offset;
 local float Alpha, X, Y;
 // Call Update() on next trail
 if (Next != None)
   Next.Update();
 // Move to where the previous part of the trail was (or player location for that matter)
 if (Pos == 0)
 {
   // Convert Owner horizontal rotation into radians
   Alpha = Owner.Rotation.Yaw / 10430.2192;
   // Rotate it through TrailOffset.Z
   Alpha += (TrailOffset.Z / 180 * Pi);
   if (Alpha > 2 * Pi)
     Alpha -= 2 * Pi;
   // Calculate cylinder co'ordinates
   X = TrailOffset.X * Cos(Alpha);
   Y = TrailOffset.X * Sin(Alpha);
   Offset.X = X;
   Offset.Y = Y;
   Offset.Z = TrailOffset.Y;
   SetLocation(Owner.Location + Offset);
 }
 else
   SetLocation(Owner.Location);
 SetRotation(Owner.Rotation);
 // Spawn additional trails as necessary
 if (Pos < MaxTrails && Next == None)
 {
   Next = Spawn(TrailerClass, self,, Location, Rotation);
   Next.Pos = Pos + 1;
 }
}
// Handle updating of trail location
simulated function Tick(float DeltaTime)
{
 // Destroy if there's no owner
 if (Owner == None)
   Destroy();
 // First trail should tell next trail to update & so on
 if (Pos > 0)
   return;
 cntr += DeltaTime;
 // Lag location update by lagBy ticks
 if (cntr >= lagBy)
 {
   cntr = 0.0;
   Update();
 }
}
defaultproperties
{
    cntr=0.0
    Pos=0
    lagBy=0.2
    MaxTrails=4
    Physics=PHYS_None
    bNetTemporary=False
    RemoteRole=ROLE_SimulatedProxy
    TrailerClass=class'Trailer'
    TrailOffset=vector(X=0,Y=0,Z=0)
}
And now a demonstration of how to derive a new class from Trailer to implement whatever type of trail we desire. In this particular case a cool motion trail.
// Motion trails for something with a mesh
class MeshTrailer extends Trailer;
#exec OBJ LOAD FILE=..\Textures\AlfaFX.utx
simulated function PostBeginPlay()
{
 // Set mesh & anims
 Mesh = Owner.Mesh;
 AnimFrame = Owner.AnimFrame;
 AnimSequence = Owner.AnimSequence;
 AnimRate = Owner.AnimRate;
 TweenRate = Owner.TweenRate;
 AnimMinRate = Owner.AnimMinRate;
 AnimLast = Owner.AnimLast;
 bAnimLoop = Owner.bAnimLoop;
 bAnimFinished = Owner.bAnimFinished;
 Super.PostBeginPlay();
}
// Update the trailer
simulated function Update()
{
 local vector Offset;
 local float Alpha, X, Y;
 // Update animation
 AnimFrame = Owner.AnimFrame;
 AnimSequence = Owner.AnimSequence;
 AnimRate = Owner.AnimRate;
 TweenRate = Owner.TweenRate;
 AnimMinRate = Owner.AnimMinRate;
 AnimLast = Owner.AnimLast;
 bAnimLoop = Owner.bAnimLoop;
 bAnimFinished = Owner.bAnimFinished;
 // Call Update() on next trail
 if (Next != None)
   Next.Update();
 // Move to where the previous part of the trail was (or player location for that matter)
 if (Pos == 0)
 {
   // Convert Owner horizontal rotation into radians
   Alpha = Owner.Rotation.Yaw / 10430.2192;
   // Rotate it through TrailOffset.Z
   Alpha += (TrailOffset.Z / 180 * Pi);
   if (Alpha > 2 * Pi)
     Alpha -= 2 * Pi;
   // Calculate cylinder co'ordinates
   X = TrailOffset.X * Cos(Alpha);
   Y = TrailOffset.X * Sin(Alpha);
   Offset.X = X;
   Offset.Y = Y;
   Offset.Z = TrailOffset.Y;
   SetLocation(Owner.Location + Offset);
 }
 else
   SetLocation(Owner.Location);
 SetRotation(Owner.Rotation);
 // Spawn additional trails as necessary
 if (Pos < MaxTrails && Next == None)
 {
   Next = Spawn(TrailerClass, self,, Location, Rotation);
   Next.Pos = Pos + 1;
 }
}
defaultproperties
{
    DrawType=DT_Mesh
    Style=STY_Translucent
    lagBy=0.1
    MaxTrails=4
    TrailerClass=class'MeshTrailer'
    Skin=FireTexture'AlfaFX.Lion3'
}