diff --git a/Assets/JAY/Scripts/PlayerController.cs b/Assets/JAY/Scripts/PlayerController.cs index 2c841575..ca3fceb2 100644 --- a/Assets/JAY/Scripts/PlayerController.cs +++ b/Assets/JAY/Scripts/PlayerController.cs @@ -70,6 +70,8 @@ public class PlayerController : CharacterBase, IObserver _actionDash = new PlayerActionDash(); PlayerInit(); + + SwitchBattleMode(); } private void Update() @@ -87,7 +89,9 @@ public class PlayerController : CharacterBase, IObserver } // 공격 입력 처리 - if (Input.GetKeyDown(KeyCode.X) && (_currentAction == null || !_currentAction.IsActive)) { + if (Input.GetKeyDown(KeyCode.X) && (_currentAction == null || !_currentAction.IsActive) + && (CurrentState != PlayerState.Win && CurrentState != PlayerState.Dead)) { + Debug.Log("X 버튼 Down 됨"); StartAttackAction(); } @@ -171,6 +175,7 @@ public class PlayerController : CharacterBase, IObserver if (_weaponController.IsAttacking) return; // 이미 공격 중이면 실행 안함 if (_currentAction == _attackAction) { + Debug.Log($"Attack True"); _attackAction.EnableCombo(); _weaponController.AttackStart(); } @@ -178,8 +183,10 @@ public class PlayerController : CharacterBase, IObserver public void SetAttackComboFalse() { if (_currentAction == _attackAction) { + Debug.Log($"Attack False"); + // 이벤트 중복 호출? 공격 종료 시 SetAttackComboFalse가 아니라 ~True로 끝나서 오류 발생. (공격 안하는 상태여도 공격으로 판정됨) _attackAction.DisableCombo(); - _weaponController.AttackEnd(); + _weaponController.AttackEnd(); // IsAttacking = false로 변경 } } @@ -207,6 +214,16 @@ public class PlayerController : CharacterBase, IObserver public void OnNext(GameObject value) { + float playerAttackPower = _weaponController.AttackPower * attackPower; + + if (value.CompareTag("Enemy")) // 적이 Enemy일 때만 공격 처리 + { + var enemyController = value.transform.GetComponent(); + if (enemyController != null) + { + enemyController.TakeDamage(playerAttackPower); + } + } } public void OnError(Exception error) diff --git a/Assets/JAY/Scripts/WeaponController.cs b/Assets/JAY/Scripts/WeaponController.cs index 199dbe04..10ec8aa5 100644 --- a/Assets/JAY/Scripts/WeaponController.cs +++ b/Assets/JAY/Scripts/WeaponController.cs @@ -49,7 +49,11 @@ public class WeaponController : MonoBehaviour, IObservable _playerController = GetComponent(); if (_playerController == null) { - _playerController = GameObject.FindGameObjectWithTag("Player").GetComponent(); + var player = GameObject.FindGameObjectWithTag("Player"); + if (player != null) + { + _playerController = player.GetComponent(); + } } _previousPositions = new Vector3[_triggerZones.Length]; @@ -65,12 +69,26 @@ public class WeaponController : MonoBehaviour, IObservable } _isAttacking = true; _hitColliders.Clear(); + + StopAllCoroutines(); + StartCoroutine(AutoEndAttack()); // 자동 공격 종료 for (int i = 0; i < _triggerZones.Length; i++) { _previousPositions[i] = transform.position + transform.TransformVector(_triggerZones[i].position); } } + + private IEnumerator AutoEndAttack() + { + yield return new WaitForSeconds(0.6f); // 0.6초 가량 대기 + + if (_isAttacking) // 아직 공격 중이면 + { + Debug.Log("공격 자동 종료 - 타임아웃"); + AttackEnd(); + } + } public void AttackEnd() { @@ -101,17 +119,6 @@ public class WeaponController : MonoBehaviour, IObservable if (!_hitColliders.Contains(hit.collider)) { _hitColliders.Add(hit.collider); - - Debug.Log("hit.collider.name: " + hit.collider.name); - if (hit.collider.gameObject.CompareTag("Enemy")) - { - var enemyController = hit.transform.GetComponent(); - if (enemyController != null) - { - enemyController.TakeDamage(AttackPower * _playerController.attackPower); - } - } - Notify(hit.collider.gameObject); } } diff --git a/Assets/KSH/DungeonLogic.cs b/Assets/KSH/DungeonLogic.cs index 3a8bccc6..e5b66d8f 100644 --- a/Assets/KSH/DungeonLogic.cs +++ b/Assets/KSH/DungeonLogic.cs @@ -5,6 +5,8 @@ using UnityEngine; public class DungeonLogic : MonoBehaviour { + [SerializeField] private DungeonPanelController _dungeonPanelController; + [NonSerialized] public bool isCompleted = false; // 던전 클리어 여부 [NonSerialized] public bool isFailed = false; // 던전 실패 여부 @@ -24,19 +26,42 @@ public class DungeonLogic : MonoBehaviour // 죽음 이벤트 구독 if (_player != null) { + _player.OnGetHit += OnPlayerGetHit; _player.OnDeath += OnPlayerDeath; } if (_enemy != null) { + _enemy.OnGetHit += OnEnemyGetHit; _enemy.OnDeath += OnEnemyDeath; } } - // 플레이어 사망 처리 - private void OnPlayerDeath(CharacterBase player) + private void OnPlayerGetHit(CharacterBase player) + { + if (isFailed || isCompleted) return; // 어느 한 쪽 사망시 더이상 피격 X + + // TODO: 플레이어 피격 효과음 + + var result = _dungeonPanelController.SetPlayerHealth(); + if (!result) // 하트 모두 소모 + { + player.Die(); + } + } + + private void OnEnemyGetHit(CharacterBase enemy) + { + if (isFailed || isCompleted) return; + + // TODO: 에너미 피격 효과음 + + _dungeonPanelController.SetBossHealthBar(enemy.currentHP); + } + + // 플레이어 사망 처리 + private void OnPlayerDeath() { - Debug.Log("player name:" + player.characterName); if (!isFailed) // 중복 실행 방지 { FailDungeon(); @@ -44,9 +69,8 @@ public class DungeonLogic : MonoBehaviour } // 적 사망 처리 - private void OnEnemyDeath(CharacterBase enemy) + private void OnEnemyDeath() { - Debug.Log("enemy name:" + enemy.characterName); if (!isCompleted) // 중복 실행 방지 { CompleteDungeon(); @@ -61,8 +85,10 @@ public class DungeonLogic : MonoBehaviour Debug.Log("던전 공략 성공~!"); isCompleted = true; OnDungeonSuccess?.Invoke(); + + _dungeonPanelController.SetBossHealthBar(0.0f); // 보스 체력 0 재설정 - // 성공 UI 표시 ?? 강화 표기 + _player.SetState(PlayerState.Win); // TODO: 강화 시스템으로 넘어가고 일상 맵으로 이동 } } @@ -76,10 +102,13 @@ public class DungeonLogic : MonoBehaviour isFailed = true; OnDungeonFailure?.Invoke(); - // 죽음 애니메이션 + 실패 UI 표시 ? - // GameManager.Instance.ChangeToHomeScene(); + _player.SetState(PlayerState.Dead); - StartCoroutine(DelayedSceneChange()); // 테스트를 위해 3초 대기 후 전환 + // enemy가 더이상 Trace 하지 않도록 처리 + _player.gameObject.layer = LayerMask.NameToLayer("Ignore Raycast"); + _enemy.SetState(EnemyState.Idle); + + StartCoroutine(DelayedSceneChange()); // 3초 대기 후 전환 } } diff --git a/Assets/KSH/DungeonPanelController.cs b/Assets/KSH/DungeonPanelController.cs new file mode 100644 index 00000000..952bad9e --- /dev/null +++ b/Assets/KSH/DungeonPanelController.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.UI; + +public class DungeonPanelController : MonoBehaviour +{ + [SerializeField] private Slider _bossHealthBar; // 0~1 value + [SerializeField] private Image[] _playerHealthImages; // color 값 white / black 로 조정 + private int _countHealth = 0; + + public void SetBossHealthBar(float hp) // hp: 0~100 + { + float normalizedHp = hp / 100f; // 0~1 사이 값으로 조정 + _bossHealthBar.value = normalizedHp; + } + + // false 반환 시 사망 처리 + public bool SetPlayerHealth() + { + StartCoroutine(WaitForOneSecond()); + // out of index error 방지 + if (_countHealth > _playerHealthImages.Length - 1) return false; + + _playerHealthImages[_countHealth].color = Color.black; + _countHealth++; + return _countHealth <= _playerHealthImages.Length - 1; + } + + IEnumerator WaitForOneSecond() + { + yield return new WaitForSeconds(1.0f); + } +} diff --git a/Assets/KSH/DungeonPanelController.cs.meta b/Assets/KSH/DungeonPanelController.cs.meta new file mode 100644 index 00000000..5f53a9f2 --- /dev/null +++ b/Assets/KSH/DungeonPanelController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 75caf6a3958eb0745a00749003e12d73 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/KSH/DungeonTestScene.unity b/Assets/KSH/DungeonTestScene.unity index b8ce1c3e..5c48dbf7 100644 --- a/Assets/KSH/DungeonTestScene.unity +++ b/Assets/KSH/DungeonTestScene.unity @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a3f988deb686f35d4ca0aeb90dcf4d726ccff34a27d1b5f0e020b0ceeb008606 -size 185731 +oid sha256:7b994f0db1a78d57442ff714bbde098d085ed1ac985def8e2890bcee787c2d72 +size 236615 diff --git a/Assets/Scripts/Character/CharacterBase.cs b/Assets/Scripts/Character/CharacterBase.cs index 4a35b463..b0f4e2ea 100644 --- a/Assets/Scripts/Character/CharacterBase.cs +++ b/Assets/Scripts/Character/CharacterBase.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; using System.Collections.Generic; using UnityEngine; @@ -17,7 +18,8 @@ public abstract class CharacterBase : MonoBehaviour [Header("상태 이상")] public List statusEffects = new List(); - public event System.Action OnDeath; // 사망 이벤트 + public event Action OnDeath; // 사망 이벤트 + public event Action OnGetHit; // 피격 이벤트 protected virtual void Start() { @@ -26,6 +28,8 @@ public abstract class CharacterBase : MonoBehaviour public virtual void TakeDamage(float damage) { + if (currentHP <= 0) return; + float actualDamage = Mathf.Max(0, damage - defensePower); currentHP -= Mathf.RoundToInt(actualDamage); Debug.Log($"{characterName}이 {actualDamage}의 피해를 입었습니다. 현재 체력: {currentHP}"); @@ -33,14 +37,17 @@ public abstract class CharacterBase : MonoBehaviour if (currentHP <= 0) { Die(); + return; } + + OnGetHit?.Invoke(this); } public virtual void Die() { Debug.Log($"{characterName}이 사망했습니다."); // TODO: 사망 처리 - OnDeath?.Invoke(this); + OnDeath?.Invoke(); } // 상태이상 추가 메서드