About
Deliverone is an arcade-style delivery mission game with dogfighting. It was the first group project I was a part of for 2 weeks back in Autumn 2020. C# and programming overall was still quite new to me. While I definitely would have written the code differently were I to rewrite it today, we've received a lot of compliments about how good the movement feels, and I am still very happy about the end result.
This project was a very over-scoped for our knowledge at the time (especially seeing as we barely even knew the engine). But despite that and despite our differences within the group, we worked together and overcame it. Very fun!
Project Overview
Language
C#
Engine
Unity
Platform
PC
Year
2020
Development Time
2 weeks
Team Size
10
My contributions
Initially, two different control schemes for the drone were suggested:
A tank-style control scheme where:
A strafe-style control scheme where:
After a couple of iterations and back and forths with the designers, both of these ideas were discarded in favor of a more uniquely designed control scheme.
A VTOL aircraft is an aircraft that flies like a plane, but takes off and lands like a helicopter.
VTOL = Vertical take-off and landing
We were going to go for an arcadey version of a VTOL aircraft, meaning two control schemes in one, based on a simple-to-fly "fly where I point to"-mechanic.
The feel we wanted to achieve when flying around in the game was to feel just as though you are simply pointing a reticle in a direction and having the drone fly there.
Simply pushing forward
One way you could achieve this is by simply giving the drone a velocity along its own Z-axis (Z = forward in Unity), and having the same axis point towards the mouse target on a plane.
The drone will turn and fly in the direction you point to, but what about the camera?
The issue you'll face trying this is that no matter which direction you decide to turn, you can't really see where you're about to go, as the direction you're facing is never the same as the one you're always moving straight forward, even if the camera is following after.
The movement direction of the drone in relation to the camera if you just add a velocity to the mesh's forward vector.
Note how it is constantly traveling off the screen.
Sometimes the angle camera angle will even make it so that more than half of what the actual pilot would see is blocked from view.
Troublesome oxymorons
Everything mentioned below is assuming you are using the drone's center point as the pivot point for the camera arm.
Moving the mouse collision plane further away:
... and moving it closer has the opposite problem.
Making the camera lerp into a chase camera position based on the drone's forward direction:
One (me :) ) may initially think that adjusting the smooth time the camera has to move into position behind the drone can solve the second problem. But all we found in testing is that the shorter smooth time you give the follow camera to rotate into position, the closer you'll get to have the relationship between the mouse and camera movement be 1:1, like in an over-the-shoulder shooter. The longer you make the smooth time, the more it will just feel like poorly implemented mouse acceleration.
Increasing the camera smooth time:
Decreasing the camera smooth time:
Implementing camera chasing this way wasn't even an oxymoron, it was just bad all around.
Camera-relative movement. The 'a' and 'd' keys are rotating the drone around it's own upwards Y-axis, in addition to the rotation added by the mouse to the same axis.
Segregating pitch, yaw, camera and mesh transforms
To achieve this, first of all, the plane the mouse ray collides with got swapped for a sphere. This was a quick and simple but important fix for making the sensitivity curve for the mouse turning make more sense. Robert, an artist on my team, was nice enough to create a half 3D sphere mesh for me to use as a collision box.
Using a mesh rather than manually calculating the would-be collision points in 3D space meant we saved precious development time for an insignificant performance cost.
The transforms
Target Pivot
Every frame, a ray is cast from the camera onto the half-sphere. The direction towards the hitpoint of this ray is considered the forward direction of the transform called targetPivot.
Yaw
The Y-coordinate of the cross product between the yaw transform's forward direction (this vector is always aligned with the ground plane, as the yaw-transform never pitches) and the target pivots forward direction ends up being an indicator of how much the player intends to turn (yaw).
Pitch
The pitch is functioning the same way as the yaw, except that it is using the x-coordinate of the cross product instead, to adjust the pitch. Furthermore, it constrains the pitch to exposed values, so that the drone can't fly straight down/up or upside down (or mess with the steering of the yaw transform).
Camera
The Camera rotates around the player with the help of the CameraArm, which is always following the position of the drone. It's pitch depends on the target pivot transform (float cameraPitch = Vector3.dot(Vector3.up, targetPivotTransofmr.forward;).
In addition to rotating around the player, the camera's FOV fluctuates based on the player's movement speed, to heighten the feeling of speed.
To ensure stability and stick to simpler quaternion calculations, the only pitching the player will experience is that of the camera and the mesh.
The yaw transform has a yaw value of 0 to τ radians and is independent from the mesh's and the camera's rotation.
Having the yaw transform separate means that the mouse direction and the drone's forward-moving direction are independently tracked, solving our previous "mouse acceleration" issue.
Taking the Y-value of the cross product between the drone's forward direction (ignoring pitch, aka the yaw transform's forward direction) and the target pivot's forward direction (see picture above) allows us to determine the direction to turn, regardless of the camera's pitch. That is as long as the camera's pitch doesn't get too close to -Y or +Y (global/world space), which it is clamped not to do.
A reminder of Unity's transform implementation
The VTOL
Turning the drone into a VTOL was simply implemented as making two different movement systems that you can swap between on-the-fly whenever you want. In code, I decided to call them PlaneMovement and HelicopterMovement.
The HelicopterMovement mode is meant to make picking up and delivering packages easier and is quite similar to Plane Movement, but you can "hover" mid-air as well as go straight up and straight down. The pattern for switching between the different movement modes is very simple.
Obligatory 2024 edit: I would never have gone down the inheritance route here if I wrote this today.
These methods get called several times per second with a fixed timestep
The Player actor object keeps a PlayerMovement component object with two subclasses, PlaneMovement and HelicopterMovement.
The PlayerMovement component object keeps one of its different "variants" of itself as the active variant (or mode), and redirects calls that depend on the currently active variant down to the child classes.
Credits
Art
Samantha Baqvel
Lena Fredén
Roberto Marcos Söderström
Music
Robert Arnborg
Programming
Oliver Lebert
Henrik Nilsson
Jupiter Thulin
Design
Robert Arnborg
Damian Becedas
Sean Falk
Copyright © 2024 Henrik Nilsson