
前回、地下2・3階の敵に、それぞれの行動を付けました。でも、地下3階をクリアしても、奥には何もいませんでした・・・。今回は、いよいよ地下3階の奥に、ボスの「禁じられた本」を出したいと思います!
- シリーズの目次:ターン制RPGの作り方
- 前回:#22 地下2・3階の敵
今回作るもの

- 地下3階の4回目のバトルに、ボスの「禁じられた本」が出る
- バトルの始めに登場の演出(暗くなる・ページが舞う・揺れながら現れる)
- ボスは、5つの行動を決まった順番でくり返す(#22の「順番に使う」を使う)
- ボスからは にげられない。せきか・ふうじ も効かない
- ボスに勝つと、地下3階をクリア
| 順番 | 行動 | 中身 | 使うクラス |
|---|---|---|---|
| 1 | ページを よぶ | まほうのページを2枚、左右に呼ぶ | SummonCommand(新しく作る) |
| 2 | くらやみの波 | 仲間の全員に、まほう 威力 14 | AttackSpellCommand(全体) |
| 3 | かみつく | こうげき。ときどき どく | 本の虫の Bite |
| 4 | ためる | 「ちからを ためている!」 | 前回の Charge |
| 5 | おおきな かみつき | こうげき の2倍 | MultiHitSkillCommand |
| スクリプト | 変えること |
|---|---|
| EnemyData・BattleUnit | 画面に出す大きさと、ボスかどうか |
| UnitView | 大きさを変える、隠す・現れる |
| BattleEffects | 揺らすのを、ほかのスクリプトからも使えるように |
| BossEntrance(新しく作る) | 登場の演出 |
| BattleManager | ボスがいたら、登場の演出から始める |
| BattleLineup・BattleContext | バトルの途中で、敵を足す |
| SummonCommand(新しく作る) | なかまを呼ぶ |
| AttackSpellCommand | 敵が使う全体のまほう |
| RunCommand・StatusSpellCommand | ボスからは にげられない、動けなくなる状態が効かない |
ボスのデータを作ろう

この節では、ボスの絵とデータを用意して、地下3階の最後に出します。
絵と、画面に出す大きさ
素材のTurnBasedRpgAssets.zipに、ボスの絵と、登場の演出で使うページの絵を足しました。
- zipをダウンロードし直して、
TurnBasedRpg/SpritesのEnemiesとEffectsをプロジェクトに入れる- enemy_forbidden_book(512×512)・fx_page(64×64)
ボスは、ほかの敵より大きく出したいので、EnemyDataに「画面に出す大きさ」を持たせます。usesMovesInOrderの下に、2つ足します。
[SerializeField] private float displaySize = 380f; // 画面に出す大きさ(ふつうの敵は 380)
[SerializeField] private bool isBoss; // ボス(にげられない・動けなくなる状態が効かない)
UsesMovesInOrderの下に、プロパティを足します。
public float DisplaySize => displaySize;
public bool IsBoss => isBoss;
- 欄に
= 380fと書いておくと、今までの敵のアセットは、何もしなくても380のままです。あとから足した欄は、書いておいた最初の値で読み込まれます
UnitViewのSetupEnemyの最後に、1行足します。
rectTransform.sizeDelta = Vector2.one * data.DisplaySize; // ボスは大きく
BattleUnitのIsDeadの下に、1行足します。
public bool IsBoss => EnemyData != null && EnemyData.IsBoss; // ボスなら true
- 仲間は
EnemyDataが null なので、先に null かどうかを調べています
ボスのデータ
- Data/Enemiesに、Create>TurnBasedRpg>Enemy Data で ForbiddenBook を作り、上の画像のとおりに入れる
| 欄 | 値 |
|---|---|
| Enemy Name | 禁じられた本 |
| Stats | HP 420・こうげき 26・ぼうぎょ 16・すばやさ 10・まりょく 8 |
| Exp・Gold | 200・300 |
| Moves | SummonPages・DarkWave・Bite・Charge・BigBite(重みはどれも1。この節のあとで作る) |
| Uses Moves In Order | チェック |
| Display Size | 560 |
| Is Boss | チェック |
- HPは、地下3階の敵(30〜40)の10倍以上です。何ターンもかけて戦う相手にしています。強さの細かい調整は、次の#24でします
地下3階の4回目

- Data/Floorsの Floor3 を選び、Battlesを 4 にして、Element 3 のEnemiesを ForbiddenBook の1つだけにする
#21で、戦う回数はbattlesの数で決まるようにしました。要素を1つ足すだけで、右上の表示も「ちか3かい 4/4」になり、ボスに勝つと地下3階のクリアになります。
登場の演出

ボスは、いきなり立っているのではなく、目をさますところを見せたいです。流れは次のとおりです。
- 画面が暗くなる
- ボスのいる場所から、ページが舞い上がる。小さく揺れ続ける
- 明るくなりながら、ボスが小さいところから大きくなって現れる
- 大きく1回揺れる
- 「禁じられた本が めを さました!」
隠す・現れる
UnitViewのPlayWinAsyncの上に、2つのメソッドを足します。
// 透明にして隠す(登場の演出の前)
public void Hide()
{
image.color = new Color(1f, 1f, 1f, 0f);
}
// 現れる:小さいところから大きくなりながら、だんだん見えてくる(DisappearAsync の逆)
public async Awaitable AppearAsync(float seconds)
{
float time = 0f;
while (time < seconds)
{
time += Time.deltaTime;
float t = Mathf.Clamp01(time / seconds);
transform.localScale = Vector3.one * Mathf.Lerp(0.6f, 1f, t);
image.color = new Color(1f, 1f, 1f, t);
await Awaitable.NextFrameAsync(destroyCancellationToken);
}
transform.localScale = Vector3.one;
SetDimmed(isDimmed); // 状態の色に戻す
}
- #8の
DisappearAsync(しぼみながら薄くなる)の、ちょうど逆です。この回の「ページを呼ぶ」でも使います
揺らす
#8で作ったBattleEffectsの揺れは、ダメージの大きさで揺らすprivateのメソッドでした。登場の演出からも、幅と時間を決めて揺らせるようにします。ShakeAsyncを、次の2つに書き換えます。
private async Awaitable ShakeAsync(int damage)
{
await ShakeAsync(Mathf.Min(damage * shakePerDamage, maxShake), shakeSeconds);
}
// 画面を揺らす。strength は揺れの幅、seconds は揺れている時間(だんだん弱くなる)
public async Awaitable ShakeAsync(float strength, float seconds)
{
Vector2 start = stage.anchoredPosition;
float timeLeft = seconds;
while (timeLeft > 0f)
{
float power = strength * (timeLeft / seconds);
stage.anchoredPosition = start + Random.insideUnitCircle * power;
await Awaitable.NextFrameAsync(destroyCancellationToken);
// ヒットストップ中も揺れるように、unscaledDeltaTime で減らす
timeLeft -= Time.unscaledDeltaTime;
}
stage.anchoredPosition = start;
}
- 中身は今までと同じで、幅と時間を引数でもらうようにしただけです。ダメージで揺らすほうは、それを呼ぶだけになります
- 同じ名前で引数がちがうメソッドは、いくつでも作れます(オーバーロード)
舞うページ

#8の火花(HitSparks)と同じ作り方で、ページのパーティクルを作ります。
- Hierarchyで右クリック>Effects>Particle System で作り、名前を PageStorm にする
- 下の表のとおりに入れて、Prefabsフォルダへドラッグしてプレハブにし、Hierarchyからは消す
| モジュール | 欄 | 値 |
|---|---|---|
| メイン | Duration・Looping | 1.2・外す |
| メイン | Start Lifetime | Random Between Two Constants:1.2〜2.2 |
| メイン | Start Speed | Random Between Two Constants:1〜3 |
| メイン | Start Size | Random Between Two Constants:0.4〜0.8 |
| メイン | Start Rotation | Random Between Two Constants:0〜360 |
| メイン | Gravity Modifier | -0.15(ふわっと上へ) |
| メイン | Simulation Space・Stop Action | World・Destroy |
| Emission | Rate over Time・Bursts | 40・0秒に30個 |
| Shape | Shape・Radius | Circle・1.2 |
| Rotation over Lifetime | Angular Velocity | Random Between Two Constants:-229〜229 |
| Noise | Strength・Frequency | 1.5・0.6(ひらひら揺れる) |
| Color over Lifetime | Color | アルファを、70%まで255、最後に0 |
| Texture Sheet Animation | Mode・Sprite | Sprites・fx_page |
| Renderer | Material・Sorting Layer | Sprite-Unlit-Default・Effect |
- Gravity Modifier をマイナスにすると、重力と逆向き(上)へ動きます。ページが下から吹き上がるように見えます
- Noise を付けると、まっすぐ飛ばずに、ひらひらと揺れながら舞います
BossEntrance

- 「BossEntrance」スクリプトを作る
using UnityEngine;
// ボスの登場:暗くなって、ページが舞い、地面が揺れて、ボスが現れる
public class BossEntrance : MonoBehaviour
{
[SerializeField] private ScreenFade screenFade;
[SerializeField] private BattleEffects battleEffects;
[SerializeField] private ParticleSystem pagesPrefab; // 舞うページ(PageStorm)
[SerializeField] private Transform pagesPoint; // ページを出す場所(ボスのうしろ)
public async Awaitable PlayAsync(UnitView boss)
{
boss.Hide(); // 最初は見えない
// 暗くなって、ページが舞い始め、ゴゴゴ…と小さく揺れる
await screenFade.FadeAsync(0.7f, 0.5f);
Instantiate(pagesPrefab, pagesPoint.position, Quaternion.identity);
await battleEffects.ShakeAsync(6f, 1.2f);
// 明るくなりながら、ボスが現れて、大きく揺れる
Awaitable brighten = screenFade.FadeAsync(0f, 0.6f);
await boss.AppearAsync(0.6f);
await battleEffects.ShakeAsync(30f, 0.5f);
await brighten;
}
}
- 暗くするのは、#11のぜんめつで使ったScreenFadeです。0.7まで暗くして、ボスが現れるときに0へ戻します
- 明るくするのと、ボスが現れるのを、いっしょに始めています。暗いところから、だんだん姿が見えてくるようにするためです
- Hierarchyの Effects に、Add ComponentでBossEntranceを付けて、上の画像のとおりに入れる(Pages Pointは、Canvasの中の EnemyArea)
BattleManagerに、欄を足します。
[SerializeField] private BossEntrance bossEntrance; // ボスの登場の演出
StartBattleAsyncの最後の行(「〇〇たちが あらわれた!」)の前に、次のように足します。
context.Lineup = lineup;
BattleUnit boss = lineup.FindBoss();
if (boss != null)
{
messageText.text = ""; // 演出の間は、メッセージを空にしておく
await bossEntrance.PlayAsync(boss.View);
await messageWindow.ShowAsync($"{boss.Name}が めを さました!");
return;
}
context.LineupとFindBossは、次の節で作ります- Battleを選び、BattleManagerの Boss Entrance に、Effectsをドラッグする
Hideは、PlayAsyncの最初で、待たずにすぐ呼んでいます。awaitの前なら、画面に1コマも出る前に隠せます。待ったあとで隠すと、一瞬ボスが見えてしまいます。
ボスの行動

ボスの5つの行動のうち、3つは今までのクラスで作れます。新しく作るのは「ページを よぶ」だけです。
バトルの途中で、敵を足す
今まで、敵はバトルの始めに並べるだけでした。「ページを よぶ」のために、途中からも足せるようにします。
BattleLineupのSetupの、敵を並べるループを、次のように書き換えます。
EnemyData[] enemyGroup = LoadEnemies();
for (int i = 0; i < enemyGroup.Length; i++)
{
// 真ん中をそろえて横に並べ、1体おきに上下へずらす
float x = (i - (enemyGroup.Length - 1) / 2f) * enemySpacing;
float y = (i % 2 == 0) ? 60f : -60f;
AddEnemy(enemyGroup[i], new Vector2(x, y));
}
}
Setupの下に、次のメソッドを足します。
// 敵を1体、position(EnemyArea の中の位置)に出す。EnemyUnits は左から順になるように入れる
public BattleUnit AddEnemy(EnemyData enemy, Vector2 position)
{
UnitView view = Instantiate(enemyPrefab, enemyArea);
((RectTransform)view.transform).anchoredPosition = position;
view.SetupEnemy(enemy);
BattleUnit unit = new BattleUnit(enemy, view);
int index = 0;
while (index < EnemyUnits.Count && PositionOf(EnemyUnits[index]).x < position.x)
{
index++;
}
EnemyUnits.Insert(index, unit);
return unit;
}
// position に、まだ立っている敵がいるか(呼んだ敵を、同じ場所に重ねないため)
public bool HasLivingEnemyAt(Vector2 position)
{
foreach (BattleUnit unit in EnemyUnits)
{
if (!unit.IsDead && Vector2.Distance(PositionOf(unit), position) < 1f)
{
return true;
}
}
return false;
}
// 立っているボス(いなければ null)
public BattleUnit FindBoss()
{
foreach (BattleUnit unit in EnemyUnits)
{
if (unit.IsBoss && !unit.IsDead)
{
return unit;
}
}
return null;
}
// 敵の、EnemyArea の中の位置
private Vector2 PositionOf(BattleUnit unit)
{
return ((RectTransform)unit.View.transform).anchoredPosition;
}
- AddEnemy:今まで
Setupのループの中に書いていた「作って、置いて、見た目を作る」を、1つのメソッドにまとめました EnemyUnitsは、左から順に並ぶように、横の位置で入れる場所を決めています(Insert)。ねらう相手を選ぶとき、左右のキーで選ぶ順番が、画面の並びと同じになります。後ろに足すだけだと、ボスの左のページが、右のページのあとになってしまいます
BattleContextのEffectsの下に、1行足します。
public BattleLineup Lineup; // 並び(敵を呼んで足すとき)
SummonCommand

