using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; public enum NPCType { Villager, QuestGiver, Vendor, Guard, Trainer } public enum NPCRelationship { Hostile, Neutral, Friendly } public enum NPCState { Idle, Wandering, Talking, Attacking, Dead } public enum DialogueBehavior { SingleLine, Sequential, Random } [RequireComponent(typeof(Collider))] public class NPC : MonoBehaviour, IInteractable { [Header("Identity")] [SerializeField] private string npcName = "Villager"; [SerializeField] private string npcTitle = "Townsperson"; [SerializeField] private Sprite portrait; [Header("NPC Configuration")] [SerializeField] private NPCType npcType = NPCType.Villager; [SerializeField] private NPCRelationship relationship = NPCRelationship.Neutral; [SerializeField] private NPCState currentState = NPCState.Idle; [SerializeField] private float interactionRadius = 2.5f; public NPCRelationship Relationship { get => relationship; set => relationship = value; } public NPCState CurrentState { get => currentState; set => currentState = value; } [Header("Dialogue Settings")] [SerializeField] private DialogueBehavior dialogueType = DialogueBehavior.Sequential; [TextArea(2, 5)] [SerializeField] private string[] dialogueLines = new string[] { "Hello traveler!" }; [SerializeField] private AudioClip talkSound; [Header("Wander Settings")] [SerializeField] private float wanderRadius = 5.0f; [SerializeField] private float wanderInterval = 4.0f; [SerializeField] private float moveSpeed = 2.0f; [Header("Vendor / Quest Data")] [SerializeField] private int questId = 0; [SerializeField] private List vendorInventory = new List(); [Header("Events")] [SerializeField] private UnityEvent onInteractionStart; [SerializeField] private UnityEvent onSpeakLine; private int currentDialogueIndex = 0; private Vector3 initialPosition; private Vector3 wanderTarget; private float wanderTimer; private void Start() { initialPosition = transform.position; wanderTimer = wanderInterval; // Ensure collider is present for raycasting Collider col = GetComponent(); if (col != null && !col.enabled) col.enabled = true; } private void Update() { if (currentState == NPCState.Wandering) { ProcessWander(); } } // --- IInteractable Implementation --- public void Interact(GameObject interactor) { // Face player on interact Vector3 dir = (interactor.transform.position - transform.position); dir.y = 0; if (dir != Vector3.zero) { transform.rotation = Quaternion.LookRotation(dir); } currentState = NPCState.Talking; // Trigger dialogue / action based on NPC type SpeakNextLine(); onInteractionStart?.Invoke(interactor); if (talkSound != null) { AudioSource.PlayClipAtPoint(talkSound, transform.position); } Debug.Log($"[{npcName} ({npcType})] Interacted with {interactor.name}"); } public string GetInteractionPrompt() { return npcType switch { NPCType.QuestGiver => $"Talk to {npcName} [Quest]", NPCType.Vendor => $"Trade with {npcName}", NPCType.Guard => $"Ask {npcName}", _ => $"Talk to {npcName}" }; } public float GetInteractionRadius() => interactionRadius; public Transform GetTransform() => transform; // --- Dialogue Handling --- private void SpeakNextLine() { if (dialogueLines == null || dialogueLines.Length == 0) return; string lineToSpeak = ""; switch (dialogueType) { case DialogueBehavior.SingleLine: lineToSpeak = dialogueLines[0]; break; case DialogueBehavior.Sequential: lineToSpeak = dialogueLines[currentDialogueIndex]; currentDialogueIndex = (currentDialogueIndex + 1) % dialogueLines.Length; break; case DialogueBehavior.Random: int randIndex = UnityEngine.Random.Range(0, dialogueLines.Length); lineToSpeak = dialogueLines[randIndex]; break; } onSpeakLine?.Invoke(lineToSpeak); Debug.Log($"{npcName}: \"{lineToSpeak}\""); } // --- Simple Wandering Logic --- private void ProcessWander() { wanderTimer += Time.deltaTime; if (wanderTimer >= wanderInterval) { wanderTimer = 0f; Vector2 randomCircle = UnityEngine.Random.insideUnitCircle * wanderRadius; wanderTarget = initialPosition + new Vector3(randomCircle.x, 0, randomCircle.y); } if (wanderTarget != Vector3.zero) { Vector3 moveDir = (wanderTarget - transform.position); moveDir.y = 0; if (moveDir.magnitude > 0.3f) { transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(moveDir), 8.0f * Time.deltaTime); transform.position += moveDir.normalized * moveSpeed * Time.deltaTime; } } } private void OnDrawGizmosSelected() { Gizmos.color = Color.yellow; Gizmos.DrawWireSphere(transform.position, interactionRadius); if (currentState == NPCState.Wandering) { Gizmos.color = Color.blue; Gizmos.DrawWireSphere(initialPosition, wanderRadius); } } }