🎬前回のあらすじ
前回はカメラ追従対象をプレイヤー→移動用の乗り物オブジェクトに切り替えられるようにして、乗り物オブジェクトを特定の地点まで自動的に移動する仕組みを作りました。
何を作っているか詳しく知りたい人は1の記事をご覧ください。
⚔️戦闘遷移処理
今回は②のキャラの管理処理を飛ばして、③の戦闘遷移処理を作っていきます。具体的には、乗り物オブジェクトが移動しているときにモンスターがいきなり出現して戦闘開始するような流れです。
🧩必要な処理
実は戦闘モードに遷移する切り替え処理は以前作りました。
今回は戦闘が開始する処理+どの敵を出現させるのか乱数で決める実装がメインです。
👾出現処理
ミートピアの出現処理はプレイヤーの体感で言うと乱数で決まっているようにも感じられますが、本来その実装方法だと理不尽にも戦闘が終了する直後に戦闘がまた始まってしまう事態が起こります。これはプレイヤーにとってはストレスに感じると思います。しかし、ミートピアにはそのようなものはないのです。任天堂の工夫でしょう。
実際は特定のスポーンポイントがあり、そこに到達すると戦闘が開始する。そしてどの敵が何体出現するかだけ乱数で決めていると考えます。今回はその方法を適用していきます。
📍スポーンポイント
スポーンポイントには空オブジェクトにTriggerONのColliderを持たせて、スクリプトで乗り物オブジェクトとの接触を検知して戦闘開始するという流れです。他にも乗り物オブジェクトとの距離を検知する方法も考えましたが、誤作動する確率が高くなると考えたので前者の方法を適用します。
using UnityEngine;
public class SpawnTrap : MonoBehaviour
{
[SerializeField] private EnemyTable enemyTable;
private bool triggered = false;
void OnTriggerEnter(Collider other)
{
if (!other.gameObject.CompareTag("Vehicle"))
return;
if (triggered)
return;
//一回通過したら今後は適用外にするためのフラグ(誤作動防止)
triggered = true;
//戦闘用のフラグ(今回は気にしないで)
AreaManager.Instance.battleStarted = true;
BattleTransition.Instance.isTransition = true;
//モンスター抽選
GameObject enemy = EnemySelector.GetRandomEnemy(enemyTable);
//戦闘モードに遷移
BattleTransition.Instance.StartBattle(enemy);
}
}
こんな感じです。誤作動を防止するためのフラグを入れ忘れていたのでそこが反省点です。元の戦闘遷移処理を少し拡張するだけだったので難なくこなせたと思います。
🎲抽選処理
抽選処理です。まずはSystem.Serializableでキャラのプレハブと重みを定義できるようにしてScriptableObjectでアセットのように使えるようにします。
[CreateAssetMenu(menuName = "Encounter/EnemyTable")]
public class EnemyTable : ScriptableObject
{
public EnemyEntry[] enemies;
}
[System.Serializable]
public class EnemyEntry
{
public GameObject enemyPrefab;
public int weight; // 出現しやすさ
}
これを抽選します。先ほどのSpawnTrap.csで呼び出した処理です。
using UnityEngine;
public static class EnemySelector
{
public static GameObject GetRandomEnemy(EnemyTable table)
{
int total = 0;
//前半で抽選
foreach (EnemyEntry e in table.enemies)
total += e.weight;
int r = Random.Range(0, total);
int sum = 0;
//ここからは抽選で選ばれた数字がどの範囲にあるのかを判定
foreach(EnemyEntry e in table.enemies)
{
sum += e.weight;
//rを超えたらその範囲にあるということ
if (r < sum)
return e.enemyPrefab;
}
return null;
}
}
基本的にはRigidbodyやTransformばかりいじっていてランダム関数はあまり使う機会がなかったのでロジックの理解に少し時間がかかりました。sumを増やしながらrにランダムで格納した数字を超えているか判定して、真のタイミングでそのときに加算したオブジェクトを返しています。これが結果的にrから範囲内にあるの敵が選ばれるようになっているということです。
🧠抽選処理の図解(重み付きランダムの仕組み)
0〜49 → スライム
50〜79 → コウモリ
80〜99 → オオカミ
一度目のループ(foreach)=重みの総数から抽選するため
r = 72の場合(コウモリ)
sum = 0
二度目のループ=一体ずつ重みを加算してどのモンスターが選ばれたか判定
sum += 50(=50)
r < sum → 偽
sum += 30(=80)
r < sum → 真
コウモリの重みを足すとsumの数値を越したのでコウモリが抽選されたということがわかる。
ちなみにこのクラスはインスタンス化する必要がない(何かのオブジェクトにつける設計図ではない)のでMonoBehaviourは付けないほうがいいらしいです。
🎥切り替えなきゃ
戦闘に移行したといってカメラの状態を変更しなければ追従対象は変わりません。なので戦闘遷移処理のタイミングでカメラの追従対象を変更するために更新処理を追加しなければいけないことに気が付きました。
//戦闘開始
TravelCtrl.Instance.State = VehicleState.Idle;
//戦闘終了
TravelCtrl.Instance.State = VehicleState.Moving;
これで完了です。
🧪デバッグ
前回と同様にとりあえず動作するか試していきます。

まずはVehicleというタグを作って乗り物オブジェクトにセットします。


空のスポーンポイントにTrigger付きのColliderを持たせます。とりあえず乗り物オブジェクトにあたる位置と大きさに調整します。

敵が一体しかいないので、とりあえずこのようなアセットを作りました。
準備ができたので早速デバッグします。
🌟結果
スポーンポイントにあたっても反応なしでした。しかし、この原因は明確でした。それは、乗り物オブジェクトにRigidbodyが付いていないからでした。OnTriggerの条件として、接触するオブジェクトのどちらか一方にはRigidbodyをつけなければならなかったのです。つけた結果は、正しい挙動で移動→戦闘→移動に遷移することができました。
⚠️しかし...
Rigidbodyを追加した結果正しく動いたのですが、goalオブジェクトに到達すると乗り物が止まって微振動し始めました。そしてカメラの追従対象が操作キャラに変更にならなくなったのです。これはおそらくRigidbodyを追加したために起きた物理挙動によるバグだと考えました。そのため、RigidbodyのIs KinematicというものをONにしました。これは、どうしてもRigidbodyを持ちたいけど物理計算処理が邪魔だから適用外にするというものです。つまりTransformの移動が優先されます。これは移動をMoveTowards()にしている今回の処理にぴったりです。
Is KinematicをONにした結果、処理が正しい挙動で行われるようになりました。これで実装完了です。
🔧次回予告
次回はトーク処理を組み込みます。かなり大幅な変更になって大変だと思いますが頑張ろうと思います。
Thank You For Reading!