- Scripts/Commandsに「SummonCommand」スクリプトを作る
using System.Collections.Generic;
using UnityEngine;
// なかまを呼ぶ(禁じられた本の ページを よぶ):決めた場所のうち、空いているところに敵を出す
[CreateAssetMenu(fileName = "NewSummon", menuName = "TurnBasedRpg/Commands/Summon")]
public class SummonCommand : BattleCommand
{
[SerializeField] private EnemyData summoned; // 呼ぶ敵
[SerializeField] private Vector2[] positions; // 出てくる場所(EnemyArea の中の位置)
public override async Awaitable ExecuteAsync(BattleUnit user, BattleAction action, BattleContext context)
{
Awaitable cast = user.View.PlayCastAsync(); // 力をためる(ボスは、ふくらむ)
await context.Message.ShowAsync($"{user.Name}は {summoned.EnemyName}を よびだした!");
await cast;
user.View.ResumeIdle();
// 空いている場所に出して、みんないっしょに現れる
List<Awaitable> appears = new List<Awaitable>();
foreach (Vector2 position in positions)
{
if (!context.Lineup.HasLivingEnemyAt(position))
{
BattleUnit unit = context.Lineup.AddEnemy(summoned, position);
appears.Add(unit.View.AppearAsync(0.4f));
}
}
if (appears.Count == 0)
{
await context.Message.ShowAsync("しかし だれも こなかった!");
return;
}
foreach (Awaitable appear in appears)
{
await appear;
}
await context.Message.ShowAsync($"{summoned.EnemyName}が あらわれた!");
}
}
- 呼ぶ場所を決めておき、まだページが立っている場所には出しません。5ターンごとに呼ぶので、前に呼んだページを倒していなければ、増えすぎないようにしています
- 呼んだページが動くのは、次のターンからです。このターンの行動は、もう決まっているからです
- 呼んだページを倒しても、経験値とお金がもらえます(勝ったときに、
EnemyUnitsの全員の分を足すため)
| ファイル名 | 作るメニュー | 中身 |
|---|---|---|
| SummonPages | Create>TurnBasedRpg>Commands>Summon | Command Name:ページを よぶ、Summoned:MagicPage、Positions:(-400, -100)・(400, -100) |
くらやみの波

