The game finally has a driveable vehicle! The biggest challenge in this latest update was solving the tricky problem of getting the car to stick to the track and have correct rotations relative to the road surface while moving and steering. Another small but important addition is the guardrails which help to improve the sense of speed as they fly by.
I’ll be releasing a playable OpenGL build soon, there are a couple more features I want to add, including some options on the main menu to let you adjust parameters of the track generator. For now, here’s a video of what the game looks like at this point.
Making an arcade racer is difficult because physics engines aren’t meant for blatantly physics-defying and unrealistic things involving hovercars. You either have to heavily tweak the physics engine to try and make it behave the way you want, or build your own fake physics from scratch. I chose the latter approach because I like having full control over how things work (or don’t work), as opposed to tinkering with a complex pre-built system that I don’t know the inner workings of.
Solving this problem involved learning a lot about quaternions which I previously regarded as mysterious and somewhat intimidating. This is going to be a longer post than usual because I want to take this opportunity to write a bit about one of my favorite subjects: the process of learning in general.
Knowing vs Understanding
I believe there is an important distinction between knowing and understanding. Knowing is a matter of committing information to memory. Knowing requires some regular maintenance, refreshing your memory to make sure you still know what you thought you knew. Absorbing and retaining information accounts for most of the work involved in learning whether independently or at school.
Understanding is more elusive and hard to pin down, but you know it when you feel it. It’s what we’re talking about when we say that something suddenly clicked for us. It’s a natural intuition about not only how but why something works. Understanding is the ultimate goal of learning and is difficult to achieve not because it necessarily takes more effort, or requires you to be “smarter” but because it works in mysterious ways. There are many different kinds of thinkers in the world, and it’s perhaps impossible to know exactly what will trigger that click in a particular person’s mind.
To be clear, I’m not suggesting that spending time memorizing information is a bad thing. It is a valuable and worthwhile part of the process of learning but the end goal should always be to understand. It’s also important to recognize that understanding is not the guaranteed by-product of more knowledge. Sometimes you need to step back from what you think you know and approach a subject in a new way that might seem overly simple or even completely wrong.
The Trouble With Quaternions
Which brings me to quaternions. I have known about them for a long time. I’ve done a lot of work with 3D software, and took a special interest in rigging. I became very familiar with the headache-inducing problem known as gimbal lock. I understand why it happens and I know that quaternions fix it. But I don’t have much of a concept of how or why they work.
Thinking about rotations in terms of degrees around the x, y, and z axes is easy to visualize. When we use the rotation tool in any sort of 3D software it typically shows a gimbal with three rings surrounding an object. You can imagine how those rings would function if they were a physical object, like a gyroscope or that spinning contraption they use for astronaut training (it’s apparently called an aerotrim.)
The difficulty with quaternions in Unity is that there is a layer of abstraction involved when working with them. What I mean by this is that a quaternion stores information in the form of a complex number. This information isn’t human-readable to anybody without a truly brilliant understanding of abstract math, so we don’t manipulate it directly. Instead we have various ways of “encoding” rotations as quaternions, such as the
This can lead to situations where you’re using
Euler to define rotations in one part of your code, using
AngleAxis to create a quaternion somewhere else and then multiplying that by some objects
transform.rotation, multiplying quaternions by other quaternions, quaternions to vectors, using vectors to make new quaternions with
LookRotation, etc etc. It’s confusing because it feels like working with a lot of separate tools instead of one coherent system.
While trying to make the car in my game move properly I had a click moment that helped me to finally understand how to combine all of those different tools without getting lost. I’m a visual thinker, and the visualization that worked for me looks like this:
Most of the time when I’m dealing with rotations in a game it’s because I want an object to point in a certain direction. Since the position and movement of an object are represented by vectors it is tempting to also use a vector to tell an object which direction to point. Imagine you’re holding the object with an outstretched arm. You may be able to point the object in any direction, but you are also able to change its orientation by rotating your wrist.
So in order to represent all possible rotations you actually need two vectors: a forward and an up. I like to picture them as being connected and looking something like the object pictured above. This is not what a quaternion looks like, they are not just vectors attached to other vectors. However, it is a great way to see how the different methods for working with quaternions are conceptually similar, and finding similarities between things that appear separate and different is a major step toward understanding.
Imagine pointing that big green arrow in a certain direction, and then spinning it on its long axis to decide where the small arrow should point. That’s basically the
LookRotation method1. It takes a forward vector (big arrow) and an up vector (small arrow) as arguments. Now imagine doing the opposite, you point the small arrow in some direction and then, holding the small arrow in place, you spin the big one around like a weather vane. That’s
AngleAxis. It takes an up vector, and how many degrees you’ve spun around it.
Once I started thinking about rotations this way I started to see useful connections between rotations and vectors. At any point on the road in my game I have a method that returns the surface normal. Projecting the local forward vector of the car on to the surface defined by that normal gives me the thrust vector of the car relative to the road surface. Using
LookRotation in the direction of thrust with the track normal as the up vector gives me the quaternion for which direction the car should be facing. Multiply that by a second quaternion created with
AngleAxis that represents the steering angle in degrees per frame and the car turns properly.
The result: F-Zero style arcade physics in which the car sticks to the road and moves relative to its surface normal. Problem solved!
The Power of Simplicity
If this seems overly simple it’s because it is, and that’s not necessarily a bad thing. A major obstacle to understanding a large and complicated concept is assuming that we don’t have enough knowledge yet, or maybe that we’re not “smart enough” to get it. More often than not the opposite is true and we are actually over thinking it. The largest and most complicated ideas are connected to simple and familiar concepts on low levels that aren’t immediately obvious.
Discovering these connections is tremendously rewarding and is the number one thing that motivates me to keep learning and creating things.
- This isn’t a perfect visualization for LookRotation since the forward and up arguments wont always be perpendicular [return]