This document is about: QUANTUM 2
SWITCH TO

Vehicular Combat

Level 4
Available in the Gaming Circle and Industries Circle
Circle

Overview

The KEO Vehicular Combat sample is a vehicular combat game built with Quantum. The samples includes two types of vehicles (a lightweight buggy and a heavy 8-wheeler tank) which are used to battle in an ongoing free for all deathmatch.

The sample was developed by Redcatpig and is based on their upcoming game KEO.

Technical Highlights

  • Vehicle Controller with Driving Physics
  • Multi Axis Configuration Support
  • Dynamic Suspension View Formula
  • Damage System
  • Hitscan Weapon System

Screenshots

Download

Version Release Date Download
2.1.0 May 11, 2023 Quantum Vehicular Combat 2.1.0 Build 214

Multi Axis Configuration Support

By default the sample supports up to 4 wheel axles and 8 wheels (2 wheels per axle). It is possible to create vehicles with more axles & wheels by increasing the fixed array size in the VehicleState component; simply set it to the maximum amount axles the largest vehicle may have.

C#

component VehicleState {
    asset_ref<VehicleConfig> Config;
    DriveTrain DriveTrain;

    Byte AxleCount;
    array<AxleState>[4] Axles; // modify this value for support more axles

    [HideInInspector] FP TimeFlipped;
    [HideInInspector] FP TimeFalling;
    [HideInInspector] FP Fuel;
}

Axle Setup

For each vehicle, set the Axle Count for that particular vehicle. Each Axle requires an AxleConfig, WheelConfig, SuspensionConfig and TireConfig to be functional.

keo axle setup example
Axle Setup Example.

Axle Config

AxleConfig allows to set the offset of the axle which is its position relative to the vehicle's center / pivot.

keo axle config example
Axle Config Example.

Each axle may apply Traction and/or Steering. If the axle is situated towards the rear of the vehicle, and the wheels also apply steering (such as in the case of the tank vehicle), the InvertSteering option has to be checked in order for the wheels to apply the correct steering.

C#

struct AxleState {
    asset_ref<AxleConfig> AxleConfig;

    asset_ref<WheelConfig> WheelConfig;
    asset_ref<SuspensionConfig> SuspensionConfig;
    asset_ref<TireConfig> TireConfig;

    [HideInInspector] FP SteerAngle;
    [HideInInspector] WheelState WheelL;
    [HideInInspector] WheelState WheelR;
}

Drive Assist

In the Drive Assist section, the max and min steering angles for each steering axles. This helps players not oversteering or losing control at higher speeds.

keo drive assist example
Drive Assist.

Suspension

The suspension is visualized on the Unity-side by reading the values of the car physics in the Quantum simulation. The class responsible for this is called AxleView.cs.

As this is pure visual representation, the suspension visual reacts to the simulation but the suspension itself is not part of the simulation. Thus the following section are entirely about Unity-side representation and code.

Dynamic Suspension View

The formula included in the sample for the custom suspension view is specific to the type of suspension created in KEO's vehicles, but the logic can be adapted for other suspension types.

The suspension is composed of:

  • Left/Right Wheel: The visual wheel which will rotate on its X axis according to the vehicles movement.
keo left-right wheel example
  • Left/Right Pivot: The object of which will get its Y position set according to the suspension compression (taken from the Quantum Simulation), and will rotate on its Y axis to simulate steering (wheel objects must be children of the pivots).
keo left-right pivot example
  • Left/Right Fixed Pivot: The object which will be synchronised with the wheels Y position and contains all "inner" parts of the a suspension bar (explained below).
keo left-right pivot fixed example

Suspension Formula

The suspension bars are composed of an inner top bar ( A ), outer top bar ( B ), inner bottom ( C ) and outer bottom ( D ).

keo suspension bar example

The formula driving the visuals is the following:

keo formula equation

The idea behind the formula is to simulate a hydraulic bar opening and closing by taking the wheels Y position, and rotating the inner and outer suspension pieces accordingly.

For this the starting angle (Alpha) of the given suspension arm is required. The starting angle is always 0 for the top bar (Case #1), and must be measured for the bottom bar (See Alpha in Case #2 in the diagram below). The Offset, i.e. its initial Y distance from the position where the bar rotates, is also needed (See Offset in Case #2 in the diagram below). Lastly, the length of the suspension arm (Beta) when completely parallel to the worlds X axis (See Beta in Case #1) has to be accounted for. The best way to measure these values is using whichever 3D Software was used to create the vehicles.

C#

LeftPivot.localRotation = Quaternion.Euler(0, axleState.WheelL.SteerAngle.AsFloat, 0);
RightPivot.localRotation = Quaternion.Euler(0, axleState.WheelR.SteerAngle.AsFloat, 0);

if (vehicleViewUpdateData.VehiclePhysicsBody->IsSleeping)
{
    return;
}

// update wheels suspension travel
var suspensionMaxTravel = _suspensionConfig.MaxTravel.AsFloat;

var leftSuspensionCompression = axleState.WheelL.Suspension.CompressionPrevious.AsFloat - suspensionMaxTravel;
var rightSuspensionCompression = axleState.WheelR.Suspension.CompressionPrevious.AsFloat - suspensionMaxTravel;

LeftPivot.localPosition = _leftPivotPosition + new Vector3(0, leftSuspensionCompression + _anchorOffset, 0) / Scale;
RightPivot.localPosition = _rightPivotPosition + new Vector3(0, rightSuspensionCompression + _anchorOffset, 0) / Scale;

LeftFixedPivot.localPosition = _leftFixedPivotPosition + new Vector3(0, leftSuspensionCompression + _anchorOffset, 0) / Scale;
RightFixedPivot.localPosition = _rightFixedPivotPosition + new Vector3(0, rightSuspensionCompression + _anchorOffset, 0) / Scale;

C#

float angleTop;
float angleBottom;

if (wheelLocalPos.y < 0)
{
    angleBottom = (Mathf.Atan((Mathf.Abs(wheelLocalPos.y) + _vehicleView.BottomOffset) / _vehicleView.BottomBeta) * 180 / Mathf.PI - _vehicleView.Alpha) * -1;
    angleTop = (Mathf.Atan((Mathf.Abs(wheelLocalPos.y) + _vehicleView.TopOffset) / _vehicleView.TopBeta) * 180 / Mathf.PI) * -1;
}
else
{
    angleBottom = Mathf.Atan((Mathf.Abs(wheelLocalPos.y) + _vehicleView.BottomOffset) / _vehicleView.BottomBeta) * 180 / Mathf.PI - _vehicleView.Alpha;
    angleTop = Mathf.Atan((Mathf.Abs(wheelLocalPos.y) + _vehicleView.TopOffset) / _vehicleView.TopBeta) * 180 / Mathf.PI;
}

if (leftSide)
{
    angleTop = angleTop < 0 ? Mathf.Abs(angleTop) : angleTop * -1;
    angleBottom = angleBottom < 0 ? Mathf.Abs(angleBottom) : angleBottom * -1;
}

suspensionInnerBottom.localRotation = Quaternion.Euler(new Vector3(0, 0, angleBottom));
suspensionInnerTop.localRotation = Quaternion.Euler(new Vector3(0, 0, angleTop));
suspensionOuterBottom.localRotation = Quaternion.Euler(new Vector3(0, 0, angleBottom));
suspensionOuterTop.localRotation = Quaternion.Euler(new Vector3(0, 0, angleTop));
keo formula diagram
Back to top