Train3D needs the ability to handle thousands of vehicles roaming about the world at the same time. A little research shows that there is some overhead to using the Unity Update() method for a bunch of game objects so my first optimization was to load up my vehicle scripts into a List<T> and step through all of them (using indexes, not a foreach loop) from a single Update() call. That certainly improved things but not as much as I hoped.
Time is the critical resource in a high frame rate game. I'm targeting 60fps at 4k resolution. That means every single thing I compute in a frame has to take less than 16.6ms total. Any longer and the frame rate will drop.
One strategy would be to not update every vehicle on every frame. Another would be to only update vehicles at a slower pace (like 30fps). Neither of those really meets my objective. So, I update every vehicle's position on every frame at the moment. When I ramp up to 5,000 vehicles or more, the Unity profiler shows that it is my script code that is the bottleneck. Here's what some of the "turn left" code looked like:
I want my vehicles to make a smoothly curving left turn and I need to compute the X,Y position on the curve and also the correct angle to point during the curve. This worked great but the Mathf.Sin and Mathf.Cos calls add up quickly at scale.
The quick optimization is to precompute the answers and use a lookup table. I realized that I was calculating the exact same X,Y values for every vehicle making a left turn and there was no reason to do that every frame for every vehicle. I precomputed the X,Y coordinates and the rotation angle for 100 steps along the curve. A quick check of the max zoom in level and the number of pixels on the screen showed that 100 steps is more than enough to make the turn look smooth.
The updated code looks like this:
To figure out which of the 100 steps to look up, I keep track of the total distance of the turn and the total distance moved so far. That gives me a normalized 0.00 to 1.00 range for how far along the curved the vehicle has traveled. Multiply by 100 and you have the index 0 to 100 into the lookup table.
The lookup tables have more than doubled the max vehicle capacity of the engine. The extra compute time is going to come in handy for all of the rest of the features I'd like to include. Now the bottleneck is the GPU when I have too many game objects on screen at the same time.