DELIVERONE

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!

Year

2020

Engine

Unity

Language

C#

Project Overview

Language

C#

Engine

Unity

Platform

PC

Year

2020

Development Time

2 weeks

Team Size

10

My contributions

  • Movement
  • Physics and collisions
  • Camera

Development

Vision

Initially, two different control schemes for the drone were suggested:

A tank-style control scheme where:

  • the mouse independently moves the camera
  • the keys independently adjust the drone's velocity and turns

A strafe-style control scheme where:

  • the mouse moves the camera and turns the drone
  • the keys adjust velocity in relation to the camera

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 challenge of following a mouse target

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:

  • improves visibility when turning
  • makes turning slower and mouse movements less impactful

 ... and moving it closer has the opposite problem.


Making the camera lerp into a chase camera position based on the drone's forward direction:

  • improves the viewing angle
  • makes both camera and movement shaky and unpredictable, as the turning angle is no longer linearly relative between the camera's and the drone's forward directions


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:

  • feels like more terrible mouse acceleration
  • increases the time it takes for you to see the things in front of the drone - making the problem we set out to solve in the first place even worse


Decreasing the camera smooth time:

  • makes the controls feel closer and closer to an fps or over-the-shoulder style shooter - not the control scheme we wanted


Implementing camera chasing this way wasn't even an oxymoron, it was just bad all around.

The solution

So how would you go about making sure that the camera is always seeing as much as possible of the direction you're going towards, while at the same time having the drone fly towards a target location on the screen?

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.

Well, instead of applying the forward velocity in the drone mesh's forward direction, you can apply the forward velocity in the camera's forward direction. Turning can be achieved by having a separate "target pivot transform" that follows the drone's position. Instead of just rotating the drone to the direction of the mouse, you rotate this transform to point in the direction of the mouse and decide its direction independent of the actual mesh transform.

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