I'm working on a VR car racing game for Oculus Quest
Sun 26 July 2020Tagged: software, games
I've been playing a bit of Mini Motor Racing X on the Oculus Quest recently. It's good. The experience of driving a car in virtual reality is amazing, but this particular game is too much "game" and not enough "simulator" for my taste. I've always preferred Gran Turismo to Mario Kart, for example. Unfortunately, the Quest is quite an unusual platform, and none of the popular racing games are available for it. I recently discovered that the open source game engine Godot has Oculus Quest support, so I'd like to make a basic car racing simulator for Oculus Quest.
Here's a video of what I have at the moment, which is basically just Bastiaan Olij's Godot Vehicle Demo but with Quest integration:
My issues with Mini Motor Racing X are that the tracks are too small, the cars are too slow, and the cars handle unreasonably well. This means you can drive everywhere with the throttle wide open and you never have to brake, so apart from the "boost" powerup that spawns at random, it just becomes a steering game. And the boost powerup means you don't get a fair attempt at a lap record each time, because good boosts are critical for good lap times, and you might get boosts spawning in unlucky places. If you want to see what Mini Motor Racing X looks like, I have this video. I want to play something closer to Gran Turismo, but in VR.
Now, obviously, I can't make anything close to Gran Turismo. That would be too much work. But I can probably make something that is a bit closer to Gran Turismo than Mini Motor Racing X is, so that's my goal.
My favourite way of playing Gran Turismo was the "Time Trial" mode, where you're the only car on the track, and you're trying to set the fastest lap time, racing against a "ghost" of your current best time. You don't have to worry about AI cars being too fast or too slow, and you get a good clean lap with no traffic every lap. From an implementation perspective, the Time Trial mode is good because the game doesn't have to draw more than 1 car, doesn't have to simulate more than 1 car, doesn't need an AI driver implementation, and doesn't need to worry about collision detection between cars. This saves a lot of work. It's also a good way to turn a single player game into a multiplayer game: just pass the controller around and give everyone N laps to set their best time.
If you throw away everything that could possibly be thrown away, a car racing game becomes remarkably simple. All you're left with is a circuit and a car. Then you just take control inputs from the user, simulate physics, and draw the results. How hard can it be?
I found it useful to post about the keyboard project while working on it, both because the act of putting thoughts into words forces one to discover what they actually are, and also because of the helpful feedback I got from other people who read about it, so I expect I will do the same with this project. Unlike the keyboard project, a racing game doesn't have an obvious defined "finished" condition, which means it is liable to end up "unfinished". But I also don't know if anyone other than me will ever want to play it, so maybe that's fine.
The Godot Vehicle Demo is an excellent starting point for this project. So far all I have done is added Oculus Quest integration, made the track larger, and tried to muddle with the physics settings to make it drive a bit more realistically. I haven't got very far, but I've got far enough to convince myself that I can make this work as long as I actually try, and don't get bored of it.
Here is a list of things I want to do to turn the existing Godot Vehicle Demo into the racing game I want to play:
Car physics
Currently the car is simulated using Godot's VehicleBody, which uses the "raycast vehicle" method from the Bullet physics engine. This represents the car as a single rigid body, instead of having a separate body for each wheel. From the top of each wheel arch, a ray is cast directly down to find the point that the wheel touches the ground. I looked at the code but don't fully understand it.
(Screen capture from Ivan Novozhilov's UE4 RayCast Vehicle Tutorial | Suspension video).
The raycast vehicle doesn't actually have any wheels, which means all interactions between the wheels and the ground need to be faked. Also the wheels fall through the ground, rather than pushing the chassis up, if the maximum suspension force is exceeded. "Raycast vehicle" is a quick and dirty method that works well enough for a lot of cases, but I'm not sure I consider it satisfactory for my game. I don't see why we can't create a rigid body for the car, a rigid body for each wheel, and add joint constraints to join them together. Then I think the only "hack" we'd need is to manipulate the tyre friction based on Pacejka's Magic Formula, and the rest of it would work for free. Maybe I just don't know enough about game physics to know why this wouldn't work? Failing that, we're probably left with adding more bodges on top of the raycast vehicle.
Currently the tyre friction is set in some arbitrary units that I don't understand. The field is called "friction slip" rather than something like "coefficient of friction", so I'm not even completely sure what it's trying to model. From the code it appears to be used as a simple coefficient of friction, but values around 1.0 (which would be quite a grippy tyre) result in a very slippery driving experience. The documentation suggests using values between 0 and 1, but the default value is 10.5, so I don't get it.
The Godot Vehicle Demo does not include any model of air resistance. I had a first attempt at this by applying a braking force equal to what the air resistance should be, but this is not realistic because braking forces are applied at the contact patch of the tyres, and air resistance is applied at the centre of drag. Braking forces result in weight transfer towards the front wheels. With air resistance the weight transfer is reduced or even reversed depending on where the centre of drag is relative to the centre of mass. I.e., if the centre of drag is above the centre of mass, then air resistance would transfer weight onto the rear wheels instead.
Currently the engine has a fixed "force" value, which is then modulated by the throttle position. 100% throttle = 100% force. This is basically an engine that has a flat torque curve all the way to infinite RPM. The only reason it can't reach infinite speed is because of the air resistance I applied to the brakes. I want to improve this by modelling the engine RPM, and copying the torque curve from a real engine. I think the way to do this, in each timestep, is:
- compute current engine RPM as a function of the average of the rear wheels' RPM (this basically assumes an open diff, which is another variable we might want to control later on), by dividing by the current gear ratio
- look up torque available at this RPM in torque curve
- modulate available torque by throttle position
- multiply this torque value by the gear ratio implied by the diff and selected gear
- apply the resulting torque to the rear wheels
(Internal combustion engine torque curve, from Car Throttle's article on electric vehicle torque).
I'm not sure how you apply the resulting torque to the rear wheels through the diff. Maybe you just apply equal torque to both wheels? Maybe you split it according to the current difference in wheel RPM? Both of these seem wrong. Need to try and find this out.
Also we probably want to say that if the computed engine RPM is too low (say, below 1000 RPM), then the clutch is disengaged and we just apply a tickover RPM instead, otherwise the car would have basically no torque from a standstill.
The Godot Vehicle Demo initially had the car's centre of mass literally level with the ground. This is a good workaround for the raycast vehicle's tendency to roll over as soon as it loses grip, but is not very realistic. As just one example, there is no weight transfer under braking, because the centre of mass lies in the same plane as the braking forces. I raised the centre of mass to something I thought looked more believable but the car kept rolling over. I've now got it set about level with the very bottom of the floor of the car, which is 10cm off the ground at full suspension compression.
The car is extremely difficult to control, and I'm not quite sure why. The rear end is extremely twitchy, especially when there is a bump or a crest in the road and on corner entry. You can see in the video that I am having quite a hard time keeping the car from spinning. I expect this twitchiness is a result of the raycast vehicle's fakery of basically everything about the interaction between the wheels and the ground. Hopefully it goes away if I manage to create the rigid body model.
The car was initially quite twitchy even while driving in a straight line at modest speeds, but I solved this by adding 1° of toe out to the front wheels. That this helped is actually quite a good endorsement of the raycast vehicle model, because almost all real cars run with a slight toe-out, for the same reason: imagine driving along with both front wheels pointing dead straight. If there is now any perturbation in the steering angle (for sake of argument: you steer 1° left), then the car starts to turn left, which shifts weight onto the right-hand front wheel. Since there is no toe-out, the right-hand front wheel is now pointing slightly left, so shifting weight onto it causes more turning: this is instability. Now consider what would happen if there was a slight toe-out: when the weight shifts on to the right-hand front wheel, it is now pointing either straight or slightly right, which counteracts the weight transfer and keeps the car going in a straight line. (Update 2020-08-18: My Dad informs me that most road cars are actually set up with toe in, so this paragraph might just be totally bogus).
The car currently has no Ackermann on the steering geometry. This means the inside wheel and the outside wheel both steer by the same angle, instead of the inside wheel turning tighter.
(From Wikipedia).
The suspension currently moves each wheel in a perfect straight line up and down. In real life this is quite hard to achieve. I think front forks on a motorbike are the only practical solution. Anything involving pivot points, wishbones, swinging arms, etc. will move in a curved path rather than a straight line. But I think that I don't mind the "perfect" suspension model. It just makes the car easier to set up because you don't have to account for effects like bump steer, or the camber angle changing as the suspension position changes.
(From Wikipedia).
Helpful resources for simulating car physics include Brian Beckman's Physics of Racing Series, and Marco Monster's Car Physics for Games.
Circuit
The circuit is laid out within Godot by defining a Bezier curve and then sweeping a track cross section along it. This is a really good way to quickly experiment with different track layouts, but it is slightly inflexible because it does not allow for variation of track width or texture, and it does not allow us to produce cambered corners. The track is always perfectly parallel to the ground across the width of the road at all places. I probably want to make the track in Blender, based on a similar system of extruding a cross section through a path, but then manually touch up the result to make a more interesting circuit, and import the resulting graphics mesh, and a simplified collision mesh, into Godot.
Currently the grass is just as grippy as the tarmac. The grass should be a separate collision mesh with a separate friction value. For that matter, I don't actually know where the friction of the road surface is configured. I couldn't find a setting for it anywhere, which might go some way to explaining why the "friction slip" values for the tyres have to be so high.
I want to make a better track layout. I don't yet know if I'm going to copy the layout of a real track using aerial photographs, or if I'm going to invent one. My favourite track in Gran Turismo was Deep Forest, which is an invented one, so maybe that's the way to go. Maybe I could just copy my favourite sections from real-life roads and race tracks and string them together into the ultimate race circuit.
The circuit could also do with having some kerbs, rumble strips, maybe gravel traps. Just some variation really to make it more interesting.
Environment
Currently the environment outside the circuit consists of some grass edges of the track, a grass-textured flat plane, and a skybox. This is a pretty barren world to go racing in. Originally the track in the Vehicle Demo had some procedurally-generated barriers, but they used up too much GPU time so I took them out. I think I want to add some simpler barriers and decorate them with textures instead of geometry.
I want to add some objects at the sides of the track to use as braking markers as it is currently very difficult to reliably brake in the right place each lap because everywhere looks exactly the same. This will probably be a combination of explicit distance markers on corner entry, and other random items of interest scattered about (maybe trees, bushes, rocks, bridges, little buildings?).
It would be cool to add some joke adverts on the walls or on billboards. If you come up with a fake company that you want included, send me the artwork and I'll try and use it as long as it's not clearly bad or offensive in some way, and not a real company.
Currently the track's collision mesh has tall walls on it that prevent you from driving off the edge of the road. I am not a fan of invisible game walls. I think I'd prefer to let the car drive anywhere where there isn't an actual wall, including falling off cliffs and such.
Sound
The game is currently totally silent. The single most important sound in any racing game is obviously the engine noise. Once I have the engine modelled for the purpose of the physics, I will be able to use the calculated RPM to inform the engine noise. I think engine noise will want to be composed of a small number of separate noises mixed together, maybe something like:
- basic engine sound, increasing in frequency and amplitude with engine RPM
- induction noise, increasing in frequency and amplitude with engine RPM, and in amplitude with throttle position
- various exhaust note sounds, increasing in frequency with engine RPM, and in amplitude with how close the engine RPM is to the "resonant" frequency of that particular exhaust note
- exhaust crackle, played whenever the throttle position is rapidly reduced at high RPM?
We'd also want to add some road noise, which is played from the position of each wheel, with frequency and amplitude in proportion to wheel speed, and amplitude also modulated by the weight on that corner. Wind noise can be a simple whooshing sound that increases in amplitude with road speed. And whenever there is a large change in suspension load, we could make a bang or rattling noise come from that corner of the car.
Input
Currently the throttle comes from the right-hand trigger, brake comes from left-hand trigger, and steering angle comes from the angle between the left and right controller. In that sense, the controllers act like a steering wheel. In Mini Motor Racing X you need to use the "grab" buttons to grab your controllers onto the in-game steering wheel, but once they're grabbed on you can wave them around wherever you want and the steering angle still comes from the angle between the controllers.
Making the player hold down the grab button sucks, because your middle fingers get tired pointlessly: there is no scenario in a racing game where I don't want to be holding the steering wheel, why make me do it explicitly? So we'll just say the player is always holding the wheel. Having the hands permanently "attached to the wheel" also frees up the grab button for other purposes. I think I want to use the middle fingers to do flappy-paddle-style gear changes.
One problem with using the VR controllers as a steering wheel is that they're not actually attached to each other, or to anything else. I have some vague plans to build a little desk-mounted steering wheel that the controllers would attach to, so that way they rotate around a fixed centre and don't move relative to each other. This is a bit like the VR "gun stocks" you can get for 2-handed guns, but for a steering wheel instead of a gun.
(From drHEEB's Oculus Quest and RiftS Gun Stock Magnetic Remix on Thingiverse).
It would be cool to be able to use pedals instead of triggers for throttle and brake. I don't know how hard it is to connect a bluetooth game controller to the Oculus Quest, but if it is easy then it might be worth looking at. I expect bluetooth racing pedals are a product you can just buy, but if not then I don't expect it would be a very complicated thing to make.
Game stuff
The reversing logic is a bit glitchy. If you squeeze the brake, the car slows to a stop. If you then release and reapply the brake, it starts reversing, as intended. If you then release the brake and apply the throttle, it sometimes accelerates you backwards instead of forwards! I haven't looked very hard at the logic, because I expect the problem will disappear once I switch to the engine-and-gearbox model with reverse as a selectable gear.
I currently have no way to write any text to the user. The Vehicle Demo project draws the car speed at top-left of the window, but this doesn't work in VR because there is no window. I tried adding a "Label" object positioned 50cm in front of the camera, but it didn't show up in VR. I don't know why. It would be helpful to be able to write text for debugging purposes.
The dashboard has several dials on it, but they currently do nothing. I think I probably want to ignore/remove the existing dials and make a "VR-optimised" dashboard for the car that indicates road speed, RPM, and selected gear. Maybe with some of those fancy LEDs that indicate when you're approaching the redline.
The shadows are really low resolution. I played with some of the shadow settings but couldn't work out what was causing it.
(This pic is from the Godot editor on my PC - the shadows are even worse inside the actual game).
Some of the textures inside the Godot editor have normal maps which mean they appear physically textured instead of just flat images, but inside the Quest the normal maps don't work. I expect this is a setting somewhere that improves performance on the Quest, but normal maps look great so I want to try and work out how to enable them, just to see if they are going to be viable.
Even though I am keen to play the game in VR for the immersion, I want to be able to play it on the PC for testing purposes. It takes about 45 seconds to compile and upload the game to the Quest, which makes for a frustrating edit-compile-test cycle.
The game doesn't yet have a way to time your laps. In principle this is as easy as counting the elapsed time between when you last crossed the finish line and when you next cross the finish line, but there are some more subtleties to it, for example if you cross the finish line, turn around, and then cross it again in the opposite direction, that shouldn't count as a lap. Probably the answer is to add some invisible checkpoints around the lap that must be crossed in the correct sequence in order to count as a valid lap.
Once we can time laps, we should also record the car's position at each point during the lap so that it can be saved and replayed as a "ghost" car.
I don't currently have any way to provide menus inside the game. You're driving the car, and that's it. It would be handy to add some VR menu system so that you can change the setup of the car without having to recompile the game, and it would also be useful in letting you save/load ghost times.
The road viewing angle is very low, which means you don't get a very good view of the track ahead, and the texture also looks awful. Not sure what to do about this. I expect adding barriers and scenery would make it easier to see where the track goes, maybe that's enough.
The game does not give a good sensation of speed. I expect this would be helped by improved scenery and also sound. I don't know if there's anything else we could do. Maybe make the driver's head bob around a bit at speed? Or maybe in response to acceleration?
And obviously once everything works properly, the game would benefit from having more than 1 car and more than 1 track! For now I'm happy with just one. If you want to contribute cars or tracks, please get in touch.
If you like my blog, please consider subscribing to the RSS feed or the mailing list: