
ここまでで、カードを使って敵のHPを減らせるようになりました。でも、まだ敵は何もしてきません・・・。今回は、「あなたのターン → 敵のターン → またあなたのターン」の流れを作ります。ターン終了ボタンを押すと、敵が#8で出した予告どおりに行動して、また自分の番に戻ってくるようにしてみたいと思います!
- シリーズの目次:デッキ構築カードゲームの作り方
- 前回:#8 HPのゲージと敵の行動の予告を作ろう
今回作るもの
- ターンの状態(あなたのターン・敵のターン)
- あなたのターンの始め:防御を0に戻す、エネルギーを3に戻す、5枚配る
- ターン終了:手札を捨てて、敵のターンへ
- 敵のターン:1体ずつ、予告どおりに行動する
- 「あなたのターン」「敵のターン」の帯
| スクリプト | 役割 | 付ける場所 |
|---|---|---|
| BattleManager | ターンの流れを動かす | BattleManager(新しく作る空のオブジェクト) |
| TurnBanner | 「あなたのターン」などの帯を出して消す | TurnBanner |
| Fighter(足す) | 防御を0に戻す | - |
| EnemyView(足す) | 次の行動へ進む | - |
| Deck(直す) | 最初の5枚は BattleManager が配る | - |
勝ち負け(敵を全部倒す・HPが0になる)は、#12で作ります。
前回の続きから
前回の最後の状態から始めます。新しい素材はありません。
ターンの流れを考えよう
まず、1ターンの中で何が起きるかを並べてみます。

