見出し画像

【Unity】遺跡に光と霧を灯す(暗所ライティング・ランタン・コルーチン・フォグ・環境音で作る探索の空気感)


第1章「はじめに」

主旨

Unity入門書は読み終えたけれど、その先の技術書がない!
そんな方のための「初級から中級への架け橋」を目指す連載記事です。
3Dアクションゲームを開発して行きます🎮

  1. ✅ [公開済み] 第1弾:『【Unity】人型モデルを動かす(Blend Tree・CharacterController・Input Systemで作るTPS移動の基礎)』

  2. ✅ [公開済み] 第2弾:『【Unity】バージョンアップ完全ガイド(移行手順・エラー対応・容量節約まで)』

  3. ✅ [公開済み] 第3弾:『【Unity】ProBuilderで遺跡をブロックアウトする(中空トンネル・T字路・広間をメニュー操作だけで作る)』

  4. 🆕 【今回の記事】第4弾:『【Unity】遺跡に光と霧を灯す(暗所ライティング・ランタン・コルーチン・フォグ・環境音で作る探索の空気感)』

  5. 📅 [執筆予定] 第5弾:『一人称と『調べる』 — 自作Input ActionsとRaycastインタラクション』

  6. 📅 [執筆予定] 第6弾:『文字は世界の一部 — TMP日本語化・多言語対応・記帳』

  7. 📅 [執筆予定] 第7弾:『データで仕掛けを量産する — ScriptableObjectカタログと抽選機(心臓部) 』

  8. 📅 [執筆予定] 第8弾:『迷いの回廊 — ループ・テレポート・導き』

  9. 📅 [執筆予定] 第9弾:『番人ゴーレム — 巡回AIと、隠れて逃げるアクション』

  10. 📅 [執筆予定] 第10弾:『ゲームの骨格 — シーン・セーブ・音』

  11. 📅 [執筆予定] 第11弾:『演出と結末 — コルーチン演出・推理ボード・マルチエンディング』

  12. 📅 [執筆予定] 第12弾:『仕上げと公開 — 調整・ビルド・itch.io公開』

なお、筆者は入門書を読み終えたばかりの初心者です。
自身の備忘録も兼ね、調べながら書いておりますため、何卒、あたたかい目で見守っていただければと思います🐣

みなさんこんにちは。連載「初級から中級への架け橋」シリーズ、今回で第4弾です。

前回の第3弾では、ProBuilderを使って「中を歩ける通路・T字路・広間」という遺跡の骨組みを組み上げました。ところが、いざ中を歩いてみると壁と天井で囲ったはずなのに、なぜか昼間のように明るいのです。

石造りの遺跡なのに、これでは探索のドキドキ感がまるでありません ……。

今回はこの「明るすぎる遺跡」に手を入れます。環境光を落として暗闇を作り、プレイヤーにはランタンを持たせ、空間には霧を漂わせる。さらにポストプロセスで色味を整え、リフレクションプローブで映り込みを足し、環境音をループさせて …… 目と耳の両方から『探索の空気感』を作り込んでいきます。

そして、本記事のもうひとつの主役が『コルーチン』です。「霧がゆっくり満ちていく」「光がじわっと灯る」といった、数秒かけたなめらかな変化を書くためのUnity必修テクニックで、本連載では今後、演出のほとんどをこのコルーチンで組み立てていきます。今回はその入門に、まるまる1章を割きました。ここを押さえておくと、以降の弾がぐっと楽になります。

読み終える頃には、暗い遺跡をランタンの明かりを頼りに歩き、Fキーひとつで霧がなめらかに満ち引きする。そんな空間が手元に完成しています🏛️

👩‍💻 こんな方に向けた内容です
・第3弾までを読んで、遺跡のブロックアウトまで作れた方
・「暗い空間+手持ちの明かり」という定番のダンジョン演出を作ってみたい方
・コルーチンを基礎からしっかり押さえたい方
・フォグやポストプロセスで画面の雰囲気を一段上げたい方

🛠️ 動作検証環境
・macOS Tahoe 26.5.1
・Unity 6.5(6000.5.5f1)+ URP(Universal Render Pipeline)
・URPパッケージ:17.5.0(Unity 6.5にバンドルされている版)
・VRoid Studio製アバター(VRM0.x、UniVRM v0.131.2、Package Manager経由)
・Starter Assets - ThirdPerson | URP 1.1.7(アニメーションのみ流用)
・Cinemachine 3.1.7
・ProBuilder 6.1.2(第3弾で導入済み)
※ Unityエディタは日本語化して使用(メニュー名は日本語表記)


本記事の構成

本記事は「暗くする → 灯す → 仕組みを学ぶ → 空気を足す」という流れの、9章構成になっています。第5章だけは手を動かさない「読む章」ですが、第6章の実践のための布石なので、飛ばさず順番に進めるのがおすすめです。

地下遺跡の不気味な空気感と、探索のシステムを順を追って作っていきます。
  • 第1章「はじめに」・第2章「天井を付けたのに明るいままなのはなぜか?」
    シリーズの主旨と、手を動かす前の予備知識。壁で光を遮るのではなく『環境光そのものを暗くし、必要な場所だけを照らす』という今回の方針をここで押さえます。

  • 第3章「暗所ライティング」
    Directional Lightと環境光を絞って遺跡を暗くし、ポイントライトで陰影を足します。「ライトを置いたのに2つまでしか光らない」というURPの上限設定のハマりどころもここで扱います。

  • 第4章「ランタン実装」
    プレイヤーに"持ち歩ける光"を持たせます。Spot Light+Emissionの2点セット、Input Systemへの新アクション(Lキー)追加、点灯・消灯スクリプトまで。

  • 第5章「コルーチン入門」
    本記事の、そして本連載の最重要章です。決まり文句・yieldの使い分け・Mathf.Lerp・多重起動防止の定石を、じっくり解説します。

  • 第6章「フォグ(霧)」
    前章の知識の実践編。組み込みフォグの設定と、霧の濃さをなめらかに変化させるFogController(シングルトン+コルーチン)を作ります。「ビルドすると霧が消える」罠への対策も。

  • 第7章「ポストプロセス」・第8章「リフレクションプローブ」
    画面の最終仕上げです。VignetteとColor Adjustmentsで色味を整え、広間に「映り込み」を足して質感を一段深くします。

  • 第9章「環境音ループ」
    音素材の入手先とライセンスの注意点(AI生成素材の扱い含む)、環境音のループ再生設定。目と耳の両方で「遺跡の空気」を完成させます。

