From 2e401f104263af10cacce49766061c131633e3f9 Mon Sep 17 00:00:00 2001 From: hydrozen1178 Date: Fri, 30 Jan 2026 17:53:18 +0900 Subject: [PATCH] .. --- Assets/0.SCENE/MainGame.unity | 9 ++- .../OutAnime/ThrowAnime.fbx.meta | 9 ++- .../Player Anime/AttackAnime1.anim | 6 +- .../Player Anime/AttackAnime2.anim | 2 +- .../Player Anime/AttackAnime3.anim | 6 +- Assets/7.Other Code/MonsterClass.cs | 67 +++++++++++------ Assets/7.Other Code/NormalMonster.cs | 74 +++++++------------ 7 files changed, 93 insertions(+), 80 deletions(-) diff --git a/Assets/0.SCENE/MainGame.unity b/Assets/0.SCENE/MainGame.unity index fc808bda..22330efa 100644 --- a/Assets/0.SCENE/MainGame.unity +++ b/Assets/0.SCENE/MainGame.unity @@ -146660,6 +146660,9 @@ PrefabInstance: insertIndex: -1 addedObject: {fileID: 2123637860} m_AddedComponents: + - targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: 76cee5628aa6b874c96342f004fa138b, type: 3} + insertIndex: -1 + addedObject: {fileID: 1432447527} - targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: 76cee5628aa6b874c96342f004fa138b, type: 3} insertIndex: -1 addedObject: {fileID: 1432447519} @@ -146672,9 +146675,6 @@ PrefabInstance: - targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: 76cee5628aa6b874c96342f004fa138b, type: 3} insertIndex: -1 addedObject: {fileID: 1432447533} - - targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: 76cee5628aa6b874c96342f004fa138b, type: 3} - insertIndex: -1 - addedObject: {fileID: 1432447527} - targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: 76cee5628aa6b874c96342f004fa138b, type: 3} insertIndex: -1 addedObject: {fileID: 1432447531} @@ -146787,8 +146787,9 @@ MonoBehaviour: pAnim: {fileID: 1432447533} playerHealth: {fileID: 1432447526} weaponHitBox: {fileID: 1938848879} - attackCooldown: 0.4 + attackCooldown: 0.5 fullChargeTime: 3 + postComboDelay: 1.2 --- !u!114 &1432447528 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Assets/4.PlayerAnimation/OutAnime/ThrowAnime.fbx.meta b/Assets/4.PlayerAnimation/OutAnime/ThrowAnime.fbx.meta index ddcab618..f8f6d064 100644 --- a/Assets/4.PlayerAnimation/OutAnime/ThrowAnime.fbx.meta +++ b/Assets/4.PlayerAnimation/OutAnime/ThrowAnime.fbx.meta @@ -85,7 +85,14 @@ ModelImporter: mirror: 0 bodyMask: 01000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000 curves: [] - events: [] + events: + - time: 0.26926506 + functionName: OnAttackEnd + data: + objectReferenceParameter: {instanceID: 0} + floatParameter: 0 + intParameter: 0 + messageOptions: 0 transformMask: [] maskType: 3 maskSource: {instanceID: 0} diff --git a/Assets/4.PlayerAnimation/Player Anime/AttackAnime1.anim b/Assets/4.PlayerAnimation/Player Anime/AttackAnime1.anim index d2ba0a17..4fe88afc 100644 --- a/Assets/4.PlayerAnimation/Player Anime/AttackAnime1.anim +++ b/Assets/4.PlayerAnimation/Player Anime/AttackAnime1.anim @@ -65218,14 +65218,14 @@ AnimationClip: m_HasMotionFloatCurves: 0 m_Events: - time: 0.6333333 - functionName: OnAttackShake + functionName: StartWeaponCollision data: objectReferenceParameter: {fileID: 0} floatParameter: 0 intParameter: 0 messageOptions: 0 - - time: 0.6333333 - functionName: StartWeaponCollision + - time: 0.65 + functionName: OnAttackShake data: objectReferenceParameter: {fileID: 0} floatParameter: 0 diff --git a/Assets/4.PlayerAnimation/Player Anime/AttackAnime2.anim b/Assets/4.PlayerAnimation/Player Anime/AttackAnime2.anim index 09e678a5..fe38c2ce 100644 --- a/Assets/4.PlayerAnimation/Player Anime/AttackAnime2.anim +++ b/Assets/4.PlayerAnimation/Player Anime/AttackAnime2.anim @@ -47825,7 +47825,7 @@ AnimationClip: floatParameter: 0 intParameter: 0 messageOptions: 0 - - time: 0.43333334 + - time: 0.45 functionName: OnAttackShake data: objectReferenceParameter: {fileID: 0} diff --git a/Assets/4.PlayerAnimation/Player Anime/AttackAnime3.anim b/Assets/4.PlayerAnimation/Player Anime/AttackAnime3.anim index 54f1a12a..cbe625ab 100644 --- a/Assets/4.PlayerAnimation/Player Anime/AttackAnime3.anim +++ b/Assets/4.PlayerAnimation/Player Anime/AttackAnime3.anim @@ -67324,14 +67324,14 @@ AnimationClip: m_HasMotionFloatCurves: 0 m_Events: - time: 0.2 - functionName: OnAttackShake + functionName: StartWeaponCollision data: objectReferenceParameter: {fileID: 0} floatParameter: 0 intParameter: 0 messageOptions: 0 - - time: 0.20410536 - functionName: StartWeaponCollision + - time: 0.21666667 + functionName: OnAttackShake data: objectReferenceParameter: {fileID: 0} floatParameter: 0 diff --git a/Assets/7.Other Code/MonsterClass.cs b/Assets/7.Other Code/MonsterClass.cs index fce2e95a..9d1d9287 100644 --- a/Assets/7.Other Code/MonsterClass.cs +++ b/Assets/7.Other Code/MonsterClass.cs @@ -1,5 +1,6 @@ using UnityEngine; using UnityEngine.AI; +using System.Collections; // ⭐ IEnumerator 사용을 위해 추가 using System; public class MonsterClass : MonoBehaviour, IDamageable @@ -18,6 +19,11 @@ public class MonsterClass : MonoBehaviour, IDamageable protected AudioSource audioSource; protected bool isHit; protected bool isDead; + protected bool isAttacking; + + [Header("AI 설정 (공격 후 휴식)")] + [SerializeField] protected float attackRestDuration = 1.5f; // ⭐ 공격 후 쉬는 시간 (초) + protected bool isResting; // ⭐ 현재 쉬고 있는 상태인지 확인 [Header("경험치")] [SerializeField] protected int expReward = 10; @@ -33,7 +39,6 @@ public class MonsterClass : MonoBehaviour, IDamageable [Header("--- 드랍 설정 ---")] [SerializeField] private GameObject potionPrefab; [SerializeField][Range(0f, 100f)] private float potionDropChance = 30f; - [Space(10)] [SerializeField] private GameObject[] weaponPrefabs; [SerializeField][Range(0f, 100f)] private float weaponDropChance = 5f; @@ -47,15 +52,23 @@ public class MonsterClass : MonoBehaviour, IDamageable OnHealthChanged?.Invoke(currentHP, maxHP); } - // ⭐ [애니메이션 버그 해결] 매 프레임 속도를 체크해서 애니메이터에 전달합니다. protected virtual void Update() { if (isDead) return; - if (agent != null && animator != null) + // ⭐ [수정] 피격(isHit), 공격(isAttacking), 또는 휴식(isResting) 중이면 이동을 멈춥니다. + bool shouldStop = isHit || isAttacking || isResting; + + if (agent != null && agent.isOnNavMesh) { - // 에이전트의 현재 이동 속도를 가져와서 "Speed" 파라미터에 넣어줍니다. - float currentSpeed = agent.velocity.magnitude; + agent.isStopped = shouldStop; + if (shouldStop) agent.velocity = Vector3.zero; + } + + if (animator != null) + { + // 멈췄을 때는 속도를 0으로 전달하여 Idle 애니메이션이 나오게 합니다. + float currentSpeed = (agent != null && !shouldStop) ? agent.velocity.magnitude : 0f; animator.SetFloat("Speed", currentSpeed); } } @@ -75,12 +88,37 @@ public class MonsterClass : MonoBehaviour, IDamageable protected virtual void StartHit() { isHit = true; + isAttacking = false; + isResting = false; // ⭐ 맞으면 쉬는 걸 중단하고 바로 아파해야 합니다. + StopAllCoroutines(); // 진행 중인 휴식 루틴을 끕니다. + if (agent && agent.isOnNavMesh) { agent.isStopped = true; agent.velocity = Vector3.zero; } + animator.Play(Monster_GetDamage, 0, 0f); if (hitEffect) hitEffect.Play(); if (hitSound) audioSource.PlayOneShot(hitSound); } + public virtual void OnAttackStart() { isAttacking = true; isResting = false; } + + // ⭐ [수정] 공격이 끝나면 바로 움직이는 게 아니라 '휴식' 코루틴을 실행합니다. + public virtual void OnAttackEnd() + { + isAttacking = false; + if (!isDead && !isHit) + { + StartCoroutine(RestAfterAttack()); + } + } + + // ⭐ 공격 후 대기 코루틴 + private IEnumerator RestAfterAttack() + { + isResting = true; + yield return new WaitForSeconds(attackRestDuration); // 설정한 시간만큼 대기 + isResting = false; + } + public virtual void OnHitEnd() { isHit = false; if (agent && agent.isOnNavMesh) agent.isStopped = false; } protected virtual void Die() @@ -88,7 +126,6 @@ public class MonsterClass : MonoBehaviour, IDamageable if (isDead) return; isDead = true; - // 아이템 날아감 방지: 몬스터 시체의 충돌을 끕니다. Collider col = GetComponent(); if (col != null) col.enabled = false; @@ -97,7 +134,6 @@ public class MonsterClass : MonoBehaviour, IDamageable if (agent && agent.isOnNavMesh) { agent.isStopped = true; agent.velocity = Vector3.zero; } animator.Play(Monster_Die, 0, 0f); - Debug.Log("애니메이션테스트"); if (deathSound) audioSource.PlayOneShot(deathSound); Destroy(gameObject, 1.5f); @@ -105,11 +141,7 @@ public class MonsterClass : MonoBehaviour, IDamageable private void TryDropItems() { - if (potionPrefab != null && UnityEngine.Random.Range(0f, 100f) <= potionDropChance) - { - SpawnItem(potionPrefab); - } - + if (potionPrefab != null && UnityEngine.Random.Range(0f, 100f) <= potionDropChance) SpawnItem(potionPrefab); if (weaponPrefabs != null && weaponPrefabs.Length > 0 && UnityEngine.Random.Range(0f, 100f) <= weaponDropChance) { int randomIndex = UnityEngine.Random.Range(0, weaponPrefabs.Length); @@ -121,22 +153,15 @@ public class MonsterClass : MonoBehaviour, IDamageable { Vector3 spawnPos = transform.position + Vector3.up * 0.5f; GameObject item = Instantiate(prefab, spawnPos, Quaternion.identity); - - // ✅ [유저 요청] 배율 없이 프리팹의 원래 크기를 100% 그대로 적용 item.transform.localScale = prefab.transform.localScale; if (item.TryGetComponent(out var rb)) { - // 튀어오르는 힘은 유저님이 설정하신 0.02f 유지 Vector3 popDir = Vector3.up * 0.02f + UnityEngine.Random.insideUnitSphere * 0.02f; rb.AddForce(popDir, ForceMode.Impulse); - - if (prefab != potionPrefab) - { - rb.AddTorque(UnityEngine.Random.insideUnitSphere * 0.3f, ForceMode.Impulse); - } + if (prefab != potionPrefab) rb.AddTorque(UnityEngine.Random.insideUnitSphere * 0.3f, ForceMode.Impulse); } } public void OnDeathAnimEnd() { } -} +} \ No newline at end of file diff --git a/Assets/7.Other Code/NormalMonster.cs b/Assets/7.Other Code/NormalMonster.cs index 8b24ad20..e1b7a656 100644 --- a/Assets/7.Other Code/NormalMonster.cs +++ b/Assets/7.Other Code/NormalMonster.cs @@ -10,12 +10,10 @@ public class NormalMonster : MonsterClass [SerializeField] private float damage = 10f; [SerializeField] private float attackRange = 2f; [SerializeField] private float attackDelay = 1.5f; - [SerializeField] private float postAttackDelay = 1.5f; + // [SerializeField] private float postAttackDelay = 1.5f; // ⭐ MonsterClass의 attackRestDuration을 사용하므로 삭제 권장 private float lastAttackTime; - // ⭐ MonsterClass에 OnHealthChanged가 있으므로 여기서는 삭제합니다. - [Header("공격 애니메이션")] [SerializeField] private string[] attackAnimations = { "Monster_Attack_1" }; [Header("이동 애니메이션")] @@ -33,12 +31,15 @@ public class NormalMonster : MonsterClass private Transform player; private int attackIndex; - private bool isAttacking; + + // ❌ [삭제] bool isAttacking; + // ⭐ 부모(MonsterClass)에 이미 있으므로 자식에서 또 선언하면 에러가 납니다! + private bool isPlayerInZone; protected override void Start() { - base.Start(); + base.Start(); // 부모의 Start 실행 player = GameObject.FindWithTag("Player")?.transform; agent.stoppingDistance = attackRange - 0.4f; animator.applyRootMotion = false; @@ -46,13 +47,17 @@ public class NormalMonster : MonsterClass protected override void Update() { + // ⭐ 부모의 Update(이동 제한 로직)를 먼저 실행합니다. + base.Update(); + if (isDead || player == null) return; - // ⭐ CS0103 에러 해결: 아래 정의된 Patrol()을 호출 + // 피격 중이거나 공격 중, 혹은 쉬는 중(isResting)에는 AI 로직을 타지 않습니다. + if (isHit || isAttacking || isResting) return; + if (isPlayerInZone) HandlePlayerTarget(); else Patrol(); - // ⭐ CS0103 에러 해결: 아래 정의된 UpdateMovementAnimation()을 호출 UpdateMovementAnimation(); } @@ -60,21 +65,12 @@ public class NormalMonster : MonsterClass { float distance = Vector3.Distance(transform.position, player.position); - if (distance <= agent.stoppingDistance + 0.05f) - { - agent.isStopped = true; - agent.velocity = Vector3.zero; - } - else if (!isAttacking) - { - agent.isStopped = false; - } - + // 공격 범위 안에 들어왔을 때 if (distance <= attackRange - stopBuffer) { - if (!isAttacking) TryAttack(); + TryAttack(); } - else if (Time.time >= nextRepathTime && !isAttacking) + else if (Time.time >= nextRepathTime) { agent.SetDestination(player.position); nextRepathTime = Time.time + repathInterval; @@ -89,41 +85,33 @@ public class NormalMonster : MonsterClass string attackName = attackAnimations[attackIndex]; attackIndex = (attackIndex + 1) % attackAnimations.Length; - isAttacking = true; - agent.isStopped = true; - agent.velocity = Vector3.zero; + // ⭐ 부모의 OnAttackStart를 호출하여 isAttacking을 true로 만들고 이동을 멈춥니다. + OnAttackStart(); animator.Play(attackName, 0, 0f); } - // ⭐ CS0506 해결: 부모의 virtual 메서드를 정상적으로 재정의 - public override void TakeDamage(float amount) + // ⭐ [수정] 부모의 가상 메서드를 오버라이드합니다. + public override void OnAttackStart() { - base.TakeDamage(amount); // 부모의 OnDamaged(UI 신호 포함)를 실행 + base.OnAttackStart(); // 부모의 isAttacking = true 로직 실행 } - public void OnAttackEnd() + // ⭐ [수정] 부모의 가상 메서드를 오버라이드하여 '휴식' 기능을 활성화합니다. + public override void OnAttackEnd() { - StartCoroutine(PostAttackWait()); + base.OnAttackEnd(); // 부모의 'n초간 휴식' 코루틴을 실행합니다! } - private IEnumerator PostAttackWait() + public void OnAttackHit() // 애니메이션 이벤트 (공격 판정 시점) { - yield return new WaitForSeconds(postAttackDelay); - isAttacking = false; - if (agent && agent.isOnNavMesh) agent.isStopped = false; - } - - public void OnAttackHit() - { - if (player == null) return; + if (player == null || isHit || isDead) return; PlayerHealth playerHealth = player.GetComponent(); if (playerHealth != null) playerHealth.TakeDamage(damage); } - // ⭐ 복구된 이동 애니메이션 함수 void UpdateMovementAnimation() { - if (isAttacking || isHit) return; + if (isAttacking || isHit || isResting) return; if (agent.velocity.magnitude < 0.1f) animator.Play(Monster_Idle); @@ -131,7 +119,6 @@ public class NormalMonster : MonsterClass animator.Play(Monster_Walk); } - // ⭐ 복구된 순찰 함수 void Patrol() { if (Time.time < nextPatrolTime) return; @@ -155,11 +142,4 @@ public class NormalMonster : MonsterClass { if (other.CompareTag("Player")) isPlayerInZone = false; } - - protected override void StartHit() - { - isAttacking = false; - if (agent && agent.isOnNavMesh) agent.isStopped = false; - base.StartHit(); - } -} +} \ No newline at end of file