From 649603e232aab90cae473e5f246c8f2e70be6ce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20P=C4=9Bnkava?= Date: Thu, 4 Sep 2025 21:03:43 +0200 Subject: [PATCH] Fix: AI Fixed problem with attacks on player and navmesh (will need some Quality of life improvements in the future) --- .../Prefabs/Levels/MapLayout1.asset.meta | 2 +- 3D blobici/Assets/Prefabs/enemy/Blob.prefab | 16 +- 3D blobici/Assets/Scenes/GenTest.unity | 3 +- .../Assets/Scripts/Enemy/EnemyAttack.cs | 199 +++++++++++++++--- .../Assets/Scripts/Enemy/EnemyMovement.cs | 191 ++++++++++------- 5 files changed, 303 insertions(+), 108 deletions(-) diff --git a/3D blobici/Assets/Prefabs/Levels/MapLayout1.asset.meta b/3D blobici/Assets/Prefabs/Levels/MapLayout1.asset.meta index d37e1e6..daed164 100644 --- a/3D blobici/Assets/Prefabs/Levels/MapLayout1.asset.meta +++ b/3D blobici/Assets/Prefabs/Levels/MapLayout1.asset.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 0c4ecdc10844c394b92ccd08ee36d635 +guid: 8379cbdc96f21cc47a95cab23068d69a NativeFormatImporter: externalObjects: {} mainObjectFileID: 11400000 diff --git a/3D blobici/Assets/Prefabs/enemy/Blob.prefab b/3D blobici/Assets/Prefabs/enemy/Blob.prefab index 581526f..8e03961 100644 --- a/3D blobici/Assets/Prefabs/enemy/Blob.prefab +++ b/3D blobici/Assets/Prefabs/enemy/Blob.prefab @@ -79,8 +79,11 @@ MonoBehaviour: agent: {fileID: -7480623267348950463} player: {fileID: 7001416999833331379, guid: f0df263e5be65a041848d5a8bab85af1, type: 3} updatePathInterval: 0.5 - attackRange: 2 - attackCooldown: 1 + roomCheckInterval: 2 + attackRange: 3 + sightRange: 50 + patrolRange: 5 + enemyAttack: {fileID: 5415715946912615516} animator: {fileID: 0} --- !u!114 &8002523264253901019 MonoBehaviour: @@ -109,10 +112,13 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 333830e3ba10f2942b70a753c4c281de, type: 3} m_Name: m_EditorClassIdentifier: - attackRange: 5 + attackRange: 3 attackRate: 2 attackDamage: 10 attackAngle: 45 + player: {fileID: 7001416999833331379, guid: f0df263e5be65a041848d5a8bab85af1, type: 3} + animator: {fileID: 0} + agent: {fileID: -7480623267348950463} --- !u!65 &146755941370531150 BoxCollider: m_ObjectHideFlags: 0 @@ -128,11 +134,11 @@ BoxCollider: serializedVersion: 2 m_Bits: 0 m_LayerOverridePriority: 0 - m_IsTrigger: 1 + m_IsTrigger: 0 m_ProvidesContacts: 0 m_Enabled: 1 serializedVersion: 3 - m_Size: {x: 4.4725122, y: 2.472512, z: 4.2375846} + m_Size: {x: 2, y: 1, z: 2} m_Center: {x: 0, y: 0.6187928, z: 0} --- !u!195 &-7480623267348950463 NavMeshAgent: diff --git a/3D blobici/Assets/Scenes/GenTest.unity b/3D blobici/Assets/Scenes/GenTest.unity index 1be9ece..c38f2ca 100644 --- a/3D blobici/Assets/Scenes/GenTest.unity +++ b/3D blobici/Assets/Scenes/GenTest.unity @@ -160,7 +160,8 @@ MonoBehaviour: CorridorStraight: {fileID: 8047827979703692770, guid: 1a5d554c0c76caf4195cae47e098b79d, type: 3} CorridorStraightUnlit: {fileID: 2016417306107577256, guid: 92d9025262a022a499862d352c2724ee, type: 3} DoorCorridor: {fileID: 5603417387143431118, guid: 9eccbf5a500a0d2429e6008a713a49fd, type: 3} - layout: {fileID: 11400000, guid: 0c4ecdc10844c394b92ccd08ee36d635, type: 2} + layout: {fileID: 11400000, guid: 8379cbdc96f21cc47a95cab23068d69a, type: 2} + useGlobalNavMesh: 1 RoomDistance: 40 --- !u!4 &23489964 Transform: diff --git a/3D blobici/Assets/Scripts/Enemy/EnemyAttack.cs b/3D blobici/Assets/Scripts/Enemy/EnemyAttack.cs index fdbb897..b489358 100644 --- a/3D blobici/Assets/Scripts/Enemy/EnemyAttack.cs +++ b/3D blobici/Assets/Scripts/Enemy/EnemyAttack.cs @@ -1,52 +1,193 @@ +using System.Collections; using UnityEngine; +using UnityEngine.AI; public class EnemyAttack : MonoBehaviour { - - [SerializeField] private float attackRange = 5f; + [Header("Attack Settings")] + [SerializeField] private float attackRange = 2f; [SerializeField] private float attackRate = 2f; [SerializeField] private float attackDamage = 10f; [SerializeField] private float attackAngle = 45f; - private float lastAttackTime = 0f; + [Header("References")] + [SerializeField] private Transform player; + [SerializeField] private Animator animator; + [SerializeField] private NavMeshAgent agent; + + private float lastAttackTime = 0f; + private bool canAttack = true; + private bool isAttacking = false; + private float attackCooldownTimer = 0f; - // Start is called once before the first execution of Update after the MonoBehaviour is created void Start() { + // Najdi reference + GameObject playerObject = GameObject.FindWithTag("Player"); + if (playerObject != null) + { + player = playerObject.transform; + } + + if (animator == null) animator = GetComponent(); + if (agent == null) agent = GetComponent(); + + lastAttackTime = -attackRate; // Umožní útok hned na začátku } - // Update is called once per frame void Update() { - } - - private void FixedUpdate() - { - - } - - private void OnTriggerEnter(Collider other) - { - - } - - private void OnTriggerStay(Collider other) - { - var player = other.GetComponent(); - - if (player != null && Time.time - lastAttackTime > attackRate) + // Update cooldown timeru + if (!canAttack) { - var health = other.GetComponent(); - if (health != null) + attackCooldownTimer -= Time.deltaTime; + if (attackCooldownTimer <= 0f) { - health.ModifyHealth(-attackDamage); - Debug.Log("Enemy attacked! Next attack in: " + (1f / attackRate) + " seconds"); - lastAttackTime = Time.time; + canAttack = true; } } } - private void OnTriggerExit(Collider other) + // Metoda pro pokus o útok - volána z EnemyMovement + public bool TryAttack() { + if (player == null || !canAttack || isAttacking) + { + Debug.Log($"Attack failed: player={player != null}, canAttack={canAttack}, isAttacking={isAttacking}"); + return false; + } + + // Kontrola vzdálenosti a úhlu k hráči + float distanceToPlayer = Vector3.Distance(transform.position, player.position); + bool inRange = distanceToPlayer <= attackRange; + bool inAngle = IsPlayerInAttackAngle(); + bool cooldownReady = Time.time - lastAttackTime >= attackRate; + + Debug.Log($"TryAttack: range={inRange}, angle={inAngle}, cooldown={cooldownReady}, distance={distanceToPlayer}"); + + if (inRange && cooldownReady) + { + StartCoroutine(PerformAttack()); + return true; + } + + Debug.Log("Attack conditions not met"); + return false; } -} + + private bool IsPlayerInAttackAngle() + { + if (player == null) return false; + + Vector3 directionToPlayer = (player.position - transform.position).normalized; + + // Ignoruj Y osu pro výpočet úhlu + directionToPlayer.y = 0; + Vector3 forward = transform.forward; + forward.y = 0; + + float angle = Vector3.Angle(forward, directionToPlayer); + + // Větší tolerance úhlu (zvýšeno z 22.5° na 60°) + return angle <= attackAngle; // Používáme celý attackAngle, ne polovinu + } + + public bool IsPlayerInAttackRange() + { + if (player == null) return false; + + float distance = Vector3.Distance(transform.position, player.position); + bool inRange = distance <= attackRange * 1.1f; + bool inAngle = IsPlayerInAttackAngle(); + + Debug.Log($"AttackRange Check - Distance: {distance}/{attackRange}, InRange: {inRange}, Angle: {inAngle}"); + + return inRange && inAngle; + } + + private IEnumerator PerformAttack() + { + isAttacking = true; + canAttack = false; + lastAttackTime = Time.time; + attackCooldownTimer = attackRate; + + // Ulož původní stav agenta + bool wasStopped = agent.isStopped; + + // Zastav pohyb během útoku + agent.isStopped = true; + + // Spustit animaci útoku + if (animator != null) + { + animator.SetTrigger("Attack"); + } + + // Počkej chvíli před aplikováním poškození + yield return new WaitForSeconds(0.3f); + + // Aplikuj poškození, pokud je hráč stále v dosahu + if (player != null && Vector3.Distance(transform.position, player.position) <= attackRange * 1.2f) + { + var health = player.GetComponent(); + if (health != null) + { + health.ModifyHealth(-attackDamage); + Debug.Log("Enemy attacked player for " + attackDamage + " damage!"); + } + } + + // Počkej na dokončení animace + yield return new WaitForSeconds(0.7f); + + // Obnov pohyb pouze pokud nebyl původně zastavený + if (!wasStopped) + { + agent.isStopped = false; + } + + isAttacking = false; + + // canAttack se nastaví v Update pomocí timeru + Debug.Log("Attack finished, cooldown started"); + } + + // Metoda pro vizuální reprezentaci útočného úhlu + private void OnDrawGizmosSelected() + { + Gizmos.color = Color.red; + Gizmos.DrawWireSphere(transform.position, attackRange); + + Gizmos.color = Color.yellow; + Vector3 leftDir = Quaternion.Euler(0, -attackAngle / 2, 0) * transform.forward; + Vector3 rightDir = Quaternion.Euler(0, attackAngle / 2, 0) * transform.forward; + + Gizmos.DrawRay(transform.position, leftDir * attackRange); + Gizmos.DrawRay(transform.position, rightDir * attackRange); + Gizmos.DrawLine(transform.position + leftDir * attackRange, transform.position + rightDir * attackRange); + } + + // Public metody pro komunikaci s EnemyMovement + public bool CanAttack() + { + return canAttack && !isAttacking; + } + + + public bool IsAttacking() + { + return isAttacking; + } + + public float GetCooldownProgress() + { + return Mathf.Clamp01(attackCooldownTimer / attackRate); + } + + public void SetAttackState(bool state) + { + canAttack = state; + if (!state) isAttacking = false; + } +} \ No newline at end of file diff --git a/3D blobici/Assets/Scripts/Enemy/EnemyMovement.cs b/3D blobici/Assets/Scripts/Enemy/EnemyMovement.cs index b7e408f..1d826b6 100644 --- a/3D blobici/Assets/Scripts/Enemy/EnemyMovement.cs +++ b/3D blobici/Assets/Scripts/Enemy/EnemyMovement.cs @@ -12,15 +12,18 @@ public class EnemyMovement : MonoBehaviour [Header("Combat")] public float attackRange = 2f; - public float attackCooldown = 1f; public float sightRange = 20f; public float patrolRange = 5f; - private bool canAttack = true; + + [Header("References")] + public EnemyAttack enemyAttack; private enum EnemyState { Patrolling, Chasing, Attacking } private EnemyState currentState = EnemyState.Patrolling; private Vector3 patrolCenter; private Vector3 patrolTarget; + private float lastStateChangeTime; + private float minAttackStateDuration = 1.0f; [Header("Animation")] public Animator animator; @@ -34,9 +37,16 @@ public class EnemyMovement : MonoBehaviour player = playerObject.transform; } + // Najdi EnemyAttack komponentu + if (enemyAttack == null) + { + enemyAttack = GetComponent(); + } + // Nastav výchozí pozice patrolCenter = transform.position; GenerateNewPatrolTarget(); + lastStateChangeTime = Time.time; // Spustit coroutines if (agent != null) @@ -80,58 +90,119 @@ public class EnemyMovement : MonoBehaviour { GenerateNewPatrolTarget(); } + + // Kontrola, zda hráč není v dohledu i během patrolování + if (player != null && enemyAttack != null && enemyAttack.IsPlayerInAttackRange()) + { + ChangeState(EnemyState.Attacking); + } } private void ChaseBehavior() { if (player == null) { - currentState = EnemyState.Patrolling; + ChangeState(EnemyState.Patrolling); return; } float distanceToPlayer = Vector3.Distance(transform.position, player.position); - if (distanceToPlayer <= attackRange) + // Přepni do attack stavu pouze pokud může útočit a je v dosahu + if (enemyAttack != null && enemyAttack.IsPlayerInAttackRange() && enemyAttack.CanAttack()) { - currentState = EnemyState.Attacking; - agent.isStopped = true; + ChangeState(EnemyState.Attacking); + return; } - else if (distanceToPlayer > sightRange * 1.5f) + + if (distanceToPlayer > sightRange * 1.5f) { - // Hráč je příliš daleko, vrať se k patrolování - currentState = EnemyState.Patrolling; + ChangeState(EnemyState.Patrolling); GenerateNewPatrolTarget(); } } private void AttackBehavior() { - if (player == null) + if (player == null || enemyAttack == null) { - currentState = EnemyState.Patrolling; - agent.isStopped = false; + ChangeState(EnemyState.Chasing); return; } + // RYCHLEJŠÍ a PŘESNĚJŠÍ otáčení k hráči + Vector3 directionToPlayer = (player.position - transform.position).normalized; + + // Ignoruj Y osu pro rotaci + directionToPlayer.y = 0; + + if (directionToPlayer != Vector3.zero) + { + Quaternion targetRotation = Quaternion.LookRotation(directionToPlayer); + transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, 15f * Time.deltaTime); // Zvýšená rychlost otáčení + } + + // Zkus zaútočit + bool attackAttempted = enemyAttack.TryAttack(); + float distanceToPlayer = Vector3.Distance(transform.position, player.position); - // Otoč se k hráči - Vector3 directionToPlayer = (player.position - transform.position).normalized; - Quaternion targetRotation = Quaternion.LookRotation(new Vector3(directionToPlayer.x, 0, directionToPlayer.z)); - transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, 5f * Time.deltaTime); - - // Útok na hráče - if (canAttack && distanceToPlayer <= attackRange) + // Zůstaň v attack stavu pokud může útočit nebo se otáčí k hráči + if (!attackAttempted && enemyAttack.CanAttack() && enemyAttack.IsPlayerInAttackRange()) { - StartCoroutine(Attack()); + return; } // Pokud je hráč příliš daleko, pokračuj v pronásledování - if (distanceToPlayer > attackRange * 1.2f) + if (distanceToPlayer > attackRange * 1.5f && !enemyAttack.IsAttacking()) { - currentState = EnemyState.Chasing; - agent.isStopped = false; + ChangeState(EnemyState.Chasing); + } + } + + private IEnumerator CheckPlayerInRange() + { + while (true) + { + if (player != null && !enemyAttack.IsAttacking()) + { + float distanceToPlayer = Vector3.Distance(transform.position, player.position); + + if (currentState == EnemyState.Patrolling && distanceToPlayer <= sightRange) + { + ChangeState(EnemyState.Chasing); + } + else if (currentState == EnemyState.Chasing && distanceToPlayer > sightRange * 1.5f) + { + ChangeState(EnemyState.Patrolling); + } + else if (currentState == EnemyState.Chasing && distanceToPlayer <= attackRange && enemyAttack.CanAttack()) + { + ChangeState(EnemyState.Attacking); + } + } + + yield return new WaitForSeconds(0.3f); + } + } + + private void ChangeState(EnemyState newState) + { + if (currentState == newState) return; + + currentState = newState; + lastStateChangeTime = Time.time; + + // Specifické akce při změně stavu + switch (newState) + { + case EnemyState.Chasing: + agent.isStopped = false; + break; + case EnemyState.Patrolling: + agent.isStopped = false; + GenerateNewPatrolTarget(); + break; } } @@ -139,11 +210,11 @@ public class EnemyMovement : MonoBehaviour { while (true) { - if (currentState == EnemyState.Chasing && player != null) + if (currentState == EnemyState.Chasing && player != null && !enemyAttack.IsAttacking()) { agent.SetDestination(player.position); } - else if (currentState == EnemyState.Patrolling) + else if (currentState == EnemyState.Patrolling && !enemyAttack.IsAttacking()) { agent.SetDestination(patrolTarget); } @@ -152,37 +223,12 @@ public class EnemyMovement : MonoBehaviour } } - private IEnumerator CheckPlayerInRange() - { - while (true) - { - if (player != null && currentState != EnemyState.Attacking) - { - float distanceToPlayer = Vector3.Distance(transform.position, player.position); - - if (distanceToPlayer <= sightRange && distanceToPlayer > attackRange) - { - currentState = EnemyState.Chasing; - } - else if (distanceToPlayer <= attackRange) - { - currentState = EnemyState.Attacking; - agent.isStopped = true; - } - } - - yield return new WaitForSeconds(0.5f); - } - } - private IEnumerator CheckCurrentRoom() { while (true) { - // Zkontroluj, zda je enemy stále v platné místnosti - if (!agent.isOnNavMesh) + if (!agent.isOnNavMesh && !enemyAttack.IsAttacking()) { - Debug.LogWarning("Enemy is off NavMesh, attempting to warp..."); agent.Warp(transform.position); } @@ -192,11 +238,9 @@ public class EnemyMovement : MonoBehaviour private void GenerateNewPatrolTarget() { - // Vyber náhodný cíl v okolí výchozí pozice Vector2 randomCircle = Random.insideUnitCircle * patrolRange; patrolTarget = patrolCenter + new Vector3(randomCircle.x, 0, randomCircle.y); - // Zajisti, že cíl je na NavMesh NavMeshHit hit; if (NavMesh.SamplePosition(patrolTarget, out hit, patrolRange, NavMesh.AllAreas)) { @@ -204,23 +248,6 @@ public class EnemyMovement : MonoBehaviour } } - private IEnumerator Attack() - { - canAttack = false; - - // Spustit animaci útoku - if (animator != null) - { - animator.SetTrigger("Attack"); - } - - // Zde můžeš přidat logiku poškození hráče - Debug.Log("Enemy attacks player!"); - - yield return new WaitForSeconds(attackCooldown); - canAttack = true; - } - // Voláno při smrti nepřítele public void Die() { @@ -231,11 +258,14 @@ public class EnemyMovement : MonoBehaviour agent.isStopped = true; } - // Zde můžeš přidat animaci smrti atd. + if (enemyAttack != null) + { + enemyAttack.SetAttackState(false); + } + Destroy(gameObject, 2f); } - // Pro vizualizaci v editoru private void OnDrawGizmosSelected() { Gizmos.color = Color.red; @@ -254,4 +284,21 @@ public class EnemyMovement : MonoBehaviour Gizmos.DrawLine(transform.position, patrolTarget); } } + + private void OnGUI() + { + if (Application.isPlaying && enemyAttack != null) + { + GUI.Label(new Rect(10, 30, 300, 20), $"State: {currentState}"); + GUI.Label(new Rect(10, 50, 300, 20), $"CanAttack: {enemyAttack.CanAttack()}"); + GUI.Label(new Rect(10, 70, 300, 20), $"IsAttacking: {enemyAttack.IsAttacking()}"); + GUI.Label(new Rect(10, 90, 300, 20), $"Cooldown: {enemyAttack.GetCooldownProgress():P0}"); + + if (player != null) + { + float distance = Vector3.Distance(transform.position, player.position); + GUI.Label(new Rect(10, 110, 300, 20), $"Distance: {distance:F2}"); + } + } + } } \ No newline at end of file