「くらやみの波」は、仲間の全員に当たるまほうです。#14のAttackSpellCommand(いなずま)の「全体」を使います。
ただし、今のAttackSpellCommandの全体は、context.Enemies(敵の全員)に当てています。敵が使うと、敵の全員に当たってしまいます。前回作ったLivingOpponents(使う人の相手の全員)に書き換えます。
AttackSpellCommandのExecuteAsyncの最初を、次のように書き換えます。
// 全体なら、まだ立っている相手の全員。1人なら、ねらった相手(倒れていたら別の相手)
List<BattleUnit> targets = new List<BattleUnit>();
if (TargetsAll)
{
targets.AddRange(context.LivingOpponents(user)); // 敵が使えば、仲間の全員
}
AddRangeは、別のリストの中身を、まとめて足すメソッドです- 仲間が使ったときは、今までと同じ「敵の全員」になります
ためて、おおきな かみつき


- Data/EnemySkillsに、2つ作る
| ファイル名 | 作るメニュー | Command Name | そのほか |
|---|---|---|---|
| DarkWave | Create>TurnBasedRpg>Skills>Attack Spell | くらやみの波 | Mp Cost 0、Effect Prefab:MagicBurst、Targets All:チェック、Power:14 |
| BigBite | Create>TurnBasedRpg>Skills>Multi Hit | おおきな かみつき | Mp Cost 0、Damage Rate:2、Hit Count:1 |
- 「かみつく」は本の虫の Bite、「ためる」は前回の Charge を、そのまま入れます
- ForbiddenBookのMovesに、上から SummonPages・DarkWave・Bite・Charge・BigBite を入れる(重みはどれも1)
「ためる」のすぐ次に「おおきな かみつき」が来るように並べています。ためているのを見たら、次のターンに ぼうぎょ(受けるダメージ半分)で備えられます。5つで1回りなので、何ターン目に何が来るかも、覚えれば読めるようになります。
スライムページを呼ぶ → 波 → かみつく → ためる → おおきく かみつく、の順番ですね!
ボスだけの決まり


