Game Pivot and Trajectory Interception Deep Dive
Hey all! In this dev log I'll talk through the design and technical challenges associated with trajectory prediction and AI in an ambitious project like Retrograde: Legends and how an ultimately design driven core loop pivot has enabled me to leverage months of prior learning to build a better technical approach to orbital mechanics AI behavior in a fraction of the time.
First, some background: I'm refactoring much of Retrograde's core loop to focus more on what's really fun - orbiting planets and blowing stuff up in microgravity. As part of that I've decided to move away from the economic and open-world simulation aspects of the game that seemed to distract from the core experience.
The game's new direction is a darkly humorous, orbital mechanics roguelike in which you are stellar warlord bent on conquering the galaxy one planet at a time. Engage in Newtonian physics powered pitched battles in orbit around planets, moons and stars. Use real-world inspired propulsion systems, weapons and sensor mechanics to destroy all resistance. Collect materials to add components to your ship and maintain your home system's Dyson sphere on your quest for galactic conquest.
The Challenges Associated with Trajectory Prediction and NPC AI
The previous iteration of the game yielded a depth of knowledge about the design and technical puzzles involved in building such ambitious gameplay and AI systems tied to orbital mechanics and the vast distances involved in a believable hard sci-fi space sim, originally set in our solar system.
The AI in particular became a serious concern from a technical perspective due to performance problems and from a design perspective due to the difficulty of getting NPC ships to traverse and fight effectively and believably. First: the calculations required to complete necessarily long and detailed trajectories and collision predictions for all AI ships coupled with the AI's reliance on on-demand calculation of hypothetical trajectories to determine optimal maneuvers created huge performance bottlenecks.
Second: the major shifts in navigational scale that enabled the AI to tread the needle between other ships or dock at a station while also being able to plot accurate courses between planets within a solar system were very difficult to maintain and required a lot of "magic numbers" that led to bad AI behavior when anything from the scale of the map to the mass of reactors changed.
Third: because the first iteration of Retrograde: Legends was not just an orbital mechanics and combat sim but also a living-world and economic sim, NPC ships had to be managed on two separate logic layers, meaning the ships had to be able to manage navigation within the physics simulation using their trajectories and propulsion systems while also being able to despawn themselves at any moment and continue functioning through a lower resolution "strategic layer". This transference of data between two separate simulation layers in order to create seamless and consistent encounters with NPCs across huge areas greatly increased the technical complexity of both layers and the intermediary logic that managed transferring NPCs from strategic to physics simulation and vice versa.
Approach to NPC AI in the Pivot
After months of work and testing, I decided to pivot the game to be smaller scale and more focused on the most fun interactions I'd discovered in the earlier prototype. There are many reasons this is the correct way to serve the vision of an fun, accessible, hard sci-fi, orbital mechanics game but I'd be lying if I said that getting a chance to start fresh on the AI without all of the economic and strategic sim logic wasn't a major draw to a smaller version of the game.
In the new iteration of Retrograde: Legends, the player fights through a myriad of procedurally generated star systems one planet at a time. Because the play area is now constrained to a single planetary system at any time, the performance and trajectory resolution concerns that plagued the previous iteration of AI are much less of a concern now.
This has allowed me far more headroom to create finer resolution trajectory predictions giving NPC ships more detailed information about their environment and potential collisions and intercepts and also freed up production time to be spent on overall better AI behavior rather than wrestling with the basic technical design of the prediction systems.
How Does Trajectory Prediction Work?
There are several methods that can be used to predict the trajectory of objects influenced by one or more gravitational fields. The method I used for the first iteration of AI and trajectory prediction is basic numerical integration. The method I've chosen for the second iteration is a slight variation on that technique but using a dynamic timestep.
Here's how it works. Every object in the game has a position, velocity and acceleration at any given time. Position is self explanatory. Velocity describes how fast an object is moving and in what direction. Acceleration describes how quickly an object's velocity is changing and in what direction.
Using these variables, it's possible to describe the path an object will travel over some future period of time, given we know that object's current velocity.
You must decide on how many points you want your object's trajectory to have then determine the timestep between each point. More on the time step later. The first calculation we need to do is to determine the object's current acceleration. This is influenced by whether the object is under thrust and the direction and magnitude of any nearby celestial bodies. Acceleration here describes how much our velocity changes due to external forces acting on this object over the next time step.
Acceleration = Gravitional Acceleration + Thrust Acceleration
The next step is applying our new acceleration to our velocity. We have to multiply the acceleration of this time step by our delta time. This describes how much time should have passed between this step in our trajectory and the last.
Velocity = Acceleration * Delta Time
Velocity is just change in position over time so in order to determine the position of our object at the given point in time, we add velocity to position.
Position = Position + Velocity
The formulas themselves are very simple and more importantly, easy to maintain and read. I've found the complexity comes mainly through the implementation of the algorithms to be performant and the set up of the backing data to be accessible and organized in such a way that allows AI systems to answer the questions they need without needing to redo work each on demand while also providing accurate enough predictions to be able to moderate the rate at which the trajectory prediction needs to be recalculated.
The new position, acceleration and velocity are stored in an array and the new position is compared against every other point in every other trajectory being calculated. This full list of trajectories is pruned to ensure that only objects that really need trajectories calculated are being worked on such as all ships, missiles and any object that an NPC ship might be tracking.
Trajectory collision detection is the most expensive part of the entire trajectory prediction and AI system because of the number of times the algorithm is required to iterate. If there are 20 ships each with a trajectory 100 points long, each ship must check its 100 points against the 100 points of nineteen other ships. This piles up to a big performance pileup very quickly.
To alleviate this strain, trajectories are split into groups which are calculated in batches spread across multiple frames to keep the per-frame performance impact of increased trajectory loads at the expense of each trajectory's refresh rate which is acceptable for the level of detailed needed for a cartoonishly scaled game like this.
A knock on effect of a smaller play area in this iteration of the game is that I'm able to tweak how the time step in the trajectory prediction works. Normally in solving a problem like this you want to keep the time step of each trajectory point constant to ensure accuracy. However I wanted to be able to build trajectories that maintained a set distance per step to allow for longer trajectories at more consistent performance cost to avoid some big pitfalls and inefficiencies at longer trajectory lengths observed in the earlier method.
The new method uses a dynamic time step estimated from the calculated time step of the previous trajectory point to scale acceleration and velocity. The pseudocode goes as follows:
Acc = Thrust Acc + GravAcc
Vel = Vel + Acc * Last Delta Time
Pos = Pos + Vel * Last Delta Time
Delta Time = Point Distance / Velocity.Magnitude
Last Delta Time = Delta Time
Of course removing the fixed time step in favor of a fixed step distance introduces inaccuracy into the trajectory prediction immediately but I've found that the newly reduced playspace size renders the short term inaccuracies with this method a non-issue especially when coupled with direct velocity tracking intercept methods at short-distances. The performance and overall long term accuracy improvements make up for it in other areas as well.
AI Logic and Trajectory Interception
If you're still with me, thanks for reading! You're probably wondering when I'm going to get to the point. Here it is!
Comments
Post a Comment