1. 기본 전투 루프 프로토타입
- 플레이어와 적을 하나씩 두고, HP와 턴 교대 시스템만 먼저 구현.
- “플레이어 → 적 → 플레이어” 순서로 턴이 진행되는 구조가 최소 단위
현재 글에서 구현할 부분이다

일단 턴제 게임에서 가장 핵심이 되는
Turn Manager를 만들어 주어야 한다
현재 만들고 싶은 흐름은
플레이어가 턴을 시작하고 종료를 하면 TurnManager가 그걸 받아서 적 객체에 턴 시작을 알리고 다시 턴 종료시 플레이어에게 턴 시작을 알리는 구조가 될 것 같다
선턴 시스템, 속도 개념에 대한 고찰
전투 진입시 플레이어 캐릭터와 적 캐릭터는 이하의 공식에 따라 선턴 우선도를 잡음
자신의 속도 스탯 + 1-6 주사위값 = 선턴 우선도
이후 선턴 우선도가 높은쪽이 우선해서 전투를 시작함
TurnManager 초기 TurnOwner = 선턴 시작 주체
속도 시스템에 대한 생각(아직은 아이디어 단계)
각 전투당 턴을 잡기 위해서는 SP(Speed Point)를 채워야함
채워야 하는 SP는 FP(Field Point)로 현재 지형에 따라 결정됨
전투가 시작되고 턴의 주체가 턴을 종료할시 주체의 속도스탯 + 1-3 주사위 값이 TurnManager의 TurnSP라는 변수에 더해짐
이때 TurnSP > FP일시 턴이 시작됨
즉, 상대의 속도가 느릴시 다른 상대는 여러번 턴을 진행하는것이 가능함
A = Speed 3/ B = Speed 1 주체가 있을시
FP는 5라 가정함
- A가 선턴을 진행, 종료
- TM에 A의 TurnSP에 3이 더해짐 // A 3 / 5
- B가 턴을 진행, 종료
- TM에 B의 TurnSP에 1이 더해짐 // B 1 / 5
- A의 턴이 돌아왔으나 A의 TurnSP가 아직 TurnSP > FP를 만족하지 못했기에 턴 진행 불가
- 2 반복 // A 6 / 5
- B의 턴이 돌아 왔으나 B의 TurnSP가 아직 TurnSP > FP를 만족하지 못했기에 턴 진행 불가
- 4 반복 // B 2 / 5
- A의 턴이 돌아오고 TM의 TurnSP가 6이기에 -FP를 진행하여 1이되고 A의 턴을 진행, 종료 // A 1 / 5
- 2 반복 // A 4 / 5
- 7 반복
- 4 반복 // B 3 / 5
- 5 반복
- 2 반복 // A 7 / 5
- 7 반복
- 4 반복 // B 4 / 5
- 9 반복 // A 2 / 5
- 2 반복 // A 5 / 5
- 7 반복
- 3 반복 // B 5 / 5
이런 속도 개념도 만들어는 두었지만 이후에 넣도록 한다
using UnityEngine;
public class TurnManager : MonoBehaviour
{
private enum TurnOwner
{
Player,
Enemy
}
private int _currnetTurn = 0;
private TurnOwner _turnOwner = new TurnOwner();
/// <summary>
/// 현재 주체의 턴을 끝내고 타 주체에게 턴으로 넘겨줌
/// </summary>
public void NextTurn()
{
if (_turnOwner == TurnOwner.Player)
{
}
else if (_turnOwner == TurnOwner.Enemy)
{
}
}
}
TurnManager의 기본 형태이다
using UnityEngine;
public class PlayerCharacter : MonoBehaviour
{
private int _hp = 30;
}
using UnityEngine;
public class EnemyCharacter : MonoBehaviour
{
private int _hp = 10;
}
각 캐릭터는 이러한 형태를 가진다
using UnityEngine;
using UnityEngine.UI;
public class FightUIManager : MonoBehaviour
{
[Header("플레이어 턴 종료 버튼")]
[SerializeField]
private Button _endTurnButton;
void Awake()
{
_endTurnButton.onClick.AddListener(() =>
PlayerTurnEnd()
);
}
private void PlayerTurnEnd()
{
}
}
플레이어가 보는 UI의 작동을 관리할 매니저이다
이때 TurmManager에서 턴이 시작되었음을 알려줄 방법이 필요 하기에, 이를 확인할 방법으로 Action을 주거나 bool을 줘야 할 것 같다
그리고 TurnManager는 광역적으로 씬에서 사용되기에 싱글톤으로 호출을 할 수 있도록 하여야 할 것 같다
using UnityEngine;
public class MonoSingleton<T> : MonoBehaviour where T : MonoBehaviour
{
public static T Instance { get; private set; }
protected virtual void Awake()
{
var self = this as T;
if (Instance != null && Instance != self)
{
Destroy(gameObject);
return;
}
Instance = self;
}
}
싱글톤으로 만들수 있는 가상 클래스를 작성한 뒤
턴 매니저에 적용해서 사용한다
using UnityEngine;
public sealed class TurnManager : MonoSingleton<TurnManager>
{
private enum TurnOwner
{
Player,
Enemy
}
private int _currnetTurn = 0;
private TurnOwner _turnOwner = new TurnOwner();
/// <summary>
/// 현재 주체의 턴을 끝내고 타 주체에게 턴으로 넘겨줌
/// </summary>
public void NextTurn()
{
if (_turnOwner == TurnOwner.Player)
{
_turnOwner = TurnOwner.Enemy;
}
else if (_turnOwner == TurnOwner.Enemy)
{
_turnOwner = TurnOwner.Player;
}
else
{
Debug.LogError("TurnManager : State Error");
}
}
}
최종적으로는 이러한 형태가 된다
그 다음으로는 NextTurn호출시 플레이어나, 적이나 둘다 시작을 인식을 받아야 하는데, 이는 Action delegate로 하려 한다
using UnityEngine;
public sealed class TurnManager : MonoSingleton<TurnManager>
{
public event System.Action<TurnOwner> OnTurnChanged;
public enum TurnOwner
{
Player,
Enemy
}
private int _currentTurn = 0;
private TurnOwner _turnOwner = TurnOwner.Player;
/// <summary>
/// 현재 주체의 턴을 끝내고 타 주체에게 턴으로 넘겨줌
/// </summary>
public void NextTurn()
{
_turnOwner = (_turnOwner == TurnOwner.Player) ? TurnOwner.Enemy : TurnOwner.Player;
_currentTurn++;
OnTurnChanged?.Invoke(_turnOwner);
}
}
TurnManager에 System.Action<>으로 호출을 할 수 있도록 해준뒤
using UnityEngine;
public class PlayerCharacter : MonoBehaviour
{
private int _hp = 30;
void Awake()
{
if (TurnManager.Instance != null)
TurnManager.Instance.OnTurnChanged += TurnChanged;
}
private void TurnChanged(TurnManager.TurnOwner owner)
{
if (owner == TurnManager.TurnOwner.Player)
{
Debug.Log("Player's Turn Start");
}
else
{
Debug.Log("Player's Turn End");
}
}
}
각 객체가 Action을 구독한다
이때 구독의 타이밍은 Start가 좀 더 나은 것 같아서 수정했다
Awake일시 준비가 되어 있지 않을 수 있다
using UnityEngine;
using UnityEngine.UI;
public class FightUIManager : MonoBehaviour
{
[Header("플레이어 턴 종료 버튼")]
[SerializeField]
private Button _endTurnButton;
void Awake()
{
_endTurnButton.onClick.AddListener(() =>
PlayerTurnEnd()
);
}
private void PlayerTurnEnd()
{
TurnManager.Instance.NextTurn();
}
}
그후 UIManager에서 버튼에 기능을 연결 해준다
이제 과정은 대강 되었고, 시작을 어떻게 할지 작성한다