上記、9つの章の構成となってます。


第2章「天井を付けたのに明るいままなのはなぜか?」

第3弾で作った遺跡は壁や天井で囲まれているため、各パーツ(中空トンネル・T字路・広間)を組み合わせれば、中は暗くなるはずです。しかし、Unityでそのまま再生すると「屋内なのに妙に明るい」状態になってしまいます。

これは、Unityのリアルタイム環境光が「光の遮蔽」を考慮せず、空間全体を均等に照らしてしまうからです。 物理的に正しく光を遮るには、ベイクGI(事前計算)やAPVといった仕組みが必要ですが、初〜中級のステップとしては少し重すぎます。

そこで今回は発想を変えます。
「壁で光を遮る」のではなく『環境光そのものを暗くし、必要な場所だけをライトで照らす』というゲーム定番のアプローチを取ります。これなら処理も軽く、「ランタンで探索する」という今回のテーマにもぴったりです。


■ 補足知識(今後ステップアップするための技術)

物理的に正しく光を計算したい場合は、以下の仕組み(中級者向け)を使います。

📝 ベイクGI(事前計算)
「光と影の模様を、あらかじめ壁に直接ペイント」しておく処理。リアルタイムに光の計算をしないため、スマホでも動くほど処理が非常に軽いのが特徴です。その代わり、あらかじめ描き込んでいるため「太陽が動く」「ランタンを持って歩く」といった後からの光の変化には対応できません。

📝 APV(Adaptive Probe Volumes)
「空間の明るさを記録する『見えないセンサー』を、自動でばら撒く」処理。ベイクGIだけだと浮いてしまう「動くキャラクター」を、空間の光に自然に馴染ませるための最新技術です。従来は手作業で置いていたセンサーを、Unityが賢く自動で配置してくれるため、クオリティの高い空間照明が作れます。


第3章「暗所ライティング」

Directional Light を絞ってみる

シーンに最初からある Directional Light(太陽光)を選び、インスペクターで Intensity(強度) を下げてみましょう。たとえば 1 → 0.05 くらいに。

ゲームビューに切り替え確認。思ったほど暗くなりません(シーンビューと比べ)。


環境光を絞る

