Unity本格入門:いきのこバトルで学ぶメインシーンの時間と昼夜
題材・出典: 技術評論社刊『作って学べる Unity本格入門[Unity 6対応版]』(賀好 昭仁 著)に基づく学習補助の解説です。書籍の代替提供を目的とせず、コード掲載は学習上必要な範囲(必要最小限)にとどめます。利用条件は書籍記載(P.4〜5)および出版社サポート情報に従ってください。本シリーズ目次(書籍・著作の注記)
対象読者:第1回を読んだあと、メインシーンの「世界の時間」とライトまわりを追いたい方
いきのこバトルにおけるMainSceneControllerとRoundLightを中心に、ゲーム内時間の進み方・昼夜の切り替え・コルーチンと DOTween を読み解きます。
前提:第1回:タイトル・セーブデータ でシーン遷移とセーブの流れを把握していると分かりやすいです。PlayerStatus の満腹度はここでは「時間とともに減る」ことだけ押さえ、詳細は第3回以降で触れます。
記事の目次
- この記事で扱う範囲
- コードを全部見てみよう
- クラス関係(この記事で登場する範囲)
- UMLで整理する(シーケンス)
- ポイント解説(①〜⑦)
- コードの流れを整理しよう
- 自分でカスタマイズしてみよう!
- まとめ
ポイント一覧
- ①
SingletonMonoBehaviourInSceneBaseとInstance - ②
MinutesInGameで時間・満腹・太陽の向きをまとめて更新 - ③
RoundLightで平行光の角度を変える - ④ 昼夜の判定
IsNightと DOTween で明るさを変える - ⑤ コルーチン
TimerLoopで時間を刻む - ⑥
GameOverと遅延後のシーン遷移 - ⑦
PassedDayとゲーム内の「日」
この記事で扱う範囲
- 対象ファイル:
MainSceneController.cs、RoundLight.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;
}
}
}クラス関係(この記事で登場する範囲)
説明(学習のヒント):シーン内シングルトン基底を経由して、MainSceneController と RoundLight が Instance でつながる関係です。
UMLで整理する(シーケンス)
TimerLoop が MinutesInGame を進めたあと、set 内で満腹・ライト・昼夜がまとめて更新される流れを表します。
説明(学習のヒント):loop はゲーム中ずっと繰り返す区間です。Timer が時間を進め、MSC が満腹・ライト・昼夜をまとめて更新します。
ポイント①:SingletonMonoBehaviourInSceneBase と Instance
MainSceneController と RoundLight は、この基底クラスを継承しています。シーンに1つだけ置く前提で、Awake で Instance に自分を登録します。どこからでも MainSceneController.Instance のように参照でき、マネージャ系の定番パターンです(セーブ用の純粋 C# シングルトンとは別物です)。
ポイント②:MinutesInGame で時間・満腹・太陽の向きをまとめて更新
set 内で次のことを一度に行っています。
- 経過分だけ
playerStatus.SatietyValueを減らす(満腹度) _minutesInGameを更新する_minutesInGameから太陽(平行光)の X 回転角xDegreeを計算し、RoundLight.Instance.RotateToに渡す
ゲーム内の「分」は整数で持ち、一定間隔で MinutesInGame を増やすことで世界が進みます。
ポイント③:RoundLight で平行光の角度を変える
RotateTo は transform.eulerAngles をそのまま設定しています。Y は -60 固定、X が時間に応じて回るイメージです。これで太陽が回り、影の向きや明るさのベースが変わります。
ポイント④:昼夜の判定 IsNight と DOTween で明るさを変える
IsNight = xDegree < 0 || 180 < xDegree;角度の範囲で「夜かどうか」を決めています。prevIsNight と比較し、昼→夜または夜→昼に変わったフレームだけ DOTween.To で Light.intensity を 0〜1 の間でなめらかに変化させます。DOTween はアニメーション補間用のライブラリです。
ポイント⑤:コルーチン TimerLoop で時間を刻む
private IEnumerator TimerLoop()
{
while (!_isGameOver)
{
yield return new WaitForSeconds(0.1f);
MinutesInGame += 5;
}
}0.1 秒ごとに「5分」進めるので、実時間の流れとゲーム内時間の進み方を独立して調整できます。GameOver が呼ばれると _isGameOver が true になり、ループを抜けます。
ポイント⑥: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回)。
コードの流れを整理しよう
説明(学習のヒント):上から下に進みます。MinutesInGame の set が一箇所に分岐しているので、prop から横に広がる処理は「同じタイミングで起きること」です。
自分でカスタマイズしてみよう!
挑戦①:1日の長さを変える
MinutesOnDay を変えると、太陽の一周が速くなったり遅くなったりします。TimerLoop の加算値と合わせて調整してみましょう。
挑戦②:夜の判定をログする
IsNight が変わったときだけ Debug.Log すると、昼夜の切り替わりが追いやすくなります。
まとめ
- シーン内シングルトンで
MainSceneControllerとRoundLightをどこからでも参照する MinutesInGameのプロパティで、時間・満腹・ライト角度・昼夜を一括更新する- DOTween でライトの強さを補間し、コルーチンで定期的にゲーム内時間を進める
GameOverでループを止め、遅延のあと第1回と同じ要領でスコアを渡してシーン遷移する
次は 第3回:プレイヤーと新 Input System で、実際の操作と移動のコードを追います。
最終更新:2026年4月