😆

Unityでゆるっと「8番ライク」を組んでみたメモ

に公開

最近また「8番出口」系のスクショをXで見かけることが増えて、「これUnityなら最低限どこまで縮められるかな?」という実験をしてみました。

ちゃんとしたチュートリアルというより、「8番ライク 作り方」を調べながら自分が試した最小構成メモです。
断定じゃなくて「たぶんこうすると楽だった」というログくらいの温度感で読んでもらえれば。

ターゲットとしては「Unityは触ったことある」「でも8番出口っぽいループゲームをどう分解したらいいかピンと来てない」くらいの人を想定しています。
C#は最低限書ける前提です。

まず「8番ライク」をざっくり分解してみる

最初にやったのは、8番出口そのものを再現しようとするのをやめることでした。
あれを丸ごとUnityでやろうとすると、グラもライトもUIも全部フルコースになってしまうので、個人開発だとまず挫折しそうだなと…。

なので、「8 番 出口 作り方 unity」っぽいものを考えるときに、ルールをかなりざっくり削りました。
自分なりに最低限必要そうだなと思ったのは、この3つだけです。

  • 同じ場所を何度もループしているように見えること
  • ループごとに“何か変”な異変が起こることがある
  • プレイヤーが「進む/戻る」を選ぶだけでゲームが進むこと

ここまで削ると、いわゆる「8 番 出口 ゲーム 作り方」というより、「ループ+ランダムイベントのシンプルなゲーム」のイメージに寄ってきます。
今回はそのくらいの軽さを目標にしました。

ゲームエンジンとしてはUE5でもいいと思うんですが、わたしは普段Unityを触っているので、そのままUnityを使っています。
「8 番 出口 ゲーム エンジン」で迷っている人は、すでに慣れている方を選ぶのがいちばん幸せかもです。

プロジェクト最小構成:シーン1枚+数個のスクリプト

構成を盛り始めるとキリがないので、「駅or地下通路の1フロアだけ作る」という前提でプロジェクトを組みました。
シーンは基本1枚です。
タイトルやリザルトはあってもなくてもOK。

フォルダ構成もかなり雑にこんな感じにしています。

Assets/
Scenes/
LoopScene.unity
Scripts/
LoopController.cs // ループ処理
AnomalyManager.cs // 異変のON/OFF
JudgeController.cs // 進む/戻るの判定
Prefabs/
Chunk.prefab // 廊下のブロック
Player.prefab

「8 番 出口 制作 ツール」を調べると、DCCツールやらUIツールやらいろいろ出てきますが、今回はBlenderもFigmaも一旦忘れて、Unity Editor内で完結させました。
モデルはCubeを伸ばしただけとかでも、とりあえず雰囲気は確認できます。

Zenn向けなので、「きれいなアートを目指す」というより「ループと判定だけ動く最小コード」の方に全振りしています。
あとから真面目に作る場合も、このミニマム版を先に通しておくと、デバッグがだいぶ楽になりました。

ループ部分:3つの廊下ブロックをぐるぐる回すだけ

「8 番 出口 ループ」を再現しようとすると、「無限に続く廊下どうする?」が最初の壁になりがちです。
個人的には、Prefabをランタイム生成するより「最初から3枚並べておいて位置だけ動かす」が一番シンプルでした。

やっていることはよくあるエンドレスランナーとほぼ同じで、プレイヤーが十分進んだら一番後ろのブロックを先頭に持ってくるだけです。
コードはこんな感じにしています。

public class LoopController : MonoBehaviour
{
public Transform player;
public Transform[] chunks; // 3〜4個くらい
public float chunkLength = 20f;

void Update()
{
    foreach (var c in chunks)
    {
        // プレイヤーから見て一定距離後ろに行ったら前に回す
        if (player.position.z - c.position.z > chunkLength)
        {
            c.position += Vector3.forward * chunkLength * chunks.Length;
        }
    }
}

}

これで「進んでも進んでも同じ廊下が続いている」感じはとりあえず出ます。
ガチで作るならライトのベイクとかNavMeshとかも考える必要がありますが、最小構成だと一旦全部リアルタイムライト+簡単なColliderだけに割り切ってしまっても動きます。

最初はPrefabをInstantiateしていたんですが、ライトベイク周りがちょっと面倒だったので、今のところは「すべてシーンに直置き」方式に落ち着いてます。
もしちゃんとベイクしたいなら、動かすのはブロックじゃなくてプレイヤー側をワープさせるスタイルの方が綺麗かもです。

異変のON/OFFをランダムで切り替える

次は「異変どうする?」の話です。
ここもあまり作り込みすぎると大変なので、最初は「単にオブジェクトのON/OFFをランダムに切り替える」だけにしました。
例えば“変なポスター”と“普通のポスター”を差し替えるくらいのイメージです。

Unity側では、異変ごとにGameObjectを1つ用意しておいて、SetActive(true/false)を叩くだけにしています。
雰囲気だけ見るなら、このくらいの雑さでも十分ゲームっぽくなります。

public class AnomalyManager : MonoBehaviour
{
[System.Serializable]
public class Anomaly
{
public string id;
public GameObject obj;
}

public Anomaly[] anomalies;
public float probability = 0.5f;

Anomaly current;

public void Roll()
{
    // いったん全部OFF
    foreach (var a in anomalies) a.obj.SetActive(false);
    current = null;

    // そもそも異変なしパターン
    if (Random.value > probability) return;

    // ランダムで1つだけON
    var idx = Random.Range(0, anomalies.Length);
    current = anomalies[idx];
    current.obj.SetActive(true);
}

public bool HasAnomaly()
{
    return current != null;
}

}