これは太陽を消しても、空(スカイボックス)由来の環境光が空間全体を照らし続けてからです。ここを絞ります。

  1. メニューから ウィンドウ → レンダリング → ライティング(Window > Rendering > Lighting)を開きます。

  2. Environment(環境)タブを選びます。

  3. Environment Lighting セクションの Source(ソース) を、既定の Skybox から Color に変更します。

  4. すぐ下に出てくる Ambient Color を、暗いこげ茶〜黒に近い色に設定します(真っ黒 #000000 にすると陰が潰れて不自然なので、#111015 くらいの“ほぼ黒”がおすすめ)。

ゲーム開始「▶」で確認。広場の中に入ると、いい感じに暗くなっています。


ポイントライトで「陰影」を足す

暗いだけでは何も見えないので、壁掛けの松明のような Point Light(ポイントライト) を置いて、局所的に照らします。

  1. ヒエラルキーで右クリック →(またはメニューの)ゲームオブジェクト > ライト > ポイントライト。

  2. 名前を TorchLight_01 にします。

  3. TreasureHall(広場) の壁際、床から少し高い位置(例:Y=3 付近)に配置。

  4. インスペクターで Color を暖色(オレンジ寄り)に、Intensity を 2〜5、Range を部屋の広さに合わせて 8〜12 くらいに。

暖色のポイントライトを置くだけで、暗い石壁に光と影のコントラストが生まれ、ぐっと遺跡らしくなります。


1つのオブジェクト内で、ライトは複数設置できる?

先ほど名前を「TorchLight_01 」としたのは、もし複数置きたい場合のことを考慮してです(TorchLight_01、02、03 ……)。

ただ、筆者の環境では、1つのオブジェクト(例:TreasureHall)内で、複数おいても実質光るのは2つまででした(デフォルト設定のままでは)

URPのライト数設定は「グラフィックス」「品質」の2箇所あります。

■ 設定箇所1「グラフィックス」

メニュー:編集 → プロジェクト設定 → グラフィックス。
「Default Render Pipeline」のインスペクターを開き、追加ライト
の『オブジェクト制限ごと』。

■ 設定箇所2「品質」

メニュー:編集 → プロジェクト設定 → 品質。
「レンダーパイプラインアセット」のインスペクターを開き、追加ライト
の『オブジェクト制限ごと』。

📝 なぜデフォルトでは2つまでなのか?
これは負荷対策です。ポイントライトなどの「追加ライト」は、1つのオブジェクトを照らすたびに描画コストがかかります。特に品質側のURPDefaultResourcesは、モバイル端末を含むあらゆる環境で無難に動くことを優先した、控えめな初期値になっています。「ライトを増やすほど重くなる」ことを踏まえて、まずは低めの上限からスタートし、必要に応じて開発者が引き上げる——という設計思想です。


第4章「ランタン実装」

光源と見た目を作る

暗くできたので、プレイヤーに“持ち歩ける光”=ランタンを持たせます。構成は「照らすための Spot Light」+「光って見えるガラス玉(Emission)」の2点セットです。

■ 光源

ヒエラルキーでアバターを展開し、その子オブジェクトとして作成します。
(アバターを右クリック)ライト > スポットライト を作成。名前を LanternLight に。

手元〜前方を照らすよう、位置は腰〜胸のあたり、少し前傾に。色 は暖色、強さ 3 前後、範囲 6〜10、外円錐角 60〜90 くらいから調整

・内側の円錐角(Inner Cone Angle):光が最大強度で当たる範囲。
・外側の円錐角(Outer Cone Angle):これが一般に言う「Spot Angle」。ここまでが光の届く範囲全体で、内側から外側にかけて徐々に減光していく。

■ 「光っているガラス玉」の見た目

同じように(アバターを右クリック)3D オブジェクト > スフィア を作成。名前を LanternGlow に。スケール を 0.1 にして小さな玉に。
また、スフィアについている Sphere Collider は不要なので削除(右クリック > コンポーネントを削除)。


光るマテリアル(Emission)を作る

Assets 内で右クリック > 作成 > マテリアル。名前を LanternGlowMat などに。Shader が Universal Render Pipeline/Lit になっていることを確認。

インスペクターで放出(Emission) をオンにします。
放出マップ の「色」をクリックし、暖色(オレンジ寄り)を選択。カラーピッカー右下の 強さ(HDR 強度) を 1〜2.5 くらい上げると、あとで足す Bloom(グロー)に反応してふわっと光ります。

このマテリアルを LanternGlow(スフィア)にドラッグして適用。


入力(L キー)を追加する

第1弾で使ったプロジェクト全体のアクション(InputSystem_Actions)に、ランタン点灯用のアクションを足します。

  1. 「編集」→「プロジェクト設定」→「Input System Package 」。

  2. Playerを選択、中央のActionsの「+」をクリック、名前をLanternとする。追加されたLanternアクションを選ぶと、右側の「Action Properties」にAction Typeの設定が出てきます。ここをButtonに変更してください。

  1. Lanternの左にある「▶」(三角のアイコン)をクリックして展開してください。

  2. 展開すると、その下に「No Binding」または既存のバインディング行が現れます。それをクリックすると、Binding Properties」のBinding欄にPathという項目が出てきます。

  3. 「By Character Mapped to Key」を選択。

By Character Mapped to Key」を選択すると、適用キーの選択欄が表示されるので『 L 』に設定します。


📝 By Character Mapped to Key

By Character Mapped to Keyは両方の環境で安全で、By Locationの方が地域による差が出やすい、という関係です。


ランタンの点灯・消灯を管理するスクリプト

Lキーが押されるたびに、LanternLight(照らす光)とLanternGlow(光って見える玉)の2つを同時に切り替える、ランタンの点灯・消灯を管理するスクリプトです。スクリプト名は「LanternController.cs」とします。

using UnityEngine;
using UnityEngine.InputSystem; // 新 Input System を使うために必要

// ランタンの点灯/消灯を管理するスクリプト。
// プレイヤー(またはランタン一式をまとめた親)にアタッチします。
public class LanternController : MonoBehaviour
{
    // [SerializeField] を付けると、private のままでもインスペクターから
    // オブジェクトをドラッグ&ドロップで割り当てられるようになります。
    [SerializeField] private Light lanternLight;      // LanternLight(Spot Light)を割り当てる
    [SerializeField] private Renderer glowRenderer;   // LanternGlow(Sphere)の Renderer を割り当てる

    private InputAction lanternAction;                // L キーのアクション(Awakeで取得する)
    private Material glowMaterial;                    // 実行時に複製される、このオブジェクト専用のマテリアル
    private bool isOn = true;                          // 現在の点灯状態(true=点灯/false=消灯)

    void Awake()
    {
        // プロジェクト全体アクション(InputSystem_Actions)の中から、
        // 名前が "Lantern" のアクションを探して変数に入れておく。
        // ここで見つからない(null になる)ときは、4-3で Save Asset を
        // 押し忘れているか、アクション名のスペルが違う可能性が高い。
        lanternAction = InputSystem.actions.FindAction("Lantern");

        // renderer.material は「このオブジェクト専用にコピーされた
        // マテリアル」を返してくれる仕組みです。
        // これを使わずに sharedMaterial を直接書き換えると、
        // 同じマテリアルを使っている他のオブジェクトまで
        // 一緒に光ってしまうので注意(前段で触れた「共有マテリアル事故」)。
        if (glowRenderer != null)
        {
            glowMaterial = glowRenderer.material;
        }
    }

    void OnEnable()
    {
        // Input System のアクションは、作っただけでは入力を受け取りません。
        // 明示的に Enable() を呼んで「監視を開始する」必要があります。
        // ?. は「lanternActionがnullでなければ実行する」という安全な書き方です。
        lanternAction?.Enable();
    }

    void Start()
    {
        // ゲーム開始時点の状態(isOn = true)を、実際の見た目・光に反映しておく。
        // これをやらないと、変数上は「点灯」なのに画面は消灯のまま、というズレが起きる。
        ApplyState(isOn);
    }

    void Update()
    {
        // WasPressedThisFrame() は「このフレームでちょうど押された瞬間」だけ true になるメソッド。
        // 押しっぱなしの間ずっと true になる IsPressed() とは違うので、
        // 連打しなくても1回押すごとに1回だけ切り替わる(トグル動作)。
        if (lanternAction != null && lanternAction.WasPressedThisFrame())
        {
            isOn = !isOn;      // 現在の状態を反転させる(true⇄false)
            ApplyState(isOn);  // 反転させた新しい状態を、実際の光と見た目に適用する
        }
    }

    // 点灯状態(on)を受け取り、Spot Light と Emission の両方に反映させるメソッド。
    // 「状態を反映する処理」を1か所にまとめておくことで、
    // Start() からも Update() からも同じロジックを使い回せる。
    private void ApplyState(bool on)
    {
        // 1) 実際に空間を照らす Spot Light 本体をオン/オフする
        if (lanternLight != null)
        {
            lanternLight.enabled = on;
        }

        // 2) 光って見えるガラス玉(Emission)をオン/オフする
        if (glowMaterial != null)
        {
            if (on)
            {
                // Emissionの機能自体を有効化するキーワードをオンにする。
                // これがオフのままだと、色を設定しても発光として反映されない。
                glowMaterial.EnableKeyword("_EMISSION");
                // 実際に光る色を設定する。URP/Lit シェーダーでは
                // プロパティ名が "_EmissionColor"(HDRPは別名なので注意)。
                // Color.white * 1.5f は「白を1.5倍明るくした色」という意味。
                glowMaterial.SetColor("_EmissionColor", Color.white * 1.5f);
            }
            else
            {
                // 消灯時は、まず発光色を黒にして光を無くしてから、
                glowMaterial.SetColor("_EmissionColor", Color.black);
                // キーワード自体もオフにしておく(念のための二重の消灯)。
                glowMaterial.DisableKeyword("_EMISSION");
            }
        }
    }
}

※ Assets/Scripts内にLanternController.csを作成。

■ どこにアタッチするか
このスクリプトは、プレイヤー(アバターのルートオブジェクト)にアタッチします。LanternLightやLanternGlow自体には付けません。

📝「Lキーそのものに何かを貼り付ける」という概念はUnityにはなく、代わりに「プレイヤーが、毎フレームLキーの状態を自分で確認しにいく」という設計になっています。Update()の中でWasPressedThisFrame()を毎フレームチェックしているのが、まさにその部分です。

  1. ヒエラルキーでアバター(model_testのルート)を選択

  2. インスペクターの「コンポーネントを追加」からLanternControllerを検索してアタッチ

  3. アタッチすると、インスペクターにLantern LightとGlow Rendererという2つの空欄が現れる

  4. Lantern Light欄に、ヒエラルキーのLanternLightをドラッグ&ドロップ

  5. Glow Renderer欄に、同じくLanternGlowをドラッグ&ドロップ

ゲーム開始「▶」で確認。Lキーでランタンの点灯・消灯の切り替えができるようになっているはずです。

📝 割り当てを忘れると、lanternLightやglowRendererがnullのままになり、if (lanternLight != null)のチェックで処理がスキップされて「Lキーを押しても何も起きない」状態になります。動かないときは、まずこの2つの割り当てを疑ってください。


第5章「コルーチン入門」

コルーチン

「霧がゆっくり満ちる」「ランタンがふっと灯る」など 、こうした数フレームにまたがる連続的な変化を1つのまとまった処理として直感的に記述できる機能がコルーチンです。

📝 この章は「読んで理解する」章です
 
新しいスクリプトを作ってオブジェクトにアタッチする、という作業は今回ありません。次の章「フォグ(霧)」で実際にFogController.csというコルーチンを使ったスクリプトを書きますが、その事前解説 / 予習として、コルーチンの仕組みだけを先にじっくり理解しておく回です。ここで基礎を掴んでおくと、第6章のコードがすんなり読めるようになります。


他言語のコルーチンとの違い

「処理を途中で待てる」点は同じですが、他言語(KotlinやPythonなど)のコルーチン(非同期処理)とは仕組みが大きく異なります。

Unity のコルーチンは非同期(マルチスレッド)ではありません。

メインスレッド上で「中断ポイントを持てるだけの関数」であって、yield return で中断している間に他のフレーム処理が進むだけです。したがって、コルーチンの中で重い計算を一気にやると、普通にゲームがカクつきます(固まります)。
※ 他言語のように「コルーチン=非同期 / 並行処理」ではない点に要注意。


普通の関数との違い

普通の関数は、呼ばれたら最後まで一気に実行して終わります。途中で「ここで1秒待つ」はできません(while で待つとゲームごと固まります)。

コルーチンは違います。処理を途中で中断して、次のフレームや指定秒後に、続きから再開できます。だから「毎フレーム少しずつ濃くする」を素直に書けるのです。


3つの決まり文句

Unityのコルーチンには決まった型があります。

  1. 戻り値の型を IEnumerator にする

  2. 中断したい場所に yield return を書く

  3. 呼び出すときは普通に呼ばず、StartCoroutine(...) で起動する

以下は、あるオブジェクトの透明度を1秒かけて少しずつ薄くしていく例です(このコードは動作イメージをつかむためのサンプルです。実際に作成 /  アタッチする必要はありません)。

using System.Collections; // IEnumerator を使うために必要な名前空間
using UnityEngine;

public class FadeExample : MonoBehaviour
{
    // コルーチンは戻り値の型を IEnumerator にする、という決まりがあります。
    // 通常の void 関数とはここが違います。
    IEnumerator Fade()
    {
        // このオブジェクトの Renderer(見た目を描画するコンポーネント)を取得
        Renderer r = GetComponent<Renderer>();
        // 現在のマテリアルの色を取得しておく(alpha=透明度を後で書き換える)
        Color c = r.material.color;

        // alpha(透明度)を 1(不透明)から 0(透明)まで、
        // 0.1ずつ減らしながらループする
        for (float alpha = 1f; alpha >= 0; alpha -= 0.1f)
        {
            c.a = alpha;             // 今の周回でのalpha値を色にセット
            r.material.color = c;    // 実際にマテリアルへ反映(この瞬間、画面上の色が変わる)
            yield return null;       // ★ここが中断ポイント。
                                      // 処理はここでいったん止まり、
                                      // 「次のフレームになったら、この続きから再開してね」
                                      // とUnityに予約して、いったん関数を抜ける。
        }
        // ループが終わる(alphaが0を下回る)と、ここに到達してコルーチンは終了する
    }
}

yield return null と WaitForSeconds

中断のしかたには種類があり、代表的なのが次の2つです。

  • yield return null
    「次のフレーム」で再開します。フレームごとに少しずつ動かしたいときはこれ。

  • yield return new WaitForSeconds(0.1f)
    指定した秒数だけ待ってから再開します。フレームレートに左右されず「0.1秒おき」を作れます。

違いを並べて比べてみましょう。

using System.Collections;
using UnityEngine;

public class YieldExample : MonoBehaviour
{
    // 【パターン1】毎フレーム実行される
    IEnumerator EveryFrame()
    {
        for (int i = 0; i < 5; i++)
        {
            Debug.Log("フレーム実行:" + i);
            yield return null;  // 次のフレームまで待つ
        }
        // 60fpsなら、5回のループはおよそ0.08秒で終わる(あっという間)
    }

    // 【パターン2】1秒おきに実行される
    IEnumerator EverySecond()
    {
        for (int i = 0; i < 5; i++)
        {
            Debug.Log("1秒経過:" + i);
            yield return new WaitForSeconds(1f);  // 1秒待つ
        }
        // こちらは5回のループに約5秒かかる(ゆっくり)
    }
}

使い分けの目安は「なめらかな変化を見せたいなら yield return null」「一定間隔で何かを繰り返したいなら WaitForSeconds」です。

霧がじわっと濃くなる演出は前者、「3秒ごとに水滴の音を鳴らす」のような処理は後者が向いています。


Mathf.Lerp でなめらかに

「今の値から目標値へ、割合 t(0〜1)でなめらかに補間する」には Mathf.Lerp(a, b, t) を使います。

・t = 0 のとき → a(開始値)
・t = 1 のとき → b(目標値)
・t = 0.5 のとき → ちょうど真ん中の値

つまり t を 0 から 1 へ少しずつ増やしながら Lerp を呼び続けると、値が a から b へなめらかに変化していきます。これがフェードの正体です。

using System.Collections;
using UnityEngine;

public class LerpExample : MonoBehaviour
{
    // 数値を 0 から 10 へ、2秒かけてなめらかに変化させる例
    IEnumerator LerpValue()
    {
        float start = 0f;      // 開始値
        float target = 10f;    // 目標値
        float duration = 2f;   // かける秒数
        float time = 0f;       // 経過時間を数えるカウンター

        // 経過時間が指定秒数に達するまでループ
        while (time < duration)
        {
            // Time.deltaTime は「前フレームからの経過秒数」。
            // これを足し続けることで、実時間の経過を測れる。
            time += Time.deltaTime;

            // t は 0 → 1 へ進む「進捗率」。
            // 経過時間 ÷ 全体の時間 で計算できる。
            float t = time / duration;

            // start から target へ、進捗率 t の分だけ進んだ値を求める
            float value = Mathf.Lerp(start, target, t);
            Debug.Log("現在の値:" + value);

            yield return null;  // 次のフレームまで待つ
        }

        // ループを抜けた後、誤差をなくすため最後にきっちり目標値を入れておく
        // (time が duration をわずかに超えて終わるため、
        //   t が 1.0 ちょうどにならないことがある)
        Debug.Log("最終値:" + target);
    }
}

「timeを足していき、tを求め、Mathf.Lerpで補間し、yield return nullで次フレームへ」は、次の章「フォグ(霧)」で書くFogController.csとまったく同じ骨格です。ここを理解しておけば、霧のフェードのコードもすんなり読めます。


多重起動を防ぐ定石

L キーや F キーを連打すると、同じコルーチンが何本も同時に走って値が暴れます。これを防ぐ定石が「走っているコルーチンの参照を持っておき、新しく始める前に止める」です。

// StartCoroutine() の戻り値は Coroutine 型。これを変数に保存しておくと、
// 後から「このコルーチンを止めたい」というときにピンポイントで止められる。
private Coroutine fadeRoutine; // 走行中のコルーチンを覚えておく変数

void StartFade()
{
    // fadeRoutineがnullでない=すでに何かが走っている、ということなので、
    // 新しく始める前に、まず今のものを止める(これが多重起動防止のキモ)
    // ○ 正しい止め方
    if (fadeRoutine != null) StopCoroutine(fadeRoutine);

    // 新しいコルーチンを開始し、その参照を変数に控えておく。
    // 次にStartFade()が呼ばれたときは、この参照を使って止められる。
    fadeRoutine = StartCoroutine(Fade());
}

よくある誤解として「スクリプトを enabled = false にすればコルーチンも止まる」がありますは、実際は止まりません。

// × やりがちなNG(Updateは止まるが、コルーチンは裏で動き続ける)
GetComponent<FogController>().enabled = false;

インスペクターでコンポーネント名の横のチェックボックスを外す操作も、これと同じことをしています。Update()は確かに止まるので「全部止まった」と錯覚しやすいのですが、コルーチンだけは何事もなかったかのように動き続けます。 気づかないまま放置すると、すでに破棄したオブジェクトを参照し続けて NullReferenceException を延々と吐き出す、といった厄介な事態にもなります。

コルーチンが自動で止まるのは「コンポーネントが破棄(Destroy)されたとき」か「アタッチ先の GameObject 自体が非アクティブ(SetActive(false))になったとき」だけです。コンポーネントを付けたまま処理だけを止めたいなら、必ず明示的に止めてください。

// ○ 正しい止め方
if (fadeRoutine != null) StopCoroutine(fadeRoutine);

※ すべてのコルーチンをまとめて止めたい場合は StopAllCoroutines() も使えます。


第6章「フォグ(霧)」

フォグ(霧)の役割と基本設定

なぜ「フォグ(霧)」を使うのか?

フォグは単なる「天候」の表現だけでなく、3Dゲームにおいて「空気感(深み)」と「見せたくないもののカバー」の両方を担う非常に重要なテクニックです。

📝 URP でも RenderSettings.fog は有効
「URP だと標準の組み込みフォグは使えない」という古い情報を見かけることがありますが、Unity 6.5 の URP においても、シーン全体にかかる組み込みフォグ(RenderSettings.fog)は問題なく機能します。

■ フォグモードの種類(3種類)
設定できるフォグの減衰モードは以下の3つです。

横軸:距離 / 縦軸:霧の濃さ

今回は遺跡らしい自然な霞みが欲しいので Exponential を使用します。Density(密度)は 0.01〜0.1 の範囲で調整してみてください(目安として 0.05 あたりからスタートすると分かりやすいです)。


エディタでの設定手順

  1. ウィンドウ > レンダリング > ライティング を開く。

  2. 「環境」タブを選ぶ。

  3. 一番下の その他の設定 セクションを開く。← ここにフォグがあります。

  4. Fog チェックボックスをオン。

  5. 色(霧の色。石壁になじむ暗いグレー〜青灰色が探索向き)、モード を Exponential、密度 を 0.05に。


■ シーンビューでフォグを表示する

シーンビューにフォグが出ない / 逆に出すぎて作業しづらいときは、上部のツールバーにある View Options(表示オプション)オーバーレイの中の Fog トグルで切り替えられます。

📝 あくまでシーンビューの見え方だけを変えるもので、ゲーム本編(Game ビュー/ビルド)には影響しません。


■ ゲーム開始しても「霧が出ていない」と感じたら
シーンビューでは霧が出ているけど、ゲームシーンやゲーム開始時に「霧が出ていない」と感じる場合があります。

これは霧の色が背景(スカイボックス)と同系色だからです。今の霧は青灰色で、空も青系。だから「霧がかかった」というより「もともとこういう色の空間」に見えてしまいます。

Density(密度) を一時的に 0.3 くらいまで上げてみてください。画面が霧の色で覆われれば、正しく効いています(確認後は 0.05 に戻してください)。Density 0.05 は控えめな値なので、遠くを見ないと変化が分かりにくいのが普通です。また、霧の色を背景と似た色にしていると効果に気づきにくいので、テスト中だけ白や水色など目立つ色にするのも有効です。


霧の濃さをなめらかに変化させるスクリプト(FogController.cs)

霧の濃さを、コルーチンを使って時間をかけてなめらかに変化させるスクリプト『FogController.cs(シングルトン+コルーチンでフェード)』を作成しましょう。

💡 前章で学んだ「timeを足す → tを求める → Mathf.Lerpで補間 → yield return null」という骨格が、そのまま使われています。

まず、このスクリプトを載せる入れ物を用意します。

ヒエラルキーで右クリック →「空のオブジェクトを作成」。名前を FogController に変更。

📝  位置(Transform)はどこでも構いません(霧はシーン全体にかかるため、座標は無関係です)

using System.Collections;
using UnityEngine;

// フォグの密度をコルーチンでなめらかに変化させる管理役。
// 空の GameObject(名前: FogController)にアタッチして使う。
public class FogController : MonoBehaviour
{
    // どこからでも呼べるようにするシングルトン参照
    public static FogController Instance { get; private set; }

    [SerializeField] private float minDensity = 0.005f; // 霧が引いたときの薄さ
    [SerializeField] private float maxDensity = 0.06f;  // 霧が満ちたときの濃さ
    [SerializeField] private float fadeDuration = 2.0f; // フェードにかける秒数

    private Coroutine fadeRoutine; // 走行中のフェードを覚えておく(多重起動防止)
    private bool isThick = false;  // 現在「濃い側」かどうか

    void Awake()
    {
        // シングルトンの基本形:既にいたら自分を消す
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
            return;
        }
        Instance = this;

        // 起動時は組み込みフォグを有効化し、薄い状態から始める
        RenderSettings.fog = true;
        RenderSettings.fogMode = FogMode.Exponential;
        RenderSettings.fogDensity = minDensity;
    }

    // 外部(FキーのトグルやトリガーなどからでもOK)から呼ぶ切り替え
    public void ToggleFog()
    {
        isThick = !isThick;
        float target = isThick ? maxDensity : minDensity;

        // 走っていたら止めてから、新しいフェードを開始(多重起動防止の定石)
        if (fadeRoutine != null) StopCoroutine(fadeRoutine);
        fadeRoutine = StartCoroutine(FadeDensity(target));
    }

    // 現在の密度から target まで fadeDuration 秒かけて補間するコルーチン
    private IEnumerator FadeDensity(float target)
    {
        float start = RenderSettings.fogDensity; // 開始時点の密度
        float time = 0f;

        while (time < fadeDuration)
        {
            time += Time.deltaTime;               // 経過時間を積算
            float t = time / fadeDuration;        // 0 → 1 の進捗
            // start から target へ、割合 t でなめらかに補間
            RenderSettings.fogDensity = Mathf.Lerp(start, target, t);
            yield return null;                    // 次フレームまで中断
        }

        // 誤差をなくすため最後にきっちり目標値へ
        RenderSettings.fogDensity = target;
        fadeRoutine = null;
    }
}

