yo
This commit is contained in:
@@ -1,163 +0,0 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.AI;
|
||||
using Invector;
|
||||
using Invector.vEventSystems;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class AnimatorAI : MonoBehaviour, vIAnimatorStateInfoController
|
||||
{
|
||||
protected Animator animator;
|
||||
protected NavMeshAgent agent;
|
||||
protected vHealthController healthController;
|
||||
protected EnemyAI enemyAI;
|
||||
protected KamikazeAI kamikazeAI;
|
||||
|
||||
[Header("Force Settings")]
|
||||
public bool forceGrounded = true;
|
||||
public float movementBoost = 1.2f;
|
||||
public float dampTime = 0.1f;
|
||||
|
||||
public vAnimatorStateInfos animatorStateInfos { get; protected set; }
|
||||
|
||||
#region Animator Parameters
|
||||
protected vAnimatorParameter isDead, isGrounded, isStrafing, isSprinting, isAiming;
|
||||
protected vAnimatorParameter verticalVelocity, horizontalVelocity, inputMagnitude;
|
||||
protected vAnimatorParameter groundDistance, moveSet_ID, attackID, triggerReaction, resetState;
|
||||
#endregion
|
||||
|
||||
protected Vector3 lastPosition;
|
||||
protected float currentV, currentH, currentMagnitude, calculatedSpeed;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
animator = GetComponentInChildren<Animator>();
|
||||
if (animator == null) animator = GetComponentInParent<Animator>();
|
||||
|
||||
agent = GetComponent<NavMeshAgent>();
|
||||
if (agent == null) agent = GetComponentInParent<NavMeshAgent>();
|
||||
|
||||
healthController = GetComponentInChildren<vHealthController>();
|
||||
enemyAI = GetComponent<EnemyAI>();
|
||||
kamikazeAI = GetComponent<KamikazeAI>();
|
||||
|
||||
if (animator)
|
||||
{
|
||||
animator.applyRootMotion = false;
|
||||
animator.updateMode = AnimatorUpdateMode.Normal;
|
||||
|
||||
// Reset all layers initially to prevent T-Pose
|
||||
for (int i = 1; i < animator.layerCount; i++) animator.SetLayerWeight(i, 0f);
|
||||
|
||||
animatorStateInfos = new vAnimatorStateInfos(animator);
|
||||
InitializeParameters();
|
||||
Debug.Log($"<color=green>[AnimSystem]</color> Đã kích hoạt trên {gameObject.name}");
|
||||
}
|
||||
|
||||
lastPosition = transform.position;
|
||||
}
|
||||
|
||||
protected virtual void OnEnable() { this.Register(); }
|
||||
protected virtual void OnDisable() { this.UnRegister(); }
|
||||
|
||||
protected virtual void InitializeParameters()
|
||||
{
|
||||
isDead = ValidateAndInit("isDead");
|
||||
isGrounded = ValidateAndInit("isGrounded");
|
||||
if (!isGrounded.isValid) isGrounded = ValidateAndInit("IsGrounded");
|
||||
isStrafing = ValidateAndInit("IsStrafing");
|
||||
isSprinting = ValidateAndInit("IsSprinting");
|
||||
isAiming = ValidateAndInit("IsAiming");
|
||||
verticalVelocity = ValidateAndInit("InputVertical");
|
||||
horizontalVelocity = ValidateAndInit("InputHorizontal");
|
||||
inputMagnitude = ValidateAndInit("InputMagnitude");
|
||||
groundDistance = ValidateAndInit("GroundDistance");
|
||||
moveSet_ID = ValidateAndInit("MoveSet_ID");
|
||||
attackID = ValidateAndInit("AttackID");
|
||||
triggerReaction = ValidateAndInit("TriggerReaction");
|
||||
resetState = ValidateAndInit("ResetState");
|
||||
}
|
||||
|
||||
private vAnimatorParameter ValidateAndInit(string pName) => new vAnimatorParameter(animator, pName);
|
||||
|
||||
protected virtual void Update()
|
||||
{
|
||||
if (animator == null || agent == null) return;
|
||||
|
||||
UpdateMovementParameters();
|
||||
UpdateCombatParameters();
|
||||
}
|
||||
|
||||
protected virtual void UpdateMovementParameters()
|
||||
{
|
||||
SetBool(isGrounded, forceGrounded);
|
||||
SetFloat(groundDistance, 0f);
|
||||
|
||||
Vector3 delta = transform.position - lastPosition;
|
||||
calculatedSpeed = delta.magnitude / Time.deltaTime;
|
||||
lastPosition = transform.position;
|
||||
|
||||
Vector3 localVel = transform.InverseTransformDirection(delta / Time.deltaTime);
|
||||
|
||||
float maxS = (enemyAI) ? enemyAI.moveSpeed : (kamikazeAI ? agent.speed : 3f);
|
||||
if (maxS <= 0) maxS = 3f;
|
||||
|
||||
float targetV = (localVel.z / maxS) * movementBoost;
|
||||
float targetH = (localVel.x / maxS) * movementBoost;
|
||||
|
||||
currentV = Mathf.Lerp(currentV, targetV, 10f * Time.deltaTime);
|
||||
currentH = Mathf.Lerp(currentH, targetH, 10f * Time.deltaTime);
|
||||
currentMagnitude = new Vector2(currentH, currentV).magnitude;
|
||||
|
||||
// ÉP GIÁ TRỊ VÀO ANIMATOR
|
||||
SetFloat(verticalVelocity, currentV);
|
||||
SetFloat(horizontalVelocity, currentH);
|
||||
SetFloat(inputMagnitude, currentMagnitude);
|
||||
}
|
||||
|
||||
protected virtual void UpdateCombatParameters()
|
||||
{
|
||||
// 1. Kiểm tra trạng thái AI
|
||||
bool isShooting = (enemyAI && enemyAI.IsShootingBurst);
|
||||
bool hasArtifact = (enemyAI && enemyAI.playerHasArtifact);
|
||||
|
||||
// 2. Cập nhật MoveSet và Aiming
|
||||
SetInt(moveSet_ID, hasArtifact ? 1 : 0);
|
||||
SetBool(isAiming, hasArtifact);
|
||||
SetBool(isStrafing, hasArtifact);
|
||||
|
||||
// 3. Xử lý BẮN SÚNG (Layer 6 trong Animator của bạn)
|
||||
if (isShooting)
|
||||
{
|
||||
// Bật Layer bắn súng lên 1 (Smooth)
|
||||
animator.SetLayerWeight(6, Mathf.Lerp(animator.GetLayerWeight(6), 1f, 15f * Time.deltaTime));
|
||||
SetInt(attackID, 1); // Kích hoạt animation bắn trong Blend Tree của Layer 6
|
||||
}
|
||||
else
|
||||
{
|
||||
// Tắt Layer bắn súng về 0
|
||||
animator.SetLayerWeight(6, Mathf.Lerp(animator.GetLayerWeight(6), 0f, 10f * Time.deltaTime));
|
||||
SetInt(attackID, 0);
|
||||
}
|
||||
|
||||
if (enemyAI && enemyAI.IsDodging) SetAnimatorTrigger(triggerReaction);
|
||||
}
|
||||
|
||||
#region Helpers
|
||||
protected void SetBool(vAnimatorParameter p, bool v) { if (p.isValid) animator.SetBool(p, v); }
|
||||
protected void SetFloat(vAnimatorParameter p, float v) { if (p.isValid) animator.SetFloat(p, v, dampTime, Time.deltaTime); }
|
||||
protected void SetInt(vAnimatorParameter p, int v) { if (p.isValid) animator.SetInteger(p, v); }
|
||||
|
||||
public void SetAnimatorTrigger(vAnimatorParameter trigger)
|
||||
{
|
||||
if (trigger.isValid) StartCoroutine(SetTriggerRoutine(trigger));
|
||||
}
|
||||
|
||||
private IEnumerator SetTriggerRoutine(int targetHash)
|
||||
{
|
||||
animator.SetTrigger(targetHash);
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
animator.ResetTrigger(targetHash);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35bba55c2a743d042ab1fff35e29db50
|
||||
@@ -1,47 +0,0 @@
|
||||
using Invector;
|
||||
using UnityEngine;
|
||||
|
||||
public class AutoDestroy : MonoBehaviour
|
||||
{
|
||||
public int damageAmount = 30;
|
||||
void Start()
|
||||
{
|
||||
Destroy(gameObject,2f);
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
// Debug: Log tên và tag của bất cứ thứ gì đạn chạm vào
|
||||
Debug.Log(
|
||||
$"Laser collided with: {other.name} | Tag: {other.tag} | Layer: {LayerMask.LayerToName(other.gameObject.layer)}");
|
||||
|
||||
// Kiểm tra nếu trúng Player
|
||||
if (other.CompareTag("Player") || other.GetComponentInParent<vIHealthController>() != null)
|
||||
{
|
||||
var healthController = other.GetComponentInParent<vIHealthController>();
|
||||
|
||||
if (healthController != null)
|
||||
{
|
||||
Debug.Log(
|
||||
$"<color=red>HIT PLAYER!</color> Found health controller on {healthController.gameObject.name}. Applying {damageAmount} damage.");
|
||||
var damage = new vDamage(damageAmount);
|
||||
damage.sender = transform;
|
||||
damage.hitPosition = transform.position;
|
||||
healthController.TakeDamage(damage);
|
||||
}
|
||||
|
||||
// Luôn phá hủy đạn khi trúng Player
|
||||
Impact();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void Impact()
|
||||
{
|
||||
|
||||
|
||||
// Phá hủy đạn ngay lập tức
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 950ee3c6c086a3b4fa9a7f1e544c1651
|
||||
@@ -1,133 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
|
||||
namespace Hallucinate.AI
|
||||
{
|
||||
public class ConversationManager : MonoBehaviour
|
||||
{
|
||||
public static ConversationManager Instance { get; private set; }
|
||||
|
||||
[Header("Settings")]
|
||||
public int maxSimultaneousConversations = 3;
|
||||
public float maxConversationDuration = 120f; // 2 minutes
|
||||
|
||||
private List<ConversationSession> activeSessions = new List<ConversationSession>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance == null) Instance = this;
|
||||
else Destroy(gameObject);
|
||||
}
|
||||
|
||||
public bool CanStartConversation()
|
||||
{
|
||||
return activeSessions.Count < maxSimultaneousConversations;
|
||||
}
|
||||
|
||||
public void StartConversation(EnemyAI initiator, EnemyAI responder)
|
||||
{
|
||||
if (!CanStartConversation()) return;
|
||||
|
||||
ConversationSession session = new ConversationSession(initiator, responder, maxConversationDuration);
|
||||
activeSessions.Add(session);
|
||||
StartCoroutine(RunConversation(session));
|
||||
}
|
||||
|
||||
private IEnumerator RunConversation(ConversationSession session)
|
||||
{
|
||||
Debug.Log($"<color=cyan>[ConvManager]</color> Starting: {session.initiator.npcName} & {session.responder.npcName}");
|
||||
|
||||
// Phase 1: Initiator speaks
|
||||
bool phase1Complete = false;
|
||||
session.RequestDialogue(session.initiator, (success) => phase1Complete = true);
|
||||
|
||||
float startTime = Time.time;
|
||||
while (!phase1Complete && Time.time < startTime + 10f) yield return null;
|
||||
|
||||
if (phase1Complete && !session.isInterrupted)
|
||||
{
|
||||
yield return new WaitForSeconds(4f); // Reading time
|
||||
|
||||
// Phase 2: Responder speaks
|
||||
bool phase2Complete = false;
|
||||
session.RequestDialogue(session.responder, (success) => phase2Complete = true);
|
||||
|
||||
float phase2StartTime = Time.time;
|
||||
while (!phase2Complete && Time.time < phase2StartTime + 10f) yield return null;
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(4f);
|
||||
EndConversation(session);
|
||||
}
|
||||
|
||||
public void EndConversation(ConversationSession session)
|
||||
{
|
||||
if (activeSessions.Contains(session))
|
||||
{
|
||||
session.Cleanup();
|
||||
activeSessions.Remove(session);
|
||||
Debug.Log($"<color=cyan>[ConvManager]</color> Ended session. Active: {activeSessions.Count}");
|
||||
}
|
||||
}
|
||||
|
||||
public void InterruptConversation(EnemyAI npc)
|
||||
{
|
||||
ConversationSession session = activeSessions.Find(s => s.initiator == npc || s.responder == npc);
|
||||
if (session != null)
|
||||
{
|
||||
session.isInterrupted = true;
|
||||
EndConversation(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ConversationSession
|
||||
{
|
||||
public EnemyAI initiator;
|
||||
public EnemyAI responder;
|
||||
public float durationLimit;
|
||||
public bool isInterrupted;
|
||||
|
||||
public ConversationSession(EnemyAI initiator, EnemyAI responder, float limit)
|
||||
{
|
||||
this.initiator = initiator;
|
||||
this.responder = responder;
|
||||
this.durationLimit = limit;
|
||||
|
||||
initiator.isTalking = true;
|
||||
responder.isTalking = true;
|
||||
|
||||
// Set references for Gizmos and Facing
|
||||
initiator.SetTalkingPartner(responder);
|
||||
responder.SetTalkingPartner(initiator);
|
||||
}
|
||||
|
||||
public void RequestDialogue(EnemyAI speaker, Action<bool> callback)
|
||||
{
|
||||
if (isInterrupted) { callback?.Invoke(false); return; }
|
||||
|
||||
EnemyAI listener = (speaker == initiator) ? responder : initiator;
|
||||
|
||||
// Face each other
|
||||
speaker.FaceTarget(listener.transform.position);
|
||||
listener.FaceTarget(speaker.transform.position);
|
||||
|
||||
string prompt = $"You are {speaker.npcName} talking to {listener.npcName}. Previous context: None. " +
|
||||
"Keep it natural and short.";
|
||||
|
||||
GeminiService.Instance.GetResponse(speaker.persona, prompt, (json) => {
|
||||
if (isInterrupted) { callback?.Invoke(false); return; }
|
||||
speaker.ProcessDialogueResult(json);
|
||||
callback?.Invoke(true);
|
||||
});
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
{
|
||||
if (initiator != null) initiator.isTalking = false;
|
||||
if (responder != null) responder.isTalking = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ebf63e5e8f429234b89a746833c4ca4e
|
||||
@@ -1,603 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AI;
|
||||
using System.Linq;
|
||||
using UnityEngine.InputSystem;
|
||||
using Invector;
|
||||
using Invector.vCharacterController;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
[Serializable]
|
||||
public class DialogueResult
|
||||
{
|
||||
public string text;
|
||||
public float speedMod;
|
||||
public float suspicionMod;
|
||||
public float aggressionMod; // Ảnh hưởng delay bắn (0.1 -> 1.0)
|
||||
public float braveryMod; // Ảnh hưởng ngưỡng Panic
|
||||
public float healthMod; // Hồi máu hoặc mất máu tâm lý
|
||||
}
|
||||
|
||||
[RequireComponent(typeof(NavMeshAgent))]
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
[RequireComponent(typeof(vHealthController))]
|
||||
public class EnemyAI : MonoBehaviour
|
||||
{
|
||||
[Header("References")]
|
||||
public Transform player;
|
||||
private NavMeshAgent agent;
|
||||
private Rigidbody rb;
|
||||
private FieldOfView fov;
|
||||
private Collider mainCollider;
|
||||
private vHealthController health;
|
||||
|
||||
[Header("Movement Settings")]
|
||||
public float moveSpeed = 3f;
|
||||
public float rotateSpeed = 10f;
|
||||
|
||||
[Header("Patrol Settings")]
|
||||
public float patrolWaitTime = 2f;
|
||||
private float currentWaitTime = 0f;
|
||||
public float patrolSpeed = 2.5f;
|
||||
public float patrolRadius = 12f;
|
||||
private Vector3 startPosition;
|
||||
|
||||
[Header("Combat State")]
|
||||
public bool playerHasArtifact;
|
||||
public bool isAggroedBySound;
|
||||
public GameObject laserPrefab;
|
||||
public Transform firePoint;
|
||||
public float minShootDelay = 1.8f; // Tăng nhẹ delay để đỡ khó
|
||||
public float maxShootDelay = 4.0f;
|
||||
private float nextShootTime;
|
||||
|
||||
[Header("Dodge Settings")]
|
||||
public float dodgeForce = 8f;
|
||||
public float dodgeDuration = 0.2f;
|
||||
public float dodgeCooldown = 2.0f; // Tăng cooldown né đòn
|
||||
private bool isDodging = false;
|
||||
private float nextDodgeTime = 0f;
|
||||
|
||||
[Header("Advanced AI States")]
|
||||
public bool isPanicking = false;
|
||||
public bool isEnraged = false;
|
||||
public float panicHealthThreshold = 50f; // Chạy ngược lại khi < 50% máu
|
||||
public float regenRate = 1.5f;
|
||||
public float regenDelay = 5f;
|
||||
private float lastDamageTime;
|
||||
|
||||
[Header("Personality (Randomized)")]
|
||||
private float personalApproachWeight;
|
||||
private float personalMinCombatDistance;
|
||||
private float personalBurstMax;
|
||||
private float personalStrafeIntensity;
|
||||
|
||||
[Header("Artifact Combat Upgrades")]
|
||||
public float minStrafeDuration = 0.5f;
|
||||
public float maxStrafeDuration = 2.2f;
|
||||
public float maxSpreadAngle = 7f;
|
||||
public float burstInterval = 0.15f;
|
||||
|
||||
public float approachWeight = 0.35f;
|
||||
public float minCombatDistance = 5.0f;
|
||||
|
||||
private float nextStrafeChangeTime;
|
||||
private int strafeDirectionSign = 1;
|
||||
private bool isShootingBurst = false;
|
||||
|
||||
public bool IsDodging => isDodging;
|
||||
public bool IsShootingBurst => isShootingBurst;
|
||||
|
||||
[Header("Conversation Settings")]
|
||||
public string npcName = "Guard";
|
||||
[TextArea] public string persona = "You are a bored security guard.";
|
||||
public float talkRange = 12f;
|
||||
public float talkCooldown = 60f;
|
||||
private float lastTalkTime;
|
||||
public bool isTalking;
|
||||
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 = 30f;
|
||||
|
||||
public Node rootNode;
|
||||
|
||||
void Start()
|
||||
{
|
||||
agent = GetComponent<NavMeshAgent>();
|
||||
rb = GetComponent<Rigidbody>();
|
||||
fov = GetComponent<FieldOfView>();
|
||||
chatBubble = GetComponentInChildren<Hallucinate.UI.ChatBubble>(true);
|
||||
mainCollider = GetComponent<Collider>();
|
||||
health = GetComponent<vHealthController>();
|
||||
|
||||
// RANDOM TÍNH CÁCH
|
||||
personalApproachWeight = Random.Range(0.2f, 0.7f);
|
||||
personalMinCombatDistance = Random.Range(3f, 8f);
|
||||
personalBurstMax = Random.Range(2, 5);
|
||||
personalStrafeIntensity = Random.Range(0.5f, 1.5f);
|
||||
|
||||
health.onReceiveDamage.AddListener(OnReceiveDamage);
|
||||
health.onDead.AddListener(OnDead);
|
||||
|
||||
if (gameObject.layer == LayerMask.NameToLayer("Default"))
|
||||
{
|
||||
int enemyLayer = LayerMask.NameToLayer("Enemy");
|
||||
if (enemyLayer != -1)
|
||||
{
|
||||
gameObject.layer = enemyLayer;
|
||||
Debug.Log($"[AI {npcName}] Đã chuyển sang Layer: Enemy");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[AI {npcName}] CẢNH BÁO: Không tìm thấy Layer 'Enemy' trong Project! Hãy tạo Layer 'Enemy' để súng có thể bắn trúng.");
|
||||
}
|
||||
}
|
||||
|
||||
rb.isKinematic = true;
|
||||
rb.freezeRotation = true;
|
||||
startPosition = transform.position;
|
||||
|
||||
if (player == null)
|
||||
{
|
||||
GameObject playerObj = GameObject.FindGameObjectWithTag("Player");
|
||||
if (playerObj != null) player = playerObj.transform;
|
||||
}
|
||||
|
||||
InitTree();
|
||||
}
|
||||
|
||||
void InitTree()
|
||||
{
|
||||
var dodgeSequence = new Sequence(new List<Node> { new TaskNode(CheckDodgeConditions), new TaskNode(ActionDodge) });
|
||||
var panicSequence = new Sequence(new List<Node> { new TaskNode(CheckPanicConditions), new TaskNode(ActionRetreat) }); // Thay Panic bằng Retreat
|
||||
var laserSequence = new Sequence(new List<Node> { new TaskNode(CheckCombatConditions), new TaskNode(ActionFocusAndShoot) });
|
||||
var chaseSequence = new Sequence(new List<Node> { new TaskNode(CheckCanSeePlayer), new TaskNode(ActionChasePlayer) });
|
||||
var investigateSequence = new Sequence(new List<Node> { new TaskNode(CheckHasInvestigateTarget), new TaskNode(ActionInvestigate) });
|
||||
var talkSequence = new Sequence(new List<Node> { new TaskNode(CheckCanTalkToNPC), new TaskNode(ActionTalk) });
|
||||
var patrolAction = new TaskNode(ActionPatrol);
|
||||
|
||||
rootNode = new Selector(new List<Node>
|
||||
{
|
||||
dodgeSequence,
|
||||
panicSequence,
|
||||
laserSequence,
|
||||
chaseSequence,
|
||||
investigateSequence,
|
||||
talkSequence,
|
||||
patrolAction
|
||||
});
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (player == null || health.isDead) return;
|
||||
|
||||
if (mainCollider != null && !mainCollider.enabled) mainCollider.enabled = true;
|
||||
|
||||
HandleHealthRegen();
|
||||
|
||||
suspicionLevel = Mathf.Max(0, suspicionLevel - Time.deltaTime * 0.5f);
|
||||
if (suspicionLevel <= 0f && !isEnraged) isAggroedBySound = false;
|
||||
|
||||
if (!isTalking && !isDodging && !isPanicking && agent.isStopped)
|
||||
agent.isStopped = false;
|
||||
|
||||
rootNode?.Evaluate();
|
||||
}
|
||||
|
||||
private void HandleHealthRegen()
|
||||
{
|
||||
if (Time.time > lastDamageTime + regenDelay && health.currentHealth < health.maxHealth)
|
||||
{
|
||||
float currentRegenSpeed = regenRate;
|
||||
float healthPercent = (float)health.currentHealth / health.maxHealth;
|
||||
|
||||
// Tăng tốc hồi máu khi máu cực thấp (< 25%)
|
||||
if (healthPercent < 0.25f)
|
||||
currentRegenSpeed *= 4f;
|
||||
else if (healthPercent < 0.5f)
|
||||
currentRegenSpeed *= 2f;
|
||||
|
||||
health.AddHealth((int)(currentRegenSpeed * Time.deltaTime));
|
||||
}
|
||||
}
|
||||
|
||||
#region HEALTH EVENTS
|
||||
|
||||
private void OnReceiveDamage(vDamage damage)
|
||||
{
|
||||
lastDamageTime = Time.time;
|
||||
isAggroedBySound = true;
|
||||
suspicionLevel = 100f;
|
||||
StopConversation();
|
||||
|
||||
// PHẢN ỨNG TỨC THÌ: Alert toàn bộ lân cận
|
||||
AlertNeighbors(damage.hitPosition);
|
||||
|
||||
// PHẢN ỨNG TỨC THÌ: Reset delay bắn để phản công nhanh hoặc né
|
||||
nextShootTime = Time.time + 0.5f;
|
||||
|
||||
// Né đòn Elden Ring (Tăng tỉ lệ né khi trúng dame)
|
||||
if (Time.time > nextDodgeTime && !isDodging && Random.value < 0.7f)
|
||||
{
|
||||
StartCoroutine(DodgeRollRoutine());
|
||||
}
|
||||
|
||||
// Tự động Enrage nếu bị dồn vào đường cùng (nhưng đồng thời vẫn có thể bỏ chạy nếu không phẫn nộ)
|
||||
if (health.currentHealth < health.maxHealth * 0.2f && !isEnraged)
|
||||
{
|
||||
EnterEnrageMode();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDead(GameObject killer)
|
||||
{
|
||||
Debug.Log($"<color=black>[AI {npcName}] DIED.</color>");
|
||||
|
||||
// 1. Vô hiệu hóa va chạm và di chuyển ngay lập tức
|
||||
if (mainCollider != null) mainCollider.enabled = false;
|
||||
agent.enabled = false;
|
||||
|
||||
// 2. Kích hoạt Enrage cho đồng đội xung quanh
|
||||
Collider[] hitColliders = Physics.OverlapSphere(transform.position, alertRange);
|
||||
foreach (var hit in hitColliders)
|
||||
{
|
||||
EnemyAI ally = hit.GetComponentInParent<EnemyAI>();
|
||||
if (ally != null && ally != this) ally.EnterEnrageMode();
|
||||
}
|
||||
|
||||
// 3. Tự hủy sau 3 giây (để kịp chạy animation chết hoặc hiệu ứng)
|
||||
Destroy(gameObject, 3f);
|
||||
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
public void EnterEnrageMode()
|
||||
{
|
||||
if (isEnraged) return;
|
||||
isEnraged = true;
|
||||
isPanicking = false;
|
||||
|
||||
moveSpeed *= 1.3f; // Giảm nhẹ buff speed
|
||||
minShootDelay *= 0.6f;
|
||||
maxShootDelay *= 0.6f;
|
||||
|
||||
if (chatBubble != null) chatBubble.Show("I'LL TAKE YOU DOWN WITH ME!", 2f);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region CONDITIONS
|
||||
|
||||
private NodeState CheckDodgeConditions()
|
||||
{
|
||||
if (isDodging) return NodeState.Success;
|
||||
// Tự né khi thấy Player đang bắn
|
||||
if (fov != null && fov.canSeePlayer && Mouse.current.leftButton.isPressed && Time.time > nextDodgeTime)
|
||||
return NodeState.Success;
|
||||
return NodeState.Failure;
|
||||
}
|
||||
|
||||
private NodeState CheckPanicConditions()
|
||||
{
|
||||
if (isEnraged) return NodeState.Failure; // Đang điên thì không sợ, chiến đến chết
|
||||
|
||||
// Nếu máu dưới ngưỡng thiết lập (ví dụ 50%), kích hoạt trạng thái tháo chạy
|
||||
if (health.currentHealth < (health.maxHealth * (panicHealthThreshold / 100f)))
|
||||
{
|
||||
return NodeState.Success;
|
||||
}
|
||||
|
||||
isPanicking = false;
|
||||
return NodeState.Failure;
|
||||
}
|
||||
|
||||
private NodeState CheckCombatConditions()
|
||||
{
|
||||
bool shouldCombat = playerHasArtifact || isAggroedBySound || isEnraged;
|
||||
if (shouldCombat) StopConversation();
|
||||
return shouldCombat ? NodeState.Success : NodeState.Failure;
|
||||
}
|
||||
|
||||
private NodeState CheckCanSeePlayer()
|
||||
{
|
||||
bool canSee = fov != null && fov.canSeePlayer;
|
||||
if (canSee) { StopConversation(); AlertNeighbors(transform.position); suspicionLevel = 100; }
|
||||
return canSee ? NodeState.Success : NodeState.Failure;
|
||||
}
|
||||
|
||||
private NodeState CheckHasInvestigateTarget()
|
||||
{
|
||||
if (fov != null && fov.lastKnownPlayerPosition != Vector3.zero && suspicionLevel > investigationThreshold)
|
||||
return NodeState.Success;
|
||||
return NodeState.Failure;
|
||||
}
|
||||
|
||||
private NodeState CheckCanTalkToNPC()
|
||||
{
|
||||
if (playerHasArtifact || isAggroedBySound || isEnraged || (fov != null && fov.canSeePlayer)) return NodeState.Failure;
|
||||
if (Time.time < lastTalkTime + talkCooldown || isTalking) return NodeState.Failure;
|
||||
if (Hallucinate.AI.ConversationManager.Instance == null || !Hallucinate.AI.ConversationManager.Instance.CanStartConversation()) return NodeState.Failure;
|
||||
|
||||
Collider[] hitColliders = Physics.OverlapSphere(transform.position, talkRange);
|
||||
foreach (var hit in hitColliders)
|
||||
{
|
||||
if (hit.gameObject == gameObject) continue;
|
||||
EnemyAI other = hit.GetComponentInParent<EnemyAI>();
|
||||
if (other != null && !other.isTalking && !other.isEnraged)
|
||||
{
|
||||
if (gameObject.GetInstanceID() < other.gameObject.GetInstanceID())
|
||||
{
|
||||
Hallucinate.AI.ConversationManager.Instance.StartConversation(this, other);
|
||||
return NodeState.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NodeState.Failure;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ACTIONS
|
||||
|
||||
public void HearNoise(Vector3 location, float volume)
|
||||
{
|
||||
suspicionLevel += volume * 20f;
|
||||
if (fov != null) fov.lastKnownPlayerPosition = location;
|
||||
if (suspicionLevel >= investigationThreshold) isAggroedBySound = true;
|
||||
if (suspicionLevel >= alertNeighborsThreshold) AlertNeighbors(location);
|
||||
StopConversation();
|
||||
}
|
||||
|
||||
public void AlertNeighbors(Vector3 threatPos)
|
||||
{
|
||||
Collider[] hitColliders = Physics.OverlapSphere(transform.position, alertRange);
|
||||
foreach (var hit in hitColliders)
|
||||
{
|
||||
EnemyAI neighbor = hit.GetComponentInParent<EnemyAI>();
|
||||
if (neighbor != null && neighbor != this)
|
||||
{
|
||||
neighbor.TriggerCombatAlert(threatPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void TriggerCombatAlert(Vector3 sourceLocation)
|
||||
{
|
||||
if (isEnraged) return;
|
||||
suspicionLevel = 100f;
|
||||
isAggroedBySound = true;
|
||||
if (fov != null) fov.lastKnownPlayerPosition = sourceLocation;
|
||||
StopConversation();
|
||||
}
|
||||
|
||||
// Hành động tháo chạy khi máu thấp
|
||||
private NodeState ActionRetreat()
|
||||
{
|
||||
isPanicking = true;
|
||||
agent.isStopped = false;
|
||||
agent.speed = moveSpeed * 1.3f; // Chạy nhanh hơn bình thường để thoát thân
|
||||
|
||||
// Tính toán hướng ngược lại với Player
|
||||
Vector3 retreatDir = (transform.position - player.position).normalized;
|
||||
Vector3 targetPos = transform.position + retreatDir * 15f + Random.insideUnitSphere * 5f;
|
||||
|
||||
if (!agent.pathPending && agent.remainingDistance < 1f)
|
||||
{
|
||||
NavMeshHit hit;
|
||||
if (NavMesh.SamplePosition(targetPos, out hit, 10f, 1))
|
||||
{
|
||||
agent.SetDestination(hit.position);
|
||||
}
|
||||
}
|
||||
|
||||
if (chatBubble != null && Random.value < 0.005f) chatBubble.Show("I NEED TO RECOVER!", 1.5f);
|
||||
|
||||
// Khi máu đã hồi phục trên 50%, dừng chạy và quay lại tấn công
|
||||
if (health.currentHealth >= health.maxHealth * 0.5f)
|
||||
{
|
||||
isPanicking = false;
|
||||
return NodeState.Success;
|
||||
}
|
||||
|
||||
return NodeState.Running;
|
||||
}
|
||||
|
||||
private NodeState ActionTalk()
|
||||
{
|
||||
if (isTalking) { agent.isStopped = true; return NodeState.Running; }
|
||||
return NodeState.Failure;
|
||||
}
|
||||
|
||||
private void StopConversation()
|
||||
{
|
||||
if (isTalking && Hallucinate.AI.ConversationManager.Instance != null)
|
||||
{
|
||||
Hallucinate.AI.ConversationManager.Instance.InterruptConversation(this);
|
||||
}
|
||||
}
|
||||
|
||||
private NodeState ActionPatrol()
|
||||
{
|
||||
isPanicking = false;
|
||||
agent.isStopped = false;
|
||||
agent.speed = patrolSpeed;
|
||||
if (!agent.pathPending && agent.remainingDistance <= agent.stoppingDistance)
|
||||
{
|
||||
currentWaitTime += Time.deltaTime;
|
||||
if (currentWaitTime >= patrolWaitTime)
|
||||
{
|
||||
Vector3 randomDest = startPosition + Random.insideUnitSphere * patrolRadius;
|
||||
NavMeshHit hit;
|
||||
if (NavMesh.SamplePosition(randomDest, out hit, patrolRadius, 1)) agent.SetDestination(hit.position);
|
||||
currentWaitTime = 0f;
|
||||
}
|
||||
}
|
||||
return NodeState.Running;
|
||||
}
|
||||
|
||||
private NodeState ActionChasePlayer()
|
||||
{
|
||||
isPanicking = false;
|
||||
agent.isStopped = false;
|
||||
agent.speed = moveSpeed;
|
||||
agent.SetDestination(player.position);
|
||||
return NodeState.Running;
|
||||
}
|
||||
|
||||
private NodeState ActionInvestigate()
|
||||
{
|
||||
agent.isStopped = false;
|
||||
agent.speed = moveSpeed * 0.7f;
|
||||
agent.SetDestination(fov.lastKnownPlayerPosition);
|
||||
if (Vector3.Distance(transform.position, fov.lastKnownPlayerPosition) < 1.5f)
|
||||
{
|
||||
currentWaitTime += Time.deltaTime;
|
||||
if (currentWaitTime > 3f) { fov.lastKnownPlayerPosition = Vector3.zero; return NodeState.Success; }
|
||||
}
|
||||
return NodeState.Running;
|
||||
}
|
||||
|
||||
private NodeState ActionFocusAndShoot()
|
||||
{
|
||||
isPanicking = false;
|
||||
if (player == null) return NodeState.Failure;
|
||||
if (agent.hasPath) agent.ResetPath();
|
||||
agent.isStopped = false;
|
||||
|
||||
Vector3 targetPos = player.position;
|
||||
if (!playerHasArtifact && fov != null && !fov.canSeePlayer && fov.lastKnownPlayerPosition != Vector3.zero)
|
||||
targetPos = fov.lastKnownPlayerPosition;
|
||||
|
||||
Vector3 bodyDir = (targetPos - transform.position);
|
||||
bodyDir.y = 0f;
|
||||
if (bodyDir != Vector3.zero)
|
||||
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(bodyDir), rotateSpeed * Time.deltaTime);
|
||||
|
||||
if (Time.time >= nextStrafeChangeTime)
|
||||
{
|
||||
strafeDirectionSign = new int[] { -1, 1, 0 }[Random.Range(0, 3)];
|
||||
nextStrafeChangeTime = Time.time + Random.Range(minStrafeDuration, maxStrafeDuration);
|
||||
}
|
||||
|
||||
Vector3 moveDir = Vector3.zero;
|
||||
if (strafeDirectionSign != 0 && bodyDir != Vector3.zero)
|
||||
{
|
||||
Vector3 normal = bodyDir.normalized;
|
||||
moveDir = new Vector3(-normal.z, 0, normal.x) * strafeDirectionSign * personalStrafeIntensity;
|
||||
|
||||
float dist = Vector3.Distance(transform.position, targetPos);
|
||||
if (dist > personalMinCombatDistance) moveDir += normal * personalApproachWeight;
|
||||
}
|
||||
|
||||
if (moveDir != Vector3.zero)
|
||||
{
|
||||
agent.speed = moveSpeed * (isEnraged ? 1f : 0.75f);
|
||||
agent.Move(moveDir.normalized * agent.speed * Time.deltaTime);
|
||||
}
|
||||
|
||||
if (firePoint != null)
|
||||
firePoint.rotation = Quaternion.LookRotation((targetPos + Vector3.up * 1f) - firePoint.position);
|
||||
|
||||
if (Time.time >= nextShootTime && !isShootingBurst)
|
||||
{
|
||||
int burstCount = Random.Range(1, (int)personalBurstMax + (isEnraged ? 2 : 0));
|
||||
StartCoroutine(ShootBurstRoutine(burstCount));
|
||||
nextShootTime = Time.time + Random.Range(minShootDelay, maxShootDelay);
|
||||
}
|
||||
|
||||
return NodeState.Running;
|
||||
}
|
||||
|
||||
private IEnumerator ShootBurstRoutine(int bulletCount)
|
||||
{
|
||||
isShootingBurst = true;
|
||||
for (int i = 0; i < bulletCount; i++)
|
||||
{
|
||||
if (laserPrefab == null || firePoint == null) break;
|
||||
float spread = isEnraged ? maxSpreadAngle * 0.5f : maxSpreadAngle;
|
||||
Quaternion rot = firePoint.rotation * Quaternion.Euler(Random.Range(-spread, spread), Random.Range(-spread, spread), 0f);
|
||||
Instantiate(laserPrefab, firePoint.position, rot);
|
||||
yield return new WaitForSeconds(isEnraged ? burstInterval * 0.7f : burstInterval);
|
||||
}
|
||||
isShootingBurst = false;
|
||||
}
|
||||
|
||||
private NodeState ActionDodge()
|
||||
{
|
||||
if (!isDodging) StartCoroutine(DodgeRollRoutine());
|
||||
return NodeState.Running;
|
||||
}
|
||||
|
||||
private IEnumerator DodgeRollRoutine()
|
||||
{
|
||||
isDodging = true;
|
||||
agent.enabled = false;
|
||||
rb.isKinematic = false;
|
||||
if (mainCollider != null) mainCollider.enabled = true;
|
||||
|
||||
Vector3 dir = (player.position - transform.position).normalized;
|
||||
Vector3 perp = new Vector3(-dir.z, 0, dir.x) * (Random.value > 0.5f ? 1 : -1);
|
||||
rb.AddForce(perp * (isEnraged ? dodgeForce * 1.3f : dodgeForce), ForceMode.Impulse);
|
||||
|
||||
yield return new WaitForSeconds(dodgeDuration);
|
||||
rb.linearVelocity = Vector3.zero;
|
||||
rb.isKinematic = true;
|
||||
agent.enabled = true;
|
||||
nextDodgeTime = Time.time + (isEnraged ? dodgeCooldown * 0.6f : dodgeCooldown);
|
||||
isDodging = false;
|
||||
}
|
||||
|
||||
public void ProcessDialogueResult(string json)
|
||||
{
|
||||
try
|
||||
{
|
||||
DialogueResult result = JsonUtility.FromJson<DialogueResult>(json);
|
||||
if (chatBubble != null) chatBubble.Show(result.text);
|
||||
|
||||
// Áp dụng modifiers từ Gemini
|
||||
moveSpeed = Mathf.Clamp(moveSpeed + result.speedMod, 1f, 8f);
|
||||
suspicionLevel = Mathf.Clamp(suspicionLevel + result.suspicionMod, 0, 100);
|
||||
|
||||
// Modifier hung hãn: giảm delay bắn (max 50%)
|
||||
minShootDelay = Mathf.Clamp(minShootDelay * (1f - result.aggressionMod), 0.5f, 5f);
|
||||
maxShootDelay = Mathf.Clamp(maxShootDelay * (1f - result.aggressionMod), 1f, 10f);
|
||||
|
||||
// Modifier can đảm: thay đổi ngưỡng panic
|
||||
panicHealthThreshold = Mathf.Clamp(panicHealthThreshold - result.braveryMod, 0, 80);
|
||||
|
||||
// Modifier máu
|
||||
if (result.healthMod != 0)
|
||||
{
|
||||
health.AddHealth((int)result.healthMod);
|
||||
}
|
||||
|
||||
lastTalkTime = Time.time;
|
||||
Debug.Log($"[AI {npcName}] Gemini influence: Speed:{result.speedMod}, Aggro:{result.aggressionMod}, Bravery:{result.braveryMod}");
|
||||
}
|
||||
catch { if (chatBubble != null) chatBubble.Show(json); }
|
||||
}
|
||||
|
||||
public void SetTalkingPartner(EnemyAI partner) { talkingPartner = partner; }
|
||||
public void FaceTarget(Vector3 pos)
|
||||
{
|
||||
Vector3 dir = (pos - transform.position);
|
||||
dir.y = 0;
|
||||
if (dir != Vector3.zero) transform.rotation = Quaternion.LookRotation(dir);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
Gizmos.color = isEnraged ? Color.red : Color.green;
|
||||
Gizmos.DrawWireSphere(transform.position, alertRange);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2224c27a7e8678e4a85f6604ba5e669a
|
||||
@@ -1,28 +0,0 @@
|
||||
using UnityEngine;
|
||||
using Hallucinate.AI;
|
||||
|
||||
public class GeminiTest : MonoBehaviour
|
||||
{
|
||||
void Start()
|
||||
{
|
||||
Debug.Log("<color=cyan>[Gemini Test]</color> Bắt đầu kiểm tra kết nối API...");
|
||||
|
||||
if (GeminiService.Instance == null)
|
||||
{
|
||||
Debug.LogError("<color=red>[Gemini Test]</color> Không tìm thấy GeminiService Instance! Hãy đảm bảo bạn đã kéo script GeminiService vào một GameObject trong Scene.");
|
||||
return;
|
||||
}
|
||||
|
||||
string testPersona = "Bạn là một robot kiểm tra hệ thống.";
|
||||
string testPrompt = "Chào bạn, nếu bạn nhận được tin nhắn này, hãy trả lời: 'Kết nối Gemini thành công!'";
|
||||
|
||||
GeminiService.Instance.GetResponse(testPersona, testPrompt, (response) => {
|
||||
string finalMsg = response;
|
||||
try {
|
||||
DialogueResult result = JsonUtility.FromJson<DialogueResult>(response);
|
||||
finalMsg = result.text;
|
||||
} catch { }
|
||||
Debug.Log($"<color=green>[Gemini Test] Phản hồi từ API:</color> {finalMsg}");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e61a7aa4c1a936a43a97cf67a6e6a559
|
||||
@@ -1,85 +0,0 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using System.Collections;
|
||||
using Hallucinate.Audio;
|
||||
using Hallucinate.AI;
|
||||
|
||||
public class GerminiNPC : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private string npcPersona =
|
||||
"Ngươi là một lão thợ rèn cọc cằn tên là Tom, ngươi rất ghét những kẻ mang phế liệu đến tiệm của mình. Chỉ trả lời ngắn gọn trong 2 câu, theo phong cách trung cổ.";
|
||||
|
||||
public string playerHeldItem = "Thanh kiếm rỉ sét";
|
||||
public float interactionDistance = 5f; // Khoảng cách tối đa để nói chuyện
|
||||
public Transform playerTransform; // Gán transform của Player vào đây
|
||||
|
||||
[Header("Audio")]
|
||||
public string startTalkSound = "NPC_Interact";
|
||||
public string responseSound = "NPC_Response";
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Keyboard.current != null && Keyboard.current.fKey.wasPressedThisFrame)
|
||||
{
|
||||
if (CanSeePlayer())
|
||||
{
|
||||
AudioManager.Instance?.Play(startTalkSound, position: transform.position);
|
||||
StartCoroutine(GetGerminiReponse());
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("<color=yellow>Hệ thống:</color> Bạn ở quá xa hoặc bị tường che khuất!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanSeePlayer()
|
||||
{
|
||||
if (playerTransform == null)
|
||||
{
|
||||
// Tự tìm player nếu chưa gán
|
||||
GameObject player = GameObject.FindGameObjectWithTag("Player");
|
||||
if (player != null) playerTransform = player.transform;
|
||||
else return false;
|
||||
}
|
||||
|
||||
// 1. Check khoảng cách
|
||||
float dist = Vector3.Distance(transform.position, playerTransform.position);
|
||||
if (dist > interactionDistance) return false;
|
||||
|
||||
// 2. Check xem có bị tường che không (Raycast)
|
||||
Vector3 direction = (playerTransform.position + Vector3.up) - (transform.position + Vector3.up);
|
||||
RaycastHit hit;
|
||||
if (Physics.Raycast(transform.position + Vector3.up, direction, out hit, interactionDistance))
|
||||
{
|
||||
if (hit.collider.CompareTag("Player") || hit.collider.transform.IsChildOf(playerTransform))
|
||||
{
|
||||
return true; // Thấy đầu/người player
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private IEnumerator GetGerminiReponse()
|
||||
{
|
||||
string prompt = $"Ta muốn bán cho ông món đồ này: {playerHeldItem}";
|
||||
|
||||
Hallucinate.AI.GeminiService.Instance.GetResponse(npcPersona, prompt, (response) => {
|
||||
string finalMsg = response;
|
||||
try {
|
||||
DialogueResult result = JsonUtility.FromJson<DialogueResult>(response);
|
||||
finalMsg = result.text;
|
||||
} catch { }
|
||||
|
||||
Debug.Log($"<color=green>Tom:</color> {finalMsg}");
|
||||
AudioManager.Instance?.Play(responseSound, position: transform.position);
|
||||
|
||||
var bubble = GetComponentInChildren<Hallucinate.UI.ChatBubble>(true);
|
||||
if (bubble != null) bubble.Show(finalMsg);
|
||||
});
|
||||
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4efda4e7a7dcac84ca938e2264ed0276
|
||||
@@ -1,85 +0,0 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using Hallucinate.Audio;
|
||||
using Invector;
|
||||
|
||||
public class LaserProjectile : MonoBehaviour
|
||||
{
|
||||
public float speed = 15f; // Tăng tốc độ đạn để cảm giác mượt hơn
|
||||
public float lifeTime = 5f;
|
||||
public int damageAmount = 10;
|
||||
|
||||
[Header("Audio")]
|
||||
public string hitSound = "Laser_Hit";
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// Tự hủy sau một khoảng thời gian nếu không trúng gì
|
||||
Destroy(gameObject, lifeTime);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// Di chuyển đạn
|
||||
transform.position += transform.forward * speed * Time.deltaTime;
|
||||
}
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
// Debug: Log tên và tag của bất cứ thứ gì đạn chạm vào
|
||||
Debug.Log($"Laser collided with: {other.name} | Tag: {other.tag} | Layer: {LayerMask.LayerToName(other.gameObject.layer)}");
|
||||
|
||||
// 1. Kiểm tra nếu trúng Player hoặc đối tượng có Health
|
||||
var healthController = other.GetComponentInParent<vIHealthController>();
|
||||
if (other.CompareTag("Player") || healthController != null)
|
||||
{
|
||||
if (healthController != null)
|
||||
{
|
||||
Debug.Log($"<color=red>HIT PLAYER!</color> Applying {damageAmount} damage.");
|
||||
var damage = new vDamage(damageAmount);
|
||||
damage.sender = transform;
|
||||
damage.hitPosition = transform.position;
|
||||
healthController.TakeDamage(damage);
|
||||
}
|
||||
|
||||
Impact();
|
||||
return;
|
||||
}
|
||||
|
||||
// KIỂM TRA LAYER "GROUND"
|
||||
if (other.gameObject.layer == LayerMask.NameToLayer("Ground"))
|
||||
{
|
||||
Debug.Log("<color=yellow>Laser hit GROUND layer.</color>");
|
||||
Impact();
|
||||
return;
|
||||
}
|
||||
// Phá hủy đạn nếu trúng tường, sàn nhà (mọi thứ không phải trigger khác)
|
||||
|
||||
|
||||
// 2. Phá hủy đạn nếu trúng Ground, Tường, hoặc bất kỳ vật thể đặc nào (không phải Trigger)
|
||||
|
||||
if (!other.isTrigger)
|
||||
{
|
||||
Debug.Log($"Laser hit solid object: {other.name} (Ground/Obstacle). Destroying.");
|
||||
Impact();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCollisionEnter(Collision other)
|
||||
{
|
||||
if (other.gameObject.layer == LayerMask.NameToLayer("Ground") || other.gameObject.tag == "Ground")
|
||||
{
|
||||
Impact();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void Impact()
|
||||
{
|
||||
// Chạy âm thanh
|
||||
AudioManager.Instance?.Play(hitSound, position: transform.position);
|
||||
|
||||
// Phá hủy đạn ngay lập tức
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4e4f602386d4d484ea7a2a3b0c19ac21
|
||||
@@ -1,32 +0,0 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Hallucinate.AI
|
||||
{
|
||||
public class NoiseEmitter : MonoBehaviour
|
||||
{
|
||||
[Header("Settings")]
|
||||
public float defaultNoiseRange = 10f;
|
||||
public LayerMask npcLayer;
|
||||
|
||||
public void EmitNoise(float volumeMultiplier = 1f)
|
||||
{
|
||||
float range = defaultNoiseRange * volumeMultiplier;
|
||||
Collider[] hitColliders = Physics.OverlapSphere(transform.position, range, npcLayer);
|
||||
|
||||
foreach (var hit in hitColliders)
|
||||
{
|
||||
EnemyAI npc = hit.GetComponentInParent<EnemyAI>();
|
||||
if (npc != null)
|
||||
{
|
||||
npc.HearNoise(transform.position, volumeMultiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
Gizmos.color = new Color(1, 1, 0, 0.3f);
|
||||
Gizmos.DrawWireSphere(transform.position, defaultNoiseRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67d06596d1741d34594e4a68adcaf257
|
||||
Reference in New Issue
Block a user