Unityを触り始めると、つい「とりあえず全部Updateに書く」実装になりがちですよね。
ゲーム内の昼夜サイクルを作るときも、プレイヤーのスクリプトの中に
- 時間の更新
- ライトの明るさ変更
- 背景の色変更
- UIの色変更
…といった処理を全部押し込んでしまうと、あっという間にGodクラス化してしまいます。
そこでこの記事では、「色の変化だけ」を担当する小さなコンポーネントとして、
グローバルな時間変数を監視して親オブジェクトの色合いを昼夜に合わせて変える
「DayNightTint」コンポーネントを作っていきます。
時間の管理は別コンポーネントに任せて、色の反映だけをこのコンポーネントに分離することで、
プレハブの再利用性も上がり、レベルデザインもかなり楽になりますよ。
【Unity】昼夜で色がふわっと変わる!「DayNightTint」コンポーネント
ここでは、以下の2つのコンポーネントを用意します。
- DayNightTime:ゲーム内の「一日の進行度」を管理するグローバル時間コンポーネント
- DayNightTint:その時間を参照して、親オブジェクトの色合いを昼夜に合わせて変更するコンポーネント
どちらもコピペで動くように、フルコードを載せておきます。
DayNightTime.cs(グローバル時間コンポーネント)
using UnityEngine;
namespace DayNightSample
{
/// <summary>
/// ゲーム内の「一日の進行度」を管理するコンポーネント。
/// 0.0 ~ 1.0 の範囲で一日を表現します(0=夜明け前, 0.5=正午, 1=次の日の夜明け前)。
/// シーン内に1つだけ配置して、他コンポーネントが参照する想定です。
/// </summary>
public class DayNightTime : MonoBehaviour
{
// シーン内でどこからでも参照できるようにする簡易シングルトン
public static DayNightTime Instance { get; private set; }
[Header("時間設定")]
[Tooltip("ゲーム内の1日が何秒で進むか")]
[SerializeField] private float dayLengthSeconds = 120f;
[Tooltip("ゲーム開始時の時間(0~1) 0=夜明け前, 0.25=朝, 0.5=昼, 0.75=夕方")]
[Range(0f, 1f)]
[SerializeField] private float startTimeOfDay = 0.25f;
[Tooltip("時間を自動で進めるかどうか")]
[SerializeField] private bool autoAdvance = true;
// 0~1で表現される現在の一日の進行度
public float NormalizedTimeOfDay { get; private set; }
private void Awake()
{
// 簡易シングルトン(2つ以上存在したら警告を出して自分を破棄)
if (Instance != null && Instance != this)
{
Debug.LogWarning("[DayNightTime] シーン内に複数存在しています。余分な方を削除します。", this);
Destroy(this);
return;
}
Instance = this;
NormalizedTimeOfDay = Mathf.Clamp01(startTimeOfDay);
}
private void Update()
{
if (!autoAdvance || dayLengthSeconds <= 0f)
{
return;
}
// 1秒あたりにどれだけ時間が進むかを計算
float delta = Time.deltaTime / dayLengthSeconds;
// 0~1の範囲でループさせる
NormalizedTimeOfDay += delta;
if (NormalizedTimeOfDay > 1f)
{
NormalizedTimeOfDay -= 1f;
}
}
/// <summary>
/// 外部から時間を直接設定したい場合に使用します。
/// 例: ステージ開始時に朝に固定したい、など。
/// </summary>
/// <param name="normalizedTime">0~1の範囲で指定</param>
public void SetTimeOfDay(float normalizedTime)
{
NormalizedTimeOfDay = Mathf.Repeat(normalizedTime, 1f);
}
}
}
DayNightTint.cs(昼夜で色を変えるコンポーネント)
using UnityEngine;
namespace DayNightSample
{
/// <summary>
/// グローバルな DayNightTime を監視し、対象オブジェクトの色合い(Tint)を
/// 昼夜サイクルに合わせて変化させるコンポーネント。
///
/// 「色をどう変えるか」だけに責務を絞ることで、時間管理ロジックと分離しています。
/// </summary>
[DisallowMultipleComponent]
public class DayNightTint : MonoBehaviour
{
// 対象のレンダラー(SpriteRenderer, MeshRenderer など)
[Header("対象コンポーネント")]
[Tooltip("色を変更したい Renderer。未設定の場合は親(このオブジェクト)の Renderer を自動取得します。")]
[SerializeField] private Renderer targetRenderer;
[Header("色設定")]
[Tooltip("正午(昼)のときの色")]
[SerializeField] private Color dayColor = Color.white;
[Tooltip("真夜中のときの色")]
[SerializeField] private Color nightColor = Color.gray;
[Tooltip("夜明け・夕方のときに少しオレンジっぽくしたい場合の補正色")]
[SerializeField] private Color duskDawnTint = new Color(1f, 0.9f, 0.8f, 1f);
[Tooltip("夜明け・夕方の影響度(0=無効, 1=最大)")]
[Range(0f, 1f)]
[SerializeField] private float duskDawnIntensity = 0.5f;
[Header("時間カーブ")]
[Tooltip("時間(0~1)に対する明るさのカーブ。Y=1が基準。")]
[SerializeField] private AnimationCurve brightnessCurve = AnimationCurve.EaseInOut(0f, 0.2f, 0.5f, 1f);
[Header("更新設定")]
[Tooltip("毎フレーム更新するか。パフォーマンスを気にする場合は false にして、外部から手動で UpdateTint を呼ぶ設計も可能です。")]
[SerializeField] private bool updateEveryFrame = true;
// マテリアルインスタンスをキャッシュ(共有マテリアルは変更しないように注意)
private Material _materialInstance;
private void Reset()
{
// コンポーネント追加時に自動で Renderer をセットしておく
if (targetRenderer == null)
{
targetRenderer = GetComponent<Renderer>();
}
}
private void Awake()
{
if (targetRenderer == null)
{
targetRenderer = GetComponent<Renderer>();
}
if (targetRenderer == null)
{
Debug.LogWarning("[DayNightTint] Renderer が見つかりませんでした。色変更は行われません。", this);
return;
}
// 共有マテリアルを直接いじると、他のオブジェクトにも影響してしまうので
// インスタンス化したマテリアルを使用します。
_materialInstance = targetRenderer.material;
}
private void Update()
{
if (!updateEveryFrame)
{
return;
}
UpdateTint();
}
/// <summary>
/// 現在の DayNightTime に基づいて色を更新します。
/// 外部から手動で呼び出すこともできます。
/// </summary>
public void UpdateTint()
{
if (_materialInstance == null)
{
return;
}
// グローバル時間コンポーネントを取得
var time = DayNightTime.Instance;
if (time == null)
{
// シーンに DayNightTime が存在しない場合は警告を出して何もしない
Debug.LogWarning("[DayNightTint] DayNightTime.Instance が見つかりません。シーンに DayNightTime を配置してください。", this);
return;
}
float t = time.NormalizedTimeOfDay; // 0~1
// 明るさカーブを評価
float brightness = brightnessCurve.Evaluate(t);
// 昼色と夜色を補間
// 正午(0.5)に近いほど dayColor、真夜中(0 or 1)に近いほど nightColor に寄せるイメージ
float dayNightLerp = GetDayNightLerp(t);
Color baseColor = Color.Lerp(nightColor, dayColor, dayNightLerp);
// 夜明け・夕方(0.2~0.3, 0.7~0.8あたり)でオレンジっぽくする補正
float duskDawnFactor = GetDuskDawnFactor(t) * duskDawnIntensity;
if (duskDawnFactor > 0f)
{
baseColor = Color.Lerp(baseColor, baseColor * duskDawnTint, duskDawnFactor);
}
// 最終的な色 = ベースカラー × 明るさ
baseColor *= brightness;
// 実際にマテリアルに反映
_materialInstance.color = baseColor;
}
/// <summary>
/// 時間(0~1)から「昼寄りか夜寄りか」を0~1で返す。
/// 0 or 1 = 夜, 0.5 = 昼 として、なめらかに補間します。
/// </summary>
private float GetDayNightLerp(float t)
{
// 0~0.5~1 で山型にするために、0.5からの距離を使う
float distanceFromNoon = Mathf.Abs(t - 0.5f); // 0.5で0, 0 or 1で0.5
float normalized = 1f - (distanceFromNoon / 0.5f); // 0 or 1で0, 0.5で1
return Mathf.Clamp01(normalized);
}
/// <summary>
/// 夜明け・夕方のときだけ 0~1 の値を返す。
/// それ以外の時間帯では 0。
/// </summary>
private float GetDuskDawnFactor(float t)
{
// ここではシンプルに「0.2~0.3」と「0.7~0.8」を夜明け・夕方とみなす
const float range = 0.1f;
float dawnCenter = 0.25f;
float duskCenter = 0.75f;
float dawn = 1f - Mathf.Clamp01(Mathf.Abs(t - dawnCenter) / range);
float dusk = 1f - Mathf.Clamp01(Mathf.Abs(t - duskCenter) / range);
// どちらか大きい方を採用
return Mathf.Max(dawn, dusk);
}
private void OnDestroy()
{
// 生成したマテリアルインスタンスを破棄してメモリリークを防ぐ
if (_materialInstance != null)
{
Destroy(_materialInstance);
}
}
}
}
使い方の手順
ここでは、2Dの背景スプライトと3Dの建物を例にして説明します。
手順① DayNightTime をシーンに配置する
- 空の GameObject を作成し、名前を
DayNightSystemなどにします。 - 上記の
DayNightTimeスクリプトをプロジェクトに作成し、この GameObject にアタッチします。 - Day Length Seconds に「ゲーム内1日が何秒か」を設定します(例: 120秒)。
- Start Time Of Day を 0.25(朝)や 0.5(昼)など、好きな開始時間に設定します。
手順② 色を変えたいオブジェクトに DayNightTint を付ける
- 背景のスプライト(例:
Backgroundオブジェクト)を選択します。 DayNightTintスクリプトを作成し、Backgroundにアタッチします。- Target Renderer が空の場合は、
SpriteRendererが自動でセットされます。 - Day Color に昼の色(例:
RGBA(1,1,1,1))、
Night Color に夜の色(例: 少し暗いRGBA(0.4,0.4,0.5,1))を設定します。 - Dusk Dawn Tint を少しオレンジ寄り(例:
(1.0, 0.85, 0.7, 1))にすると、朝焼け・夕焼け感が出ます。
手順③ 建物や地形にも同じコンポーネントを再利用する
- 3Dの建物プレハブ(例:
HousePrefab)にDayNightTintをアタッチします。 Target RendererにMeshRendererを指定します。- Day Color を少し明るめ、Night Color をかなり暗めに設定すると、
夜になると建物全体がシルエットっぽく沈んで雰囲気が出ます。 - この状態でプレハブ化しておけば、シーン中に何個置いても自動で昼夜に追従してくれます。
手順④ 動く床や敵キャラにも適用して世界観を統一する
- 動く床プレハブ(例:
MovingPlatform)や敵キャラの本体オブジェクトにもDayNightTintを付けます。 - 敵キャラだけ少し派手にしたい場合は、Dusk Dawn Intensity を上げて、
夕方になると少し赤くなるなどの演出も簡単にできます。 - すべて同じ
DayNightTimeを監視しているので、一括で昼夜が切り替わる統一感のある世界になります。
メリットと応用
DayNightTint を使うことで、以下のようなメリットがあります。
- 責務が明確:時間管理(
DayNightTime)と色変化(DayNightTint)が分離されていて、どちらも小さなコンポーネントで完結します。 - プレハブ再利用が楽:建物、敵、動く床など、どのプレハブにも同じコンポーネントを貼るだけで昼夜対応できます。
- レベルデザインがシンプル:シーンデザイナーは「どのオブジェクトを昼夜対応にするか」をチェックボックス感覚で決めるだけです。
- 調整がしやすい:明るさカーブや色をインスペクターからいじるだけで、
朝焼けを強くしたり、夜を真っ暗にしたりといった微調整が可能です。
このように「時間を見るだけのコンポーネント」と「時間に応じて何かをするコンポーネント」を分けておくと、
今後「ライトの強さを変える」「BGMのボリュームを変える」「敵の行動パターンを時間帯で変える」など、
さまざまな昼夜対応コンポーネントを追加してもコードがスパゲッティになりにくくなります。
改造案:特定の時間帯だけ光るエフェクトを足す
例えば、「夜の間だけオブジェクトを少し発光させたい」という場合、
DayNightTint に次のようなメソッドを追加して、UpdateTint() の最後で呼ぶだけでも実現できます。
/// <summary>
/// 夜の時間帯だけ少し発光させる簡易エフェクト例。
/// Emission カラーを持つマテリアルで使用してください。
/// </summary>
private void ApplyNightEmission(float t)
{
if (_materialInstance == null)
{
return;
}
// ここでは 0.8~1.0 と 0.0~0.2 を「夜」とみなす
float nightFactor = 0f;
if (t <= 0.2f)
{
nightFactor = 1f - (t / 0.2f); // 0で最大, 0.2で0
}
else if (t >= 0.8f)
{
nightFactor = (t - 0.8f) / 0.2f; // 0.8で0, 1で最大
}
// 夜のときだけ少し青白く光らせる
Color emissionColor = new Color(0.4f, 0.6f, 1.0f) * nightFactor;
_materialInstance.SetColor("_EmissionColor", emissionColor);
}
このように、小さな void 関数を足していくスタイルで機能を拡張していけば、
巨大な God クラスを作らずに、昼夜演出をどんどんリッチにしていけます。
まずはシンプルな DayNightTint から導入して、プロジェクト全体のコンポーネント設計をスリムにしていきましょう。