Assets/Scripts内にFogController.csを作成します。

■ アタッチと設定
作成したスクリプトを、先ほどのFogControllerオブジェクトにアタッチします。

ヒエラルキーでFogControllerオブジェクトを選択。インスペクターの「コンポーネントを追加」からFogController.csをアタッチ。

アタッチすると、インスペクターに3つの数値が現れます。[SerializeField]を付けておいたおかげで、コードを書き換えなくてもここで調整できるようになっています。

💡「霧の変化が速すぎる/遅すぎる」と感じたらFade Durationを、「濃さが物足りない」ならMax Densityを、それぞれインスペクターで変えてみてください。スクリプトを書き直す必要はありません。


■ 「シングルトン」とは何か

シングルトン 「シーンにただ1つだけ存在し、どこからでもアクセスできる」という仕組み

このスクリプトには、これまで出てこなかった書き方が入っています。

public static FogController Instance { get; private set; }

これはシングルトンと呼ばれる仕組みで、ざっくり言うと「このスクリプトは、シーンにただ1つだけ存在する。そしてどこからでも呼び出せる」という宣言です。

なぜこれが必要かというと、次の項で書くFogDemoToggle.csから、FogControllerの機能を呼び出したいからです。シングルトンにしておくと、

FogController.Instance.ToggleFog();