ループごとにRoll()を呼ぶと、「今回は異変あり/なし」と「どの異変が出るか」が決まります。
8 番 出口 個人 開発系の記事を見ていると、もっと細かく確率を調整している例もありますが、個人的には0.3〜0.5くらいのざっくり確率でも「たまに起きる」感じは出ました。

慣れてきたら、「異変」と「ただの変化(人数が増えたとか)」を分けて、別の乱数で動かすとゲーム性が増します。
最初からそこまでやると自分が疲れるので、このあたりは後から足していくのが良さそうです。

「進む」「戻る」の判定ロジックを最低限だけ書く

ルール部分はかなりシンプルにしました。
ざっくり書くと、

  • 異変がある状態で「戻る」 → 正解(ゴールに一歩近づく)
  • 異変がある状態で「進む」 → ミス(最初に戻る)
  • 異変がない状態で「進む」 → 正解
  • 異変がない状態で「戻る」 → ミス

これをC#に落とすと、こんな感じの最低限コードになりました。

public class JudgeController : MonoBehaviour
{
public AnomalyManager anomaly;
public int currentLoop = 1;
public int goalLoop = 8;

public void OnPressForward()
{
    var has = anomaly.HasAnomaly();
    if (has) ResetLoop();
    else NextLoop();
}

public void OnPressBack()
{
    var has = anomaly.HasAnomaly();
    if (has) NextLoop();
    else ResetLoop();
}

void NextLoop()
{
    currentLoop++;
    if (currentLoop > goalLoop)
    {
        // クリア処理
        Debug.Log("Clear!");
        // シーン遷移など
    }
    else
    {
        // 次のループへ
        anomaly.Roll();
        // プレイヤー位置リセットなど
    }
}

void ResetLoop()
{
    currentLoop = 1;
    anomaly.Roll();
    // スタート位置に戻す
    Debug.Log("Reset");
}

}

UIはボタンでもキー入力でも何でもいいんですが、わたしは一旦「キーボードのWで進む/Sで戻る」を仮で割り当てて、のちほどボタンに差し替えました。
Zennの記事としてはキー入力の方が早く試せるので、こっちの方が向いているかもしれません。

ループ数は8にしていますが、ここはお好みで。
短くしたければ3〜4でもいいですし、長くして「だんだん異変の確率が上がる」みたいな調整もアリだと思います。
ロジック的にはgoalLoopを変えるだけなので、あとから決めてしまってもそんなに困りませんでした。

簡単なUIと演出を足して“それっぽさ”を出す

ここまでで、見た目は雑でも「ループ+異変+判定」は一応動きます。
とはいえ真っ暗な廊下にキューブだけだとテンションが上がらないので、最低限のUIと演出だけ軽く足しておきました。

やったことはこのくらいです。

  • Canvas上に「出口番号」っぽいTextを1つ置く
  • ループごとにcurrentLoopを表示して“出口に近づいている感”を出す
  • Directional Lightの色を少しだけ緑寄りにして、地下っぽさを足す

UIの更新は本当に1行で済むので、Judge側から投げてしまっています。

public TMPro.TextMeshProUGUI loopLabel;

void UpdateLoopUI()
{
loopLabel.text = $"出口 {currentLoop}";
}

「8番出口 着想」をそのまま真似るのも良いんですが、個人的には“舞台の設定”だけ少しオリジナル寄りにしておくと、モチベーションが保ちやすかったです。
自分の場合は「使われていない地下商店街」みたいなテーマにして、看板やポスターを異変にしています。

音回りはまだ何もやっていなくて、環境音だけ後で足そうかな、くらいの気持ちです。
8番出口 UE5系の動画を見るとサウンドの重要度が高そうなんですが、まずはゲームルール部分の検証を優先しました。

作ってみての反省と、次やるなら足したいところ

ここまでが「Zennに投げるにはギリ動いている最小構成」くらいのラインです。
実際に触ってみて思った反省点もいくつか出てきました。

ひとつは、「異変のパターンが少ないとすぐ飽きる」という当たり前の話です。
コード側はシンプルに済ませたので、今度時間が取れたら“異変だけを増やす回”みたいな作業をしようかなと思っています。
エンジン側はもう触らず、アセットとパラメータだけ足していく感じですね。

もうひとつは、「プレイヤーの行動に応じて確率が変わる仕組み」は今回は入れていないので、本家のような“ジワジワやばくなる感じ”はそこまで出ていません。
状態を1つ持たせて、ミスが続いたら異変が出やすくなる…みたいな調整も、そのうち試してみたいところです。

とはいえ、「8番ライク 作り方」で検索して最初に触るサンプルとしては、このくらいの軽さでも十分かなという感触がありました。
ループ処理とランダムイベントの組み合わせは、別ジャンルのゲームでも割と使い回せそうなので、個人的には良い練習題材だったなと。

もっと腰を据えて作り込みたい場合は、Unity以外の制作ツールや本家の作り込みも研究した方が良さそうですが、まずはこのミニマム版を手元で動かしてみてから、次の一歩を考えるのがおすすめかもしれません。

ゲーム制作を体系的に学びたいタイミングが来たら、わたしは動画講座を併用することが多くて、最近だとUnity入門の森の【Unity6対応】Unity ホラー風3D脱出ゲーム講座【全10回】 【雰囲気あふれる8番ライクゲーを作ろう】あたりを眺めつつ「次はどのジャンルで遊ぼうかな」と考えることが多いです。

Discussion