So firstly of all we have to set up our games scripts. I started with
TrainInfo.uc
class TrainInfo extends UTGame;
event PlayerController Login(string Portal, string Options, const UniqueNetId UniqueId, out string ErrorMessage)
{
local NavigationPoint StartSpot;
local PlayerController NewPlayer;
local string InName;
local rotator SpawnRotation;
local Mustang_Content Pawn;
local Pawn thepawn;
StartSpot = FindPlayerStart( None, 0, Portal );
if( StartSpot == None )
{
ErrorMessage = PathName(WorldInfo.Game.GameMessageClass) $ ".FailedPlaceMessage";
return None;
}
SpawnRotation.Yaw = StartSpot.Rotation.Yaw;
NewPlayer = SpawnPlayerController(StartSpot.Location, SpawnRotation);
if( NewPlayer == None )
{
`log("Couldn't spawn player controller of class "$PlayerControllerClass);
ErrorMessage = PathName(WorldInfo.Game.GameMessageClass) $ ".FailedSpawnMessage";
return None;
}
NewPlayer.StartSpot = StartSpot;
thepawn = spawn(class 'TrainPawn' ,,,StartSpot.Location+Vect(0,0,100)); //+100 to make sure the two pawns do not get stuck
Pawn = spawn(class 'Mustang_Content',,,StartSpot.Location);
NewPlayer.possess(thepawn,false);
Pawn.TryToDrive(thePawn);//The ****!
return NewPlayer;
}
defaultproperties
{
PlayerControllerClass=class'TrainGame.TrainPlayerController'
DefaultPawnClass=class'TrainGame.TrainPawn'
bDelayedStart=false
}
TrainPawn.uc
class TrainPawn extends UTPawn;
DefaultProperties
{
}
TrainPlayerController
class TrainPlayerController extends UTPlayerController;
var SplineConstraint Constraint; //Constraint
struct sSplinePath
{
var SplineActor StartAnchor;
var SplineActor EndAnchor;
var array Path;
var SplineActor NearAnchorLow;
var SplineActor NearAnchorHigh;
var float RecentPosition;
var float RespectivePosition;
var Vector RecentLocation;
};
simulated event PostBeginPlay()
{
super.PostBeginPlay();
//Setup our Constraint
Constraint = new class'SplineConstraint';
Constraint.Initialize(WorldInfo);
}
event PlayerTick( float DeltaTime )
{
if(Constraint.Pawn == none)
Constraint.SetConstraint(Pawn);
super.PlayerTick(DeltaTime);
Constraint.Update(DeltaTime);
}
event Possess(Pawn inPawn, bool bVehicleTransition)
{
Super.Possess(inPawn, bVehicleTransition);
SetBehindView(true);
}
DefaultProperties
{
}
SplineConstraint.uc
class SplineConstraint extends Object;
var Pawn Pawn; //Pawn to Constrain to the Spline
var array SplineCache; //Cache of our Current SplineActors
var Vector StartPosition; //Pawn's Start location on the Path
var float Leeway; //if the Pawn is farther than this amount the constraint will Lerp it back
var float LerpSpeed; //How fast the Pawn gets Lerp'd into position
var bool bEnableDebug; //Is Debugging enabled for the SplineConstraint
exec function ShowConstraint()
{
bEnableDebug = !bEnableDebug;
if(bEnableDebug)
{
`log("Show Constraints Enabled");
}
else
{
`log("Show Constraints Disabled");
}
}
/**
* Initializes the class and finds the SplineActors
* @param WorldInfo
*/
function Initialize(WorldInfo WorldInfo)
{
local SplineActor Current;
//Add Our splines to our Cache
foreach WorldInfo.DynamicActors( class'SplineActor', Current)
{
SplineCache.AddItem( Current );
Current.nextOrdered = Current.GetBestConnectionInDirection(vect(0,1,0));
if(Current.nextOrdered != none)
{
Current.nextOrdered.prevOrdered = Current;
}
}
}
/**
* Set the Pawn for Constraint and Initialize Constraint
*/
function SetConstraint(Pawn PawnToConstrain)
{
Pawn = PawnToConstrain;
Initialize(Pawn.WorldInfo);
}
/**
* Updates and Enforces the Constraint
*/
function Update(float DeltaTime)
{
local SplineActor Spline; //Used to iterate through our SplineCache
local SplineActor CurrentSplineActor; //Current SplineActor We are Focused on
local SplineActor PrevSplineActor; //Previous SplineActor from CurrentSplineActor
local SplineActor NextSplineActor; //Next SplineActor from CurrentSplineActor
local Vector RequiredLocation; //Position on the spline we should be constrained to
local Vector Direction; //Direction we should be facing. Heading Vector. using Rotator(Direction)
local float BestDistance; //Used to find the closest SplineActor
local float Distance; //Distance on the Spline to find a Location
local float DotProduct; //Used to find which side of the CurrentSplineActor we are on.
//Temp Debug Value
local float Z;
//Set to a High Number
BestDistance = 10000;
//Find the closest SplineActor that has a Previous and Next Ordered SplineActor
foreach SplineCache( Spline )
{
if(VSize( Pawn.Location - Spline.Location ) < BestDistance)
{
// If this Spline doesn't have a PrevOrdered or NextOrdered then skip it.
// Our CurrentSplineActor needs to be one that has a Prev and Next Ordered SplineActor
if(Spline.prevOrdered == none || Spline.nextOrdered == none)
continue;
//Set our CurrentSplineActor
CurrentSplineActor = Spline;
//Update our BestDistance to this Spline and then check for a closer SplineActor
BestDistance = VSize( Pawn.Location - Spline.Location);
}
}
//Don't continue if we don't have a CurrentSplineActor
if(CurrentSplineActor == none)
return;
NextSplineActor = CurrentSplineActor.nextOrdered;
PrevSplineActor = CurrentSplineActor.prevOrdered;
//Need a Previous and Next SplineActor for the Constraint to Work.
//Check here just in case
if( NextSplineActor == none || PrevSplineActor == none)
return;
//Set our starting location.
//Playerstart should be close to this in the Map.
if(StartPosition == vect(0,0,0))
{
StartPosition = CurrentSplineActor.prevOrdered.Location; //Generally the start pos will be on a Prev SplineActor
Pawn.SetLocation(StartPosition);
}
//Find out which side of the CurrentSplineActor we are on for our positon Calculations.
DotProduct = Normal(Pawn.Location - CurrentSplineActor.Location) dot Normal(CurrentSplineActor.Location - NextSplineActor.Location);
//If the DotProduct is Less than 0 than we are in between the Current and Next SplineActors
//If the DotProduct is Greater than 0 than we are in between the Previous and Current SplineActors
if(DotProduct Leeway)
{
//use Lerp
RequiredLocation.X = Lerp(Pawn.Location.X, RequiredLocation.X, LerpSpeed);
RequiredLocation.Y = Lerp(Pawn.Location.Y, RequiredLocation.Y, LerpSpeed);
//Set Location
Pawn.SetLocation(RequiredLocation);
}
//Face our Pawn to the the direction the Spline is faceing
//Needs to be modified to go both directions and dependent on the mouse cursor position.
Pawn.FaceRotation(Rotator(Direction), DeltaTime);
//Check if we should show Debug
if(bEnableDebug)
{
//Draw a line from Pawn.Location to NextSplineActor.Location
Pawn.DrawDebugLine(Pawn.Location, NextSplineActor.Location, 255,0,0, false);
//Draw a line from Pawn.Location to CurrentSplineActor.Location
Pawn.DrawDebugLine(Pawn.Location, CurrentSplineActor.Location, 0,255,0, false);
//Draw a line from Pawn.Location to PrevSplineActor.Location
Pawn.DrawDebugLine(Pawn.Location, PrevSplineActor.Location, 255, 0, 0, false);
RequiredLocation.Z = Z;
//Draw a line from Pawn.Location to RequiredLocation
Pawn.DrawDebugLine(Pawn.Location, RequiredLocation, 0, 0, 255, false);
//Draw Boxes around the SplineActors to show their Location
foreach SplineCache( Spline )
{
Pawn.DrawDebugBox(Spline.Location, vect(10,10,10), 255, 255, 0, false);
}
}
}
DefaultProperties
{
bEnableDebug = true
Leeway = 10
LerpSpeed = 0.5
}
I also added a spline constraint class which you will see referenced in the playercontroller class. I will talk a little
bit more about the spline constraint class later on but this basically glues the pawn to the tracks so that they can only move backwards and forwards
(Which is useful when running a train!!)
Pete