という書き方だけで、わざわざインスペクターで参照を割り当てなくても呼び出せます。LanternControllerのときは[SerializeField]でドラッグ&ドロップして繋ぎましたが、シングルトンならその手間が不要です。

■ 重複を防ぐ「保険」をかける

Awake()の中で「すでにInstanceが存在していたら自分をDestroyする」という処理を入れているのは、うっかりFogControllerを2つ作ってしまったときの保険です。2つあると「どちらが本物か」が分からなくなるので、後から現れた方を自動で消しています。

■ シングルトンの注意点

シングルトンを使う際の注意点 (乱用厳禁)

シングルトンは便利ですが、乱用すると「どこからでも書き換えられる変数」が増えて、後から不具合の原因を追いにくくなります。今回のように「シーンに1つだけあれば十分で、あちこちから呼びたいもの」に限って使うのが安全です。


霧をFキーで切り替するスクリプト(FogDemoToggle.cs)

Fキーが押されたらFogControllerに「霧を切り替えて」と伝えるだけの、動作確認用の小さなスクリプトです。霧を変化させる処理そのものはFogControllerが持っているので、こちらは入力を受け取る係に徹しています。

using UnityEngine;
using UnityEngine.InputSystem;

// F キーで FogController のフォグをトグルするデモ用スクリプト。
// FogController と同じ GameObject(FogController オブジェクト)にアタッチする。
public class FogDemoToggle : MonoBehaviour
{
    private InputAction fogAction; // F キーのアクション