TurnManager에서 StartBattle로 전투 시작을 해야할 것 같다
원래는 속도를 통한 선공 결정이 있지만 일단은 스킵하고 구현한다
using UnityEngine;
public sealed class TurnManager : MonoSingleton<TurnManager>
{
public event System.Action<TurnOwner> OnTurnChanged;
public enum TurnOwner
{
Player,
Enemy
}
[Header("현재 턴 카운트")]
[SerializeField]
private int _currentTurn = 0;
[Header("현재 턴 주체")]
[SerializeField]
private TurnOwner _turnOwner = TurnOwner.Player;
private bool _battleStarted = false;
private bool _battleOver = false;
/// <summary>전투 시작: 현재 _turnOwner를 첫 주체로 알림</summary>
public void StartTurn()
{
if (_battleStarted || _battleOver) return;
_battleStarted = true;
_currentTurn = 1;
OnTurnChanged?.Invoke(_turnOwner);
}
/// <summary>
/// 현재 주체의 턴을 끝내고 타 주체에게 턴으로 넘겨줌
/// </summary>
public void NextTurn()
{
if (!_battleStarted || _battleOver) return;
_turnOwner = (_turnOwner == TurnOwner.Player) ? TurnOwner.Enemy : TurnOwner.Player;
_currentTurn++;
OnTurnChanged?.Invoke(_turnOwner);
}
}
전투 확인용 bool
battleStarted와 battleOver를 사용한다
using System.Collections;
using UnityEngine;
public class EnemyCharacter : MonoBehaviour
{
private int _hp = 10;
private bool _acting = false;
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;
StartCoroutine(EnemyTurnEnd());
}
else
{
Debug.Log("Enemy's Turn End");
_acting = false;
}
}
private IEnumerator EnemyTurnEnd()
{
yield return new WaitForSeconds(3f);
if (TurnManager.Instance != null)
TurnManager.Instance.NextTurn();
}
}
적은 해당 Action을 받고 일정 시간 후, 턴을 넘겨준다
using UnityEngine;
using UnityEngine.UI;
public class FightUIManager : MonoBehaviour
{
[Header("플레이어 턴 종료 버튼")]
[SerializeField]
private Button _endTurnButton;
void Start()
{
_endTurnButton.onClick.AddListener(() =>
PlayerTurnEnd()
);
if (TurnManager.Instance != null)
{
TurnManager.Instance.OnTurnChanged += HandleTurnChanged;
// 초기 상태도 반영
HandleTurnChanged(TurnManager.Instance.CurrentOwner);
}
}
private void HandleTurnChanged(TurnManager.TurnOwner owner)
{
_endTurnButton.interactable = (owner == TurnManager.TurnOwner.Player);
}
private void PlayerTurnEnd()
{
_endTurnButton.interactable = false;
if (TurnManager.Instance.CurrentOwner == TurnManager.TurnOwner.Player)
{
TurnManager.Instance.NextTurn();
}
}
}
UI또한 Action을 받아서 버튼 활성, 비활성을 조절하며 플레이어 턴을 조작한다
https://github.com/NoNamed02/Project_DT
GitHub - NoNamed02/Project_DT
Contribute to NoNamed02/Project_DT development by creating an account on GitHub.
github.com

'개발일지 > 게임개발' 카테고리의 다른 글
| Project_DT - 적 공격 상태 수정(유한 상태 기계) / 실패 (0) | 2025.09.18 |
|---|---|
| Project DT - 적 공격 구현 (0) | 2025.09.10 |
| 유니티 3D - 방입장 카메라 무빙 구현 (3) | 2025.08.28 |
| Unity 2D 게임 개발 - 객체 A B를 잇는 화살표 그리기 (5) | 2025.08.17 |
| Unity 게임 개발 - 세이브 기능 (0) | 2025.05.05 |