I’m making a free webgl game”pirates“with battles and puzzles and wanted to create some variety in the gameplay by adding racing levels, with two modes depending on the level, a classic one at the end of the first one and another one to break the record using the track bonus to freeze the chrono timer, nothing new. Yes, I am a big fan of CTR. I’m going to write a short tutorial split into three articles on how I created a LapManager that handles two lap controls, how I use my own simple AI engine and how I tweaked it to become sophisticated in classic mode, and how I realized the struggle with the clock.
First of all, in our engine everything happens in 2D plane, collisions, AI and everything else except 3D models. This is quite common to save CPU computation time in another dimension, but anything can work in a 3D environment, for example, instead of using 2D convex polygons, you could use 3D convex shells, or instead of using lines, you could use planes . My method is an approximation to improve performance, JavaScript is not particularly fast, especially on mobile, so I use segments to divide the main route into segments, although someone can use a Bezier curve or a Catmull-Rome curve to be more precise I believe , that it is over-engineered, Keep it simple!
The lap manager registers all line segments in the order that determines the natural order of the races on the track.
Simply register segments to save them on the map:
const route_segments_1 =
[
name: 'GvRay2(51,44)' , name: 'GvRay2(48,46)' , name: 'GvRay2(31,41)' ,
name: 'GvRay2(13,49)' , name: 'GvRay2(9,31)' , name: 'GvRay2(14,1)' ,
name: 'GvRay2(32,9)' , name: 'GvRay2(43,8)' , name: 'GvRay2(51,16)'
];
level.lap_manager.set_route( level, route_segments_1 );
Also, I register all the ships and assign a control route defined as nodes to their AI agent, the AI route uses points in the 2D plane:
const route_marks_1 =
[
node: 'GvRaceMark4' , node: 'GvRaceMark12' , node: 'GvRaceMark1' ,
node: 'GvRaceMark23' , node: 'GvRaceMark2' , node: 'GvRaceMark34' ,
node: 'GvRaceMark3'
];
ship_race_1 = level.get_object( 'GvRaceShip1' );
ship_race_1.set_auto_route( route_marks_1, 'GvRaceMark4', true, 0.4, 8.0 );
level.lap_manager.add_vehicle( ship_race_1 );
ship_race_2 = level.get_object( 'GvRaceShip2' );
ship_race_2.set_auto_route( route_marks_1, 'GvRaceMark4', true, 0.2, 8.0 );
level.lap_manager.add_vehicle( ship_race_2 );
ship_race_3 = level.get_object( 'GvRaceShip3' );
ship_race_3.set_auto_route( route_marks_1, 'GvRaceMark4', true, 0.3, 8.0 );
level.lap_manager.add_vehicle( ship_race_3 );
player = level.get_object( 'player' );
level.lap_manager.add_vehicle( player );
The lap manager receives beam collisions when a registered ship collider comes into contact with a segment on a level registered as a track segment. The first segment is the one that increases the ship’s laps or triggers a win/lose event. It’s a simple approach that works for now as long as I don’t control the glitches and cheaters.
![](https://uploads.gamedev.net/tutorials/monthly_2023_07/8b9cecf68b5743aa82c4bf2be3e4c51b.pos_fixing.png)
The positions are fixed as soon as the ships complete the last lap in order and they will not change, the task is to calculate the dynamic position of the ships. It’s all about priorities, I get all the ships that haven’t finished the race and put the order in a certain order, the ships with more laps go first, if they have the same number of laps, I sort it by the last segment of the race they came in, in as a last resort, if they are in the same race segment, I calculate the distance to the line segment, and the one with the further distance is the one ahead. This is approximate, but works well enough.
![](https://uploads.gamedev.net/tutorials/monthly_2023_07/e7d64886b0ad49b58823c77e634abe54.pos_by_distance.png)
Nothing fancy, it works fine if the ships follow a standard route, if they take a shortcut, the ship may appear to be behind when other ships cross the next line segments, but once the crafty player gets the next line segment, they have to go back to the right way to hopefully take first place or regain a good ranking.
![](https://uploads.gamedev.net/tutorials/monthly_2023_07/550c48ad7bb24b63b6f61209d35ac7b8.shortcut.png)
The algorithm does not take into account