Merge pull request #25 from Degulleo/DEG-108-던전-체력-구현

Deg 108 던전 체력 구현
This commit is contained in:
Sehyeon 2025-04-25 17:33:39 +09:00 committed by GitHub
commit ceb721bebc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 133 additions and 27 deletions

View File

@ -70,6 +70,8 @@ public class PlayerController : CharacterBase, IObserver<GameObject>
_actionDash = new PlayerActionDash(); _actionDash = new PlayerActionDash();
PlayerInit(); PlayerInit();
SwitchBattleMode();
} }
private void Update() private void Update()
@ -87,7 +89,9 @@ public class PlayerController : CharacterBase, IObserver<GameObject>
} }
// 공격 입력 처리 // 공격 입력 처리
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(); StartAttackAction();
} }
@ -171,6 +175,7 @@ public class PlayerController : CharacterBase, IObserver<GameObject>
if (_weaponController.IsAttacking) return; // 이미 공격 중이면 실행 안함 if (_weaponController.IsAttacking) return; // 이미 공격 중이면 실행 안함
if (_currentAction == _attackAction) { if (_currentAction == _attackAction) {
Debug.Log($"Attack True");
_attackAction.EnableCombo(); _attackAction.EnableCombo();
_weaponController.AttackStart(); _weaponController.AttackStart();
} }
@ -178,8 +183,10 @@ public class PlayerController : CharacterBase, IObserver<GameObject>
public void SetAttackComboFalse() { public void SetAttackComboFalse() {
if (_currentAction == _attackAction) { if (_currentAction == _attackAction) {
Debug.Log($"Attack False");
// 이벤트 중복 호출? 공격 종료 시 SetAttackComboFalse가 아니라 ~True로 끝나서 오류 발생. (공격 안하는 상태여도 공격으로 판정됨)
_attackAction.DisableCombo(); _attackAction.DisableCombo();
_weaponController.AttackEnd(); _weaponController.AttackEnd(); // IsAttacking = false로 변경
} }
} }
@ -207,6 +214,16 @@ public class PlayerController : CharacterBase, IObserver<GameObject>
public void OnNext(GameObject value) public void OnNext(GameObject value)
{ {
float playerAttackPower = _weaponController.AttackPower * attackPower;
if (value.CompareTag("Enemy")) // 적이 Enemy일 때만 공격 처리
{
var enemyController = value.transform.GetComponent<EnemyController>();
if (enemyController != null)
{
enemyController.TakeDamage(playerAttackPower);
}
}
} }
public void OnError(Exception error) public void OnError(Exception error)

View File