    void Awake()
    {
        // ※ 事前に InputSystem_Actions に "Fog"(Button型 / F キー)を追加しておくこと。
        //    手順としては、先述の「ランタンの点灯・消灯を管理するスクリプト(LanternController.cs)」で "Lantern" を追加したときと同じです。
        fogAction = InputSystem.actions.FindAction("Fog");
    }

    void OnEnable()
    {
        // アクションを有効化しないと入力を受け取れない(4-4と同じ)
        fogAction?.Enable();
    }

    void Update()
    {
        // F キーが押された瞬間だけ反応する
        if (fogAction != null && fogAction.WasPressedThisFrame())
        {
            // シングルトンなので、参照を割り当てなくてもこの1行で呼び出せる。
            // ?. は「FogController.Instance が null でなければ実行する」という意味で、
            // FogControllerオブジェクトを置き忘れていてもエラーで止まらないための保険。
            FogController.Instance?.ToggleFog();
        }
    }
}

Assets/Scripts内にFogDemoToggle.csを作成します。

■ 入力(F キー)を追加する
Input ActionsにFogアクションを追加しましょう。
※ 流れとしては『ランタン実装:入力(L キー)を追加する』とほぼ同じです

  1. 「編集」→「プロジェクト設定」→「Input System Package 」。

  2. Playerを選択、中央のActionsの「+」をクリック、名前をFogとする。追加されたFogアクションを選ぶと、右側の「Action Properties」にAction Typeの設定が出てきます。ここをButtonに変更してください。

  1. Fogの左にある「▶」(三角のアイコン)をクリックして展開してください。

  2. 展開すると、その下に「No Binding」または既存のバインディング行が現れます。それをクリックすると、Binding Properties」のBinding欄にPathという項目が出てきます。

  3. 「By Character Mapped to Key」を選択。

