ゲームAIは、状態ごとに整理すると管理しやすくなります。
ステートマシンパターンが、実装の核心です。
この記事では、設計から実装まで詳しく解説します。
✨ この記事でわかること
- ステートマシンの基本概念
- 状態の定義と遷移
- SRPGでの実装例
- アクションゲームでの実装例
- 実装例とコード

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

ゲーム開発において「賢いAI」とは、状況に合わせて迷いなく行動を選択できるAIを指します。
しかし、AIに複雑な動きをさせようとすればするほど、プログラムはスパゲッティのように絡まり、予期せぬバグを生みがちです。
そこで登場するのが「ステートマシン(有限状態機械)」です。
これは、AIの複雑な頭脳を「現在の状態」というシンプルな箱に分けて整理する設計手法です。
なぜこの手法が最強のAIを作る鍵となるのか、その基本構造を見ていきましょう。
ステートマシンを構成する「最強の3要素」は以下の通りです。
- 状態(State):AIが今まさに何をしているか(例:のんびり待機、必死に追跡、怒りの攻撃)。一度に取れる状態は必ず「1つだけ」に限定するのがポイントです。
- 遷移(Transition):状態を切り替えるための「きっかけ」です。「プレイヤーが視界に入った」「HPが半分になった」といった条件がこれにあたります。
- アクション(Action):それぞれの状態で実行される具体的な処理です。待機中なら「アニメーション再生」、追跡中なら「経路探索」といった具合に、役割を明確に分離します。
なぜステートマシンが「最強」の設計なのか?
ステートマシンが最強と言われる最大の理由は、「AIの振る舞いを100%コントロール下に置けるから」です。
もしステートマシンを使わずに「if文」だけでAIを作ろうとすると、「攻撃しながら歩き、同時に待機アニメーションが流れる」といった矛盾したバグが簡単に発生してしまいます。
しかし、状態を箱で分けるステートマシンなら、一度に一つの行動しか許可しないため、動作が非常に安定します。
また、新しい行動(例:体力が減ったら逃げる)を追加したい時も、既存のコードを壊さずに「新しい箱と矢印」を付け足す感覚で拡張できるのです。
この安定感と拡張性こそが、プロの現場でも長年愛用されている理由です。
では、この強力な仕組みを具体的にどうプログラムに落とし込んでいくのか。

