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

534 lines
20 KiB
C#

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;
[Range(0.3f, 1f)] public float steeringFactor = 1f;
internal float SteeringSensitivity
{
get => steeringSensitivity;
set => steeringSensitivity = value;
}
public Vector3 LocalVelocity => velocityLocal;
public Transform FollowTarget => cameraFollowTarget;
private void Awake()
{
vehicleRigidbody = GetComponent<Rigidbody>();
targetTracker = GetComponent<VehicleTracker>();
wheelRadius = sphereRigidbody.GetComponent<SphereCollider>().radius;
maxGroundDistance = wheelRadius + 0.5f;
nosController = GetComponent<NOSController>();
}
public void InitializeWithData(CarData data)
{
carData = data;
InitializeVehicle();
}
public void InitializeVehicle()
{
isRunning = true;
if (carData != null)
{
maxSpeed = carData.maxSpeed;
baseAcceleration = carData.acceleration;
downforce = carData.traction;
}
accelerationForce = baseAcceleration;
}
public void StopVehicle()
{
isRunning = false;
velocityLocal = Vector3.zero;
currentSpeed = 0f;
steeringAI = 0f;
}
private void Update()
{
if (!isRunning) return;
ProcessInputs();
ApplyControls();
UpdateVisuals();
if (isAIControlled)
{
accelerationForce = Mathf.Lerp(accelerationForce, baseAcceleration, Time.deltaTime);
}
}
private void FixedUpdate()
{
if (!isRunning) return;
CheckGround();
HandleMovement();
}
//private void ProcessInputs()
//{
// if (isAIControlled)
// {
// 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);
// if (distanceToTarget < targetStoppingDistance)
// inputVertical = 0f;
// else if (angleToTarget > 60f)
// inputVertical = 0.3f;
// else if (angleToTarget > 30f)
// inputVertical = 0.6f;
// else
// inputVertical = 0.95f;
// inputHorizontal = 0f;
// }
// else
// {
// inputVertical = CnInputManager.GetAxis("Vertical");
// inputHorizontal = CnInputManager.GetAxis("Horizontal");
// }
//}
private void ProcessInputs()
{
if (isAIControlled)
{
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);
if (distanceToTarget < targetStoppingDistance)
inputVertical = 0f;
else if (angleToTarget > 60f)
inputVertical = 0.3f;
else if (angleToTarget > 30f)
inputVertical = 0.6f;
else
inputVertical = 0.95f;
inputHorizontal = 0f;
}
else
{
// 👇 Dampen steering sensitivity
inputHorizontal = Mathf.Clamp(CnInputManager.GetAxis("Horizontal") * 0.85f, -1f, 1f);
inputVertical = Mathf.Clamp(CnInputManager.GetAxis("Vertical"), -1f, 1f);
}
}
private void UpdateVisuals()
{
counterSteerValue = (Mathf.Abs(velocityLocal.x) > 20f) ? -1f : 1f;
float steerAngle = 45f * counterSteerValue * Mathf.Clamp(steeringAI, -1f, 1f);
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 (carBodyVisual != null)
{
if (velocityLocal.z > 1)
{
float tiltZ = Mathf.Clamp(desiredTurnAngle * steeringAI, -bodyTiltAmount, bodyTiltAmount);
carBodyVisual.localRotation = Quaternion.Slerp(
carBodyVisual.localRotation,
Quaternion.Euler(Mathf.Lerp(0, -5f, velocityLocal.z / maxSpeed), 0f, tiltZ),
0.05f
);
}
else
{
carBodyVisual.localRotation = Quaternion.Slerp(carBodyVisual.localRotation, Quaternion.identity, 0.05f);
}
}
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 float previousSteeringAI = 0f;
//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 = Mathf.Sign(Vector3.SignedAngle(transform.forward, movementDirection, Vector3.up))
// * _turnCurve.Evaluate(desiredTurnAngle / 160f);
// }
// else
// {
// steeringAI = 0f;
// }
// if (!isAIControlled)
// steeringAI += inputHorizontal;
// // ✅ Speed-based steering reduction (but not too much)
// float speedFactor = Mathf.Clamp01(CurrentSpeed / maxSpeed);
// steeringAI *= (1f - speedFactor * 0.3f); // only 30% reduction at top speed
// // ✅ No input smoothing for racing responsiveness
// steeringAI = Mathf.Clamp(steeringAI, -1f, 1f);
// accelerationInput = Mathf.Clamp(accelerationInput, -1f, 1f);
// currentSpeed = Mathf.Round(CurrentSpeed);
//}
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 = Mathf.Sign(Vector3.SignedAngle(transform.forward, movementDirection, Vector3.up))
* _turnCurve.Evaluate(desiredTurnAngle / 160f);
}
else
{
steeringAI = 0f;
}
if (!isAIControlled)
steeringAI += inputHorizontal;
steeringAI = Mathf.Clamp(steeringAI, -1f, 1f);
accelerationInput = Mathf.Clamp(accelerationInput, -1f, 1f);
currentSpeed = Mathf.Round(CurrentSpeed);
}
private void CheckGround()
{
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);
// Apply lateral friction
wheelFrictionMaterial.dynamicFriction = _frictionCurve.Evaluate(Mathf.Abs(velocityLocal.x / 100f));
steeringMultiplier = _turnCurve.Evaluate(velocityLocal.magnitude / maxSpeed);
if (Grounded)
{
steeringSign = Mathf.Sign(velocityLocal.z);
// Steering torque
if (inputVertical > 0.1f || velocityLocal.z > 1f)
vehicleRigidbody.AddTorque(Vector3.up * (steeringAI * steeringSign * steeringSensitivity * 100f * steeringMultiplier));
else if (inputVertical < -0.1f || velocityLocal.z < -1f)
vehicleRigidbody.AddTorque(Vector3.up * (steeringAI * steeringSign * steeringSensitivity * 100f * steeringMultiplier));
// Brake logic (if needed)
// You can add: sphereRigidbody.constraints = brakeInput > 0.1f ? RigidbodyConstraints.FreezeRotationX : RigidbodyConstraints.None;
// Velocity alignment using Lerp (matches reference)
if (Mathf.Abs(inputVertical) > 0.1f && brakeInput < 0.1f)
{
Vector3 targetVelocity = vehicleRigidbody.transform.forward * (inputVertical * maxSpeed);
sphereRigidbody.velocity = Vector3.Lerp(
sphereRigidbody.velocity,
targetVelocity,
accelerationForce / 10f * Time.fixedDeltaTime
);
}
// Downforce for grip
sphereRigidbody.AddForce(-transform.up * (downforce * sphereRigidbody.mass));
// Align car with surface
vehicleRigidbody.MoveRotation(
Quaternion.Slerp(
vehicleRigidbody.rotation,
Quaternion.FromToRotation(vehicleRigidbody.transform.up, surfaceHit.normal) * vehicleRigidbody.transform.rotation,
0.12f
)
);
}
else
{
// Optional: allow air control
if (allowAirControl)
{
vehicleRigidbody.AddTorque(Vector3.up * (steeringAI * steeringSensitivity * 100f * steeringMultiplier));
}
// Apply velocity while airborne
sphereRigidbody.velocity = Vector3.Lerp(
sphereRigidbody.velocity,
vehicleRigidbody.transform.forward * (inputVertical * maxSpeed) + Vector3.down * gravityForce * 9.8f,
accelerationForce / 25f * Time.deltaTime
);
}
}
//private void HandleMovement() //second
//{
// velocityLocal = vehicleRigidbody.transform.InverseTransformDirection(vehicleRigidbody.velocity);
// wheelFrictionMaterial.dynamicFriction = _frictionCurve.Evaluate(Mathf.Abs(velocityLocal.x / 100f));
// 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
// );
// }
//}
//private void HandleMovement() //original
//{
// velocityLocal = vehicleRigidbody.transform.InverseTransformDirection(vehicleRigidbody.velocity);
// Stronger grip(less drifting)
// wheelFrictionMaterial.dynamicFriction = _frictionCurve.Evaluate(Mathf.Abs(velocityLocal.x / 100f));
// steeringMultiplier = _turnCurve.Evaluate(velocityLocal.magnitude / maxSpeed);
// if (Grounded)
// {
// steeringSign = Mathf.Sign(velocityLocal.z);
// Steering torque with reduced sensitivity and damping
// if (Mathf.Abs(accelerationInput) > 0.1f)
// {
// float torqueAmount = steeringAI * steeringSign * steeringSensitivity * 100f * steeringMultiplier;
// vehicleRigidbody.AddTorque(Vector3.up * torqueAmount);
// }
// if (Mathf.Abs(accelerationInput) > 0.1f)
// {
// Vector3 targetVelocity = vehicleRigidbody.transform.forward * (accelerationInput * maxSpeed);
// sphereRigidbody.velocity = Vector3.Lerp(sphereRigidbody.velocity, targetVelocity, accelerationForce / 10f * Time.fixedDeltaTime);
// }
// Add more downforce to reduce slip
// sphereRigidbody.AddForce(-transform.up * (downforce * sphereRigidbody.mass));
// Align car with surface normal
// vehicleRigidbody.MoveRotation(
// Quaternion.Slerp(
// vehicleRigidbody.rotation,
// Quaternion.FromToRotation(vehicleRigidbody.transform.up, surfaceHit.normal) * vehicleRigidbody.transform.rotation,
// 0.12f
// )
// );
// Dampen lateral velocity to reduce sliding
// Vector3 localVel = vehicleRigidbody.transform.InverseTransformDirection(vehicleRigidbody.velocity);
// localVel.x *= 0.85f; // Reduce side slip
// vehicleRigidbody.velocity = vehicleRigidbody.transform.TransformDirection(localVel);
// }
// else
// {
// Allow some air control if enabled
// if (allowAirControl)
// {
// vehicleRigidbody.AddTorque(Vector3.up * (steeringAI * steeringSensitivity * 100f * steeringMultiplier));
// }
// Gravity and air movement
// 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);
}
}