By Character Mapped to Key」を選択すると、適用キーの選択欄が表示されるので『 F 』に設定します。


■ スクリプトをアタッチする
FogDemoToggle.csは、FogControllerオブジェクトにアタッチします。(先述のFogController.csと一緒にアタッチする形になる)

📝 なぜ同じオブジェクトに2つ載せるのか
FogDemoToggleは「入力を受け取ってFogControllerに伝える」だけの小さな役割なので、専用のオブジェクトを作るまでもありません。関連するもの同士をまとめておくと、ヒエラルキーが散らかりません。


■ 動作確認
ゲーム開始「▶」で確認。Fキーでフォグの切り替えができるようになっているはずです。

・1回押す → 霧がじわっと2秒かけて濃くなる
・もう1回押す → 霧が2秒かけて晴れていく

先述しましたが、もし「霧が出ていない」と感じたら値を調整してみましょう。

例えば、色を水色 / Density(密度) を一時的に 0.3 くらいまで上げてみると、霧がはっきりと見えるかと思います。


ビルドでフォグが消える問題(シェーダーストリッピング)

エディタでは霧が出るのに、ビルドすると消える。先ほどのように値を極端にしても、そうなる場合は『未使用のフォグ用シェーダーバリアントがビルド時に除去される』かも知れません。

メニュー:編集 → プロジェクト設定 → グラフィックス。
Fog Modes プルダウンの既定は Automaticです。Automatic だと「どのシーンでも使っていないフォグモード」は除去されます。

スクリプトから RenderSettings.fog を動的に切り替える今回のようなケースでは、Fog Modes を Custom にして、使うモード(今回は Exponential)を明示的にオンにしておくのが安全です。


⚠️ 【追記:2026年8月7日(重要なお知らせ)】

Unity 6.4〜6.5世代のInput System特有のバグ

ただいま第5弾を絶賛執筆中なのですが、Unity 6.4〜6.5世代のInput System特有の厄介なバグがあるようです!これから操作する方は、以下の点に注意してください。

⚠️「Project Settings」の画面からアクションを追加しても、ディスクに保存されないバグがあります。

本来、Project Settingsの画面は「自動保存されるのがUnityの慣例」であるため、意図的に「Save Asset」ボタンが配置されていません。しかし、現在のバージョンではこの自動保存処理が正しく機能しない不具合が発生しています。そのため、そのままエディタを閉じたり、アセットを複製したりすると、追加した変更(LanternやFog)が保存されず消えてしまいます。

しかもこれ、何が厄介かというと、ディスクに保存されていなくても、Unityエディタを起動中(メモリ上)はゲームプレイ時のキー操作が普通にできてしまうため、保存に失敗していることに全く気づけません。

⚠️ これを放置すると、次回(第5弾)でアセットを複製して『カスタム Input Actions アセット』を作成する際に、「追加したはずのキーが複製先に無い!」という状態に陥ってしまいます。


対応策

今後のアクション編集は、Project Settingsからではなく、Assetsフォルダ内にある InputSystem_Actions を直接ダブルクリックして専用ウィンドウを開いて行ってください。

そして、編集後は『Save Asset』を必ず押すこと。


第7章「ポストプロセス」

ポストプロセスとは

ここまでで遺跡は「暗くて、ランタンで照らせて、霧がかかる」空間になりました。でも、まだ少し「素材そのまま」感があります。

その理由は、Unityが計算した色をそのまま画面に出しているからです。実際の映画やゲームは、撮影 / レンダリングした映像に対して、最後に必ず色味の調整(カラーグレーディング)を施しています。同じ映像でも、少し暗く落として彩度を下げるだけで「重厚な雰囲気」になり、逆に明るく鮮やかにすれば「爽やかな印象」になる。この最終仕上げがポストプロセスです。

URPでは Volume(ボリューム) という仕組みで扱います。「画面全体にかけるフィルターの束」だと考えてください。今回は次の2つを使います。

  • Vignette(ビネット)
    画面の四隅を暗く落とす。視線が中央に集まり、「暗がりをのぞき込んでいる」感覚が生まれます

  • Color Adjustments(色調整)
    全体の明るさ / コントラスト / 彩度を整える。石造りの遺跡らしい、ひんやりした色味を作ります。

📝 元のライトやマテリアルを一切いじらずに、画面の印象だけを後から変えられるのがポストプロセスの利点です。「暗すぎたかな」と思ったときも、ライトを1つずつ調整し直すのではなく、ここで露出を上げれば全体をまとめて明るくできます。


グローバルボリュームを作る

メニュー:ゲームオブジェクト > ボリューム > グローバルボリューム。作られた Global Volume を選び、Volume コンポーネントの  New 押下。

「オーバーライドを追加」→「Post-processing」を選択。

「Color Adjustments」を選択。

好みに合わせ、値を調整。

同じように『Vignette』も作成し調整

「強さ」のチェックを入れて 0.3〜0.5、「色」 を黒に。画面の四隅が暗く落ち、視線が中央に集まります。

⚠️ 各パラメータは、名前の左のチェックボックスを入れないと反映されません。


カメラの「ポストプロセス」をチェックする

⚠️ ここが URP ポストプロセスで一番ハマる場所です

  1. ヒエラルキーで「Main Camera」 を選択。

  2. インスペクターのCamera コンポーネントのレンダリング欄「ポストプロセス」のチェックをオンにする。

新規に作ったカメラでは、このポストプロセスは既定でオフです。「Volume は完璧なのに何も起きない」の9割はこれです。

■ 動作確認
ゲーム開始「▶」で確認。雰囲気が変わっているかと思います。


第8章「リフレクションプローブ」

リフレクションプローブとは

いま広場の中を歩くと、壁や床がやけにのっぺりしているように感じるかも知れません。

原因は「映り込み」です。現実の石床や金属は、周囲の光や壁の色をうっすら反射しています。ところがUnityの既定では、オブジェクトは空(スカイボックス)を映すようになっています。つまり、屋内にいるのに床が「外の空」を反射している状態です。当然、そこにあるはずの壁も松明の灯りも映らないので、質感が平坦になります。

