using UnityEngine; using CnControls; public enum SurfaceDetection { RayCast, SphereCast } public class VehicleController : MonoBehaviour { [SerializeField] float currentSpeed = 0f; [SerializeField] float steeringAI = 0f; [SerializeField] 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; 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(); targetTracker = GetComponent(); wheelRadius = sphereRigidbody.GetComponent().radius; nosController = GetComponent(); // maxGroundDistance = 1.1f; maxGroundDistance = wheelRadius + 0.5f; InitializeVehicle(); } public void InitializeVehicle() { isRunning = true; if (!isAIControlled) 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() { inputVertical = CnInputManager.GetAxis("Vertical"); inputHorizontal = CnInputManager.GetAxis("Horizontal"); } private void UpdateVisuals() { counterSteerValue = (Mathf.Abs(velocityLocal.x) > 20f) ? -1f : 1f; if (wheelFrontLeft) wheelFrontLeft.localRotation = Quaternion.Slerp(wheelFrontLeft.localRotation, Quaternion.Euler(0, 45f * counterSteerValue * steeringAI, 0), 0.1f); if (wheelFrontRight) wheelFrontRight.localRotation = Quaternion.Slerp(wheelFrontRight.localRotation, Quaternion.Euler(0, 45f * counterSteerValue * steeringAI, 0), 0.1f); frontLeftAxle?.Rotate(Vector3.right * (CurrentSpeed * 0.75f)); frontRightAxle?.Rotate(Vector3.right * (CurrentSpeed * 0.75f)); wheelRearLeft?.Rotate(Vector3.right * (CurrentSpeed * 0.75f)); wheelRearRight?.Rotate(Vector3.right * (CurrentSpeed * 0.75f)); if (speedVisualEffects) speedVisualEffects.SetActive(velocityLocal.z > 10); } 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); } } //using UnityEngine; //using CnControls; //using Fusion; //public enum SurfaceDetection //{ // RayCast, // SphereCast //} //public class VehicleController : NetworkBehaviour //{ // [SerializeField] float currentSpeed = 0f; // [SerializeField] float steeringAI = 0f; // [SerializeField] 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; // internal float SteeringSensitivity // { // get => steeringSensitivity; // set => steeringSensitivity = value; // } // public Vector3 LocalVelocity => velocityLocal; // public Transform FollowTarget => cameraFollowTarget; // [Range(0.3f, 1f)] public float steeringFactor = 1f; // public override void Spawned() // { // base.Spawned(); // InitializeVehicle(); // bool isLocalPlayer = Object.HasInputAuthority; // true on the runner that owns this object // bool isHostRunner = Runner.IsServer; // true on the host’s runner // Debug.Log("Spawned"); // // if (local && hostRunner) OR (remote && clientRunner) ⇒ this is the host’s car // if (isLocalPlayer == isHostRunner) // gameObject.name = "PlayerHost"; // else // gameObject.name = "PlayerClient"; // } // // 1) Fusion will call this every tick to collect local input // public override void FixedUpdateNetwork() // { // if (!isRunning || !RaceCountdownManager.Instance || !RaceCountdownManager.Instance.RaceStarted) // return; // if (!Object.HasInputAuthority) return; // Debug.Log("HasInputAuthority"); // if (/*Object.HasInputAuthority &&*/GetInput(out CarNetworkInput data)) // { // inputHorizontal = data.Horizontal; // inputVertical = data.Vertical; // if (data.NOS) ActivateNOS(); // } // // ✅ Only simulate physics on authoritative runner // ApplyControls(); // CheckGround(); // HandleMovement(); // UpdateVisuals(); // OK on all clients // } // // 2) Fusion runs your simulation here instead of Update/FixedUpdate // //public override void FixedUpdateNetwork() // //{ // // if (!isRunning || !RaceCountdownManager.Instance || !RaceCountdownManager.Instance.RaceStarted) // // return; // // if (!Object.HasInputAuthority) // // { // // Debug.Log("🚫 No Input Authority on this player"); // // return; // // } // // if (Object.HasInputAuthority && GetInput(out CarNetworkInput data)) // // { // // inputHorizontal = data.Horizontal; // // inputVertical = data.Vertical; // // Debug.Log("inputVertical: " + inputVertical); // // Debug.Log("inputHorizontal : " + inputHorizontal); // // if (data.NOS) // // ActivateNOS(); // // ApplyControls(); // // CheckGround(); // // HandleMovement(); // // } // // UpdateVisuals(); // //} // private void Awake() // { // vehicleRigidbody = GetComponent(); // targetTracker = GetComponent(); // wheelRadius = sphereRigidbody.GetComponent().radius; // nosController = GetComponent(); // maxGroundDistance = wheelRadius + 0.5f; // InitializeVehicle(); // } // public void InitializeVehicle() // { // isRunning = true; // if (!isAIControlled) // accelerationForce = baseAcceleration; // } // public void StopVehicle() // { // isRunning = false; // velocityLocal = Vector3.zero; // currentSpeed = 0f; // steeringAI = 0f; // } // //private void Update() // //{ // // if (!isRunning) return; // // UpdateVisuals(); // // ProcessInputs(); // // ApplyControls(); // //} // private void ProcessInputs() // { // inputVertical = CnInputManager.GetAxis("Vertical"); // inputHorizontal = CnInputManager.GetAxis("Horizontal"); // } // private void UpdateVisuals() // { // counterSteerValue = (Mathf.Abs(velocityLocal.x) > 20f) ? -1f : 1f; // if (wheelFrontLeft) wheelFrontLeft.localRotation = Quaternion.Slerp(wheelFrontLeft.localRotation, Quaternion.Euler(0, 45f * counterSteerValue * steeringAI, 0), 0.1f); // if (wheelFrontRight) wheelFrontRight.localRotation = Quaternion.Slerp(wheelFrontRight.localRotation, Quaternion.Euler(0, 45f * counterSteerValue * steeringAI, 0), 0.1f); // frontLeftAxle?.Rotate(Vector3.right * (CurrentSpeed * 0.75f)); // frontRightAxle?.Rotate(Vector3.right * (CurrentSpeed * 0.75f)); // wheelRearLeft?.Rotate(Vector3.right * (CurrentSpeed * 0.75f)); // wheelRearRight?.Rotate(Vector3.right * (CurrentSpeed * 0.75f)); // //if (carBodyVisual) // //{ // // if (velocityLocal.z > 1) // // carBodyVisual.localRotation = Quaternion.Slerp(carBodyVisual.localRotation, // // Quaternion.Euler(Mathf.Lerp(0, -5, velocityLocal.z / maxSpeed), 0, Mathf.Clamp(desiredTurnAngle * steeringAI, -bodyTiltAmount, bodyTiltAmount)), 0.05f); // // else // // carBodyVisual.localRotation = Quaternion.Slerp(carBodyVisual.localRotation, Quaternion.identity, 0.05f); // //} // if (speedVisualEffects) // speedVisualEffects.SetActive(velocityLocal.z > 10); // } // 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); // Debug.Log("CurrentSpeed: " + currentSpeed); // } // //private void FixedUpdate() // //{ // // if (!isRunning) return; // // CheckGround(); // // HandleMovement(); // //} // 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() // { // //if (!Object.HasInputAuthority) // //{ // // Debug.LogWarning("Client without InputAuthority is simulating movement — this should not happen!"); // //} // //else // { // //if (Object.HasStateAuthority || Object.HasInputAuthority) // //{ // // velocityLocal = vehicleRigidbody.transform.InverseTransformDirection(vehicleRigidbody.velocity); // //} // velocityLocal = vehicleRigidbody.transform.InverseTransformDirection(vehicleRigidbody.velocity); // if (float.IsNaN(velocityLocal.z) || float.IsInfinity(velocityLocal.z)) // { // Debug.LogWarning($"⚠ Detected invalid velocity: {velocityLocal}. Resetting to zero."); // 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); // //nosController._isBoosting = 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); // } //}