
前回で、15種類のカードがそろいました。でも、今はまだ1回戦って「もう一度」で最初に戻るだけなので、報酬で選んだカードを使うことが出来ません・・・。今回は、1戦目 → 2戦目 → 3戦目と戦いを続けて、報酬で強くしたデッキを次の戦いに持ちこせるようにしてみたいと思います!
- シリーズの目次:デッキ構築カードゲームの作り方
- 前回:#13 特殊なカードを増やそう
今回作るもの
- 戦いのデータ(名前と、出てくる敵のリスト)
- 3つの戦いを順番につなげる。今何戦目かを上に出す
- 勝ったら「勝利!」→ 報酬を選ぶ → 休んでHP+10 → 次の戦いへ。3戦目に勝ったら「ぜんぶクリア!」
- 戦いの間に、使ったカードを全部山札に戻す(報酬のカードも入ったまま)
- 3戦目の敵「ほつれたぬいぐるみ」
| スクリプト | 役割 | 付ける場所 |
|---|---|---|
| BattleData | 1回の戦いのデータ | (付けない。アセットを作る) |
| RunManager | 戦いを順番につなげる、戦いの間に休む | RunManager(新しく作る空のオブジェクト) |
| EnemyParty(直す) | 敵を並べるのを、あとから呼べるように | - |
| BattleManager(直す) | 戦いを外から始められるように。「勝利!」と報酬はRunManagerへ | - |
| Deck(足す) | 次の戦いの前に、カードを全部山札に戻す | - |
ボスと、手下を呼ぶ行動は次の#15で作ります。
前回の続きから
前回の最後の状態から始めます。ぬいぐるみと、眠るスライムちゃんの絵は、素材のzipを入れ直すと入ります。
- DeckBuildingAssets.zip
DeckBuilding/Sprites/Enemies/enemy_plush.png(ほつれたぬいぐるみ)DeckBuilding/Sprites/Slime/sleep_01.png〜sleep_03.png(眠るスライムちゃん。1→2→3→2の順で、すやすや動く)- 自分で入れた場合は、Sprite Mode:Singleにして Apply
3戦目の敵を作ろう
#8と同じ形で、敵のデータを作ります。
- Data/Enemiesで右クリック>Create>DeckBuilding>Enemy Data、名前をPlush
- Enemy Name:ほつれたぬいぐるみ、Sprite:enemy_plush、Max Hp:44
- Actions:2つ
- Element 0:Attack 5、Block 8(防御を固めながら、少し攻撃)
- Element 1:Attack 10、Block 0

長い名前を折り返さないようにする
「ほつれたぬいぐるみ」は今までの敵より名前が長いので、そのままだと最後の1文字だけが次の行に折り返されてしまいます。
- Prefabs/Enemyを開いて、NameTextを選ぶ
- Width:260 → 380
- TextMeshProの Text Wrapping Mode:No Wrap(折り返さない。Main SettingsのAlignmentの下にあります)

Text Wrapping ModeがNormalのままだと、枠の幅に入りきらない文字が次の行へ回されます。名前のように1行で見せたい文字は、Text Wrapping Mode:No Wrapにしておくと、長い名前の敵を足しても崩れません。
戦いのデータを作ろう(BattleData)
「1戦目はブリキロボ1体」「2戦目はブリキロボとびっくり箱」のような、1回の戦いの中身をScriptableObjectにします(12-8 ScriptableObjectでデータを作ろう)。
- DeckBuilding/Scriptsに「BattleData」スクリプトを作る
using System.Collections.Generic;
using UnityEngine;
// 1回の戦いのデータ:名前と、出てくる敵のリスト。Create>DeckBuilding>Battle Data で作る
[CreateAssetMenu(fileName = "NewBattle", menuName = "DeckBuilding/Battle Data")]
public class BattleData : ScriptableObject
{
public string battleName = "1戦目"; // 上に出す名前
public List<EnemyData> enemies = new List<EnemyData>(); // 出てくる敵(左から順に並ぶ)
}
- Dataフォルダの中にBattlesフォルダを作って、右クリック>Create>DeckBuilding>Battle Dataで3つ作る
| ファイル名 | Battle Name | Enemies |
|---|---|---|
| Battle1 | 1戦目 | TinRobot |
| Battle2 | 2戦目 | TinRobot、JackBox |
| Battle3 | 3戦目 | Plush、Plush |

