226 lines
7.8 KiB
C#
226 lines
7.8 KiB
C#
using RaceFields.Vehicles;
|
|
using UnityEngine;
|
|
|
|
namespace RaceFields.Harvesters.AI
|
|
{
|
|
[RequireComponent(typeof(Rigidbody))]
|
|
public sealed class HarvesterChaseController : MonoBehaviour
|
|
{
|
|
[Header("Target")]
|
|
[SerializeField] private Transform target;
|
|
[SerializeField] private bool autoFindPlayer = true;
|
|
[SerializeField] private float targetSearchInterval = 0.35f;
|
|
|
|
[Header("Wheel Colliders")]
|
|
[SerializeField] private WheelCollider frontLeftCollider;
|
|
[SerializeField] private WheelCollider frontRightCollider;
|
|
[SerializeField] private WheelCollider rearLeftCollider;
|
|
[SerializeField] private WheelCollider rearRightCollider;
|
|
|
|
[Header("Wheel Visuals")]
|
|
[SerializeField] private Transform frontLeftVisual;
|
|
[SerializeField] private Transform frontRightVisual;
|
|
[SerializeField] private Transform rearLeftVisual;
|
|
[SerializeField] private Transform rearRightVisual;
|
|
|
|
[Header("Movement")]
|
|
[SerializeField] private float maxSpeedKmh = 55f;
|
|
[SerializeField] private float motorTorque = 30000f;
|
|
[SerializeField] private float brakeTorque = 6500f;
|
|
[SerializeField] private float maxSteerAngle = 28f;
|
|
[SerializeField] private float steeringResponse = 4f;
|
|
[SerializeField] private float stoppingDistance = 1.4f;
|
|
[SerializeField] private float reverseDistance = 0.7f;
|
|
[SerializeField] private float downforce = 45f;
|
|
|
|
[Header("Stability")]
|
|
[SerializeField] private Vector3 centerOfMass = new Vector3(0f, -0.65f, 0.2f);
|
|
|
|
private Rigidbody body;
|
|
private float nextTargetSearchTime;
|
|
private float currentSteerAngle;
|
|
|
|
private void Awake()
|
|
{
|
|
body = GetComponent<Rigidbody>();
|
|
AutoFindReferences();
|
|
|
|
body.centerOfMass = centerOfMass;
|
|
body.interpolation = RigidbodyInterpolation.Interpolate;
|
|
body.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
|
|
}
|
|
|
|
private void FixedUpdate()
|
|
{
|
|
ResolveTarget();
|
|
UpdateDriving();
|
|
ApplyDownforce();
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
UpdateWheelVisuals();
|
|
}
|
|
|
|
public void SetTarget(Transform newTarget)
|
|
{
|
|
target = newTarget;
|
|
nextTargetSearchTime = Time.time + targetSearchInterval;
|
|
}
|
|
|
|
private void UpdateDriving()
|
|
{
|
|
if (target == null)
|
|
{
|
|
ApplyBrake(brakeTorque);
|
|
ApplyMotor(0f);
|
|
SetSteering(0f);
|
|
return;
|
|
}
|
|
|
|
Vector3 localTarget = transform.InverseTransformPoint(target.position);
|
|
localTarget.y = 0f;
|
|
float distance = localTarget.magnitude;
|
|
if (distance < 0.1f)
|
|
{
|
|
ApplyBrake(brakeTorque);
|
|
ApplyMotor(0f);
|
|
return;
|
|
}
|
|
|
|
float speedKmh = body.linearVelocity.magnitude * 3.6f;
|
|
float targetAngle = Mathf.Atan2(localTarget.x, localTarget.z) * Mathf.Rad2Deg;
|
|
float desiredSteer = Mathf.Clamp(targetAngle, -maxSteerAngle, maxSteerAngle);
|
|
if (localTarget.z < 0f && Mathf.Abs(localTarget.x) < 0.5f)
|
|
desiredSteer = currentSteerAngle >= 0f ? maxSteerAngle : -maxSteerAngle;
|
|
|
|
SetSteering(desiredSteer);
|
|
|
|
if (distance <= reverseDistance)
|
|
{
|
|
ApplyBrake(0f);
|
|
ApplyMotor(-motorTorque * 0.45f);
|
|
return;
|
|
}
|
|
|
|
if (distance <= stoppingDistance)
|
|
{
|
|
ApplyBrake(brakeTorque);
|
|
ApplyMotor(0f);
|
|
return;
|
|
}
|
|
|
|
float angleFactor = Mathf.Clamp(1f - Mathf.Abs(targetAngle) / 130f * 0.5f, 0.65f, 1f);
|
|
float speedFactor = speedKmh < maxSpeedKmh ? 1f : 0f;
|
|
float torque = motorTorque * angleFactor * speedFactor;
|
|
|
|
ApplyBrake(0f);
|
|
ApplyMotor(torque);
|
|
}
|
|
|
|
private void SetSteering(float desiredAngle)
|
|
{
|
|
currentSteerAngle = Mathf.Lerp(currentSteerAngle, desiredAngle, steeringResponse * Time.fixedDeltaTime);
|
|
if (frontLeftCollider != null)
|
|
frontLeftCollider.steerAngle = currentSteerAngle;
|
|
if (frontRightCollider != null)
|
|
frontRightCollider.steerAngle = currentSteerAngle;
|
|
}
|
|
|
|
private void ApplyMotor(float torque)
|
|
{
|
|
SetMotorTorque(frontLeftCollider, torque * 0.5f);
|
|
SetMotorTorque(frontRightCollider, torque * 0.5f);
|
|
SetMotorTorque(rearLeftCollider, torque);
|
|
SetMotorTorque(rearRightCollider, torque);
|
|
}
|
|
|
|
private void ApplyBrake(float torque)
|
|
{
|
|
SetBrakeTorque(frontLeftCollider, torque);
|
|
SetBrakeTorque(frontRightCollider, torque);
|
|
SetBrakeTorque(rearLeftCollider, torque);
|
|
SetBrakeTorque(rearRightCollider, torque);
|
|
}
|
|
|
|
private void ApplyDownforce()
|
|
{
|
|
body.AddForce(-transform.up * downforce * body.linearVelocity.magnitude, ForceMode.Force);
|
|
}
|
|
|
|
private void UpdateWheelVisuals()
|
|
{
|
|
SyncWheelVisual(frontLeftCollider, frontLeftVisual);
|
|
SyncWheelVisual(frontRightCollider, frontRightVisual);
|
|
SyncWheelVisual(rearLeftCollider, rearLeftVisual);
|
|
SyncWheelVisual(rearRightCollider, rearRightVisual);
|
|
}
|
|
|
|
private void ResolveTarget()
|
|
{
|
|
if (target != null || !autoFindPlayer)
|
|
return;
|
|
|
|
if (Time.time < nextTargetSearchTime)
|
|
return;
|
|
|
|
nextTargetSearchTime = Time.time + targetSearchInterval;
|
|
ArcadeBuggyController player = Object.FindFirstObjectByType<ArcadeBuggyController>();
|
|
if (player != null)
|
|
target = player.transform;
|
|
}
|
|
|
|
private void AutoFindReferences()
|
|
{
|
|
frontLeftCollider ??= FindChildComponent<WheelCollider>("WheelCollider_FL");
|
|
frontRightCollider ??= FindChildComponent<WheelCollider>("WheelCollider_FR");
|
|
rearLeftCollider ??= FindChildComponent<WheelCollider>("WheelCollider_RL");
|
|
rearRightCollider ??= FindChildComponent<WheelCollider>("WheelCollider_RR");
|
|
|
|
frontLeftVisual ??= FindChildTransform("H_Wheel_FL");
|
|
frontRightVisual ??= FindChildTransform("H_Wheel_FR");
|
|
rearLeftVisual ??= FindChildTransform("H_Wheel_RL");
|
|
rearRightVisual ??= FindChildTransform("H_Wheel_RR");
|
|
}
|
|
|
|
private T FindChildComponent<T>(string childName) where T : Component
|
|
{
|
|
Transform child = FindChildTransform(childName);
|
|
return child != null ? child.GetComponent<T>() : null;
|
|
}
|
|
|
|
private Transform FindChildTransform(string childName)
|
|
{
|
|
Transform[] children = GetComponentsInChildren<Transform>(true);
|
|
for (int i = 0; i < children.Length; i++)
|
|
{
|
|
if (children[i].name == childName)
|
|
return children[i];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static void SetMotorTorque(WheelCollider wheel, float torque)
|
|
{
|
|
if (wheel != null)
|
|
wheel.motorTorque = torque;
|
|
}
|
|
|
|
private static void SetBrakeTorque(WheelCollider wheel, float torque)
|
|
{
|
|
if (wheel != null)
|
|
wheel.brakeTorque = torque;
|
|
}
|
|
|
|
private static void SyncWheelVisual(WheelCollider collider, Transform visual)
|
|
{
|
|
if (collider == null || visual == null)
|
|
return;
|
|
|
|
collider.GetWorldPose(out Vector3 position, out Quaternion rotation);
|
|
visual.SetPositionAndRotation(position, rotation);
|
|
}
|
|
}
|
|
}
|