※本記事にはアフィリエイトリンクが含まれています。
Amazonのアソシエイトとして、ゲームのタネは適格販売により収入を得ています。
▶ この回の完成形を、この場で遊ぶ
ブラウザで動く『1戦闘ターン制』です。Unity 6.3 LTS 完成プロジェクトと同じルール(あなた HP20/敵 HP24、こうげき5、敵こうげき4、ぼうぎょで次の被ダメージ半分)です。
記事中の C#・ダウンロード ZIP と同じルールです。はじめる(または R)で開始。自分の番だけ「こうげき」「ぼうぎょ」。
Unity の完成プロジェクト
上で遊んでいるものと同じゲームです。ZIP を解凍し、Unity 6.3 LTS(6000.3) でフォルダを開いて Assets/Scenes/Main.unity を Play。
unity-turn-battle.zip をダウンロード
はじめに
「ボタンは付けた。でも、相手の番なのに押せてしまう…どう止めるの?」——そんな疑問向けです。
この記事では、Unity 6.3 LTS(エディタ表記 6000.3) で、1戦闘だけのターン制バトルを完成させます。敵の HP を 0 にすれば勝ち、自分の HP が 0 なら負け。「もう一度」や R キーで何度でもやり直せます。
今週身につけること:自分の番/相手の番という状態。 BattleState という列挙型(enum)で Ready → PlayerTurn → EnemyTurn → Win / Lose を持ちます。こうげき・ぼうぎょのボタンは PlayerTurn のときだけ受け付けます。敵の行動は EnemyTurn に入ってから進めます。勝敗も同じ状態のひとつです。
前回(第3回)は Godot でパイプの到達判定でした。今週はエンジンを Unity だけに切り替え、RPG の「誰の番か」を学びます。
完成形と、やらないこと
- 作るもの: 1対1の戦闘。HP・こうげき・ぼうぎょ・敵の自動こうげき。勝ち/負け/やり直し。Unity ZIP と、同じルールのイン記事プレイ。
- やらないこと: 複数敵、スキルツリー、セーブ、マップ移動、アニメ本格化、Godot 併記。
- 持ち帰る1アイデア: 「入力ができる/できない」を if の散らばりで書かず、いまの状態ひとつでゲートする。


準備(Unity 6.3 LTS)
- Unity 6 の公式ページから Hub 経由で 6.3 LTS(6000.3) を入れます。第2回と同じ系統です。
- 上の unity-turn-battle.zip を解凍し、Hub でそのフォルダを開きます。自分で組む場合は 2D(または空)の新規プロジェクトでも構いません。
- メインシーンは
Assets/Scenes/Main.unity。ルート付近のTurnBattleにスクリプトが付いています。空の GameObject にTurnBattle.csを付けても、Awake で UI を生成して動きます。 - Active Input Handling は Both(Project Settings → Player)にしておきます。この回は
Input.GetKeyDownで R キーを見ます。
初心者がハマりやすい点: 相手の番でもボタンが押せるままになることです。見た目で消すだけでなく、interactable = false にするか、メソッドの先頭で state != PlayerTurn なら return します。どちらか一方だけだと「見た目は消えたのにキーで動く」などが起きやすいです。

ルール(数字は記事・ZIP・プレイで共通)
- あなた HP 20/敵 HP 24
- こうげき:敵に 5 ダメージ
- ぼうぎょ:次に受けるダメージを半分(端数は切り上げ。敵の4なら2)
- 敵の番:必ずこうげき 4(状態が EnemyTurn のときだけ)
- 敵 HP ≤ 0 で勝ち、自分 HP ≤ 0 で負け

