177 lines
5.9 KiB
C#
177 lines
5.9 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|