- あなたのターン:自分の防御を0に戻す → エネルギーを3に戻す → 5枚配る → カードを使う
- ターン終了ボタンを押す
- 敵のターン:手札を捨てる → 敵が1体ずつ行動する → 次の予告を出す
- また、あなたのターンへ
「今どちらのターンか」は、6-15 選択肢に名前を付けよう:enum(列挙型)とswitch文で紹介したenumで持ちます。流れは、時間を待ちながら順番に進めたいので、コルーチン(6-11 時間を扱う処理)で作ります。
FighterとEnemyViewに足そう
Fighter:防御を0に戻す
デッキ構築ゲームでは、防御は自分の番が来るたびに0に戻るのが一般的です(#7で「防御がターンをまたいで残る」と書いていたところです)。Fighterに、防御を0に戻すメソッドを足します。
// ★#9 防御を0に戻す(自分の番の始めに呼ぶ)
public void ResetBlock()
{
block = 0;
}
- Fighterの
Healの下に足す
EnemyView:次の行動へ進む
敵が行動したら、次の行動の予告に切りかえます。EnemyViewに足します。
// ★#9 次の行動へ進んで、予告を出し直す(最後まで行ったら最初に戻る)
public void AdvanceAction()
{
actionIndex = (actionIndex + 1) % data.actions.Count;
ShowIntent();
}
- EnemyViewの
Setupの下に足す % data.actions.Countで、行動の並びの最後まで行ったら0に戻る。ブリキロボなら「6 → 6 → 9 → 6 → …」とくり返す- ShowIntentを呼ぶので、予告が変わるたびに、#8で作った「ポン」の動きが出ます
Deck:最初の5枚を配らない
#4から、DeckのStartで最初の5枚を配っていました。今回からはターンの始めにBattleManagerが配るので、DeckのStartの最後の1行を消します。
Shuffle(drawPile);
Draw(5); // ★#9 この行を消す(最初の5枚は BattleManager が配る)
帯を作ろう(TurnBanner)
「あなたのターン」「敵のターン」を、画面の真ん中に帯で出します。
- Hudの子にTurnBanner(UI (Canvas)>Image)
- アンカー:middle-stretch(横いっぱい)、Left・Right:0、Pos Y:80、Height:140
- Source Image:なし、Color:#000000、Alpha:150ぐらい
- Raycast Targetのチェックを外す(帯がカードの操作をじゃましないように)
- Add Component>Canvas Group
- TurnBannerの子にLabel(Text – TextMeshPro)
- アンカー:stretch-stretch、Left・Top・Right・Bottom:0
- テキスト:「あなたのターン」、Font Size:72、Font Style:B、Alignment:中央、Color:白
- DeckBuilding/Scriptsに「TurnBanner」スクリプトを作る
using System.Collections;
using TMPro;
using UnityEngine;
// 「あなたのターン」「敵のターン」の帯を、画面の真ん中に出して消す
[RequireComponent(typeof(CanvasGroup))]
public class TurnBanner : MonoBehaviour
{
[SerializeField] private TextMeshProUGUI label;
[SerializeField] private float fadeSeconds = 0.2f; // 出る・消えるのにかける時間
[SerializeField] private float showSeconds = 0.6f; // 出たまま止まる時間
private CanvasGroup canvasGroup;
private void Awake()
{
canvasGroup = GetComponent<CanvasGroup>();
canvasGroup.alpha = 0f;
canvasGroup.blocksRaycasts = false;
}
// 帯を出して消す(終わるまで待てるように IEnumerator にしている)
public IEnumerator Show(string text)
{
label.text = text;
RectTransform labelRect = label.rectTransform;
// 出る:左から文字がすべりこみながら、帯が見えてくる
for (float time = 0f; time < fadeSeconds; time += Time.deltaTime)
{
float rate = time / fadeSeconds;
canvasGroup.alpha = rate;
labelRect.anchoredPosition = new Vector2(Mathf.Lerp(-200f, 0f, rate), 0f);
yield return null;
}
canvasGroup.alpha = 1f;
labelRect.anchoredPosition = Vector2.zero;
yield return new WaitForSeconds(showSeconds);
// 消える:右へ抜けながら、帯が消えていく
for (float time = 0f; time < fadeSeconds; time += Time.deltaTime)
{
float rate = time / fadeSeconds;
canvasGroup.alpha = 1f - rate;
labelRect.anchoredPosition = new Vector2(Mathf.Lerp(0f, 200f, rate), 0f);
yield return null;
}
canvasGroup.alpha = 0f;
}
}
- public IEnumerator Show
- コルーチンの中身を、ほかのスクリプトから
StartCoroutineで呼べるようにしている。呼ぶ側は帯が出て消えるまで待ってから次に進める
- コルーチンの中身を、ほかのスクリプトから
- for (float time = 0f; …; time += Time.deltaTime)
- 経過時間を数えながら、0.2秒かけてアルファ(すけ具合)と文字の位置を少しずつ変える
- canvasGroup.blocksRaycasts = false
- 帯が出ている間も、下のカードやボタンを触れるようにする
TurnBannerにTurnBannerを付けて、Label:Labelを入れます。

BattleManagerを作ろう
いよいよ、ターンの流れを動かすBattleManagerです。
- DeckBuilding/Scriptsに「BattleManager」スクリプトを作る
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
// 今どちらのターンか
public enum TurnState
{
PlayerTurn, // あなたのターン
EnemyTurn // 敵のターン
}
// ターンの流れを動かす:あなたのターン → 敵のターン → あなたのターン …
public class BattleManager : MonoBehaviour
{
[SerializeField] private Deck deck;
[SerializeField] private Energy energy;
[SerializeField] private Fighter player;
[SerializeField] private EnemyParty enemyParty;
[SerializeField] private Button endTurnButton;
[SerializeField] private TurnBanner turnBanner;
[SerializeField] private int drawCount = 5; // 毎ターン配る枚数
[SerializeField] private float enemyActionSeconds = 0.6f; // 敵1体ごとの間
private TurnState state;
public TurnState State => state;
private IEnumerator Start()
{
// ほかのスクリプトの Start(山札を混ぜる・敵を並べる)が終わるのを、1フレーム待つ
yield return null;
StartCoroutine(PlayerTurn());
}
// ターン終了ボタンから呼ぶ
public void EndTurn()
{
if (state != TurnState.PlayerTurn)
{
return; // 敵のターンの間は何もしない
}
StartCoroutine(EnemyTurn());
}
// あなたのターン
private IEnumerator PlayerTurn()
{
state = TurnState.PlayerTurn;
player.ResetBlock(); // 防御は自分の番の始めに0に戻る
energy.Refill(); // エネルギーを3に戻す
yield return StartCoroutine(turnBanner.Show("あなたのターン"));
deck.Draw(drawCount); // 5枚配る
endTurnButton.interactable = true;
}
// 敵のターン
private IEnumerator EnemyTurn()
{
state = TurnState.EnemyTurn;
endTurnButton.interactable = false; // 敵のターンの間は押せない
deck.DiscardHand();
yield return StartCoroutine(turnBanner.Show("敵のターン"));
foreach (EnemyView enemy in enemyParty.Views)
{
enemy.Fighter.ResetBlock(); // 敵の防御も、敵の番の始めに0に戻る
EnemyData.EnemyAction action = enemy.NextAction;
if (action.block > 0)
{
enemy.Fighter.AddBlock(action.block);
}
if (action.attack > 0)
{
player.TakeDamage(action.attack);
}
if (action.attack == 0 && action.block == 0)
{
Debug.Log($"{enemy.Fighter.FighterName}は力をためている・・・");
}
enemy.AdvanceAction(); // 次の予告を出す
yield return new WaitForSeconds(enemyActionSeconds);
}
StartCoroutine(PlayerTurn());
}
}
- enum TurnState と state
- 今どちらのターンかを持っておく。EndTurnは、あなたのターンのときだけ敵のターンへ進める。ボタンを2回連打しても、敵のターンが2回始まらないようにするため
- private IEnumerator Start()
- StartもIEnumeratorにすると、コルーチンとして動く。
yield return nullで1フレーム待ってから始めるのは、DeckのStart(山札を混ぜる)とEnemyPartyのStart(敵を並べる)が先に終わるようにするため - 同じシーンにあるスクリプトのStartは、どれが先に呼ばれるか決まっていません。1フレーム待つと、全部のStartが終わったあとになります
- StartもIEnumeratorにすると、コルーチンとして動く。
- yield return StartCoroutine(turnBanner.Show(…))
- 帯のコルーチンを動かして、それが終わるまで待つ。帯が消えてからカードを配る・敵が動く、という順番になる
- foreach (EnemyView enemy in enemyParty.Views)
- #8で作った敵のリストを、左から1体ずつ行動させる。1体ごとに
WaitForSecondsで0.6秒待つので、何が起きたかを目で追える
- #8で作った敵のリストを、左から1体ずつ行動させる。1体ごとに
- endTurnButton.interactable
- falseにすると、ボタンが灰色になって押せなくなる。敵のターンが終わって、あなたのターンで5枚配ったあとに戻す
スライムStartがIEnumeratorになってる・・・そんな書き方もできるんですね



Unityでは、StartをIEnumeratorにすると自動でコルーチンとして動いてくれるんだ。「最初に少し待ってから始めたい」ときに便利だよ!
BattleManagerを置いて、ボタンをつなごう
- Hierarchyで右クリック>Create Emptyで「BattleManager」を作り、BattleManagerスクリプトを付ける
- Inspectorで、次のものをドラッグ&ドロップ
| 項目 | 入れるもの |
|---|---|
| Deck | Deck |
| Energy | Hud>Energy |
| Player | PlayerArea |
| Enemy Party | EnemyArea |
| End Turn Button | Hud>EndTurnButton |
| Turn Banner | Hud>TurnBanner |
| Draw Count | 5 |
| Enemy Action Seconds | 0.6 |


ターン終了ボタンのつなぎ方も変えます。#4と#7で足した3つ(Deck.DiscardHand・Deck.Draw・Energy.Refill)は、BattleManagerが順番に呼ぶようになったので、ボタンからは消します。
- HierarchyでEndTurnButtonを選び、On Click ()の3つの欄を、それぞれ選んで「-」で消す
- 「+」で1つ足して、BattleManagerをドラッグしてBattleManager > EndTurn ()


On Click ()の3つを消し忘れると、ボタンを押したときに「手札を捨てる」「5枚引く」が2回ずつ起きて、枚数がおかしくなります。On Click ()はBattleManager.EndTurnの1つだけにしましょう。
「何を・どの順番でするか」をBattleManagerの1か所にまとめておくと、ターンの流れを変えたいとき(たとえば「ターンの始めに毒のダメージ」を足すなど)も、ここだけを直せば済みます。
動かして確認!
再生して、ターン終了ボタンを何回か押してみましょう。




#9の動作確認
- 再生すると「あなたのターン」の帯が出てから、5枚配られる
- ターン終了を押すと、手札が捨て札へ行き、ボタンが押せなくなって「敵のターン」の帯が出る
- 敵が左から1体ずつ、予告どおりに行動する(ブリキロボは6の攻撃、びっくり箱は1ターン目はためる)
- 行動したあと、予告が次の行動に変わる(ブリキロボは6、びっくり箱は16)
- 防御があると、敵の攻撃を先に受けとめる。次のあなたのターンで、防御は0に戻る
- 2ターン目のびっくり箱の16の攻撃は、防御カードで備えないと大きく減る



やっと戦えるようになりました!でも、なんだか静かですね・・・



そう、攻撃しても数字が変わるだけだもんね。次の2回で「手応え」を作るよ。反応が返ってくると、同じルールでも別のゲームみたいに気持ちよくなるんだ
あとがき
今回は、ターンの流れを作って、敵が予告どおりに攻撃してくるようになりました。予告を見て「次は16か・・・ふでばこの壁を残しておこう」と考えられるようになり、ぐっとデッキ構築ゲームらしくなりましたね。次回は、攻撃したときに敵が光ってのけぞり、ダメージの数字が飛び出す「攻撃の手応え」を作りたいと思います!
コメント
コメント一覧 (1件)
[…] 前回:#9 ターンの流れを作ろう […]