Files
CartoonCopChase/Assets/Scripts/CarHealth.cs

245 lines
9.4 KiB
C#
Raw Permalink Normal View History

2026-08-28 16:09:06 +01:00
using UnityEngine;
using ArcadeVP;
using UnityEngine.TextCore.Text;
public class CarHealth : MonoBehaviour
{
[Header("Health Settings")]
public float maxHealth = 100f;
public float currentHealth;
public bool isDestroyed = false;
public GameObject charactersHips;
[Header("Damage Physics")]
[Tooltip("Layers that can cause crash damage (e.g., Default, Obstacles). Roads and ground should NOT be in this mask. Cars will always damage each other regardless of layer.")]
public LayerMask damagingLayers = Physics.DefaultRaycastLayers; // By default hits most things, user can tweak in Inspector
[Tooltip("Minimum impact speed (relative velocity) required to take damage. Scraping is usually 1-3 m/s, ramming is 10+ m/s.")]
public float minImpactSpeed = 5f;
[Tooltip("Damage taken per 1 unit of speed above the minimum impact speed.")]
public float damageMultiplier = 2.5f;
[Tooltip("Cooldown between damage events to prevent multi-hit frame glitches.")]
public float damageCooldown = 0.2f;
private float lastDamageTime = 0f;
private ArcadeVehicleController vehicleController;
void Start()
{
currentHealth = maxHealth;
vehicleController = GetComponent<ArcadeVehicleController>();
// Ensure we receive collisions even if the colliders/rigidbodies are on child objects (like SphereRB or CarBody)
if (vehicleController != null)
{
AttachCollisionForwarder(vehicleController.rb?.gameObject);
AttachCollisionForwarder(vehicleController.carBody?.gameObject);
}
else
{
AttachCollisionForwarder(gameObject);
}
// --- FREEZE RAGDOLL ON SPAWN ---
// Unity's ragdoll wizard turns physics on by default. We must freeze them
// until the car is destroyed so the driver stays seated!
if (charactersHips != null)
{
Rigidbody[] ragdollBodies = charactersHips.GetComponentsInChildren<Rigidbody>();
foreach (Rigidbody rb in ragdollBodies)
{
rb.isKinematic = true;
}
// Disable their colliders so they don't glitch the car's physics or cause self-damage!
Collider[] ragdollColliders = charactersHips.GetComponentsInChildren<Collider>();
foreach (Collider col in ragdollColliders)
{
col.enabled = false;
}
}
}
private void AttachCollisionForwarder(GameObject targetObj)
{
if (targetObj == null) return;
CollisionForwarder forwarder = targetObj.GetComponent<CollisionForwarder>();
if (forwarder == null)
{
forwarder = targetObj.AddComponent<CollisionForwarder>();
}
forwarder.healthScript = this;
}
public void HandleCollision(Collision collision)
{
if (Time.time < lastDamageTime + damageCooldown) return;
// 1. Check if we hit another car
CarHealth otherCar = collision.gameObject.GetComponentInParent<CarHealth>();
if (otherCar == null)
{
CollisionForwarder forwarder = collision.gameObject.GetComponent<CollisionForwarder>();
if (forwarder != null) otherCar = forwarder.healthScript;
}
// 2. Filter out road/ground damage
// If we didn't hit a car, check if the object is in our allowed damaging layers mask
if (otherCar == null)
{
if ((damagingLayers.value & (1 << collision.gameObject.layer)) == 0)
{
return; // Ignored! (e.g. hit the Road or Drivable layer)
}
}
// 3. relativeVelocity is the exact difference in physical speed between the two colliding objects!
float impactSpeed = collision.relativeVelocity.magnitude;
if (impactSpeed >= minImpactSpeed)
{
float speedAdvantageMultiplier = 1f;
// If it's a car vs car crash, calculate who is faster!
if (otherCar != null && vehicleController.carBody != null && otherCar.vehicleController.carBody != null)
{
float mySpeed = vehicleController.carBody.linearVelocity.magnitude;
float theirSpeed = otherCar.vehicleController.carBody.linearVelocity.magnitude;
if (mySpeed > theirSpeed)
{
// We are faster: Take LESS damage (reduces by 5% per 1m/s advantage, max 80% reduction)
float speedDiff = mySpeed - theirSpeed;
speedAdvantageMultiplier = Mathf.Clamp(1f - (speedDiff * 0.05f), 0.2f, 1f);
}
else if (theirSpeed > mySpeed)
{
// They are faster: We take MORE damage (increases by 5% per 1m/s disadvantage, max 200% penalty)
float speedDiff = theirSpeed - mySpeed;
speedAdvantageMultiplier = Mathf.Clamp(1f + (speedDiff * 0.05f), 1f, 2.0f);
}
}
float damageToTake = (impactSpeed - minImpactSpeed) * damageMultiplier * speedAdvantageMultiplier;
TakeDamage(damageToTake);
}
}
public void TakeDamage(float amount)
{
if (currentHealth <= 0) return; // Already destroyed
currentHealth -= amount;
currentHealth = Mathf.Clamp(currentHealth, 0f, maxHealth);
lastDamageTime = Time.time;
Debug.Log($"<b>{gameObject.name}</b> took <color=orange>{amount:F1}</color> damage! Health: {currentHealth:F1}");
if (currentHealth <= 0f)
{
DestroyCar();
}
}
public void RestoreHealth(float amount)
{
if (currentHealth <= 0) return; // Can't heal a completely destroyed car (optional)
currentHealth += amount;
currentHealth = Mathf.Clamp(currentHealth, 0f, maxHealth);
Debug.Log($"<b>{gameObject.name}</b> restored <color=green>{amount:F1}</color> health! Health: {currentHealth:F1}");
}
private void DestroyCar()
{
isDestroyed = true;
Debug.Log($"<color=red><b>{gameObject.name} has been destroyed!</b></color>");
// Disable driving controls so the wreck rolls to a stop
if (vehicleController != null)
{
vehicleController.enabled = false;
}
// --- EJECT DRIVER (RAGDOLL) ---
if (charactersHips != null)
{
// Unparent from the car so the driver flies free
charactersHips.transform.SetParent(null);
// Teleport them up slightly so they don't snag on the steering wheel
charactersHips.transform.position += Vector3.up * 1.5f;
// Kill the Animator (search parents AND children to guarantee we find it)
Animator anim = charactersHips.GetComponentInParent<Animator>();
if (anim == null) anim = charactersHips.GetComponentInChildren<Animator>();
if (anim != null) anim.enabled = false;
// Enable ragdoll physics and apply force
Rigidbody[] ragdollBodies = charactersHips.GetComponentsInChildren<Rigidbody>();
Collider[] ragdollColliders = charactersHips.GetComponentsInChildren<Collider>();
// Turn all their colliders back on so they bounce on the road
foreach (Collider col in ragdollColliders)
{
col.enabled = true;
}
// FALLBACK: If you haven't set up a Unity Ragdoll yet (0 rigidbodies found),
// we will instantly create a dummy physics body so they still fly out!
if (ragdollBodies.Length == 0)
{
Rigidbody dummyRb = charactersHips.gameObject.AddComponent<Rigidbody>();
BoxCollider box = charactersHips.gameObject.AddComponent<BoxCollider>();
box.size = new Vector3(0.5f, 1.5f, 0.5f); // Rough human size
ragdollBodies = new Rigidbody[] { dummyRb };
}
Vector3 carVelocity = vehicleController != null && vehicleController.carBody != null ? vehicleController.carBody.linearVelocity : Vector3.zero;
Vector3 ejectForce = transform.forward * 15f + transform.up * 8f;
foreach (Rigidbody rb in ragdollBodies)
{
rb.isKinematic = false;
rb.useGravity = true;
rb.linearVelocity = carVelocity;
rb.AddForce(ejectForce, ForceMode.Impulse);
// Add some random spin to make the crash look absolutely brutal!
rb.AddTorque(Random.insideUnitSphere * 10f, ForceMode.Impulse);
}
}
// Disable AI script if it's an AI car
AIController ai = GetComponent<AIController>();
if (ai != null)
{
ai.currentState = AIController.AIState.Destroyed;
ai.enabled = false;
}
// Optional: Trigger explosion effect here
// Instantiate(explosionPrefab, transform.position, Quaternion.identity);
// We leave the GameObject alive so the wrecked chassis stays on the road as an obstacle!
}
}
// Small helper class to route collisions from child Rigidbodies back to the main CarHealth script
public class CollisionForwarder : MonoBehaviour
{
public CarHealth healthScript;
private void OnCollisionEnter(Collision collision)
{
if (healthScript != null)
{
healthScript.HandleCollision(collision);
}
}
}