Sidescrolling Flight Simulator
My pace of writing has slowed, because I put the little spare time I have into something else right now: I have been slowly working on code for a sidescrolling 2D flight simulator, just to see if it can be done properly.
Figure 1: Current state of progress. See full resolution.
Background
But why? Here’s what happened: I tried a sidescrolling airplane flying game (branded as a “realistic” “simulator”) on my phone out of curiosity. I was quickly put off by how unrealistic it is. This is a funny complaint to make about a 2D airplane game, but my issue isn’t graphics, fuel planning, detailed modeling of on-board systems, or anything like that. Those things are simple, but in a good way.
My beef is that this game gets fundamental flight dynamics wrong. The most obvious is that a headwind slows down your airspeed and forces you to throttle up to achieve more lift. This is completely opposite to how it should be. I could go on with other fundamental problems, but most of them boil down to this: the flight model is more that of a rocket than an airplane. I think it’s sad, because the idea holds promise.
I asked myself, How hard can it be?
Problems
Turns out it’s a little harder than I envisioned. Here are some discoveries I’ve made along the way. Maybe it’s instructive for someone.
Dynamic Modeling
On the first iteration of the flight model, I referenced performance charts for airplanes and translated the curves directly into code. This means if a particular airplane configuration eventually results in a particular stable outcome, then my flight model assumed this steady state right away.
Figure 2: Example of a performance chart. See full resolution.
It worked, but it was boring. It also lacks at least one key aspect of flight: exchanging kinetic energy and potential energy. This is done e.g. as part of stall recovery, or when flaring for landing.
While the boring model still seems to me more accurate than that of the phone game, it wasn’t what I had in mind when I thought I could do better. The second and current iteration is a basic Newtonian four-force model1 Thrust, drag, lift, and gravity somewhat opposing each other. that pretends the plane is a small point2 In particular, drag, lift, thrust, and gravity are all applied without any torsional effect.. It is incredibly satisfying seeing how that simple model actually produces results that look similar (to my amateur eye) to the performance curves in real operating handbooks!3 In a few cases, even implied physical measurements (of things like wing area) match their real-world counterparts. I did not expect that. Although I suppose that could also be a coincidence. There are only so many integers that a small plane can have as its wing area, after all. This model is much more dynamic and produces more interesting handling characteristics, but it’s also harder to tweak so that the forces are somewhat balanced and the plane is stable under a wide range of configurations.
Trial and Adjustment
I started trying to tweak the model by drawing up the performance curves I wanted to match, and then computing backwards to see what the basic physical parameters would have to be to produce those curves. This took a lot of time.
I found out that if I set the timescale of the simulator to fast-forward about 10×, I can actually just configure the plane, wait a few seconds for oscillations to end, and then record the performance measurement of interest to construct performance curves for the flight model. In other words, while it was cumbersome for me to go from performance curves to parameters, I had a really quick way of going from parameters to performance curves! That hints at a different approach to get the desired flight characteristics. If the performance curves of the flight model don’t look right, it takes just a few seconds to tweak a value, see how the plane behaves, and then either tweak some more or adopt the new value.4 A friend even suggested (as a joke!) writing a script to binary search the parameter space – that sounds like a good idea, but the overall process was over so fast that I didn’t need that.
One Thing At A Time
On the topic of tweaking, the age-old advice of tweaking just one thing at a time was very helpful. I always think I can go faster and save time by working on multiple things at once, but it almost always backfires.
In this case in particular, all four forces of the model interact to produce a result that doesn’t obviously depend linearly on only one of them. Being able to discard e.g. gravity, thrust, and drag to start out with, and focus purely on lift, was of great help to avoid confusion. Then lift can be turned off, and focus can be moved to drag, etc.
Vectors Are To Be Drawn, Not Written
At first I printed out the values of 2D vectors as numbers. It took an embarassingly long time for me to realise the mistake and instead draw them as arrows. Within minutes I discovered multiple problems with the model that had eluded me for hours.
Epsilon Instability
A recurring problem is epsilon instability with floating-point numbers. Since some parts of the model have the effect of gradually making differences between numbers smaller, eventually the difference will be very small and then5 At least this is what I suspect happens. on divisions the floating point numbers can run out of range and turn into NaN.
These problems have proven annoying to debug, too. Since I don’t need \(10^{-13}\) level precision for a 2D flight simulator, I’ve solved some of these issues by truncating numbers to 5-ish decimals. It’s ham-handed, but works really well.
Subproblems; Viewports And Terrain
Where I’m currently stuck – in the sense that I expect to have to spend some time here before I move on to other things, is terrain rendering. The first iteration simply drew a black polygon6 You gotta start somewhere, okay?, but not in a principled way at all, so every time I change the location of something on screen (or the level of magnification) there’s a lot of fixing up that needs to be done around terrain rendering.
Besides, I’m not looking forward doing collision detection with arbitrary polygons, nor texturing them sensibly.
Figure 3: Current state of tile rendering, without pretty tiles. See full resolution. Frame indicates viewport, tiles are rendered just outside of it to prevent them from popping into existence.
My current thinking is to go with a grid-based terrain instead, and texture it with tiles. This should simplify a lot, but doing it efficiently (especially at low magnification levels) and in a principled way is still enough of a challenge that I’m going to solve it separately as a subproblem and then integrate that code with the code I have now, rather than trying to combine them from the start.7 This goes back to “tweak one thing at a time.” Any time there’s a constrained sub-problem, in my experience, it’s worth trying to solve it independently first. Hopefully this can also help me explore alternatives for terrain generation.
Future Prospects
The code I have is still rough enough that I’m not ready to share it publically. I want to randomly generate terrain and external conditions, as well as plane system failures, to get some level of challenge and replayability into it. I want a sort of arcadey feeling, but at the same time I want simulator skills to transfer at least a little bit.
There’s about a 5 % chance that I get to a point where I’m ready to share it. But now you know what I’m up to.