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}" ) ;
2026-09-01 16:33:49 +01:00
// --- GAME MANAGER HOOKS FOR PLAYER ---
if ( gameObject . CompareTag ( "Player" ) & & GameManager . Instance ! = null )
{
GameManager . Instance . OnPlayerHit ( amount ) ;
}
2026-08-28 16:09:06 +01:00
if ( currentHealth < = 0f )
{
DestroyCar ( ) ;
2026-09-01 16:33:49 +01:00
if ( gameObject . CompareTag ( "Player" ) & & GameManager . Instance ! = null )
{
GameManager . Instance . GameOver ( ) ;
}
2026-08-28 16:09:06 +01:00
}
}
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 ) ;
}
}
}