Files
TopDown/Assets/Scripts/NPC.cs
2026-08-05 08:18:15 +01:00

209 lines
5.7 KiB
C#

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<string> vendorInventory = new List<string>();
[Header("Events")]
[SerializeField] private UnityEvent<GameObject> onInteractionStart;
[SerializeField] private UnityEvent<string> 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<Collider>();
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($"<color=cyan>{npcName}:</color> \"{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);
}
}
}