敵を並べるのを、あとから呼べるようにする
今のEnemyPartyは、StartでInspectorのリストから敵を並べていました。これからは戦いごとに敵が変わるので、並べる処理をSpawnというメソッドにして、外から呼ぶ形にします。EnemyPartyを次のように直します。
using System.Collections.Generic;
using UnityEngine;
// 敵のデータのリストから、敵を横に並べて作る
public class EnemyParty : MonoBehaviour
{
[SerializeField] private EnemyView enemyPrefab;
[SerializeField] private float spacing = 420f; // となりの敵との間隔
private List<EnemyView> views = new List<EnemyView>();
public List<EnemyView> Views => views;
// ★#14 敵のリストを受け取って、横に並べて作る(前は Start で、Inspector のリストから作っていた)
public void Spawn(List<EnemyData> enemies)
{
views.Clear();
// 真ん中を0にして、左右に同じだけ広げる
float left = -(enemies.Count - 1) * spacing / 2f;
for (int i = 0; i < enemies.Count; i++)
{
EnemyView view = Instantiate(enemyPrefab, transform);
((RectTransform)view.transform).anchoredPosition = new Vector2(left + i * spacing, 20f);
view.Setup(enemies[i]);
views.Add(view);
}
}
}
enemiesの項目とStartを消して、Spawn(List<EnemyData> enemies)にする- 中身は前の
Startとほとんど同じです。最初にviews.Clear()で、前の戦いのリストを空にします
BattleManagerを直す
BattleManagerは、これまでStartで自分から戦いを始めていました。これからはRunManagerが「次の戦いを始めて」と頼む形にします。
戦いの前の状態を足す
public enum TurnState
{
Ready, // ★#14 戦いが始まる前
PlayerTurn, // あなたのターン
Readyを一番上に足す- enumの変数は、何も入れないと一番上の値になります。最初は
Readyなので、Updateの「敵を全部倒したら勝ち」の判定が、敵を並べる前に動いてしまうことがありません
- enumの変数は、何も入れないと一番上の値になります。最初は
Startを、外から呼ぶメソッドに変える
IEnumerator Start()を消して、次のメソッドにします。
// ★#14 戦いを始める(RunManager から呼ぶ。前は Start で始めていた)
public void StartBattle()
{
StartCoroutine(PlayerTurn());
}
「勝利!」と報酬は、RunManagerに移す
3戦目に勝ったときは「勝利!」ではなく「ぜんぶクリア!」と出したいので、表示と報酬はRunManagerに任せます。Victoryを次のように短くします。
// ★#12 勝ち:勝ちのポーズ(★#14「勝利!」と報酬は RunManager に移した)
private IEnumerator Victory()
{
state = TurnState.Victory;
endTurnButton.interactable = false;
yield return new WaitForSeconds(1f); // 最後の敵が消えるのを待つ
deck.DiscardHand();
StartCoroutine(WinPose());
}
resultView.Show(…)から下の5行を消す- 項目の
rewardViewも使わなくなるので、消しておきます(Defeatで使うresultViewは残す)
次の戦いの前に、カードを山札に戻す
Deckに、捨て札と「なくなった札」を全部山札に戻すメソッドを足します(AddCardの上)。
// ★#14 次の戦いの前に、捨て札となくなった札を全部山札に戻して混ぜる
public void ResetForBattle()
{
drawPile.AddRange(discardPile);
drawPile.AddRange(exhaustPile);
discardPile.Clear();
exhaustPile.Clear();
Shuffle(drawPile);
UpdateCounts();
}
- 勝ったときに手札は捨て札へ行っているので、捨て札と山札を合わせれば、デッキの全部になります
- おやつタイムのような「なくなった札」も、次の戦いではまた使えるように戻します
- 報酬で選んだカードは、もう山札に入っているので、そのまま次の戦いに持ちこせます
戦いを順番につなげる(RunManager)
今何戦目かの表示
- Hudの子にRunLabel(Text – TextMeshPro)
- アンカー:top-center、Pivot Y:1、Pos Y:-24、Width:600、Height:70
- テキスト:1戦目(1/3)、Font Size:44、Font Style:B、Color:#3D2E2E、Alignment:中央
- Raycast Targetのチェックを外す
RunManager
- DeckBuilding/Scriptsに「RunManager」スクリプトを作る
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
// 戦いを順番につなげる:1戦目 → 2戦目 → 3戦目。勝ったら報酬を選んで休み、次の戦いへ
public class RunManager : MonoBehaviour
{
[SerializeField] private List<BattleData> battles = new List<BattleData>(); // 戦う順番
[SerializeField] private BattleManager battleManager;
[SerializeField] private EnemyParty enemyParty;
[SerializeField] private Deck deck;
[SerializeField] private Fighter player;
[SerializeField] private ResultView resultView;
[SerializeField] private RewardView rewardView;
[SerializeField] private TextMeshProUGUI runLabel; // 今何戦目か
[Header("戦いの間に休む")]
[SerializeField] private IdleBounce slimeIdle;
[SerializeField] private Sprite[] sleepFrames; // sleep_01・sleep_02・sleep_03
[SerializeField] private int restHeal = 10; // 休んで回復する HP
[Header("確かめるとき用")]
[SerializeField] private int startBattle = 0; // 0以外にすると、その番号の戦いから始まる(2 なら3戦目)
private IEnumerator Start()
{
// ほかのスクリプトの Start(山札を混ぜるなど)が終わるのを、1フレーム待つ
yield return null;
for (int i = startBattle; i < battles.Count; i++)
{
BattleData battle = battles[i];
runLabel.text = $"{battle.battleName}({i + 1}/{battles.Count})";
if (i > startBattle)
{
yield return StartCoroutine(Rest()); // 戦いの間に休む
deck.ResetForBattle(); // 使ったカードを全部山札に戻す
}
enemyParty.Spawn(battle.enemies);
battleManager.StartBattle();
// 勝つか負けるまで待つ
while (battleManager.State != TurnState.Victory && battleManager.State != TurnState.Defeat)
{
yield return null;
}
if (battleManager.State == TurnState.Defeat)
{
yield break; // 負けたら終わり(「もう一度」は BattleManager が出す)
}
// 勝った:最後の敵が消えて、勝ちのポーズになるのを待ってから表示
yield return new WaitForSeconds(1.5f);
bool isLast = i == battles.Count - 1;
yield return StartCoroutine(resultView.Show(isLast ? "ぜんぶクリア!" : "勝利!", new Color(1f, 0.82f, 0.25f)));
yield return new WaitForSeconds(1f);
if (isLast)
{
resultView.ShowRetry();
yield break;
}
resultView.Hide();
yield return StartCoroutine(rewardView.Open()); // 報酬を選ぶ
}
}
// 戦いの間に休む:眠るポーズをしてから HP を回復して、待機の動きに戻す
private IEnumerator Rest()
{
slimeIdle.enabled = false;
Image slimeImage = slimeIdle.GetComponent<Image>();
int[] order = { 0, 1, 2, 1, 0, 1, 2, 1 }; // 1→2→3→2 を2回
foreach (int index in order)
{
slimeImage.sprite = sleepFrames[index];
yield return new WaitForSeconds(0.2f);
}
player.Heal(restHeal); // #11 の緑の数字とキラキラも出る
yield return new WaitForSeconds(1f);
slimeIdle.enabled = true;
}
}
Startをコルーチンにして、forで戦いを1つずつ進める- 「敵を並べる → 戦いを始める → 勝つか負けるまで待つ → 報酬 → 休む → 次へ」を、上から順番に読める形で書けるのがコルーチンのいいところです
- 勝つか負けるまで
whileで待つ- BattleManagerの
Stateを毎フレーム見て、VictoryかDefeatになったら次へ進みます
- BattleManagerの
- 2戦目からは、始める前に
Restで休んで、ResetForBattleでカードを山札に戻す - 最後の戦いだけ「ぜんぶクリア!」と「もう一度」ボタン。それ以外は「勝利!」のあと報酬
Rest:#12の勝ちのポーズと同じように、IdleBounceを止めてから眠る絵を切りかえ、player.Heal(10)で回復します- HPが減っていれば、#11の緑の「+10」とキラキラ、ゲージが伸びる動きがそのまま出ます
startBattle:3戦目だけ確かめたいときなどに、2を入れると3戦目から始まります
HPは戦いの間で引きつがれます(戦いの間に回復するのは10だけ)。#9の「防御は自分の番の始めに0」とちがって、HPはリセットしません。1戦目で受けたダメージが、3戦目までじわじわ効いてくるのが、連戦の面白いところです。
置いて、つなぐ
- HierarchyにRunManager(Create Empty)を作って、Add Component>RunManager
| 項目 | 入れるもの |
|---|---|
| Battles | 「+」で3つにして、Battle1・Battle2・Battle3 |
| Battle Manager | BattleManager |
| Enemy Party | Stage>EnemyArea |
| Deck | Deck |
| Player | Stage>PlayerArea |
| Result View | Hud>ResultView |
| Reward View | Hud>RewardView |
| Run Label | Hud>RunLabel |
| Slime Idle | Stage>PlayerArea>Slime |
| Sleep Frames | 「+」で3つにして、sleep_01・sleep_02・sleep_03 |
| Rest Heal | 10 |
| Start Battle | 0 |

EnemyAreaのEnemy Partyにあった「Enemies」のリストは、スクリプトから消したのでInspectorからも消えます。どの敵が出るかは、これからはBattleDataで決めます。
動かして確認!
再生して、敵を倒してみましょう。1戦目に勝つと、報酬のあとに休んで、2戦目が始まります。

3戦目だけ確かめたいときは、RunManagerのStart Battleを2にして再生します(確かめたら0に戻しましょう)。

#14の動作確認
- 上に「1戦目(1/3)」が出て、ブリキロボ1体と戦う
- 勝つと「勝利!」→ 報酬を選ぶ → スライムちゃんが眠ってHP+10 → 「2戦目(2/3)」でブリキロボとびっくり箱が出てくる
- 1戦目で選んだ報酬のカードが、2戦目の手札に出てくる(山札の数字も1つ多い)
- 3戦目はほつれたぬいぐるみが2体。名前が1行で出る
- 3戦目に勝つと「ぜんぶクリア!」と「もう一度」ボタン
- どこかで負けると「まけてしまった・・・」と「もう一度」ボタン。「もう一度」で1戦目からやり直し
スライム報酬で選んだカードが次の戦いで手札に来ると、ちょっとうれしいですね!



「このカードを取ったから勝てた!」が、デッキ構築の一番の気持ちよさだよね。次回はいよいよボスだよ!
あとがき
今回は、戦いのデータを作って、3つの戦いを続けて遊べるようにしました。報酬で選んだカードが次の戦いに持ちこせるようになって、ようやく「デッキを育てる」遊びがつながりましたね。次回は、おもちゃの王さまの登場演出と、手下を呼ぶ行動を作って、ボス戦を作りたいと思います!
コメント
コメント一覧 (1件)
[…] 前回:#14 3連戦をつなげよう […]