I spent several hours finetuning my terrain, animation and mouse controls to make the most adrenaline-fueled track possible.
https://www.youtube.com/watch?v=tmrXzdEopiU
Player controls, if anyone’s interested:
using System.Collections;
using System.Collections.Generic;
using UnityEngine.InputSystem;
using UnityEngine;
public class PlayerControls : MonoBehaviour
{
[SerializeField] InputAction movement;
[SerializeField] float evasionTime = 1f;
// Goes from -half to half
[SerializeField] float xRange = 16f;
// Goes from 0 to total
[SerializeField] float yRange = 10f;
[SerializeField] float positionPitchFactor = -2f;
[SerializeField] float positionYawFactor = 2f;
[SerializeField] float positionRollFactor = 2f;
[SerializeField] float controlPitchFactor = 20f;
[SerializeField] float controlRollFactor = 20f;
[SerializeField] float rateChange = 120f;
Vector3 currentVelocity = Vector3.zero;
float targetX;
float targetY;
float pitch = 0;
float yaw;
float roll = 0;
void Start()
{
}
private void OnEnable() {
movement.Enable();
}
private void OnDisable() {
movement.Disable();
}
void Update()
{
CalculateTarget();
ProcessTranslation();
ProcessRotation();
}
// Determine where the player is going based on mouse coordinates
private void CalculateTarget()
{
Vector3 mousePosition = Mouse.current.position.ReadValue();
float mouseX = mousePosition.x;
float mouseY = mousePosition.y;
if (mouseX < 0)
{
mouseX = 0;
}
else if (mouseX > Screen.width)
{
mouseX = Screen.width;
}
if (mouseY < 0)
{
mouseY = 0;
}
else if (mouseY > Screen.height)
{
mouseY = Screen.height;
}
targetX = ((mouseX - Screen.width / 2) / (Screen.width / 2)) * (xRange / 2);
targetY = ((mouseY) / (Screen.height)) * (yRange);
}
// Translate the player
void ProcessTranslation()
{
transform.localPosition = Vector3.SmoothDamp(transform.localPosition, new Vector3(targetX, targetY, 0), ref currentVelocity, evasionTime);
}
// Rotate the player
void ProcessRotation() {
float pitchFromPosition = (transform.localPosition.y - yRange / 2) * positionPitchFactor;
float yThrow = transform.localPosition.y - (targetY);
float yThrowRatio = (yThrow / (yRange));
float pitchFromThrow = ProcessThrow(yThrowRatio, controlPitchFactor);
float desiredPitch = pitchFromPosition + pitchFromThrow;
float yawFromPosition = transform.localPosition.x * positionYawFactor;
float desiredYaw = yawFromPosition;
float rollFromPosition = transform.localPosition.x * positionRollFactor;
float xThrow = transform.localPosition.x - (targetX);
float xThrowRatio = (xThrow / (xRange));
float rollFromThrow = ProcessThrow(xThrowRatio, controlRollFactor);
float desiredRoll = rollFromPosition + rollFromThrow;
UpdateRotation(desiredPitch, desiredYaw, desiredRoll);
transform.localRotation = Quaternion.Euler(pitch, yaw, roll);
}
// Determine a throw's effect
float ProcessThrow(float ratio, float factor) {
bool positiveRatio = true;
if (ratio < 0) {
positiveRatio = false;
ratio = -ratio;
}
float rotationFromThrow = Mathf.Round(Mathf.Sqrt(ratio) * factor * 100) / 100;
if (!positiveRatio) {
rotationFromThrow = rotationFromThrow * -1;
}
return rotationFromThrow;
}
// Update each rotation value by the current frame's update amount
void UpdateRotation(float desiredPitch, float desiredYaw, float desiredRoll) {
float changeThisFrame = rateChange * Time.deltaTime;
pitch = updateNumber(pitch, desiredPitch, changeThisFrame);
yaw = updateNumber(yaw, desiredYaw, changeThisFrame);
roll = updateNumber(roll, desiredRoll, changeThisFrame);
}
// Update a number to approach a target
float updateNumber(float originalNum, float desiredNum, float changeAmnt) {
float newNum = originalNum;
if (newNum >= desiredNum - changeAmnt && newNum <= desiredNum + changeAmnt) {
newNum = desiredNum;
}
else if (newNum < desiredNum - changeAmnt) {
newNum += changeAmnt;
}
else if (newNum > desiredNum - changeAmnt) {
newNum -= changeAmnt;
}
newNum = Mathf.Round(newNum * 100) / 100;
//Debug.Log($"Original: {originalNum}, desired: {desiredNum}, change by: {changeAmnt}, gives us: {newNum}");
return newNum;
}
}