My First Foray Into 3D Vehicles

Up to this point, all of my previous game development experience point has been in 2D. I have become more familiar with Godot's node system and its GDScript language, and now feel confident about making a project in 3D for the first time. In it I'll attempt to make an armored vehicle combat simulation game.

This project was built with Godot 3.5 and Blender 2.8.

Version 1

The first nodes I explored were the Camera, RigidBody, and StaticBody nodes. These were used to get a feel for the physics/gravity/camera control in the 3D environment. My primary goal was learning how to manipulate the world, control and affect the physics bodies, and manipulate the 3D rendering. Next the VehicleBody and VehicleWheel nodes were explored while experimenting with movement and physics.

At this point the vehicle and the environment were both built out of primate physics elements within the Godot editor. Specifically they were made with MeshInstance nodes using standard CubeMesh/PrismMesh meshes. StaticBodies were used for the environment elements, and a RigidBody for the vehicle.

This vehicle was designed to simulate a tank, with multiple rows of wheels and a rotating turret on top. And it was given an environment with ramps and blocks to explore its handling.

In an effort to further explore a tank-like mechanic, I attempted simulating tank tracks with the wheels. Instead of steering via turning the wheels, a system was implemented where each side of the vehicle could independently control the forward/backward direction of the wheels, without turning them at all. This system worked okay, but it didn't feel particularly good and I ultimately changed this approach to something more conventional in the next version.

Another idea that was abandoned after this version: I used a control scheme here that ended up being too weird to keep. I thought it might be fun to use Q / A to control the left wheels forward/backward, and use E / D to control the right wheels forward/backward, simulating left and right control levers. Despite being harder to use than standard WASD controls, it was actually easier to implement. This QAED input technically worked, but was so unconventional and distracting that I changed it to a typical WASD control scheme in the next version.

This early version also featured a turret that rotated and elevated with the arrow keys. The third-person camera is parented under the turret (via node2d nodes that allow panning and tilting the camera.) This means that rotating the turret rotates the camera with it.

This vehicle had a lot of difficulty climbing up slopes, and always slid back down. This was due to my inexperience with the vehicle and wheel properties at this point. It was time to focus on improving the vehicle's performance and controls to make the handling and game-play more fun.

Version 2

In this version I reduced the number of wheels from 8 to 4 to more clearly focus on how each one supports the vehicle and how property changes affect the wheel behavior. Also the control scheme was changed to use normal steering and standard WASD controls instead of the experimental Q/A E/D controls from the previous version.

The vehicle in this version was again built out of primitive mesh shapes, constructed in the editor.

The parameters of the VehicleWheels and the VehicleBody were tweaked to feel more realistic and climb slopes. The tweaks were mostly very successful. However the vehicle does seem to slide down slopes ever so slightly in a way that I wasn't able to fully eliminate (and actually couldn't fix in any of the later versions), and I suspect might be an "oddity" (read, "bug") in the built-in VehicleWheel/VehicleBody implementations.

Version 3

This version brings back the ability to rotate the turret and raise the barrel. It also adds the ability to shoot a physics-based projectile while using a first-person camera mode in the turret. The vehicle also gets some dynamic lighting.

Headlights and taillights were added, as well as brake lights that illuminate when the player is holding down the brake key.

The alternate camera view (activated with V) shows a gun-view from the vehicle's turret. And left-clicking fires a single projectile. There's no cooldown yet, it shoots as fast as you click (this gets corrected in a later version.)


Version 4

This version polishes the work from the previous version by simplifying code, expanding the environment with more complex structures, and giving the player something to shoot at.

Adding a few buildings and props with approximately realistic proportions gave a better perception of the handling and speed of the vehicle and projectile.


Driving

By this version I had somewhat polished up the code for controlling the vehicle's input into something that felt easy to comprehend and work with.

Parameters:

export var horsepower = 350
export var max_rpm = 500
export var acceleration = 1.75
export var deceleration = 20.0
export var steering_speed = 1.4
export var max_steering_angle = 0.5
export var brake_while_driving = 0.0
export var brake_while_rolling = 0.0
export var brake_when_applied = 30.0
export var minimum_torque = 10
export var gear_ratio = 25

Processing:

func _physics_process(delta):
    _handle_steering(delta)
    _handle_throttle(delta)
    _handle_braking()

func _handle_steering(delta):
    steering_input = -max_steering_angle * Input.get_axis("left", "right")
    steering = lerp(steering, steering_input, steering_speed * delta)

func _handle_throttle(delta):
    engine_force = _new_engine_force(delta)
    _set_wheel_forces(engine_force / wheels.size())

func _new_engine_force(delta):
    throttle_input = Input.get_axis("back", "forward")

    var rate = delta * acceleration if abs(throttle_input) > 0.5 else delta * deceleration
    var rpm = abs(wheels[0].get_rpm())
    var new_engine_force = lerp(engine_force, throttle_input * horsepower * gear_ratio * (1 - rpm / max_rpm), rate)

    if abs(new_engine_force) < minimum_torque: new_engine_force = 0
    return new_engine_force

