Files
CartoonCopChase/Assets/Scripts/NPCController.cs

177 lines
5.9 KiB
C#
Raw Normal View History

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<NavMeshAgent>();
anim = GetComponent<Animator>();
mainCollider = GetComponent<Collider>();
// Find all ragdoll bones generated by Unity's Ragdoll Wizard
ragdollBodies = GetComponentsInChildren<Rigidbody>();
ragdollColliders = GetComponentsInChildren<Collider>();
// 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<Rigidbody>();
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);
}
}
}
}