スクリプト1:BattleFighter.cs(HPとダメージ)
ファイル名:Assets/Scripts/BattleFighter.cs。MonoBehaviour ではなく、普通の C# クラスです。TurnBattle から new して使います。
using UnityEngine;
/// <summary>
/// 1戦闘の参加者。名前・最大HP・現在HP・ダメージ処理だけを担当。
/// </summary>
public class BattleFighter
{
public string DisplayName { get; private set; }
public int MaxHp { get; private set; }
public int Hp { get; private set; }
public BattleFighter(string displayName, int maxHp)
{
DisplayName = displayName;
MaxHp = maxHp;
Hp = maxHp;
}
public bool IsAlive => Hp > 0;
/// <summary>ダメージを受け、実際に減った量を返す。</summary>
public int TakeDamage(int amount)
{
if (amount < 0)
amount = 0;
int before = Hp;
Hp = Mathf.Max(0, Hp - amount);
return before - Hp;
}
public void ResetHp()
{
Hp = MaxHp;
}
}
別ゲームで変えるところ: コンストラクタの名前と最大 HP。回復を足すなら Heal メソッドを同じ場所に追加すると分かりやすいです。
スクリプト2:状態機械の核(自分の番だけ入力)
完成版の全文は ZIP の TurnBattle.cs(UI 生成付き)です。ここでは今週の学習点だけを抜き出した核を示します。考え方は完成版と同じです。
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// 記事用の核だけ抜粋。完成版の全文は ZIP の TurnBattle.cs(UI生成付き)です。
/// 空の GameObject に TurnBattle.cs を付けて Play してください。
/// </summary>
public class TurnBattleCore : MonoBehaviour
{
const int PlayerAttack = 5;
const int EnemyAttack = 4;
public enum BattleState
{
Ready,
PlayerTurn,
EnemyTurn,
Win,
Lose
}
BattleState state = BattleState.Ready;
// 完成版では BattleFighter を使います
int playerHp = 20;
int enemyHp = 24;
bool defending;
public Button attackButton;
public Button defendButton;
public void OnAttack()
{
// 自分の番以外は何もしない=入力のゲート
if (state != BattleState.PlayerTurn)
return;
enemyHp = Mathf.Max(0, enemyHp - PlayerAttack);
defending = false;
if (enemyHp <= 0)
{
state = BattleState.Win;
RefreshButtons();
return;
}
BeginEnemyTurn();
}
public void OnDefend()
{
if (state != BattleState.PlayerTurn)
return;
defending = true;
BeginEnemyTurn();
}
void BeginEnemyTurn()
{
state = BattleState.EnemyTurn;
RefreshButtons();
// 公式: MonoBehaviour.Invoke — 少し待ってから敵行動
Invoke(nameof(EnemyAct), 0.55f);
}
void EnemyAct()
{
if (state != BattleState.EnemyTurn)
return;
int raw = EnemyAttack;
int dmg = defending ? Mathf.CeilToInt(raw * 0.5f) : raw;
playerHp = Mathf.Max(0, playerHp - dmg);
defending = false;
if (playerHp <= 0)
{
state = BattleState.Lose;
RefreshButtons();
return;
}
state = BattleState.PlayerTurn;
RefreshButtons();
}
void RefreshButtons()
{
bool playerTurn = state == BattleState.PlayerTurn;
// uGUI Button.interactable:自分の番だけ押せる
if (attackButton != null)
attackButton.interactable = playerTurn;
if (defendButton != null)
defendButton.interactable = playerTurn;
}
}
別ゲームで変えるところ: 状態の名前(例:QuizTurn / RevealTurn)、ボタンの数、敵行動の中身。Invoke の待ち時間や、待ちなしで即 EnemyAct() でも構いません。
ボタン自体は uGUI の Button(公式パッケージマニュアル)です。onClick に OnAttack / OnDefend を繋ぎます。完成版ではコードから onClick.AddListener しています。
スクリプト3:完成版の付け方(ZIP と同じ)
BattleFighter.csとTurnBattle.csをAssets/Scripts/に置く。- 空の GameObject を作り、名前を
TurnBattleにしてTurnBattleをアタッチ。 - Play。カメラ・Canvas・EventSystem・ボタンは Awake で用意されます。
- R キーは Input.GetKeyDown(Unity 6.3 のスクリプトリファレンス)で見ています。敵の待ちは MonoBehaviour.Invoke です。
Play で確認すること
- はじめる後、自分の番だけ「こうげき」「ぼうぎょ」が押せる。
- こうげきすると敵 HP が 5 減り、すぐに敵の番へ。ボタンが押せなくなる。
- ぼうぎょすると、次の敵こうげきが 4→2 になる(1回だけ)。
- 敵を倒すと勝ち表示。「もう一度」または R で Ready に戻る。

つまづきポイント(1つ)
敵の番なのに、もう一度こうげきできてしまう。 OnAttack の先頭ゲートを書き忘れたか、RefreshButtons で interactable を戻し忘れています。Play 中に Inspector で状態用の表示(完成版はメッセージ文言)が「敵の番」のとき、ボタンが灰色になっているかを見ます。
できたこと/身についたこと
- 1戦闘のターン制(目標・失敗・やり直し付き)を Unity 6.3 で完成させた。
- 自分の番/相手の番という状態で入力と敵行動を切り替えた。
- HP 処理を
BattleFighterに分け、進行役TurnBattleと役割を分けた。
使い回しメモ
- スニペット:
BattleState+ 「自分の番以外は return」+ ボタンのinteractable - アイデア: 入力可否は散らばった if より、状態の一文で決める
- 第二の用途: クイズの出題/正解表示、カードゲームのドロー/プレイ、対話の選択肢表示中ロック
次の自分のゲームで使うなら
メニューの「決定できるとき/演出中」も同じです。MenuState を用意し、演出中はボタンを閉じます。ジャンルが違っても「いま何の番か」は転用できます。
次に足してよい一言
クリティカル(たまにダメージ増)や、敵が「ためる」状態を1つ足すと、同じ状態機械のまま幅が出ます。まだマップやパーティは増やさなくて大丈夫です。
前回:『雨まで10秒』をGodotで完成させる|パイプ回転と水の到達判定
関連商品(PR)
今週の「Unity で状態を書く」学習に沿いやすいものを選びました。価格は掲載時点で変動するため、リンク先でご確認ください。
- Unityの教科書 Unity 6完全対応版
※本リンクはアフィリエイトリンクです。 - 確かな力が身につく C#「超」入門 第3版
※本リンクはアフィリエイトリンクです。 - Logicool G304 LIGHTSPEED ワイヤレスゲーミングマウス
※本リンクはアフィリエイトリンクです。


コメント