using System.Collections; using UnityEngine; using UnityEngine.AI; [RequireComponent(typeof(NavMeshAgent), typeof(Animator))] public class NPCController : MonoBehaviour { public enum NPCState { Walking, Drinking, Golfing, Ragdolled } [Header("Current State")] public NPCState currentState = NPCState.Walking; [Header("Wander Settings")] public float wanderRadius = 15f; [Tooltip("How long they stand still to drink or play golf.")] public float minActionTime = 3f; public float maxActionTime = 7f; [Header("Impact & Physics")] [Tooltip("How violently they get thrown when hit by a car.")] public float hitForceMultiplier = 1.2f; private NavMeshAgent agent; private Animator anim; private Rigidbody[] ragdollBodies; private Collider[] ragdollColliders; private Collider mainCollider; void Start() { agent = GetComponent(); anim = GetComponent(); mainCollider = GetComponent(); // Find all ragdoll bones generated by Unity's Ragdoll Wizard ragdollBodies = GetComponentsInChildren(); ragdollColliders = GetComponentsInChildren(); // Disable ragdoll physics on spawn so they can walk normally foreach (Rigidbody rb in ragdollBodies) { // Skip the root if it has a rigidbody if (rb.gameObject == this.gameObject) continue; rb.isKinematic = true; } foreach (Collider col in ragdollColliders) { if (col.gameObject == this.gameObject) continue; col.enabled = false; } StartCoroutine(BehaviorRoutine()); } void Update() { if (currentState == NPCState.Ragdolled) return; // Pass the movement speed to the Animator (Make sure you have a "Speed" float parameter in your Animator!) if (anim != null) { anim.SetFloat("Speed", agent.velocity.magnitude); } } private IEnumerator BehaviorRoutine() { while (currentState != NPCState.Ragdolled) { if (currentState == NPCState.Walking) { // Pick a random spot on the golf course Vector3 randomDir = Random.insideUnitSphere * wanderRadius; randomDir += transform.position; if (NavMesh.SamplePosition(randomDir, out NavMeshHit hit, wanderRadius, NavMesh.AllAreas)) { agent.SetDestination(hit.position); } // Wait until they reach the destination while (agent.pathPending || agent.remainingDistance > 0.5f) { if (currentState == NPCState.Ragdolled) yield break; yield return null; } // Reached destination! Pick a random action int action = Random.Range(0, 3); if (action == 0) currentState = NPCState.Walking; // Just keep walking else if (action == 1) currentState = NPCState.Drinking; else currentState = NPCState.Golfing; } else if (currentState == NPCState.Drinking) { // Play drink animation if (anim != null) anim.SetTrigger("Drink"); yield return new WaitForSeconds(Random.Range(minActionTime, maxActionTime)); currentState = NPCState.Walking; } else if (currentState == NPCState.Golfing) { // Play golf swing animation if (anim != null) anim.SetTrigger("GolfSwing"); yield return new WaitForSeconds(Random.Range(minActionTime, maxActionTime)); currentState = NPCState.Walking; } yield return null; } } // Detect if a car crashes into them private void OnCollisionEnter(Collision collision) { if (currentState == NPCState.Ragdolled) return; // If hit with enough speed (e.g. a car) if (collision.relativeVelocity.magnitude > 4f) { Vector3 impactPoint = collision.GetContact(0).point; EnableRagdoll(collision.relativeVelocity, impactPoint); } } // Also support triggers just in case your car uses trigger hitboxes private void OnTriggerEnter(Collider other) { if (currentState == NPCState.Ragdolled) return; Rigidbody otherRb = other.GetComponentInParent(); if (otherRb != null && otherRb.linearVelocity.magnitude > 4f) { EnableRagdoll(otherRb.linearVelocity, transform.position); } } private void EnableRagdoll(Vector3 impactVelocity, Vector3 impactPoint) { currentState = NPCState.Ragdolled; // Turn off AI and Animations if (agent != null) agent.enabled = false; if (anim != null) anim.enabled = false; if (mainCollider != null) mainCollider.enabled = false; StopAllCoroutines(); // Calculate how hard they get thrown // Add a little upward force so they fly through the air! Vector3 force = (impactVelocity + Vector3.up * 3f) * hitForceMultiplier; // Turn ON the ragdoll colliders foreach (Collider col in ragdollColliders) { if (col.gameObject != this.gameObject) col.enabled = true; } // Turn ON the ragdoll physics and apply the crash force foreach (Rigidbody rb in ragdollBodies) { if (rb.gameObject != this.gameObject) { rb.isKinematic = false; rb.useGravity = true; // VelocityChange ignores the mass of the bone, guaranteeing they fly away violently rb.AddForceAtPosition(force, impactPoint, ForceMode.VelocityChange); } } } }