245 lines
9.4 KiB
C#
245 lines
9.4 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
|