まずは最もシンプルで応用が効く、C#の列挙型(enum)を使った実装方法からマスターしていきましょう。
基本的なステートマシンの実装
|
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 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
using UnityEngine; public enum AIState { Idle, // 待機 Chase, // 追跡 Attack, // 攻撃 Return // 戻る } public class SimpleStateMachine : MonoBehaviour { public AIState currentState = AIState.Idle; private Transform target; private Vector3 startPosition; void Start() { startPosition = transform.position; } void Update() { switch (currentState) { case AIState.Idle: UpdateIdle(); break; case AIState.Chase: UpdateChase(); break; case AIState.Attack: UpdateAttack(); break; case AIState.Return: UpdateReturn(); break; } } void UpdateIdle() { // 敵を探す target = FindNearestEnemy(); if (target != null) { float distance = Vector3.Distance(transform.position, target.position); if (distance <= detectionRange) { currentState = AIState.Chase; } } } void UpdateChase() { if (target == null) { currentState = AIState.Idle; return; } float distance = Vector3.Distance(transform.position, target.position); if (distance <= attackRange) { currentState = AIState.Attack; } else if (distance > maxChaseRange) { currentState = AIState.Return; } else { MoveTowardsTarget(); } } void UpdateAttack() { if (target == null) { currentState = AIState.Idle; return; } float distance = Vector3.Distance(transform.position, target.position); if (distance > attackRange) { currentState = AIState.Chase; } else { PerformAttack(); } } void UpdateReturn() { float distance = Vector3.Distance(transform.position, startPosition); if (distance < 0.5f) { currentState = AIState.Idle; } else { MoveTowardsStart(); } } Transform FindNearestEnemy() { // 最も近い敵を探す // 実装は省略 return null; } void MoveTowardsTarget() { // ターゲットに向かって移動 } void MoveTowardsStart() { // 開始位置に戻る } void PerformAttack() { // 攻撃処理 } public float detectionRange = 10f; public float attackRange = 2f; public float maxChaseRange = 15f; } |
このコードで、基本的なステートマシンが実装できます。
状態に応じて、AIの行動が切り替わります。
Unity入門の森を見る 初心者歓迎!動画×プロジェクト一式で本格ゲーム制作を学べる
SRPGでの実装例

SRPGでは、ターン制のステートマシンが有効です。
実装方法を紹介します。
SRPG敵AIのステートマシン
|
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 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
public enum SRPGAIState { WaitForTurn, // ターン待機 SelectAction, // 行動選択 Move, // 移動 Attack, // 攻撃 Skill, // スキル使用 EndTurn // ターン終了 } public class SRPGEnemyAI : MonoBehaviour { public SRPGAIState currentState = SRPGAIState.WaitForTurn; private Unit currentUnit; private Unit targetUnit; private ActionCandidate selectedAction; public void ProcessTurn(Unit unit) { currentUnit = unit; currentState = SRPGAIState.SelectAction; while (currentState != SRPGAIState.EndTurn) { UpdateState(); } } void UpdateState() { switch (currentState) { case SRPGAIState.SelectAction: SelectBestAction(); break; case SRPGAIState.Move: ExecuteMove(); break; case SRPGAIState.Attack: ExecuteAttack(); break; case SRPGAIState.Skill: ExecuteSkill(); break; } } void SelectBestAction() { // 最適な行動を選択 targetUnit = FindBestTarget(); if (IsInAttackRange(targetUnit)) { selectedAction = new ActionCandidate { actionType = ActionType.Attack, targetUnit = targetUnit }; currentState = SRPGAIState.Attack; } else { selectedAction = new ActionCandidate { actionType = ActionType.Move, targetPosition = GetMoveTarget(targetUnit) }; currentState = SRPGAIState.Move; } } void ExecuteMove() { // 移動を実行 currentUnit.MoveTo(selectedAction.targetPosition); currentState = SRPGAIState.EndTurn; } void ExecuteAttack() { // 攻撃を実行 BattleSystem.ExecuteAttack(currentUnit, targetUnit); currentState = SRPGAIState.EndTurn; } void ExecuteSkill() { // スキルを実行 currentState = SRPGAIState.EndTurn; } Unit FindBestTarget() { // 最適なターゲットを探す // 実装は省略 return null; } bool IsInAttackRange(Unit target) { int distance = Mathf.Abs(currentUnit.gridX - target.gridX) + Mathf.Abs(currentUnit.gridY - target.gridY); return distance <= currentUnit.stats.attackRange; } Vector2Int GetMoveTarget(Unit target) { // 移動目標位置を計算 // 実装は省略 return Vector2Int.zero; } } |
このコードで、SRPG敵AIのステートマシンが実装できます。
ターン制に合わせて、状態が遷移します。

SRPGのAIは、ターン制に合わせて状態を管理します。行動選択→移動→攻撃の流れを、状態で管理しましょう。
Unity入門の森を見る 初心者歓迎!動画×プロジェクト一式で本格ゲーム制作を学べる
アクションゲームでの実装例

アクションゲームでは、リアルタイムのステートマシンが有効です。
実装方法を紹介します。
アクション敵AIのステートマシン
|
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 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
public enum ActionAIState { Patrol, // 巡回 Alert, // 警戒 Chase, // 追跡 Attack, // 攻撃 Stunned, // スタン Dead // 死亡 } public class ActionEnemyAI : MonoBehaviour { public ActionAIState currentState = ActionAIState.Patrol; private Transform player; private Vector3 patrolStart; private Vector3 patrolEnd; private float stateTimer = 0f; void Update() { stateTimer += Time.deltaTime; switch (currentState) { case ActionAIState.Patrol: UpdatePatrol(); break; case ActionAIState.Alert: UpdateAlert(); break; case ActionAIState.Chase: UpdateChase(); break; case ActionAIState.Attack: UpdateAttack(); break; case ActionAIState.Stunned: UpdateStunned(); break; } } void UpdatePatrol() { // 巡回 Patrol(); // プレイヤーを発見 if (DetectPlayer()) { currentState = ActionAIState.Alert; stateTimer = 0f; } } void UpdateAlert() { // 警戒状態 if (stateTimer >= alertDuration) { if (IsPlayerInRange()) { currentState = ActionAIState.Chase; } else { currentState = ActionAIState.Patrol; } stateTimer = 0f; } } void UpdateChase() { // 追跡 ChasePlayer(); float distance = Vector3.Distance(transform.position, player.position); if (distance <= attackRange) { currentState = ActionAIState.Attack; stateTimer = 0f; } else if (distance > maxChaseRange) { currentState = ActionAIState.Patrol; stateTimer = 0f; } } void UpdateAttack() { // 攻撃 if (stateTimer >= attackCooldown) { PerformAttack(); stateTimer = 0f; // 攻撃後は追跡に戻る currentState = ActionAIState.Chase; } } void UpdateStunned() { // スタン状態 if (stateTimer >= stunDuration) { currentState = ActionAIState.Alert; stateTimer = 0f; } } void Patrol() { // 巡回処理 } void ChasePlayer() { // プレイヤーを追跡 } bool DetectPlayer() { // プレイヤーを発見 return false; } bool IsPlayerInRange() { // プレイヤーが範囲内にいるか return false; } void PerformAttack() { // 攻撃処理 } public float alertDuration = 2f; public float attackCooldown = 1f; public float stunDuration = 3f; public float attackRange = 2f; public float maxChaseRange = 10f; } |
このコードで、アクション敵AIのステートマシンが実装できます。
リアルタイムで状態が遷移します。
Unity入門の森を見る 初心者歓迎!動画×プロジェクト一式で本格ゲーム制作を学べる
状態遷移の最適化

状態遷移を最適化することで、AIの動作が滑らかになります。
実装方法を紹介します。
遷移条件の整理
|
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 93 94 95 96 97 98 99 100 101 |
[System.Serializable] public class StateTransition { public AIState fromState; public AIState toState; public TransitionCondition condition; } [System.Serializable] public class TransitionCondition { public ConditionType type; public float value; public bool checkTarget; } public enum ConditionType { Distance, // 距離 Health, // HP Timer, // タイマー Flag // フラグ } public class OptimizedStateMachine : MonoBehaviour { public List<StateTransition> transitions = new List<StateTransition>(); public AIState currentState = AIState.Idle; void Update() { // 現在の状態を更新 UpdateCurrentState(); // 遷移をチェック CheckTransitions(); } void UpdateCurrentState() { switch (currentState) { case AIState.Idle: UpdateIdle(); break; // 他の状態も同様 } } void CheckTransitions() { foreach (var transition in transitions) { if (transition.fromState == currentState) { if (CheckCondition(transition.condition)) { ChangeState(transition.toState); break; } } } } bool CheckCondition(TransitionCondition condition) { switch (condition.type) { case ConditionType.Distance: return CheckDistance(condition.value); case ConditionType.Health: return CheckHealth(condition.value); case ConditionType.Timer: return CheckTimer(condition.value); default: return false; } } void ChangeState(AIState newState) { // 状態を変更 OnStateExit(currentState); currentState = newState; OnStateEnter(newState); } void OnStateEnter(AIState state) { // 状態に入った時の処理 } void OnStateExit(AIState state) { // 状態を出た時の処理 } } |
このコードで、状態遷移が最適化されます。
遷移条件をデータ化することで、管理がしやすくなります。
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 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
using UnityEngine; using System.Collections.Generic; public class CompleteStateMachine : MonoBehaviour { [Header("ステートマシン設定")] public AIState initialState = AIState.Idle; public List<StateTransition> transitions = new List<StateTransition>(); private AIState currentState; private Dictionary<AIState, System.Action> stateUpdateActions; private Dictionary<AIState, System.Action> stateEnterActions; private Dictionary<AIState, System.Action> stateExitActions; void Start() { InitializeStateMachine(); } void InitializeStateMachine() { currentState = initialState; // 状態ごとのアクションを登録 stateUpdateActions = new Dictionary<AIState, System.Action> { { AIState.Idle, UpdateIdle }, { AIState.Chase, UpdateChase }, { AIState.Attack, UpdateAttack } }; stateEnterActions = new Dictionary<AIState, System.Action> { { AIState.Idle, OnIdleEnter }, { AIState.Chase, OnChaseEnter }, { AIState.Attack, OnAttackEnter } }; stateExitActions = new Dictionary<AIState, System.Action> { { AIState.Idle, OnIdleExit }, { AIState.Chase, OnChaseExit }, { AIState.Attack, OnAttackExit } }; OnStateEnter(currentState); } void Update() { // 現在の状態を更新 if (stateUpdateActions.ContainsKey(currentState)) { stateUpdateActions[currentState](); } // 遷移をチェック CheckTransitions(); } void CheckTransitions() { foreach (var transition in transitions) { if (transition.fromState == currentState) { if (CheckCondition(transition.condition)) { ChangeState(transition.toState); break; } } } } void ChangeState(AIState newState) { if (stateExitActions.ContainsKey(currentState)) { stateExitActions[currentState](); } currentState = newState; if (stateEnterActions.ContainsKey(currentState)) { stateEnterActions[currentState](); } } // 各状態の更新処理 void UpdateIdle() { } void UpdateChase() { } void UpdateAttack() { } // 各状態の入退処理 void OnIdleEnter() { } void OnIdleExit() { } void OnChaseEnter() { } void OnChaseExit() { } void OnAttackEnter() { } void OnAttackExit() { } bool CheckCondition(TransitionCondition condition) { // 条件チェック return false; } } |
このコードで、完全なステートマシンシステムが実装できます。
状態の更新、遷移、入退処理を統合しています。
Unity入門の森を見る 初心者歓迎!動画×プロジェクト一式で本格ゲーム制作を学べる
よくある質問(FAQ)

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

ステートマシンは、AIの行動を明確にします。
状態を分けることで、デバッグもしやすくなります。
✅ 今日から始める3ステップ
- ステップ1:基本的なステートマシンを実装する(所要2時間)
- ステップ2:状態遷移の条件を実装する(所要2時間)
- ステップ3:状態の入退処理を実装する(所要1時間)
本格的にUnityを学びたい方は、Unity入門の森で実践的なスキルを身につけましょう。
あなたのペースで、少しずつ進めていけば大丈夫です。
Unity入門の森を見る 初心者歓迎!動画×プロジェクト一式で本格ゲーム制作を学べる



コメント