リフレクションプローブは、この問題を解決する仕組みです。置いた場所から周囲360度をぐるっと撮影し、その画像を「この部屋にいるオブジェクトは、これを映してください」と教えてくれます。

📝 効果は派手ではありません
 
「劇的に変わった!」とはならず、「言われてみれば床に深みが出たかも」という程度です。ただ、こういう地味な積み重ねが「なんとなく本格的に見える画面」を作ります。逆に言えば、これを飛ばしても遊べはするので、まずは体験として一度やってみる、くらいの気持ちで構いません。


配置とベイク

■ 広場(TreasureHall) 

ヒエラルキーで広場(TreasureHall) を選択。インスペクターで「静的(Static)」のチェックを付ける。

📝「ベイク(Baked)」タイプのリフレクションプローブを使う場合は絶対に必須です。 ベイクというのは「動かない背景を事前に写真に撮っておく」機能なので、Unityに「この壁と床は絶対に動かない(Static)ですよ」と教えてあげないと、カメラに映りません。

ゲーム開始「▶」で確認。まるで映画のような「重厚な雰囲気」になっているでしょう。


■ ライティングの生成

  • 広場(TreasureHall) の中央あたりに ゲームオブジェクト > ライト > リフレクションプローブ を作成します。
    例えば「広場の位置が X: 6.53, Y: 2.55, Z: -0.11」ならば、『Reflection Probeの 位置は X: 6.53, Y: 5.5, Z: -0.11』のようになります。

📝 なぜY(高さ)だけ数値を増やすかというと、部屋モデルの基準点が「床」にあるからです。 リフレクションプローブは、その場から360度を見渡して「部屋の映り込み画像」を撮影するカメラのようなものです。床にめり込んだ状態ではうまく撮影できないため、「床の高さ」に「部屋の高さの半分(今回は5の半分の2.5)」を足して、空間の『ど真ん中(空中)』にカメラの三脚を立てるイメージで配置します。

  • ボックスサイズは、広場がすっぽり収まるよう調整。
    例えば「広場のサイズが X: 12, Y: 5, Z: 12」ならば、この実寸よりも少しだけ余裕を持たせ『Reflection Probeの ボックスサイズは X: 13, Y: 6, Z: 13』のようにします。

  • インスペクターで タイプ を ベイク に設定。

  • ランタイム設定の中にある「ボックス投影(Box Projection)」のチェック

  • 最後に、インスペクターの一番下にある「ベイク」ボタンを押して画像を焼き付けます(ベイク)。

インスペクターの右下にツヤツヤの球体が現れます。

📝 これは「今撮影した360度の景色を、ツルツルのボールに反射させたらどう見えるか」を見せてくれるプレビュー(試し見)画面です。


第9章「環境音ループ」

音素材について素材はどこで手に入るか

最後に「音の空気」です。遠くの水滴や風のような環境音をループ再生します。

📝 環境音とは、その場の空気感をつくる「背景の音」のことです。水滴・風・低い響きなどをループ再生して、ずっと鳴らし続けます。効果音(足音や宝箱を開ける音など)とは役割が違うので、ここでは長めにループできる音を選ぶのがポイントです。目安として、1分以上あるものだとループの切れ目が気になりにくく、扱いやすいです。

初心者にとって使いやすいのが「Pixabay(https://pixabay.com/ja/)」です。

「Pixabay(ピクサベイ)」は 画像でおなじみのサイトですが、実は効果音・音楽も大量にあります。多くはクレジット表記が不要で、商用利用もOK、ゲームへの組み込みもOK。ただし音源をそのまま「素材」として配布・販売するのはNGです(ゲームの一部として使うのは問題ありません)。

⚠️ 曲ごとに作者が条件を追加している場合があるので、その曲のページを一度読んでおきましょう

今回のような遺跡探索に合う音を探すときは、以下のようなキーワードで検索すると良いでしょう。

遺跡探索の環境音のキーワードの例
https://pixabay.com/ja/ “ruins ambience”で検索

“ruins ambience”(遺跡の雰囲気)で検索。
ちなみに「Dawn Beyond the Ruins」をチョイスしました。
なお、ロボットアイコン(AI生成)に注目。これに関しては次項【「AI生成の音楽」について】にて説明します。

📝 他にも、音素材サイトはたくさんあります。
各サイト、各素材ごとにライセンスの確認はしておきましょう。


「AI生成の音楽」について

音楽に限った話ではないのですが、AIで作ったものを使う場合、特に各プラットフォームの規約を確認しましょう。

例えば、Steamでは、ゲーム内でプレイヤーが触れる素材(BGM、効果音、画像、文章など)に生成AIで作られたコンテンツが含まれる場合、審査時のアンケートで報告するルールになっています。

先ほど、Pixabayでロボットアイコン付きをダウンロードしたので、AI生成の音楽(事前生成された素材)です。したがって「ゲーム内にAI生成されたサウンドが含まれる」という扱いになります。

Steamworks(開発者管理画面)でゲームを出品する際、事前におこなう「コンテンツアンケート(Content Survey)」の中で、以下のように回答します。

  1. 事前生成コンテンツ(Pre-generated) の項目で「はい」を選択

  2. どの部分に使っているか(例:「BGM・サウンドの一部にAI生成素材を使用」など)を簡潔に記載

📝 アンケートで報告すると、Steamのストアページに「生成AIに関する開示」として記載が表示されます。


環境音の設定

環境音を設定しましょう。

📝 Assets内に音源ファイルを格納済みとします(パス例:Assets/Audio/ファイル名.mp3)。

  1. 空の ゲームオブジェクト を作成し、名前を Ambient に。

  2. 「コンポーネントを追加」> Audio > Audio Source を追加。

  3. 用意した音源ファイル(環境音)を一番上の Audio Generator (または AudioClip)の枠に割り当て、以下を設定

    • ループ をオン(切れ目なく繰り返す)

    • ゲーム開始時に再生 をオン(起動と同時に再生)

    • 立体音響ブレンド のスライダーを一番左の 2D(=0) に(=距離に関係なく音量一定。空間全体に均等に鳴らす環境音向き。右の3Dに寄せると距離によって音が減衰します)

ゲーム開始「▶」で確認。環境音も流れると思います。

いいなと思ったら応援しよう!

辛島信芳 ここまで読んでくれてありがとうございます!記事は基本的に無料で出していくので、気が向いたら応援してもらえると嬉しいです☕