SRPGのバトルシステムは、複雑に見えます。
しかし、処理フローを分解すれば、実装できます。
この記事では、設計から実装まで詳しく解説します。
✨ この記事でわかること
- プレイヤーターンと敵ターンの管理方法
- 行動選択UIの実装
- 攻撃判定とダメージ計算の実装
- ステートマシンを使った設計
- 実装例とコード

バトルシステムは、ステートマシンで管理すると整理しやすくなります。まずは状態を明確にしましょう。
Unity入門の森を見る 初心者歓迎!動画×プロジェクト一式で本格ゲーム制作を学べる
あなたのオリジナルゲーム、今年こそ完成させませんか?
RPG・アクション・ホラー…Unityで本格ゲームを作りたい人のための学習サイトです。
実際に完成するゲームを題材に、
ソースコード・素材・プロジェクト一式をすべて公開。
仕事や学校の合間の1〜2時間でも、
「写経→改造」で自分のゲームまで作りきれる環境です。
バトルシステムの基本フロー

バトルシステムは、以下のフローで動作します。
それぞれの処理を理解しましょう。
- ターン開始:プレイヤーターンまたは敵ターン
- 行動選択:移動・攻撃・待機など
- 行動実行:選択した行動を実行
- 結果処理:ダメージ計算、HP更新
- ターン終了:次のターンへ
このフローを実装すれば、基本的なバトルシステムが完成します。
ステートマシンでの管理
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
public enum BattleState { PlayerTurn, // プレイヤーターン EnemyTurn, // 敵ターン ActionSelect, // 行動選択中 ActionExecute, // 行動実行中 BattleEnd // バトル終了 } public class BattleSystem : MonoBehaviour { public BattleState currentState = BattleState.PlayerTurn; void Update() { switch (currentState) { case BattleState.PlayerTurn: HandlePlayerTurn(); break; case BattleState.EnemyTurn: HandleEnemyTurn(); break; case BattleState.ActionSelect: HandleActionSelect(); break; case BattleState.ActionExecute: HandleActionExecute(); break; } } } |
ステートマシンで管理することで、状態が明確になります。
デバッグもしやすくなります。
Unity入門の森を見る 初心者歓迎!動画×プロジェクト一式で本格ゲーム制作を学べる
プレイヤーターンと敵ターンの管理

ターン管理は、バトルシステムの核心です。
実装方法を紹介します。
ターン管理スクリプト
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
using UnityEngine; using System.Collections.Generic; public class TurnManager : MonoBehaviour { public BattleState currentState = BattleState.PlayerTurn; public List<Unit> playerUnits = new List<Unit>(); public List<Unit> enemyUnits = new List<Unit>(); private int currentPlayerIndex = 0; private int currentEnemyIndex = 0; public void StartBattle() { currentState = BattleState.PlayerTurn; currentPlayerIndex = 0; StartPlayerTurn(); } void StartPlayerTurn() { if (currentPlayerIndex >= playerUnits.Count) { // プレイヤーユニット全員が行動済み currentState = BattleState.EnemyTurn; currentPlayerIndex = 0; StartEnemyTurn(); } else { // 次のプレイヤーユニットのターン Unit currentUnit = playerUnits[currentPlayerIndex]; if (currentUnit.stats.currentHP > 0) { SelectUnit(currentUnit); } else { currentPlayerIndex++; StartPlayerTurn(); } } } void StartEnemyTurn() { if (currentEnemyIndex >= enemyUnits.Count) { // 敵ユニット全員が行動済み currentState = BattleState.PlayerTurn; currentEnemyIndex = 0; StartPlayerTurn(); } else { // 次の敵ユニットのターン Unit currentUnit = enemyUnits[currentEnemyIndex]; if (currentUnit.stats.currentHP > 0) { ExecuteEnemyAI(currentUnit); } else { currentEnemyIndex++; StartEnemyTurn(); } } } void SelectUnit(Unit unit) { currentState = BattleState.ActionSelect; // 行動選択UIを表示 } void ExecuteEnemyAI(Unit unit) { // 敵AIの処理 currentEnemyIndex++; StartEnemyTurn(); } public void EndTurn() { if (currentState == BattleState.PlayerTurn) { currentPlayerIndex++; StartPlayerTurn(); } } } |
このコードで、プレイヤーターンと敵ターンを管理できます。
各ユニットが順番に行動します。
Unity入門の森を見る 初心者歓迎!動画×プロジェクト一式で本格ゲーム制作を学べる
行動選択UIの実装

行動選択UIは、プレイヤーの操作を受け付けます。
実装方法を紹介します。
行動選択UIスクリプト
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
using UnityEngine; using UnityEngine.UI; public class ActionMenuUI : MonoBehaviour { public GameObject menuPanel; public Button moveButton; public Button attackButton; public Button skillButton; public Button waitButton; private Unit selectedUnit; private TurnManager turnManager; void Start() { turnManager = FindObjectOfType<TurnManager>(); moveButton.onClick.AddListener(OnMoveClicked); attackButton.onClick.AddListener(OnAttackClicked); skillButton.onClick.AddListener(OnSkillClicked); waitButton.onClick.AddListener(OnWaitClicked); menuPanel.SetActive(false); } public void ShowMenu(Unit unit) { selectedUnit = unit; menuPanel.SetActive(true); // ユニットの状態に応じてボタンを有効/無効化 UpdateButtonStates(); } void UpdateButtonStates() { // 移動済みかどうかでボタンを制御 bool canMove = !selectedUnit.hasMoved; bool canAttack = !selectedUnit.hasActed; moveButton.interactable = canMove; attackButton.interactable = canAttack; skillButton.interactable = canAttack; } void OnMoveClicked() { menuPanel.SetActive(false); // 移動範囲を表示 FindObjectOfType<MoveRangeDisplay>().ShowMoveRange(selectedUnit); } void OnAttackClicked() { menuPanel.SetActive(false); // 攻撃範囲を表示 FindObjectOfType<AttackRangeDisplay>().ShowAttackRange(selectedUnit); } void OnSkillClicked() { menuPanel.SetActive(false); // スキル選択画面を表示 } void OnWaitClicked() { menuPanel.SetActive(false); selectedUnit.hasActed = true; turnManager.EndTurn(); } } |
このコードで、行動選択UIが実装できます。
ユニットの状態に応じて、ボタンを有効/無効化します。
ユニットの状態管理
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class Unit : MonoBehaviour { public UnitStats stats; public bool hasMoved = false; public bool hasActed = false; public void ResetTurn() { hasMoved = false; hasActed = false; } public void OnMoveComplete() { hasMoved = true; } public void OnActionComplete() { hasActed = true; } } |
ユニットの行動状態を管理します。
移動済み・行動済みのフラグで、行動を制限します。
Unity入門の森を見る 初心者歓迎!動画×プロジェクト一式で本格ゲーム制作を学べる
攻撃判定とダメージ計算

攻撃判定とダメージ計算は、戦闘の核心です。
実装方法を紹介します。
攻撃範囲の計算
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
using UnityEngine; using System.Collections.Generic; public class AttackRangeDisplay : MonoBehaviour { public GameObject attackHighlightPrefab; private List<GameObject> highlights = new List<GameObject>(); public void ShowAttackRange(Unit unit, GridMap gridMap) { ClearHighlights(); int attackRange = unit.stats.attackRange; for (int x = -attackRange; x <= attackRange; x++) { for (int y = -attackRange; y <= attackRange; y++) { int distance = Mathf.Abs(x) + Mathf.Abs(y); if (distance <= attackRange) { int targetX = unit.gridX + x; int targetY = unit.gridY + y; if (gridMap.IsValidPosition(targetX, targetY)) { Vector3 position = gridMap.GetWorldPosition(targetX, targetY) + Vector3.up * 0.1f; GameObject highlight = Instantiate(attackHighlightPrefab, position, Quaternion.identity); highlights.Add(highlight); } } } } } void ClearHighlights() { foreach (var highlight in highlights) { Destroy(highlight); } highlights.Clear(); } } |
ダメージ計算の実装
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
using UnityEngine; public class BattleCalculator { public static int CalculateDamage(Unit attacker, Unit defender) { // 基本ダメージ = 攻撃力 - 防御力 int baseDamage = attacker.stats.attack - defender.stats.defense; // 最小ダメージは1 baseDamage = Mathf.Max(1, baseDamage); // ランダム要素(±10%) float randomFactor = Random.Range(0.9f, 1.1f); int finalDamage = Mathf.RoundToInt(baseDamage * randomFactor); return finalDamage; } public static bool CalculateHit(Unit attacker, Unit defender) { // 命中率 = 基本命中率 - 回避率 float hitRate = 0.9f - (defender.stats.agility * 0.01f); hitRate = Mathf.Clamp01(hitRate); return Random.Range(0f, 1f) < hitRate; } public static bool CalculateCritical(Unit attacker) { // クリティカル率 = 運 × 0.01 float critRate = attacker.stats.luck * 0.01f; return Random.Range(0f, 1f) < critRate; } } |
このコードで、ダメージ計算が実装できます。
攻撃力から防御力を引いた値が基本ダメージです。
戦闘処理の実装
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
public class BattleSystem : MonoBehaviour { public void ExecuteAttack(Unit attacker, Unit defender) { // 1. 命中判定 if (!BattleCalculator.CalculateHit(attacker, defender)) { Debug.Log($"{attacker.name}の攻撃が外れた"); return; } // 2. ダメージ計算 int damage = BattleCalculator.CalculateDamage(attacker, defender); // 3. クリティカル判定 bool isCritical = BattleCalculator.CalculateCritical(attacker); if (isCritical) { damage = Mathf.RoundToInt(damage * 1.5f); Debug.Log("クリティカル!"); } // 4. ダメージ適用 defender.TakeDamage(damage); Debug.Log($"{attacker.name}が{defender.name}に{damage}のダメージを与えた"); // 5. 戦闘アニメーション PlayAttackAnimation(attacker, defender); } void PlayAttackAnimation(Unit attacker, Unit defender) { // アニメーション処理 } } |
このコードで、戦闘処理が実装できます。
命中判定→ダメージ計算→ダメージ適用の順で処理します。

ダメージ計算式は、バランス調整の核心です。表計算ソフトで試算してから実装しましょう。
Unity入門の森を見る 初心者歓迎!動画×プロジェクト一式で本格ゲーム制作を学べる
ステートマシンを使った設計

ステートマシンを使えば、バトルシステムを整理できます。
実装方法を紹介します。
ステートベースの実装
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
public abstract class BattleStateBase { protected BattleSystem battleSystem; public BattleStateBase(BattleSystem system) { battleSystem = system; } public virtual void Enter() { } public virtual void Update() { } public virtual void Exit() { } } public class PlayerTurnState : BattleStateBase { public PlayerTurnState(BattleSystem system) : base(system) { } public override void Enter() { Debug.Log("プレイヤーターン開始"); battleSystem.SelectNextPlayerUnit(); } public override void Update() { // プレイヤーの操作を待つ } public override void Exit() { Debug.Log("プレイヤーターン終了"); } } public class EnemyTurnState : BattleStateBase { public EnemyTurnState(BattleSystem system) : base(system) { } public override void Enter() { Debug.Log("敵ターン開始"); battleSystem.ExecuteEnemyAI(); } public override void Update() { // 敵AIの処理 } public override void Exit() { Debug.Log("敵ターン終了"); } } public class BattleSystem : MonoBehaviour { private BattleStateBase currentState; void Start() { ChangeState(new PlayerTurnState(this)); } void Update() { currentState?.Update(); } public void ChangeState(BattleStateBase newState) { currentState?.Exit(); currentState = newState; currentState.Enter(); } } |
ステートマシンで管理することで、状態遷移が明確になります。
拡張もしやすくなります。
✅ ステートマシンのメリット
- 状態が明確:現在の状態が一目で分かる
- 拡張しやすい:新しい状態を追加しやすい
- デバッグしやすい:状態遷移を追跡できる
Unity入門の森を見る 初心者歓迎!動画×プロジェクト一式で本格ゲーム制作を学べる
実装例:完全なバトルシステム

実際に使える、完全なバトルシステムの実装例を紹介します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
using UnityEngine; using System.Collections.Generic; public class CompleteBattleSystem : MonoBehaviour { public TurnManager turnManager; public ActionMenuUI actionMenu; public BattleCalculator calculator; private Unit currentUnit; private Unit targetUnit; public void StartBattle(List<Unit> players, List<Unit> enemies) { turnManager.playerUnits = players; turnManager.enemyUnits = enemies; turnManager.StartBattle(); } public void OnUnitSelected(Unit unit) { currentUnit = unit; actionMenu.ShowMenu(unit); } public void OnAttackTargetSelected(Unit target) { targetUnit = target; ExecuteAttack(currentUnit, targetUnit); } void ExecuteAttack(Unit attacker, Unit defender) { // 命中判定 if (!calculator.CalculateHit(attacker, defender)) { ShowMissMessage(); return; } // ダメージ計算 int damage = calculator.CalculateDamage(attacker, defender); // クリティカル判定 if (calculator.CalculateCritical(attacker)) { damage = Mathf.RoundToInt(damage * 1.5f); ShowCriticalMessage(); } // ダメージ適用 defender.TakeDamage(damage); ShowDamageMessage(attacker, defender, damage); // ターン終了 attacker.OnActionComplete(); turnManager.EndTurn(); } void ShowMissMessage() { Debug.Log("攻撃が外れた"); } void ShowCriticalMessage() { Debug.Log("クリティカル!"); } void ShowDamageMessage(Unit attacker, Unit defender, int damage) { Debug.Log($"{attacker.name}が{defender.name}に{damage}のダメージを与えた"); } } |
このコードで、完全なバトルシステムが実装できます。
各処理を分離することで、メンテナンスしやすくなります。
Unity入門の森を見る 初心者歓迎!動画×プロジェクト一式で本格ゲーム制作を学べる
よくある質問(FAQ)

Unity入門の森を見る 初心者歓迎!動画×プロジェクト一式で本格ゲーム制作を学べる
あなたのオリジナルゲーム、今年こそ完成させませんか?
RPG・アクション・ホラー…Unityで本格ゲームを作りたい人のための学習サイトです。
実際に完成するゲームを題材に、
ソースコード・素材・プロジェクト一式をすべて公開。
仕事や学校の合間の1〜2時間でも、
「写経→改造」で自分のゲームまで作りきれる環境です。
まとめ

バトルシステムは、処理フローを分解すれば実装できます。
ステートマシンで管理することで、整理しやすくなります。
✅ 今日から始める3ステップ
- ステップ1:ステートマシンの基本構造を作る(所要2時間)
- ステップ2:ターン管理システムを実装する(所要3時間)
- ステップ3:ダメージ計算と戦闘処理を実装する(所要3時間)
本格的にUnityを学びたい方は、Unity入門の森で実践的なスキルを身につけましょう。
あなたのペースで、少しずつ進めていけば大丈夫です。
Unity入門の森を見る 初心者歓迎!動画×プロジェクト一式で本格ゲーム制作を学べる



コメント