func _handle_braking():
    if Input.is_action_pressed("brake"):
        _set_brakes(brake_when_applied)
    elif abs(throttle_input) < 0.1:
        _set_brakes(brake_while_rolling)
    else:
        _set_brakes(brake_while_driving)


Something to Hit

A target was added that the player can attempt to shoot with the turret. The target is a bright glowing cube that explodes into stylized particles when hit with a projectile.

Note:

This was posted to the Godot subreddit War Thunder inspired light armored vehicle prototype thing (my first foray into 3d).

Final Version

This version focused on trying to make the vehicle demo look more like a real game. Specifically that meant moving away from primitive shapes and starting to use purpose-made 3D models in Blender. It means having a world to explore that also isn't simply created from primitive shapes in the editor. It also means polishing the UI, title screen, explosions, sound effects, visual effects, and code to the best of my current ability.

The vehicles, buildings, foliage, and props were all modeled in Blender and imported into this project

At first, props featured just some buildings and early vehicle models, on a big flat plane.

Later, by using zylann's heightmap plugin, the terrain was generated and then manually modified and textured from within the editor. And vehicle, building, nature props were all slowly improved in iterative ways.


Driving

This one goes back to an 8 wheel configuration that now spreads out the weight of the vehicle. After the work on the previous versions building my experience with VehicleWheels, I felt I could make the vehicle handle accurately and comfortably. And this time, it worked very well.

All 8 wheels provide driving and braking power, and the front 4 wheels steer (with the 2nd row steering half as much as the 1st row.)

Dust getting kicked up was added with some particle effects.

Shooting

The feeling of shooting was improved over the previous versions by improving effects and behavior. First the speed and size of the projectile was increased, and a glowing trail was added behind it, allowing it to shoot further and stay visible longer. Then a more prominent and realistic muzzle flash with smoke and flames was added to spawn at the barrel tip.

Shooting the weapon also now has a kickback recoiling effect on the vehicle that makes the projectile feel more substantial. And now when impacting something, the shell causes an explosion with sparks, flames, and a large plume of dust and debris.

Timers were finally added that set limits on how quickly shots can be fired.

The cube targets were replaced with red barrels around the map. The barrels still explode when hit, and they emit a tall tower of billowing smoke for several minutes.

The smoke is made with particles built with billboard-oriented QuadMesh meshes. This is the same technique as used for the dust, muzzle flashes, sparks, flames, etc...

Also the rear hatch opens!

Mouse Aiming with Zoom

In the previous versions aiming the turret was controlled with the arrow keys for coding simplicity. This version improves upon that and polishes the mechanic by allowing for mouse aiming when in the first-person gunner view.

Right clicking with the mouse reduces the FOV of the camera to simulate zooming in. It also reduces the sensitivity of the mouse input by 80% making it easier to aim precisely.

While mouse aiming works in first-person, I wasn't able to get satisfactory mouse aiming working in 3rd person view. That will be fodder for a future project.

Alternative Machine Gun Turret

For my own practice in creating clean abstractions, this version adds different versions of the main vehicle. This alternate version is built with same body section but with a different turret type. This provided some great practice in using scene and class inheritance within Godot.

This alternate vehicle features a machine gun turret with a higher rate of fire, but slower projectile speed and lower damage.

Note

This was also posted to the Godot subreddit Experimenting to get more familiar with 3D in Godot. Eight-wheeled vehicles, big guns, terrain, and some explosions.

Future Focuses

Improved Suspension/Physics

Over the course of these versions I got a lot better using the 3D aspects of Godot, and especially the VehicleBody and VehicleWheel nodes. However, I was never able to fix a bug where vehicles would always slide a little bit if they were on a slope. This is, I suspect, a quirk with the native implementation of these. As a result, I would like to eventually learn enough to build my own implementation of a raycast suspension mechanic for vehicles.

Mapping

This current map was made using the heightmap addon for Godot 3.5, which provided a good base for my education here as my focus was on the vehicle physics, however I might want more control and the ability to organize my projects better by moving the terrain generation and prop placement more into Blender.

3D Modeling

I learned a lot about 3D modeling here and was fortunate enough to have a few friends who work with 3D modeling in their daily jobs that were able to provide me a bit of training and answered my questions. However, like with any art form, I have a lot more practice to apply to find my own style and voice and expertise in making suitable 3D models.

Multiplayer / AI

At the moment the only objective is stationary barrels to shoot at from a distance. It's not exactly the most rewarding game-play. It would be fantastic to have enemy AI-controlled vehicles to fight against and/or a multiplayer ability so that players could fight against their friends. However, that is work I'll have to focus on in future projects as both of those solutions require a lot more learning.

Published: October 20, 2022

Categories: gamedev