news | about | forums | links | mods | tools | tutorials | contact

Trails - author: [mr.self-destruct]
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'
}

suggestions? questions? comments? please send them all to ray@beyondunreal.com

copyright 2001 ray davis. this site and all content belongs to me unless otherwise noted, with the exception of some tutorials. the authors retain the rights to their material, and are hosted here with their express permission. everything else is mine though, so don't steal it. bastards.
privacy