using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
public class Enemy : Character
{
private bool _acting = false;
private int? _pendingNext = null;
[SerializeField]
private List<EnemyState> _states = new List<EnemyState>();
[Header("현재 상태")]
[SerializeField]
private EnemyState _currentState = null;
void Awake()
{
_states = GetComponentsInChildren<EnemyState>(true).ToList();
foreach (var state in _states)
{
state.gameObject.SetActive(false);
}
}
void Start()
{
if (TurnManager.Instance != null)
TurnManager.Instance.OnTurnChanged += TurnChanged;
_currentState = _states[0];
_currentState.OnRequestChange += ChangeState; // 초기 상태 구독
_currentState.OnActionDone += OnActionDone;
_currentState.Enter();
}
void Update()
{
// 플레이어 턴일때만 체크함
if (TurnManager.Instance != null &&
TurnManager.Instance.CurrentOwner == TurnManager.TurnOwner.Player &&
_currentState != null)
{
_currentState.CheckStateChange();
}
}
void OnDestroy()
{
TurnManager.Instance.OnTurnChanged -= TurnChanged;
if (_currentState != null)
{
_currentState.OnRequestChange -= ChangeState;
_currentState.OnActionDone -= OnActionDone;
}
}
/// <summary>
/// 현재 턴이 적의 턴일시 state의 Action()함수를 통해 행동 시행
/// </summary>
/// <param name="owner">현재 턴의 주체</param>
private void TurnChanged(TurnManager.TurnOwner owner)
{
if (owner == TurnManager.TurnOwner.Enemy)
{
Debug.Log("Enemy's Turn Start");
if (_acting) return;
else _acting = true;
_currentState.Action();
}
}
/// <summary>
/// 현재 state를 다른 state로 변경, 무조건 state의 action()이 끝난 후 호출하도록 설계할 것
/// </summary>
/// <param name="state">바꾸고 싶은 상태</param>
public void ChangeState(int stateIndex)
{
if (stateIndex < 0 || stateIndex >= _states.Count) { Debug.LogError($"idx {stateIndex}"); return; }
if (_currentState == _states[stateIndex]) return;
// 행동 중에는 보류만
if (_acting) { _pendingNext = stateIndex; return; }
ApplyState(stateIndex);
}
private void ApplyState(int stateIndex)
{
var next = _states[stateIndex];
if (_currentState == next) return;
_currentState.OnRequestChange -= ChangeState;
_currentState.OnActionDone -= OnActionDone;
_currentState.Exit();
_currentState = next;
_currentState.OnRequestChange += ChangeState;
_currentState.OnActionDone += OnActionDone;
_currentState.Enter();
}
// state 행동 끝났을시 수행
public void OnActionDone()
{
_acting = false;
// 여기서만 전환 확정
if (_pendingNext.HasValue)
{
ApplyState(_pendingNext.Value);
_pendingNext = null;
}
StartCoroutine(DelayTurnEnd());
}
public IEnumerator DelayTurnEnd()
{
yield return new WaitForSeconds(2f);
Debug.Log("enemy turn end");
TurnManager.Instance.NextTurn();
}
}
using System.Collections;
using UnityEngine;
public class EnemyState : MonoBehaviour
{
private Enemy _enemy;
public event System.Action<int> OnRequestChange;
public event System.Action OnActionDone;
protected void ActionDone() => OnActionDone?.Invoke();
[SerializeField]
protected int _nextStateIndex = 0;
void Awake()
{
_enemy = transform.parent.GetComponent<Enemy>();
}
protected void RequestChange(int stateIndex)
{
OnRequestChange?.Invoke(stateIndex);
}
/// <summary>
/// 상태 진입시 수행되는 함수
/// </summary>
public virtual void Enter()
{
gameObject.SetActive(true);
}
/// <summary>
/// 적이 자신의 턴에 할 행동
/// </summary>
public virtual void Action()
{
RequestChange(_nextStateIndex);
ActionDone();
}
/// <summary>
/// 상태 종료시 수행되는 함수
/// </summary>
public virtual void Exit()
{
gameObject.SetActive(false);
}
/// <summary>
/// 상태 변환 체크용 if조건을 넣어서 조건에 맞을 시 _wantChangeState로 이동시킴
/// </summary>
public virtual void CheckStateChange()
{
}
}
현재 코드 상태이다
크게 여러 문제점이 보이는데, 기본적으로 구독을 계속 해제, 재연결을 반복하는 불안정한 구조로 되어 있다
이 매커니즘을 크게 다시 수정하려 한다
using System.Collections;
using UnityEngine;
public class EnemyState : MonoBehaviour
{
private Enemy _enemy;
[SerializeField]
protected int _nextStateIndex = 0;
void Awake()
{
_enemy = transform.parent.GetComponent<Enemy>();
}
/// <summary>
/// 상태 진입시 수행되는 함수
/// </summary>
public virtual void Enter()
{
gameObject.SetActive(true);
}
/// <summary>
/// 적이 자신의 턴에 할 행동
/// </summary>
public virtual void Action()
{
}
/// <summary>
/// 상태 종료시 수행되는 함수
/// </summary>
public virtual void Exit()
{
gameObject.SetActive(false);
}
/// <summary>
/// 상태 변환 체크용 if조건을 넣어서 조건에 맞을 시 _wantChangeState로 이동시킴
/// </summary>
public virtual void CheckStateChange()
{
}
}
일단 구독을 유발하는 event를 전면적으로 제거했다
이를 delegate로 구현해보려 한다
state쪽에서는 해당 Action을 delegate로 사용한다
이것을 적 객체가
모든 상태에 대해서 주입을 해서 저장을 해둔다
이렇게 주입을 해둘 시 requestchange, reportdone가 state에서 어떻게든 호출이 될시 enemy쪽으로 함수 호출을 요청하게 되고, 처리는 enemy가 하게 된다
즉
private Action<int> _requestChange; private Action _reportDone;
이 2개에
_requestChange = ChangeState;
_reportDone = OnActionDone;
내부 함수를 호출하도록 연결하고
foreach (var state in _states)
state.BindCallbacks(_requestChange, _reportDone);
이 반복을 통해서 주입을 시키는데
public void BindCallbacks(Action<int> requestChange, Action reportDone)
{ RequestChange = requestChange; ReportDone = reportDone; }
주입에서는 이 RequestChange ReportDone가 state에서 invoke되면 연결된 부분을 호출하게 해서 enemy의 내부 함수를 호출하게 하는 구조로 되어 있다
'개발일지 > 게임개발' 카테고리의 다른 글
| Project_DT - 덱 기능 설계, 구현 (0) | 2025.09.23 |
|---|---|
| Project_DT - 구글 시트 parsing 기능 만들기 (0) | 2025.09.19 |
| Project_DT - 적 공격 상태 수정(유한 상태 기계) / 실패 (0) | 2025.09.18 |
| Project DT - 적 공격 구현 (0) | 2025.09.10 |
| Project DT - 턴 시스템 구현하기 (1) | 2025.09.06 |