Building out Golf Level and mission states and improved AI
This commit is contained in:
176
Assets/Scripts/NPCController.cs
Normal file
176
Assets/Scripts/NPCController.cs
Normal file
@@ -0,0 +1,176 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user