学習記事一覧 · Unity本格入門

Unity本格入門:いきのこバトルで学ぶメインシーンの時間と昼夜

題材・出典: 技術評論社刊『作って学べる Unity本格入門[Unity 6対応版]』(賀好 昭仁 著)に基づく学習補助の解説です。書籍の代替提供を目的とせず、コード掲載は学習上必要な範囲(必要最小限)にとどめます。利用条件は書籍記載(P.4〜5)および出版社サポート情報に従ってください。本シリーズ目次(書籍・著作の注記)

対象読者:第1回を読んだあと、メインシーンの「世界の時間」とライトまわりを追いたい方
いきのこバトルにおける MainSceneControllerRoundLight を中心に、ゲーム内時間の進み方昼夜の切り替えコルーチンと DOTween を読み解きます。

前提第1回:タイトル・セーブデータ でシーン遷移とセーブの流れを把握していると分かりやすいです。PlayerStatus の満腹度はここでは「時間とともに減る」ことだけ押さえ、詳細は第3回以降で触れます。


記事の目次

ポイント一覧


この記事で扱う範囲

  • 対象ファイルMainSceneController.csRoundLight.cs、共通の SingletonMonoBehaviourInSceneBase.cs(再掲)
  • 扱わない:プレイヤー操作の詳細、敵の生成(別記事)

ゲーム内時間の進行と昼夜・平行光(ライト)のイメージ

説明(学習のヒント):コルーチンで刻む ゲーム内時間 と、RoundLight による 太陽の向き・昼夜の見た目 を切り替えるイメージです。DOTween で明るさを滑らかに変える流れも記事で整理します。


コードを全部見てみよう

SingletonMonoBehaviourInSceneBase.cs

using System;
using UnityEngine;
public abstract class SingletonMonoBehaviourInSceneBase<T> : MonoBehaviour where T : MonoBehaviour
{
    public static T Instance { get; private set; }
    protected virtual void Awake()
    {
        if (null != Instance && Instance != this)
        {
            throw new Exception("シーン内に他のインスタンスが存在しています!");
        }
        Instance = this as T;
    }
}

RoundLight.cs

using UnityEngine;
public class RoundLight : SingletonMonoBehaviourInSceneBase<RoundLight>
{
    public Light Light { get; private set; }
    private void Start()
    {
        Light = GetComponent<Light>();
    }
    public void RotateTo(Vector3 to)
    {
        transform.eulerAngles = to;
    }
}

MainSceneController.cs

using System.Collections;
using DG.Tweening;
using UnityEngine;
using UnityEngine.SceneManagement;
public class MainSceneController : SingletonMonoBehaviourInSceneBase<MainSceneController>
{
    public const int IncrementMinutesPerAttack = 10;
    public const int IncrementMinutesPerCreateItem = 5;
    public bool IsNight { get; private set; }
    public int PassedDay => _minutesInGame / MinutesOnDay;
    private const int MinutesOnDay = 24 * 60 * 5;
    [SerializeField] private PlayerStatus playerStatus;
    private int _minutesInGame;
    private bool _isGameOver;
    public int MinutesInGame
    {
        get => _minutesInGame;
        set
        {
            if (_isGameOver) return;
            var elapsedMinutes = value - _minutesInGame;
            playerStatus.SatietyValue -= elapsedMinutes / 100f;
            _minutesInGame = value;
            var xDegree = (90f + 360f / MinutesOnDay * (_minutesInGame % MinutesOnDay)) % 360f;
            RoundLight.Instance.RotateTo(new Vector3(xDegree, -60f));
            var prevIsNight = IsNight;
            IsNight = xDegree < 0 || 180 < xDegree;
            if (!prevIsNight && IsNight)
            {
                DOTween.To(v => RoundLight.Instance.Light.intensity = v, 1f, 0f, 5f);
            }
            else if (prevIsNight && !IsNight)
            {
                DOTween.To(v => RoundLight.Instance.Light.intensity = v, 0f, 1f, 5f);
            }
        }
    }
    public void EatItem()
    {
        playerStatus.SatietyValue += 10;
    }
    private void Start()
    {
        MinutesInGame = 0;
        StartCoroutine(TimerLoop());
    }
    public void GameOver()
    {
        _isGameOver = true;
        DOVirtual.DelayedCall(3, () =>
        {
            GameOverSceneController.Score = MinutesInGame;
            SceneManager.LoadScene("GameOverScene");
        });
    }
    private IEnumerator TimerLoop()
    {
        while (!_isGameOver)
        {
            yield return new WaitForSeconds(0.1f);
            MinutesInGame += 5;
        }
    }
}

クラス関係(この記事で登場する範囲)

classDiagram direction TB class MonoBehaviour class SingletonMonoBehaviourInSceneBase class MainSceneController class RoundLight MonoBehaviour <|-- SingletonMonoBehaviourInSceneBase SingletonMonoBehaviourInSceneBase <|-- MainSceneController SingletonMonoBehaviourInSceneBase <|-- RoundLight MainSceneController ..> RoundLight : "Instance"

