# A train model without physics

A couple of months ago I implemented and wrote about modelling a train in Unity. It used physics to drive the train and to keep it on track. This worked quite well and interacted nicely with its environment. The downside is that the physics engine is still a bit rudimentary and when I wanted to improve the behaviour of the carriage I did not succeed. That’s why I created a new engine which does not rely on physics.This week I went to a railway modelling exhibition to show my digital train model. As these guys are extremely picky on the small details, I wanted to improve the way the train runs on the track. The thing was that the carriage has two sets of wheels each making a ‘bogie’. These bogies follow the rail independently, while the carriage itself sits on top of the two bogies. This implies that when the carriage is going through a corner in the track, the rotation angles of the bogies differ from each other. In my old model the angle was the same which looked odd.

Making the bogies follow the track was no problem, but when I connected the bogies to the carrage with a configurable joint to the carriage everything went everywhere, but did not stick together. The configurable joint was fixed in all directions except rotation around the vertical Y axis. Therefore I decided on a new engine.

This engine takes as input the current speed of the train and moves every part following the rails (locomotive, bogie) the same distance along a path. This path consists of a large number of way points aligned to the right rail mesh of the trackĀ  (using the V-key combination here is very useful). The train parts are moved the distance in the direction of the next way point for that train part unless the distance of the next way point is less than the distance which will be travelled in this update, then the next way point after that is taken.

This leads to a smooth riding train along the track. Now we have to place the carriage on top of the two bogies. This is relatively easy: the middle of the carriage sits exactly halfway between the two bogies and the rotation angle is the average of the two angles of the bogies.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class TrackFollowingTrain : MonoBehaviour {

public GameObject waypointContainer;
public GameObject[] trainparts;
public AnimationCurve startCurve;

private List<Transform> waypoints;
public List<int> currentWaypoints;

public float speed = 0.1F;
public float currentSpeed;
private float startTime;
public int direction = 0;

// Use this for initialization
void Start () {
for (int i = 0; i < trainparts.Length; i++) {
}
waypoints = GetWaypoints();
}

// Update is called once per frame
void FixedUpdate () {
if (currentWaypoints[0] >= waypoints.Count || currentWaypoints[0] < 0) {
direction = 0;
} else {
for(int i = 0; i < trainparts.Length; i++) {
currentWaypoints[i] = MoveTransformTowardsWaypoint(trainparts[i].transform, currentWaypoints[i]);
}
}
}

public void StartTrain() {
speed = 5F;
if (currentWaypoints[0] < 10) {
direction = 1;
for (int i = 0; i < currentWaypoints.Count; i++)
currentWaypoints[i]++;
} else if (direction == 0) {
direction = -1;
for (int i = 0; i < currentWaypoints.Count; i++)
currentWaypoints[i]–;
}
startTime = Time.time + 2.0F;
}

List<Transform> GetWaypoints () {
// This function takes the container object for the waypoints, then finds all of the transforms in it,
// once it has the transforms, it checks to make sure it’s not the container, and adds them to the list of waypoints.

Transform[] potentialWaypoints = waypointContainer.GetComponentsInChildren<Transform>();
List<Transform> waypoints = new List<Transform>();

foreach (Transform potentialWaypoint in potentialWaypoints) {
if (potentialWaypoint != waypointContainer.transform) {

// correction for right rail, track width = 75 cm
Vector3 correction = new Vector3(0.375F, 0.0F, 0.0F);
potentialWaypoint.Translate(correction, Space.Self);

}
}
return waypoints;
}

int MoveTransformTowardsWaypoint(Transform tpartTF, int currentWaypoint) {
currentSpeed = speed * startCurve.Evaluate((Time.time – startTime)/10);
float currentStep = currentSpeed * Time.smoothDeltaTime;

Vector3 movementVector = Vector3.MoveTowards(tpartTF.position,waypoints[currentWaypoint].position,currentStep);
float distance = Vector3.Distance(tpartTF.position, movementVector);
if (currentStep – distance > 0.001) {
// if this increment passes the waypoint, we move towards the next waypoint instead
currentWaypoint += direction;
if (currentWaypoint >= 0 && currentWaypoint < waypoints.Count) {
movementVector = Vector3.MoveTowards(tpartTF.position,waypoints[currentWaypoint].position,currentStep);
} else {
return currentWaypoint;
}
}

tpartTF.position = movementVector;
tpartTF.LookAt(waypoints[currentWaypoint]);

return currentWaypoint;
}
}

This site uses Akismet to reduce spam. Learn how your comment data is processed.