FUTURISTIC RACER PROTOTYPE
Description
I began this project as a challenge to see if I am able to make a vehicle similar to those found in F-Zero X and also as an introduction to physics programming. The physics were written without the use of Unity's Rigidbody component. I also did all the programming for the game.
This vertical slice was developed in my spare time over a period of 3 months alongside my studies at Grafisch Lyceum Utrecht.
Tech
- C#
- Unity
Work
- AI racer that follows a spline.
- Arcade physics from scratch
- Audio visualization shader for buildings
- Lap system that keeps track of lap count and time
- Implemented UI in Unity
Code Snippets
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// This script regulates the vehicle's hover height.
/// </summary>
public class CarHoverHandler : MonoBehaviour
{
private void Awake()
{
_Manager = GetComponent<CarManager>();
}
private void FixedUpdate()
{
CalculatetHover();
RotateObjectToNormal();
transform.Translate(InterpolatedGroundNormal * HoverVelocity * Time.deltaTime, Space.World);
// Store values to be used in next frame's calculation
SetPreFaceGroundNormal(FacedGroundNormal);
SetPreInterpolatedGroundNormal(InterpolatedGroundNormal);
SetPreHoverVelocoty(HoverVelocity);
}
/// <summary>
/// Looks at vehicles position on a mesh and calculates correcteve forces
/// </summary>
private void CalculateHover()
{
Vector3 faceGroundNormal = new Vector3();
Vector3 interpolatedGroundNormal = new Vector3();
float force = 0;
float gravity = 0;
Ray ray = new Ray(transform.position + (_Manager.RaycastYOffset * transform.up), -transform.up);
RaycastHit hitInfo;
// We shoot a ray downward to get mash data of the mesh where we are currently driving.
if (Physics.Raycast(ray, out hitInfo, _Manager.MaxGroundDistence + _Manager.RaycastYOffset, _Manager.GroundMask))
{
// We check if the mesh's ID has changed. If so we copy over the mesh's normals and mesh triangles.
if (MeshData._ID != hitInfo.collider.gameObject.GetComponent<MeshData>()._ID)
{
SetMeshData(hitInfo.collider.gameObject.GetComponent<MeshData>());
SetNormals(MeshData._Normals);
SetTriangles(MeshData._Triangles);
}
// We calculate an interpolated normal of the hitposition of our raycast.
Transform hitTransform = hitInfo.collider.transform;
Vector3 n0 = _Normals[_Triangles[hitInfo.triangleIndex * 3 + 0]];
Vector3 n1 = _Normals[_Triangles[hitInfo.triangleIndex * 3 + 1]];
Vector3 n2 = _Normals[_Triangles[hitInfo.triangleIndex * 3 + 2]];
Vector3 baryCenter = hitInfo.barycentricCoordinate;
interpolatedGroundNormal = n0 * baryCenter.x + n1 * baryCenter.y + n2 * baryCenter.z;
interpolatedGroundNormal = interpolatedGroundNormal.normalized;
interpolatedGroundNormal = hitTransform.TransformDirection(interpolatedGroundNormal);
faceGroundNormal = hitInfo.normal;
//We calculate a corrective force based on the vehicle's distance from the ground and flag the vehicle as grounded
float height = hitInfo.distance - _Manager.RaycastYOffset;
float forcePercent = _Manager.HoverPID.Seek(_Manager.HoverHeight, height);
force = _Manager.HoverForce * forcePercent;
gravity = _Manager.HoverGravity * height;
SetIsGrounded(true);
// If the vehicle is intersecting with the road we nullify all hover forces and gravity so the vehicle will not translate through the road.
if (height <= 0 && Mathf.Sign(height) != Mathf.Sign(PreHeight) && HoverVelocity < 0)
{
SetHoverVelocoty(0);
}
// We store the vehicles height for next frame's calculation
SetPreHeight(height);
}
// If the raycast does not hit anything the vehicle is airborn.
else
{
interpolatedGroundNormal = PreInterpolatedGroundNormal;
gravity = _Manager.FallGravity;
SetIsGrounded(false);
}
SetFaceGroundNormal(faceGroundNormal);
SetInterpolatedGroundNormal(interpolatedGroundNormal);
SetHoverVelocoty(HoverVelocity + (force * Time.deltaTime)); //adds upwards force to HoverVelocity.
SetHoverVelocoty(HoverVelocity - (gravity * Time.deltaTime)); //adds downwards force to HoverVelocity.
}
/// <summary>
/// Rotates the Collider, Model and orientation to InterpolatedGroundNormal.
/// </summary>
private void RotateObjectToNormal()
{
transform.rotation = Quaternion.FromToRotation(transform.up, InterpolatedGroundNormal) * transform.rotation;
Manager.SetModelRotation(Quaternion.Slerp(Manager.ModelTransform.rotation, transform.rotation, 4 * Time.fixedDeltaTime));
Manager.SetUpOrientationRotation(Quaternion.Slerp(Manager.OrientationLookAtTransform.rotation, transform.rotation, 7 * Time.fixedDeltaTime));
}
private CarManager _Manager;
private MeshData _MeshData; // The MeshData of the mesh that the car is currently driving on
private Vector3[] _Normals; // All vertex normals of the mesh that the car is currently driving on
private int[] _Triangles; // All Tri's of the mesh that the car is currently driving on
private Vector3 _FaceGroundNormal; // Face normal of the of the tri the car is currently driving on
private Vector3 _PreFaceGroundNormal; // Face normal of the of the tri the car was driving on on the previous fixed update
private Vector3 _InterpolatedGroundNormal; // Interpolated normal of the 3 vertecies of the tri the car is currently driving on
private Vector3 _PreInterpolatedGroundNormal; // Interpolated normal of the 3 vertecies of the tri the car was driving on on the previous fixed update
private float _HoverVelocity; // the force that is aplied to the car to get it to the ideal hover height
private float _PreHoverVelocity; // The hover velocity of the car on the previous fixed time step
private float _PreHeight; // The height of the car above the track on the previous fixed time step
private bool _IsGrounded;
#region Prop
public CarManager Manager { get { return _Manager; } }
public MeshData MeshData { get { return _MeshData; } }
public Vector3[] Normals { get { return _Normals; } }
public int[] Triangles { get { return _Triangles; } }
public Vector3 FacedGroundNormal { get { return _FaceGroundNormal; } }
public Vector3 PreFacedGroundNormal { get { return _PreFaceGroundNormal; } }
public Vector3 InterpolatedGroundNormal { get { return _InterpolatedGroundNormal; } }
public Vector3 PreInterpolatedGroundNormal { get { return _PreInterpolatedGroundNormal; } }
public float HoverVelocity { get { return _HoverVelocity; } }
public float PreHoverVelocity { get { return _PreHoverVelocity; } }
public float PreHeight { get { return _PreHeight; } }
public bool Grounded { get { return _IsGrounded; } }
public void SetMeshData(MeshData newMeshData)
{
_MeshData = newMeshData;
}
public void SetNormals(Vector3[] newNormalArray)
{
_Normals = newNormalArray;
}
public void SetTriangles(int[] newTrianglesArray)
{
_Triangles = newTrianglesArray;
}
public void SetFaceGroundNormal(Vector3 newFaceGroundNormal)
{
_FaceGroundNormal = newFaceGroundNormal;
}
public void SetPreFaceGroundNormal(Vector3 newPreFaceGroundNormal)
{
_PreFaceGroundNormal = newPreFaceGroundNormal;
}
public void SetInterpolatedGroundNormal(Vector3 newInterpolatedGroundNormal)
{
_InterpolatedGroundNormal = newInterpolatedGroundNormal;
}
public void SetPreInterpolatedGroundNormal(Vector3 newPreInterpolatedGroundNormal)
{
_PreInterpolatedGroundNormal = newPreInterpolatedGroundNormal;
}
public void SetHoverVelocoty(float newVelocity)
{
_HoverVelocity = newVelocity;
}
public void SetPreHoverVelocoty(float newPreVelocity)
{
_PreHoverVelocity = newPreVelocity;
}
public void SetPreHeight(float newPreHeight)
{
_PreHeight = newPreHeight;
}
public void SetIsGrounded(bool newIsGrounded)
{
_IsGrounded = newIsGrounded;
}
#endregion
}
Links