@ -49,7 +49,11 @@ public class WeaponController : MonoBehaviour, IObservable<GameObject>
_playerController = GetComponent<PlayerController>(); _playerController = GetComponent<PlayerController>();
if (_playerController == null) if (_playerController == null)
{ {
_playerController = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerController>(); var player = GameObject.FindGameObjectWithTag("Player");
if (player != null)
{
_playerController = player.GetComponent<PlayerController>();
}
} }
_previousPositions = new Vector3[_triggerZones.Length]; _previousPositions = new Vector3[_triggerZones.Length];
@ -65,12 +69,26 @@ public class WeaponController : MonoBehaviour, IObservable<GameObject>
} }
_isAttacking = true; _isAttacking = true;
_hitColliders.Clear(); _hitColliders.Clear();
StopAllCoroutines();
StartCoroutine(AutoEndAttack()); // 자동 공격 종료
for (int i = 0; i < _triggerZones.Length; i++) for (int i = 0; i < _triggerZones.Length; i++)
{ {
_previousPositions[i] = transform.position + transform.TransformVector(_triggerZones[i].position); _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() public void AttackEnd()
{ {
@ -101,17 +119,6 @@ public class WeaponController : MonoBehaviour, IObservable<GameObject>
if (!_hitColliders.Contains(hit.collider)) if (!_hitColliders.Contains(hit.collider))
{ {
_hitColliders.Add(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<EnemyController>();
if (enemyController != null)
{
enemyController.TakeDamage(AttackPower * _playerController.attackPower);
}
}
Notify(hit.collider.gameObject); Notify(hit.collider.gameObject);
} }
} }

View File

@ -5,6 +5,8 @@ using UnityEngine;
public class DungeonLogic : MonoBehaviour public class DungeonLogic : MonoBehaviour
{ {
[SerializeField] private DungeonPanelController _dungeonPanelController;
[NonSerialized] public bool isCompleted = false; // 던전 클리어 여부 [NonSerialized] public bool isCompleted = false; // 던전 클리어 여부
[NonSerialized] public bool isFailed = false; // 던전 실패 여부 [NonSerialized] public bool isFailed = false; // 던전 실패 여부
@ -24,19 +26,42 @@ public class DungeonLogic : MonoBehaviour
// 죽음 이벤트 구독 // 죽음 이벤트 구독
if (_player != null) if (_player != null)
{ {
_player.OnGetHit += OnPlayerGetHit;
_player.OnDeath += OnPlayerDeath; _player.OnDeath += OnPlayerDeath;
} }
if (_enemy != null) if (_enemy != null)
{ {
_enemy.OnGetHit += OnEnemyGetHit;
_enemy.OnDeath += OnEnemyDeath; _enemy.OnDeath += OnEnemyDeath;
} }
} }
// 플레이어 사망 처리 private void OnPlayerGetHit(CharacterBase player)
private void OnPlayerDeath(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) // 중복 실행 방지 if (!isFailed) // 중복 실행 방지
{ {
FailDungeon(); 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) // 중복 실행 방지 if (!isCompleted) // 중복 실행 방지
{ {
CompleteDungeon(); CompleteDungeon();
@ -61,8 +85,10 @@ public class DungeonLogic : MonoBehaviour
Debug.Log("던전 공략 성공~!"); Debug.Log("던전 공략 성공~!");
isCompleted = true; isCompleted = true;
OnDungeonSuccess?.Invoke(); OnDungeonSuccess?.Invoke();
_dungeonPanelController.SetBossHealthBar(0.0f); // 보스 체력 0 재설정
// 성공 UI 표시 ?? 강화 표기 _player.SetState(PlayerState.Win);
// TODO: 강화 시스템으로 넘어가고 일상 맵으로 이동 // TODO: 강화 시스템으로 넘어가고 일상 맵으로 이동
} }
} }
@ -76,10 +102,13 @@ public class DungeonLogic : MonoBehaviour
isFailed = true; isFailed = true;
OnDungeonFailure?.Invoke(); OnDungeonFailure?.Invoke();
// 죽음 애니메이션 + 실패 UI 표시 ? _player.SetState(PlayerState.Dead);
// GameManager.Instance.ChangeToHomeScene();
StartCoroutine(DelayedSceneChange()); // 테스트를 위해 3초 대기 후 전환 // enemy가 더이상 Trace 하지 않도록 처리
_player.gameObject.layer = LayerMask.NameToLayer("Ignore Raycast");
_enemy.SetState(EnemyState.Idle);
StartCoroutine(DelayedSceneChange()); // 3초 대기 후 전환
} }
} }

View File

@ -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);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 75caf6a3958eb0745a00749003e12d73
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/KSH/DungeonTestScene.unity (Stored with Git LFS)

Binary file not shown.

View File

@ -1,3 +1,4 @@
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
@ -17,7 +18,8 @@ public abstract class CharacterBase : MonoBehaviour
[Header("상태 이상")] [Header("상태 이상")]
public List<StatusEffect> statusEffects = new List<StatusEffect>(); public List<StatusEffect> statusEffects = new List<StatusEffect>();
public event System.Action<CharacterBase> OnDeath; // 사망 이벤트 public event Action OnDeath; // 사망 이벤트
public event Action<CharacterBase> OnGetHit; // 피격 이벤트
protected virtual void Start() protected virtual void Start()
{ {
@ -26,6 +28,8 @@ public abstract class CharacterBase : MonoBehaviour
public virtual void TakeDamage(float damage) public virtual void TakeDamage(float damage)
{ {
if (currentHP <= 0) return;
float actualDamage = Mathf.Max(0, damage - defensePower); float actualDamage = Mathf.Max(0, damage - defensePower);
currentHP -= Mathf.RoundToInt(actualDamage); currentHP -= Mathf.RoundToInt(actualDamage);
Debug.Log($"{characterName}이 {actualDamage}의 피해를 입었습니다. 현재 체력: {currentHP}"); Debug.Log($"{characterName}이 {actualDamage}의 피해를 입었습니다. 현재 체력: {currentHP}");
@ -33,14 +37,17 @@ public abstract class CharacterBase : MonoBehaviour
if (currentHP <= 0) if (currentHP <= 0)
{ {
Die(); Die();
return;
} }
OnGetHit?.Invoke(this);
} }
public virtual void Die() public virtual void Die()
{ {
Debug.Log($"{characterName}이 사망했습니다."); Debug.Log($"{characterName}이 사망했습니다.");
// TODO: 사망 처리 // TODO: 사망 처리
OnDeath?.Invoke(this); OnDeath?.Invoke();
} }
// 상태이상 추가 메서드 // 상태이상 추가 메서드