
현재 구조이다
여기에 적이 플레이어를 공격하는 구조를 추가한다

매니저를 추가로 넣어어 전투와 관련된 부분을 처리하도록 하고, 플레이어와 적을 하나의 부모를 가지도록 변경한다

ApplyDamage에 객체가 2개가 있으면 전투 기록을 하기에 좋을 것 같지만, 아직 너무 이른 기능 같아서 제거후, 간단히 데미지를 주도록 한다
using System.Collections.Generic;
using UnityEngine;
public class BattleManager : MonoSingleton<BattleManager>
{
private Character _player;
private List<Character> _enemys = new List<Character>();
public void ApplyDamage(Character Target, int Damage)
{
Target.SetHP(Target.GetHP() - Damage);
}
}
using UnityEngine;
public class Character : MonoBehaviour
{
protected int _hp = 10;
public int GetHP()
{
return _hp;
}
public void SetHP(int HP)
{
_hp = HP;
}
}
이렇게 구현을 했더니, 문제가 이렇게 할 시 플레이어 인스턴스나 적 인스턴스를 각자 가지고 있어야 하고, 귀찮게 싱글톤을 돌아서 호출하는 슬픈 현상이 생길 것 같다
using System.Collections.Generic;
using UnityEngine;
public class BattleManager : MonoSingleton<BattleManager>
{
private Character _player;
private List<Character> _enemys = new List<Character>();
public void ApplyDamageToPlayer(int Damage)
{
_player.SetHP(_player.GetHP() - Damage);
}
public void ApplyDamageToEnemy(int Damage, int num)
{
_enemys[num].SetHP(_enemys[num].GetHP() - Damage);
}
}
약간 처리를 분리해서 이렇게 하는 방향을 생각했다
GPT의 조언
문제점
- SetHP 직접 호출
HP 감소 로직이 BattleManager 안에 박혀 있어서, 캐릭터별 방어력·버프·사망 처리 같은 후속 로직을 넣기 어렵습니다.
→ Character 내부에서 TakeDamage(int amount)로 처리하는 게 안전합니다. - 인덱스로 적 지정
ApplyDamageToEnemy(int Damage, int num)처럼 리스트 인덱스로만 접근하면, 적이 죽어서 리스트가 변할 때 꼬일 수 있습니다.
→ Character 참조 자체를 파라미터로 받는 게 유연합니다. - 플레이어/적 분리
지금은 ApplyDamageToPlayer와 ApplyDamageToEnemy가 따로 있지만, 사실 둘 다 “타겟에게 데미지를 적용”이라는 같은 행위입니다.
→ 메서드 시그니처는 통합하는 편이 낫습니다.
해당 조언에 맞게 코드를 수정한다
using System.Collections.Generic;
using UnityEngine;
public class BattleManager : MonoSingleton<BattleManager>
{
private Character _player;
private List<Character> _enemys = new List<Character>();
public void ApplyDamage(Character target, int damage)
{
// 필요하면 여기서 방어력, 크리티컬 계산
int finalDamage = damage;
target.TakeDamage(finalDamage);
}
}
using UnityEngine;
public class Character : MonoBehaviour
{
protected int _hp = 10;
public int GetHP()
{
return _hp;
}
public void SetHP(int HP)
{
_hp = HP;
}
public void TakeDamage(int amount)
{
_hp -= amount;
if (_hp <= 0)
{
_hp = 0;
Die();
}
}
private void Die()
{
Debug.Log($"{name} is dead");
// 사망 처리 로직
}
}

Character쪽에 좀 더 함수를 추가하여 처리한다
이제 데미지를 넣을 수 있는 처리는 끝났고, 적 입장에서 어떻게 패턴을 가지며 공격할지 구현한다

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : Character
{
private bool _acting = false;
[SerializeField]
private List<Movement> _movements = new List<Movement>();
[SerializeField]
private int _moveIndex = 0;
void Start()
{
if (TurnManager.Instance != null)
TurnManager.Instance.OnTurnChanged += TurnChanged;
}
private void TurnChanged(TurnManager.TurnOwner owner)
{
if (owner == TurnManager.TurnOwner.Enemy)
{
Debug.Log("Enemy's Turn Start");
if (_acting) return;
_acting = true;
if (_movements[_moveIndex] != null)
{
_movements[_moveIndex].gameObject.SetActive(true);
_movements[_moveIndex].Action();
StartCoroutine(EnemyTurnEnd());
}
}
else
{
Debug.Log("Enemy's Turn End");
_acting = false;
}
}
public IEnumerator EnemyTurnEnd()
{
yield return new WaitForSeconds(5f);
if (TurnManager.Instance != null)
{
_moveIndex = (_moveIndex > _movements.Count) ? 0 : _moveIndex + 1;
TurnManager.Instance.NextTurn();
}
}
}
using UnityEngine;
public class Movement : MonoBehaviour
{
private enum ActionState
{
attack,
defence,
effect
}
private Enemy _enemy;
[SerializeField]
private ActionState _actionState = new ActionState();
[SerializeField]
private int _actionValue = 3;
void Awake()
{
_enemy = transform.parent.GetComponent<Enemy>();
}
public void Action()
{
switch (_actionState)
{
case ActionState.attack:
BattleManager.Instance.ApplyDamage(BattleManager.Instance.GetPlayer(), _actionValue);
break;
case ActionState.defence:
break;
case ActionState.effect:
break;
default:
Debug.LogError($"{gameObject.name} = Movement : use undefined ActionState");
break;
}
gameObject.SetActive(false);
}
}

적의 ai는 노드를 활용한 전처리 방식을 사용하려 한다
플레이어의 턴에서는 상태를 변경할 수 있는 상태가 되어 여려 변경 사항을 검사하며, 변경 사항에 따라 행동을 정한 뒤 자신의 턴이 오면 그 행동을 하는 형태로 설계 한다
각 노드들은 턴 시작시 action함수를 enemy에서 호출받아 실행하며, 이게 해당 턴에 적이 행동할 주체가 된다

이제 유니티에서 이렇게 세팅 후
awake에서 linq로 자식중 movement가 있는걸 list로 가져온다

자동으로 객체가 담기는것을 볼 수 있다
차례대로 잘 작동하는 것을 확인할 수 있다

다만 진행중 끝 노드에 도착시 다시 원점 복귀를 못하고 있다
조건식을 해당 방식으로 변경하여 해결한다
'개발일지 > 게임개발' 카테고리의 다른 글
| Project_DT - 턴제 전투, 적 상태 구현 (0) | 2025.09.18 |
|---|---|
| Project_DT - 적 공격 상태 수정(유한 상태 기계) / 실패 (0) | 2025.09.18 |
| Project DT - 턴 시스템 구현하기 (1) | 2025.09.06 |
| 유니티 3D - 방입장 카메라 무빙 구현 (3) | 2025.08.28 |
| Unity 2D 게임 개발 - 객체 A B를 잇는 화살표 그리기 (5) | 2025.08.17 |