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
Work
Team
Art
Programming
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