ボス戦が、にげたり、石にしたりで簡単に終わらないように、2つ決まりを足します。
にげられない
RunCommandのExecuteAsyncの、「にげだした!」のメッセージの下に、次のifを足します。
if (HasBoss(context))
{
await context.Message.ShowAsync("しかし ボスからは にげられない!");
return;
}
AverageSpeedの上に、次のメソッドを足します。
// 立っている敵に、ボスがいるか
private bool HasBoss(BattleContext context)
{
foreach (UnitView view in context.Enemies)
{
if (view.Unit.IsBoss && !view.Unit.IsDead)
{
return true;
}
}
return false;
}
- #11で作った にげる は、すばやさで確率が決まるので、何回かやれば逃げられることもありました。ボスがいるときは、確率を調べる前に止めます
せきか・ふうじ が効かない


メドゥーサの せきかのまなざし(3ターン動けない)やキョンシーの おふだ(2ターン動けない)がボスに効くと、ボスが何もできないまま終わってしまいます。
StatusSpellCommandの、かかったかを決める行を、次のように書き換えます。
// ボスには、動けなくなる状態(せきか・ふうじ)は効かない
bool isBlocked = target.IsBoss && !effect.CanAct;
isApplied[i] = !isBlocked && GameRandom.Range(0f, 100f) < chance;
- 効かないのは、動けなくなる状態だけです。へびのどく(どく)は、ボスにもかかります。ただ、どくは毎ターン最大HPの1/4なので、HPの多いボスには効きすぎます(4ターンで倒せてしまう)。どくの強さは、次の#24のバランスの見直しで直します
- 効かなかったときは、今までの「ミス」「〇〇には きかなかった!」がそのまま出ます