説明(学習のヒント):シーン内シングルトン基底を経由して、MainSceneControllerRoundLightInstance でつながる関係です。


UMLで整理する(シーケンス)

TimerLoopMinutesInGame を進めたあと、set 内で満腹・ライト・昼夜がまとめて更新される流れを表します。

sequenceDiagram participant Timer as TimerLoop participant MSC as MainSceneController participant PS as PlayerStatus participant RL as RoundLight loop Each tick Timer->>MSC: "MinutesInGame 加算" MSC->>PS: "満腹を減らす" MSC->>RL: "RotateTo" MSC->>MSC: "昼夜と DOTween" end

説明(学習のヒント)loop はゲーム中ずっと繰り返す区間です。Timer が時間を進め、MSC が満腹・ライト・昼夜をまとめて更新します。


ポイント①:SingletonMonoBehaviourInSceneBaseInstance

MainSceneControllerRoundLight は、この基底クラスを継承しています。シーンに1つだけ置く前提で、AwakeInstance に自分を登録します。どこからでも MainSceneController.Instance のように参照でき、マネージャ系の定番パターンです(セーブ用の純粋 C# シングルトンとは別物です)。


ポイント②:MinutesInGame で時間・満腹・太陽の向きをまとめて更新

set 内で次のことを一度に行っています。

  • 経過分だけ playerStatus.SatietyValue を減らす(満腹度)
  • _minutesInGame を更新する
  • _minutesInGame から太陽(平行光)の X 回転角 xDegree を計算し、RoundLight.Instance.RotateTo に渡す

ゲーム内の「分」は整数で持ち、一定間隔で MinutesInGame を増やすことで世界が進みます。


ポイント③:RoundLight で平行光の角度を変える

RotateTotransform.eulerAngles をそのまま設定しています。Y は -60 固定、X が時間に応じて回るイメージです。これで太陽が回り、影の向きや明るさのベースが変わります。


ポイント④:昼夜の判定 IsNight と DOTween で明るさを変える

IsNight = xDegree < 0 || 180 < xDegree;

角度の範囲で「夜かどうか」を決めています。prevIsNight と比較し、昼→夜または夜→昼に変わったフレームだけ DOTween.ToLight.intensity を 0〜1 の間でなめらかに変化させます。DOTween はアニメーション補間用のライブラリです。


ポイント⑤:コルーチン TimerLoop で時間を刻む

private IEnumerator TimerLoop()
{
    while (!_isGameOver)
    {
        yield return new WaitForSeconds(0.1f);
        MinutesInGame += 5;
    }
}

0.1 秒ごとに「5分」進めるので、実時間の流れとゲーム内時間の進み方を独立して調整できます。GameOver が呼ばれると _isGameOvertrue になり、ループを抜けます。


ポイント⑥:GameOver と遅延後のシーン遷移

DOVirtual.DelayedCall(3, () =>
{
    GameOverSceneController.Score = MinutesInGame;
    SceneManager.LoadScene("GameOverScene");
});

第1回と同様、GameOverSceneController.Score にスコアを入れてからゲームオーバー画面へ。3秒待ってから遷移するので、演出の余韻を取れます。


ポイント⑦:PassedDay とゲーム内の「日」

public int PassedDay => _minutesInGame / MinutesOnDay;
private const int MinutesOnDay = 24 * 60 * 5;

_minutesInGame を「1日の長さ」で割ったものが PassedDay です。敵スポーンなど別スクリプトが 経過日数に応じて難易度を上げるのに使われます(第4回)。


コードの流れを整理しよう

flowchart TD start[Start] --> timer["TimerLoop コルーチン"] timer --> add["MinutesInGame += 5"] add --> prop["MinutesInGame の set"] prop --> satiety["満腹を減らす"] prop --> light["RoundLight の角度更新"] prop --> night["昼夜判定と DOTween"] go["GameOver 呼び出し"] --> stop["_isGameOver true"] go --> wait["3秒後に Score 代入と LoadScene"]

説明(学習のヒント):上から下に進みます。MinutesInGameset が一箇所に分岐しているので、prop から横に広がる処理は「同じタイミングで起きること」です。


自分でカスタマイズしてみよう!

挑戦①:1日の長さを変える

MinutesOnDay を変えると、太陽の一周が速くなったり遅くなったりします。TimerLoop の加算値と合わせて調整してみましょう。

挑戦②:夜の判定をログする

IsNight が変わったときだけ Debug.Log すると、昼夜の切り替わりが追いやすくなります。


まとめ

  • シーン内シングルトンMainSceneControllerRoundLight をどこからでも参照する
  • MinutesInGame のプロパティで、時間・満腹・ライト角度・昼夜を一括更新する
  • DOTween でライトの強さを補間し、コルーチンで定期的にゲーム内時間を進める
  • GameOver でループを止め、遅延のあと第1回と同じ要領でスコアを渡してシーン遷移する

次は 第3回:プレイヤーと新 Input System で、実際の操作と移動のコードを追います。


最終更新:2026年4月