Network Multiplayer, UNet, Web-sockets, Split Screen, procedurally Generated, Racing, C#, python, Keras, Convolutional neural networks, Playstation 4, PC, Made with Unity
Showcased at develop Brighton 2017
My idea behind creating AGS Racer was t o make a racing game where no player knows what track layout will be, as usually in a racing game you need to learn a track to know what to expect.
The race track consists of a starting line , a finish line and an assortment of track pieces.
Each piece of the track I modelled in 3DS MAX to create prefabs with a defined curvature going left, right, up, down and straight.These track pieces are then automatically aligned based on alignment nodes defining the start and end of each part.
The following code shows the process of choosing creating the parts of the race track so that the direction is biased to avoid the track intersecting with itself excluding the starting and finishing line.
void CreateParts ()
{
Transform endPoint;
Transform startPoint;
trackDirection = Vector3.zero;
int randomTrackNum;
if (Currentpart < trackLength - 1) {
endPoint = Track [Currentpart - 1].transform.Find ( "End" ).transform;
do { // when creating new part check trackpartprefabs direction if direction is to larger try new track part
randomTrackNum = Random .Range (0, TrackPartPrefab.Length); //creates a random number
} while
(trackDirection.x + TrackPartPrefab [randomTrackNum].angle.x > maxangle ||
trackDirection.x + TrackPartPrefab [randomTrackNum].angle.x < -maxangle ||
trackDirection.y + TrackPartPrefab [randomTrackNum].angle.y > maxangle ||
trackDirection.y + TrackPartPrefab [randomTrackNum].angle.y < -maxangle ||
trackDirection.z + TrackPartPrefab [randomTrackNum].angle.z > maxangle ||
trackDirection.z + TrackPartPrefab [randomTrackNum].angle.z < -maxangle);
//end of do while loop conditions
trackDirection += TrackPartPrefab [randomTrackNum].angle; // sets new track direction
startPoint = TrackPartPrefab [randomTrackNum].part.transform.Find ( "Start" ).transform; // sets the start point of the current track piece
//Instantiates track part in the end point position of the previous track object
Track [Currentpart] = Instantiate (TrackPartPrefab [randomTrackNum].part,
endPoint.position, endPoint.rotation, StartingPoint.transform)
as
GameObject ;
Track [Currentpart].transform.Translate (-startPoint.position);
Currentpart++;
} else if (Currentpart == trackLength - 1)
{
built = true ;
buildingtrack = false ;
BuildTrack ();
}
}
During the time I took this game to display at the Kingston University booth during 2017 Brighton develop Conference, I learnt about PID controllers and realised that it would improve how the racers hovered above the track by reducing hover oscillation, so by the next day I had researched and implemented the following code.
[SerializeField] private float PIDproportion = 1; //initial force
[SerializeField] private float PIDintegral = 0; //time taken
[SerializeField] private float PIDderivative = 0.1f; //damper
private float previousError;
private float P, I, D;
float PIDoutput( float currentError, float DeltaTime)
{
P = currentError;
I += P * DeltaTime;
D = (P - previousError) / DeltaTime;
previousError = currentError;
return (P * PIDproportion + I * PIDintegral + D * PIDderivative);
}
Neural Network AI addition
A 2D convolutional network was trained using 38,000 images with controller inputs and time stamps.
the ai works by having unity send a picture of what the car sees to a python server via web-sockets, this then uses a 2D convolutional neural network to evaluate the picture and send inputs back to unity. The neural network used in this video was created from 30 minutes of gameplay
X[i] = (1 - cos(((i - 1) * 90 / loops - 1)* 3.14 / 180));
// calculates the thickness of the air-foil based upon the thickness variable and x position
Y_T[i] = (thickness / 0.2)*((0.2969*pow(X[i], 0.5)) - (0.16*X[i]) - (0.3516*(pow(X[i], 2))+ (0.2843*(pow(X[i], 3))) - (0.1015*(pow(X[i], 4))));
// calculates the camber location and camber intensity of the air-foil
Y_C[i] = (camber / (pow(camberlocation / 10, 2)))*((2 * (camberlocation/10 )*X[i]) -pow(X[i], 2));
// stores the Y coordinates for the upper and lower lines of the air-foil
upper[i] = (Y_C[i] + Y_T[i]);
lower[i] = (Y_C[i] - Y_T[i]);