update
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AI;
|
||||
using System.Linq;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
[Serializable]
|
||||
public class DialogueResult { public string text; public float speedMod; public float suspicionMod; }
|
||||
|
||||
// Quy trình ưu tiên: Né đòn --> Bắn hạ (Artifact) --> Đuổi theo (Vector) --> Điều tra --> Nói chuyện --> Đi tuần
|
||||
[RequireComponent(typeof(NavMeshAgent))]
|
||||
@@ -42,13 +47,19 @@ public class EnemyAI : MonoBehaviour
|
||||
|
||||
[Header("Conversation Settings")]
|
||||
public string npcName = "Guard";
|
||||
[TextArea] public string persona = "You are a grumpy guard protecting gold.";
|
||||
public float talkRange = 10f;
|
||||
public float talkCooldown = 15f;
|
||||
[TextArea] public string persona = "You are a bored security guard. You love coffee and hate night shifts.";
|
||||
public float talkRange = 12f;
|
||||
public float talkCooldown = 60f;
|
||||
private float lastTalkTime;
|
||||
public bool isTalking; // Public để debug
|
||||
private EnemyAI talkingPartner;
|
||||
private Hallucinate.UI.ChatBubble chatBubble;
|
||||
|
||||
[Header("Suspicion Settings")]
|
||||
public float suspicionLevel = 0f;
|
||||
public float investigationThreshold = 30f;
|
||||
public float alertNeighborsThreshold = 70f;
|
||||
public float alertRange = 20f;
|
||||
|
||||
public Node rootNode;
|
||||
|
||||
@@ -59,7 +70,6 @@ public class EnemyAI : MonoBehaviour
|
||||
fov = GetComponent<FieldOfView>();
|
||||
chatBubble = GetComponentInChildren<Hallucinate.UI.ChatBubble>(true);
|
||||
|
||||
// Rigidbody setup cho Unity 6
|
||||
rb.isKinematic = true;
|
||||
rb.freezeRotation = true;
|
||||
|
||||
@@ -69,18 +79,7 @@ public class EnemyAI : MonoBehaviour
|
||||
if (playerObj != null) player = playerObj.transform;
|
||||
}
|
||||
|
||||
/*
|
||||
// Tạm thời comment đoạn này để tránh lỗi Tag chưa định nghĩa
|
||||
if (patrolWaypoints == null || patrolWaypoints.Length == 0)
|
||||
{
|
||||
patrolWaypoints = GameObject.FindGameObjectsWithTag("PatrolPoint")
|
||||
.Select(go => go.transform).ToArray();
|
||||
}
|
||||
*/
|
||||
|
||||
InitTree();
|
||||
|
||||
Debug.Log($"<color=white>[AI {npcName}] Init complete. Waypoints: {patrolWaypoints.Length}</color>");
|
||||
}
|
||||
|
||||
void InitTree()
|
||||
@@ -107,12 +106,10 @@ public class EnemyAI : MonoBehaviour
|
||||
{
|
||||
if (player == null) return;
|
||||
|
||||
// An toàn cho NavMeshAgent
|
||||
if (!agent.isOnNavMesh)
|
||||
{
|
||||
Debug.LogWarning($"[AI {npcName}] NPC is NOT on NavMesh!");
|
||||
return;
|
||||
}
|
||||
if (!agent.isOnNavMesh) return;
|
||||
|
||||
// Decay suspicion
|
||||
suspicionLevel = Mathf.Max(0, suspicionLevel - Time.deltaTime * 0.5f);
|
||||
|
||||
if (!isTalking && !isDodging && agent.isStopped)
|
||||
agent.isStopped = false;
|
||||
@@ -139,13 +136,21 @@ public class EnemyAI : MonoBehaviour
|
||||
private NodeState CheckCanSeePlayer()
|
||||
{
|
||||
bool canSee = fov != null && fov.canSeePlayer;
|
||||
if (canSee) StopConversation();
|
||||
if (canSee) { StopConversation(); AlertNeighbors(); suspicionLevel = 100; }
|
||||
return canSee ? NodeState.Success : NodeState.Failure;
|
||||
}
|
||||
|
||||
private NodeState CheckHasInvestigateTarget()
|
||||
{
|
||||
return (fov != null && fov.lastKnownPlayerPosition != Vector3.zero) ? NodeState.Success : NodeState.Failure;
|
||||
if (fov != null && fov.lastKnownPlayerPosition != Vector3.zero)
|
||||
{
|
||||
if (suspicionLevel > investigationThreshold)
|
||||
{
|
||||
// Randomly decide to check or stay on patrol
|
||||
if (Random.value < (suspicionLevel / 100f)) return NodeState.Success;
|
||||
}
|
||||
}
|
||||
return NodeState.Failure;
|
||||
}
|
||||
|
||||
private NodeState CheckCanTalkToNPC()
|
||||
@@ -154,7 +159,14 @@ public class EnemyAI : MonoBehaviour
|
||||
if (Time.time < lastTalkTime + talkCooldown) return NodeState.Failure;
|
||||
if (isTalking) return NodeState.Success;
|
||||
|
||||
// Quét tìm NPC
|
||||
if (Hallucinate.AI.ConversationManager.Instance == null)
|
||||
{
|
||||
Debug.LogError($"[AI {npcName}] ConversationManager Instance is NULL!");
|
||||
return NodeState.Failure;
|
||||
}
|
||||
|
||||
if (!Hallucinate.AI.ConversationManager.Instance.CanStartConversation()) return NodeState.Failure;
|
||||
|
||||
Collider[] hitColliders = Physics.OverlapSphere(transform.position, talkRange);
|
||||
foreach (var hit in hitColliders)
|
||||
{
|
||||
@@ -163,10 +175,12 @@ public class EnemyAI : MonoBehaviour
|
||||
EnemyAI other = hit.GetComponentInParent<EnemyAI>();
|
||||
if (other != null && !other.isTalking && Time.time >= other.lastTalkTime + talkCooldown)
|
||||
{
|
||||
// Chỉ ID nhỏ hơn gọi để tránh trùng
|
||||
if (gameObject.GetInstanceID() < other.gameObject.GetInstanceID())
|
||||
// Kiểm tra khoảng cách thực tế giữa 2 NPC
|
||||
float dist = Vector3.Distance(transform.position, other.transform.position);
|
||||
if (dist <= talkRange && gameObject.GetInstanceID() < other.gameObject.GetInstanceID())
|
||||
{
|
||||
talkingPartner = other;
|
||||
Debug.Log($"<color=green>[AI {npcName}]</color> Found partner: {other.npcName}. Starting conversation.");
|
||||
Hallucinate.AI.ConversationManager.Instance.StartConversation(this, other);
|
||||
return NodeState.Success;
|
||||
}
|
||||
}
|
||||
@@ -178,50 +192,75 @@ public class EnemyAI : MonoBehaviour
|
||||
|
||||
#region ACTIONS
|
||||
|
||||
public void HearNoise(Vector3 location, float volume)
|
||||
{
|
||||
suspicionLevel += volume * 15f;
|
||||
if (fov != null) fov.lastKnownPlayerPosition = location;
|
||||
if (suspicionLevel >= alertNeighborsThreshold) AlertNeighbors();
|
||||
StopConversation();
|
||||
Debug.Log($"<color=orange>[AI {npcName}]</color> Heard noise! Suspicion: {suspicionLevel}");
|
||||
}
|
||||
|
||||
public void AlertNeighbors()
|
||||
{
|
||||
Collider[] hitColliders = Physics.OverlapSphere(transform.position, alertRange);
|
||||
foreach (var hit in hitColliders)
|
||||
{
|
||||
EnemyAI neighbor = hit.GetComponentInParent<EnemyAI>();
|
||||
if (neighbor != null && neighbor != this)
|
||||
{
|
||||
neighbor.suspicionLevel = Mathf.Max(neighbor.suspicionLevel, 50f);
|
||||
if (fov != null && neighbor.fov != null) neighbor.fov.lastKnownPlayerPosition = fov.lastKnownPlayerPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private NodeState ActionTalk()
|
||||
{
|
||||
if (talkingPartner == null) return NodeState.Failure;
|
||||
if (!isTalking)
|
||||
if (isTalking)
|
||||
{
|
||||
isTalking = true;
|
||||
agent.isStopped = true;
|
||||
FaceTarget(talkingPartner.transform.position);
|
||||
|
||||
Debug.Log($"<color=yellow>[AI {npcName}] Talking to {talkingPartner.npcName}</color>");
|
||||
|
||||
string prompt = $"You are {npcName}. Speak 1 short sentence in English to your colleague {talkingPartner.npcName} about the shift.";
|
||||
Hallucinate.AI.GeminiService.Instance.GetResponse(persona, prompt, (response) => {
|
||||
if (chatBubble != null) chatBubble.Show(response);
|
||||
Invoke(nameof(EndConversation), 5f);
|
||||
});
|
||||
talkingPartner.OnPartnerTalked(this);
|
||||
|
||||
// Nếu bạn diễn đi quá xa trong khi đang nói, ngắt hội thoại
|
||||
if (talkingPartner != null)
|
||||
{
|
||||
if (Vector3.Distance(transform.position, talkingPartner.transform.position) > talkRange + 2f)
|
||||
{
|
||||
Debug.Log($"[AI {npcName}] Partner moved too far. Ending conversation.");
|
||||
StopConversation();
|
||||
return NodeState.Failure;
|
||||
}
|
||||
}
|
||||
|
||||
return NodeState.Running;
|
||||
}
|
||||
return NodeState.Running;
|
||||
return NodeState.Failure;
|
||||
}
|
||||
|
||||
public void OnPartnerTalked(EnemyAI partner)
|
||||
public void ProcessDialogueResult(string json)
|
||||
{
|
||||
isTalking = true;
|
||||
talkingPartner = partner;
|
||||
agent.isStopped = true;
|
||||
FaceTarget(partner.transform.position);
|
||||
Invoke(nameof(EndConversation), 6f);
|
||||
}
|
||||
|
||||
private void EndConversation()
|
||||
{
|
||||
isTalking = false;
|
||||
lastTalkTime = Time.time;
|
||||
if (agent != null && agent.isOnNavMesh) agent.isStopped = false;
|
||||
talkingPartner = null;
|
||||
try
|
||||
{
|
||||
DialogueResult result = JsonUtility.FromJson<DialogueResult>(json);
|
||||
if (chatBubble != null) chatBubble.Show(result.text);
|
||||
|
||||
// Apply minor stat mods
|
||||
moveSpeed += result.speedMod;
|
||||
suspicionLevel = Mathf.Clamp(suspicionLevel + result.suspicionMod, 0, 100);
|
||||
lastTalkTime = Time.time;
|
||||
|
||||
Debug.Log($"<color=green>[AI {npcName}]</color> Conv result: {result.text} | SpeedMod: {result.speedMod}");
|
||||
}
|
||||
catch { if (chatBubble != null) chatBubble.Show(json); }
|
||||
}
|
||||
|
||||
private void StopConversation()
|
||||
{
|
||||
if (!isTalking) return;
|
||||
CancelInvoke(nameof(EndConversation));
|
||||
EndConversation();
|
||||
if (chatBubble != null) chatBubble.Show("Wait, what's that?!", 2f);
|
||||
if (isTalking && Hallucinate.AI.ConversationManager.Instance != null)
|
||||
{
|
||||
Hallucinate.AI.ConversationManager.Instance.InterruptConversation(this);
|
||||
if (chatBubble != null) chatBubble.Show("Wait, what was that?!", 2f);
|
||||
}
|
||||
}
|
||||
|
||||
private NodeState ActionPatrol()
|
||||
@@ -229,7 +268,7 @@ public class EnemyAI : MonoBehaviour
|
||||
if (patrolWaypoints == null || patrolWaypoints.Length == 0) return NodeState.Failure;
|
||||
|
||||
agent.isStopped = false;
|
||||
agent.speed = moveSpeed * 0.5f;
|
||||
agent.speed = moveSpeed * (suspicionLevel > 20 ? 0.7f : 0.5f); // Walk faster if suspicious
|
||||
|
||||
var target = patrolWaypoints[currentWaypointIndex];
|
||||
agent.SetDestination(target.position);
|
||||
@@ -259,10 +298,16 @@ public class EnemyAI : MonoBehaviour
|
||||
agent.isStopped = false;
|
||||
agent.speed = moveSpeed * 0.7f;
|
||||
agent.SetDestination(fov.lastKnownPlayerPosition);
|
||||
|
||||
if (Vector3.Distance(transform.position, fov.lastKnownPlayerPosition) < 1.5f)
|
||||
{
|
||||
fov.lastKnownPlayerPosition = Vector3.zero;
|
||||
return NodeState.Success;
|
||||
currentWaitTime += Time.deltaTime;
|
||||
if (currentWaitTime > 3f) // Look around for 3 seconds
|
||||
{
|
||||
fov.lastKnownPlayerPosition = Vector3.zero;
|
||||
suspicionLevel *= 0.5f; // Decrease suspicion after check
|
||||
return NodeState.Success;
|
||||
}
|
||||
}
|
||||
return NodeState.Running;
|
||||
}
|
||||
@@ -300,7 +345,12 @@ public class EnemyAI : MonoBehaviour
|
||||
isDodging = false;
|
||||
}
|
||||
|
||||
private void FaceTarget(Vector3 pos)
|
||||
public void SetTalkingPartner(EnemyAI partner)
|
||||
{
|
||||
talkingPartner = partner;
|
||||
}
|
||||
|
||||
public void FaceTarget(Vector3 pos)
|
||||
{
|
||||
Vector3 dir = (pos - transform.position);
|
||||
dir.y = 0;
|
||||
|
||||
Reference in New Issue
Block a user