どくは効くのね!・・・ちょっと効きすぎかしら?
動かして確認!


- Lobbyシーンを開いて再生し、「地下へ」→ ちか3かい(まだ行けないときは、地下1階からクリアしていく)
- 3回勝って「さきへ すすむ」を選ぶと、右上が「ちか3かい 4/4」になり、暗くなってページが舞い、禁じられた本が現れる
- 禁じられた本は、ページを よぶ → くらやみの波 → かみつく → ためる → おおきな かみつき をくり返す
- 呼んだページは、左右に出る。ねらう相手を選ぶとき、左のページ → ボス → 右のページ の順になる
- にげる を選ぶと「しかし ボスからは にげられない!」
- おふだ・せきかのまなざし は、ボスには「きかなかった!」(ページには効く)
- ボスに勝つと「ちか3かいを クリアした!」と「クリア!」のウインド
GIFは、確かめやすいように、仲間をLv8〜10にして撮っています。最後のGIFは、撮影のためにボスのHPを減らしてあります。目安のレベル(Lv7〜9)と装備で、ちょうどよく勝てるかは、次回の自動プレイで確かめます。
#23の動作確認
- ボス戦の始めに、暗くなって、ページが舞い、揺れながらボスが現れる
- ボスは、5つの行動を決まった順番でくり返す
- 呼んだページは、空いている場所にだけ出て、次のターンから動く
- くらやみの波は、仲間の全員に当たる(敵どうしには当たらない)
- ボスからは にげられない。せきか・ふうじ は効かない(どくは効く)
- ボスに勝つと、地下3階をクリアになる
あとがき
今回は、地下3階の奥に、ボスの「禁じられた本」を出しました。登場の演出はScreenFade・パーティクル・揺れの組み合わせで、行動は「順番に使う」と今までのクラスで、ほとんど作れました。新しく作ったのは、なかまを呼ぶSummonCommandくらいです。次回は、自動でバトルを進める仕組みを作って、ボスまでちょうどよく戦えるか、全体のバランスを見直したいと思います!



ページが舞って出てくるの、ドキドキしました・・・!



ためているのを見たら、ぼうぎょ よ!
コメント
コメント一覧 (1件)
[…] 前回:#23 ボス「禁じられた本」 […]