You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Driftology/Assets/Scripts/VehicleController.cs

349 lines
12 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using UnityEngine;
using CnControls;
public enum SurfaceDetection
{
RayCast,
SphereCast
}
public class VehicleController : MonoBehaviour
{
[SerializeField] float currentSpeed = 0f;
[SerializeField] float steeringAI = 0f;
public bool isAIControlled = false;
[SerializeField] SurfaceDetection groundDetection = SurfaceDetection.RayCast;
[SerializeField] LayerMask driveableSurface = -1;
[SerializeField] float maxSpeed = 100f;
[SerializeField] float baseAcceleration = 10f;
[SerializeField] float steeringSensitivity = 10f;
[SerializeField] float gravityForce = 9.8f;
[SerializeField] float downforce = 5f;
[SerializeField] float brakeThreshold = 30f;
[SerializeField] float targetStoppingDistance = 5f;
[SerializeField] bool allowAirControl = false;
[SerializeField] AnimationCurve _frictionCurve = new AnimationCurve();
[SerializeField] AnimationCurve _turnCurve = new AnimationCurve();
[SerializeField, Range(0, 10)] float bodyTiltAmount = 0.5f;
[SerializeField] Transform carBodyVisual = null;
[SerializeField, Range(0.1f, 1f)] float skidMarkWidth = 0.25f;
[SerializeField, Range(0.1f, 10f)] float skidMarkDuration = 5f;
[SerializeField] Transform wheelFrontLeft = null;
[SerializeField] Transform wheelFrontRight = null;
[SerializeField] Transform wheelRearLeft = null;
[SerializeField] Transform wheelRearRight = null;
[SerializeField] Transform frontLeftAxle = null;
[SerializeField] Transform frontRightAxle = null;
[SerializeField] GameObject speedVisualEffects = null;
[SerializeField] Rigidbody sphereRigidbody = null;
[SerializeField] PhysicMaterial wheelFrictionMaterial = null;
[SerializeField] Transform cameraFollowTarget = null;
private bool isRunning = false;
private bool isOnGround = false;
private bool hasTurboBoost = true;
public bool nosActive = false;
private float accelerationInput = 0f;
private float brakeInput = 0f;
private float accelerationForce = 0f;
private float steeringSign = 0f;
private float inputHorizontal = 0f;
private float inputVertical = 0f;
private float wheelRadius = 0f;
private float desiredTurnAngle = 0f;
private float distanceToTarget = 0f;
private float angleDifference = 0f;
private float maxGroundDistance = 0f;
private float counterSteerValue = 0f;
private float steeringMultiplier = 0f;
private Vector3 velocityLocal = Vector3.zero;
private Vector3 forwardDirection = Vector3.zero;
private Vector3 directionToTarget = Vector3.zero;
private Vector3 movementDirection = Vector3.zero;
private RaycastHit surfaceHit;
private Rigidbody vehicleRigidbody = null;
private VehicleTracker targetTracker = null;
public bool AIControlled => isAIControlled;
public bool Running => isRunning;
public bool Grounded => isOnGround;
public float SkidWidth => skidMarkWidth;
public float SkidDuration => skidMarkDuration;
public float CurrentSpeed => velocityLocal.z;
public float MaximumSpeed => maxSpeed;
public bool InputsActive => inputHorizontal != 0 || inputVertical != 0 || nosActive;
public NOSController nosController;
[SerializeField] private CarData carData;
internal float SteeringSensitivity
{
get => steeringSensitivity;
set => steeringSensitivity = value;
}
public Vector3 LocalVelocity => velocityLocal;
public Transform FollowTarget => cameraFollowTarget;
[Range(0.3f, 1f)] public float steeringFactor = 1f;
private void Awake()
{
vehicleRigidbody = GetComponent<Rigidbody>();
targetTracker = GetComponent<VehicleTracker>();
wheelRadius = sphereRigidbody.GetComponent<SphereCollider>().radius;
nosController = GetComponent<NOSController>();
// maxGroundDistance = 1.1f;
maxGroundDistance = wheelRadius + 0.5f;
//InitializeVehicle();
}
public void InitializeWithData(CarData data)
{
carData = data;
InitializeVehicle();
}
public void InitializeVehicle()
{
isRunning = true;
if (carData != null)
{
maxSpeed = carData.maxSpeed;
baseAcceleration = carData.acceleration;
//steeringSensitivity = carData.handling;
downforce = carData.traction;
// Optional: normalize handling (2060 ➜ 0.01.0 range for internal use)
//steeringFactor = Mathf.InverseLerp(20f, 60f, carData.handling);
}
accelerationForce = baseAcceleration;
}
public void StopVehicle()
{
isRunning = false;
velocityLocal = Vector3.zero;
currentSpeed = 0f;
steeringAI = 0f;
}
private void Update()
{
if (!isRunning) return;
ProcessInputs();
ApplyControls();
UpdateVisuals();
}
private void FixedUpdate()
{
if (!isRunning) return;
CheckGround();
HandleMovement();
}
private void ProcessInputs()
{
if (isAIControlled)
{
// --- Get AI-relevant steering info ---
Vector3 targetPoint = targetTracker.TargetPos;
targetPoint.y = transform.position.y;
Vector3 directionToTarget = (targetPoint - transform.position).normalized;
float angleToTarget = Vector3.Angle(transform.forward, directionToTarget);
float distanceToTarget = Vector3.Distance(transform.position, targetPoint);
// --- Dynamic throttle logic ---
if (distanceToTarget < targetStoppingDistance)
{
inputVertical = 0f; // Stop when close enough
}
else if (angleToTarget > 60f)
{
inputVertical = 0.3f; // Slow down for sharp turns
}
else if (angleToTarget > 30f)
{
inputVertical = 0.6f; // Moderate turn
}
else
{
inputVertical = 0.95f; // Full throttle
}
inputHorizontal = 0f; // Let AI use steeringAI instead
}
else
{
inputVertical = CnInputManager.GetAxis("Vertical");
inputHorizontal = CnInputManager.GetAxis("Horizontal");
}
}
private void UpdateVisuals()
{
counterSteerValue = (Mathf.Abs(velocityLocal.x) > 20f) ? -1f : 1f;
float steerAngle = 45f * counterSteerValue * steeringAI;
ApplySteeringRotation(wheelFrontLeft, steerAngle);
ApplySteeringRotation(wheelFrontRight, steerAngle);
float wheelSpin = CurrentSpeed * 0.75f;
frontLeftAxle?.Rotate(Vector3.right * wheelSpin);
frontRightAxle?.Rotate(Vector3.right * wheelSpin);
wheelRearLeft?.Rotate(Vector3.right * wheelSpin);
wheelRearRight?.Rotate(Vector3.right * wheelSpin);
if (speedVisualEffects)
speedVisualEffects.SetActive(velocityLocal.z > 10);
}
private void ApplySteeringRotation(Transform wheel, float steerAngle)
{
if (wheel == null) return;
Quaternion targetRot = Quaternion.Euler(0f, steerAngle, 0f);
wheel.localRotation = Quaternion.Slerp(wheel.localRotation, targetRot, 0.1f);
}
private void ApplyControls()
{
Vector3 targetPoint = targetTracker.TargetPos;
targetPoint.y = transform.position.y;
directionToTarget = (targetPoint - transform.position).normalized;
forwardDirection = transform.forward.normalized;
desiredTurnAngle = Mathf.Abs(Vector3.Angle(forwardDirection, Vector3.ProjectOnPlane(directionToTarget, transform.up)));
distanceToTarget = Vector3.Distance(transform.position, targetTracker.TargetPos);
movementDirection = (targetTracker.TargetPos - transform.position).normalized;
float alignment = Vector3.Dot(transform.forward, movementDirection);
angleDifference = Vector3.Angle(transform.forward, movementDirection);
brakeInput = 0f;
if (distanceToTarget > targetStoppingDistance)
{
accelerationInput = alignment > 0f ? inputVertical : (distanceToTarget > 5f ? inputVertical : -1f);
steeringAI = (Vector3.SignedAngle(transform.forward, movementDirection, Vector3.up) > 0f ? 1f : -1f)
* _turnCurve.Evaluate(desiredTurnAngle / 160f);
}
else
{
steeringAI = 0f;
}
if (!isAIControlled)
steeringAI += inputHorizontal;
currentSpeed = Mathf.Round(CurrentSpeed);
}
private void CheckGround()
{
//isOnGround = true;
if (groundDetection == SurfaceDetection.RayCast)
{
isOnGround = Physics.Raycast(sphereRigidbody.position, Vector3.down, out surfaceHit, maxGroundDistance, driveableSurface)
|| Physics.Raycast(sphereRigidbody.position + Vector3.forward * 3f, Vector3.down, out surfaceHit, maxGroundDistance, driveableSurface)
|| Physics.Raycast(sphereRigidbody.position - Vector3.forward * 3f, Vector3.down, out surfaceHit, maxGroundDistance, driveableSurface);
}
else if (groundDetection == SurfaceDetection.SphereCast)
{
isOnGround = Physics.SphereCast(sphereRigidbody.position + wheelRadius * Vector3.up, wheelRadius + 0.25f, -transform.up, out surfaceHit, maxGroundDistance, driveableSurface);
}
else isOnGround = false;
}
private void HandleMovement()
{
velocityLocal = vehicleRigidbody.transform.InverseTransformDirection(vehicleRigidbody.velocity);
if (float.IsNaN(velocityLocal.z) || float.IsInfinity(velocityLocal.z))
{
velocityLocal = Vector3.zero;
}
if (angleDifference > brakeThreshold && velocityLocal.z > 15f)
{
wheelFrictionMaterial.dynamicFriction = 0.01f;
}
else
{
wheelFrictionMaterial.dynamicFriction = _frictionCurve.Evaluate(Mathf.Abs(velocityLocal.x / 100));
}
steeringMultiplier = _turnCurve.Evaluate(velocityLocal.magnitude / maxSpeed);
if (Grounded)
{
steeringSign = Mathf.Sign(velocityLocal.z);
if (Mathf.Abs(accelerationInput) > 0.1f)
vehicleRigidbody.AddTorque(Vector3.up * (steeringAI * steeringSign * steeringSensitivity * 100f * steeringMultiplier * steeringFactor));
if (Mathf.Abs(accelerationInput) > 0.1f)
sphereRigidbody.velocity = Vector3.Lerp(sphereRigidbody.velocity, vehicleRigidbody.transform.forward * (accelerationInput * maxSpeed), accelerationForce / 10f * Time.fixedDeltaTime);
sphereRigidbody.AddForce(-transform.up * (downforce * sphereRigidbody.mass));
vehicleRigidbody.MoveRotation(
Quaternion.Slerp(
vehicleRigidbody.rotation,
Quaternion.FromToRotation(vehicleRigidbody.transform.up, surfaceHit.normal) * vehicleRigidbody.transform.rotation,
0.12f
)
);
}
else
{
if (allowAirControl)
vehicleRigidbody.AddTorque(Vector3.up * (steeringAI * steeringSensitivity * 100f * steeringMultiplier * steeringFactor));
sphereRigidbody.velocity = Vector3.Lerp(
sphereRigidbody.velocity,
(vehicleRigidbody.transform.forward * (accelerationInput * maxSpeed)) + Vector3.down * (gravityForce * 9.8f),
(accelerationForce / 25f) * Time.deltaTime
);
}
}
internal void DisableVehicle()
{
enabled = false;
sphereRigidbody.gameObject.SetActive(false);
sphereRigidbody.velocity = Vector3.zero;
sphereRigidbody.Sleep();
vehicleRigidbody.velocity = Vector3.zero;
vehicleRigidbody.Sleep();
}
public void ActivateNOS()
{
ActivateNOS(true);
}
public void ActivateNOS(bool active, float bonusAcceleration = 10f)
{
nosActive = active;
accelerationForce = active ? accelerationForce + bonusAcceleration : baseAcceleration;
}
internal void EnableVehicle()
{
enabled = true;
sphereRigidbody.gameObject.SetActive(true);
}
private void OnCollisionEnter(Collision collision)
{
Debug.Log("collision with: " + collision.gameObject.name, collision.gameObject);
}
}