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