【Cocos Creator】アタッチするだけ!DayNightTint (昼夜対応)の実装方法【TypeScript】

Godot 4ゲーム制作 実践ドリル 100本ノック

新品価格
¥1,250から
(2025/12/13 21:27時点)

Godot4& GDScriptではじめる 2Dゲーム開発レシピ

新品価格
¥590から
(2025/12/13 21:46時点)

Unity 6 C#スクリプト 100本ノック

新品価格
¥1,230から
(2025/12/29 10:28時点)

Cocos Creator100本ノックTypeScriptで書く!

新品価格
¥1,250から
(2025/12/29 10:32時点)

【Cocos Creator 3.8】DayNightTint の実装:アタッチするだけで「昼夜サイクルに応じた自動明暗(色合い)変化」を実現する汎用スクリプト

この記事では、ノードにアタッチするだけで「昼・夕方・夜」などの時間帯に応じて自動的に色を暗く/明るくしてくれる汎用コンポーネント DayNightTint を TypeScript で実装します。

グローバルな「ゲーム内時間」を参照するのではなく、コンポーネント自身が持つ時間パラメータを Inspector から調整できるようにすることで、他のスクリプトに一切依存せず、どのシーン・どのプロジェクトにもそのまま持ち込んで使える設計にします。


コンポーネントの設計方針

1. 要件の整理

  • ゲーム内の「時間」を 0.0〜1.0 の正規化値(0 = 深夜, 0.5 = 正午, 1 = 深夜に戻る)として扱う。
  • この時間値に応じて、ノードの色(UIRenderer.color / Sprite など)を暗く・明るく補正(Modulate)する。
  • 他の「GameManager」や「TimeManager」などのスクリプトに依存しない。
  • Inspector から「時間の進み方」や「昼の明るさ」「夜の暗さ」「色の変化カーブ」を調整可能にする。
  • UI(Sprite, Label, RichText など)だけでなく、3Dオブジェクト(MeshRenderer)にも適用できるように実装する。

「グローバル時間変数を監視」という要件は、外部依存を禁止という条件と衝突しやすいので、以下のように解釈して設計します。

  • DayNightTint が「ローカルな時間値」を持つ。
  • 必要であれば「自動で時間を進める」機能を ON/OFF できる。
  • 別のスクリプトから「時間値」をセットしたい場合は、public メソッドを通じて任意に外部から更新できる(ただし、DayNightTint 側は外部スクリプトを一切知らない)。

これにより、単体で完結しつつ、必要なら外部からも制御可能な汎用コンポーネントになります。

2. インスペクタで設定可能なプロパティ

以下のようなプロパティを設計します。

  • autoUpdateTime: boolean
    – 時間を自動で進めるかどうか。
    – ON: update() 内で deltaTime を使って timeOfDay を加算し、0〜1でループする。
    – OFF: 時間は外部から setTimeOfDay() で更新する想定。
  • timeOfDay: number (0.0〜1.0)
    – 現在の時間(正規化)。
    – 0.0 = 深夜、0.25 = 朝、0.5 = 正午、0.75 = 夕方、1.0 = 深夜(0に戻る)。
    autoUpdateTime = false の場合は、この値を直接 Inspector で動かして見た目を確認できる。
  • dayLengthSeconds: number
    – 0.0 〜 1.0 までの一周にかかる秒数。
    – 例: 60 にすると、現実時間 60秒で 1日が経過する。
  • dayColor: Color
    – 正午(timeOfDay = 0.5)付近で適用したい色。
    – 通常は Color.WHITE(= 無補正)を指定する。
  • nightColor: Color
    – 深夜(timeOfDay = 0.0 / 1.0)付近で適用したい色。
    – 例: Color(80, 80, 120) のように少し青みがかった暗い色にすると「夜っぽさ」が出る。
  • duskColor: Color
    – 夕方(timeOfDay ≒ 0.75)付近で適用したい色。
    – 例: Color(255, 180, 150) のようなオレンジ寄りの色。
  • dawnColor: Color
    – 朝焼け(timeOfDay ≒ 0.25)付近で適用したい色。
    – 例: Color(220, 200, 255) など、少し紫がかった明るい色。
  • brightnessCurve: Enum
    – 時間に対する明るさの変化カーブ。
    Linear: 単純に線形補間。
    SmoothStep: 緩やかに変化するカーブ。
    Sharp: 朝夕の変化を少し急峻にする。
  • applyToChildren: boolean
    – 子ノードの Renderer にも同じ色補正を適用するかどうか。
    – ON: 親ノード以下の全ての UIRenderer / MeshRenderer に対して色を適用。
    – OFF: このコンポーネントが付いているノードの Renderer のみを対象。
  • targetGroupTag: string
    – 任意の文字列タグ。空の場合は無視。
    – 例えば「Background」「Character」などのタグを付けておき、
    将来的に外部から「特定タグだけ時間を変える」などの制御をしやすくする。
  • debugLog: boolean
    – ON にすると、起動時や必要なコンポーネントが見つからない場合にコンソールへ詳細ログを出す。

また、標準コンポーネントへの依存として、以下を防御的に扱います。

  • UI 系: Sprite, Label, RichText, UIRenderer(共通基底)
  • 3D 系: MeshRenderer

これらが存在しない場合でもスクリプト自体はエラーで止まらず、警告ログを出して処理をスキップするようにします。


TypeScriptコードの実装


import { _decorator, Component, Node, Color, UIRenderer, MeshRenderer, math } from 'cc';
const { ccclass, property } = _decorator;

/**
 * 昼夜サイクルに応じてノード(および子ノード)の色を自動的に変化させるコンポーネント。
 * 他のスクリプトには依存せず、このコンポーネント単体で完結する。
 */
enum BrightnessCurveType {
    LINEAR = 0,
    SMOOTH_STEP = 1,
    SHARP = 2,
}

@ccclass('DayNightTint')
export class DayNightTint extends Component {

    @property({
        tooltip: '時間を自動で進行させるかどうか。\nON: update() 内で timeOfDay が 0〜1 の範囲でループします。\nOFF: timeOfDay は Inspector から手動で変更するか、外部から setTimeOfDay() で更新してください。'
    })
    public autoUpdateTime: boolean = true;

    @property({
        tooltip: '現在の時間(0.0〜1.0)。\n0 = 深夜, 0.25 = 朝, 0.5 = 正午, 0.75 = 夕方, 1 = 深夜(=0)。\nautoUpdateTime = false のときは、この値を直接編集して見た目を確認できます。',
        slide: true,
        min: 0,
        max: 1,
    })
    public timeOfDay: number = 0.25; // デフォルト: 朝

    @property({
        tooltip: '1日(0→1)にかかる現実時間(秒)。\n例: 60 にすると、60秒で1日が経過します。',
        min: 0.1,
    })
    public dayLengthSeconds: number = 60;

    @property({
        tooltip: '正午(timeOfDay ≒ 0.5)のときの色。\n通常は Color.WHITE(= 無補正)を指定します。'
    })
    public dayColor: Color = new Color(255, 255, 255, 255);

    @property({
        tooltip: '深夜(timeOfDay ≒ 0,1)のときの色。\n暗めかつ少し青みがかった色にすると夜らしく見えます。'
    })
    public nightColor: Color = new Color(80, 80, 120, 255);

    @property({
        tooltip: '夕方(timeOfDay ≒ 0.75)のときの色。\nオレンジ寄りの色を指定すると夕焼け感が出ます。'
    })
    public duskColor: Color = new Color(255, 180, 150, 255);

    @property({
        tooltip: '朝焼け(timeOfDay ≒ 0.25)のときの色。\n少し紫〜ピンク寄りの明るい色が合います。'
    })
    public dawnColor: Color = new Color(220, 200, 255, 255);

    @property({
        tooltip: '時間に対する明るさ変化のカーブ種類。\nLINEAR: 単純な線形補間。\nSMOOTH_STEP: 緩やかに切り替わるカーブ。\nSHARP: 朝夕の変化がやや急峻になります。',
        type: BrightnessCurveType
    })
    public brightnessCurve: BrightnessCurveType = BrightnessCurveType.SMOOTH_STEP;

    @property({
        tooltip: '子ノードの Renderer(Sprite, Label, MeshRenderer など)にも色補正を適用するかどうか。'
    })
    public applyToChildren: boolean = true;

    @property({
        tooltip: '任意のタグ文字列。このコンポーネント単体では特別な意味は持ちませんが、\n外部スクリプトから特定タグを持つ DayNightTint を検索して制御するなどの用途に使えます。'
    })
    public targetGroupTag: string = '';

    @property({
        tooltip: 'ON にすると、起動時や警告などのログをコンソールに詳細表示します。'
    })
    public debugLog: boolean = false;

    // キャッシュ用配列
    private _uiRenderers: UIRenderer[] = [];
    private _meshRenderers: MeshRenderer[] = [];

    onLoad() {
        // Renderer をキャッシュしておく(パフォーマンス向上)
        this._cacheRenderers();

        if (this._uiRenderers.length === 0 && this._meshRenderers.length === 0) {
            console.warn('[DayNightTint] 対象ノードおよび子ノードに UIRenderer / MeshRenderer が見つかりません。色補正は行われません。', this.node);
        } else if (this.debugLog) {
            console.log(`[DayNightTint] UIRenderer: ${this._uiRenderers.length}, MeshRenderer: ${this._meshRenderers.length} を検出しました。`, this.node);
        }

        // 初期状態の色を一度適用
        this._applyTint();
    }

    start() {
        if (this.debugLog) {
            console.log('[DayNightTint] start() 呼び出し。初期 timeOfDay =', this.timeOfDay.toFixed(3), 'ノード =', this.node.name);
        }
    }

    update(deltaTime: number) {
        if (this.autoUpdateTime) {
            const dayLen = Math.max(this.dayLengthSeconds, 0.001);
            const speed = 1.0 / dayLen; // 1秒あたりどれだけ進むか
            this.timeOfDay += speed * deltaTime;

            // 0〜1 の範囲でループ
            if (this.timeOfDay > 1.0) {
                this.timeOfDay -= 1.0;
            } else if (this.timeOfDay < 0.0) {
                this.timeOfDay += 1.0;
            }
        }

        this._applyTint();
    }

    /**
     * 外部から時間をセットしたい場合の API。
     * 0〜1 の範囲にクランプされます。
     */
    public setTimeOfDay(value: number) {
        this.timeOfDay = math.clamp01(value);
        if (this.debugLog) {
            console.log('[DayNightTint] setTimeOfDay() により時間が更新されました:', this.timeOfDay.toFixed(3));
        }
        this._applyTint();
    }

    /**
     * 現在の timeOfDay に対応する色を計算して適用する。
     */
    private _applyTint() {
        const t = math.clamp01(this.timeOfDay);

        // 0〜1 を 4区間に分割して、夜→朝→昼→夕→夜 を補間する
        const color = this._evaluateColor(t);

        // 実際に Renderer へ適用
        for (const r of this._uiRenderers) {
            r.color = color;
        }
        for (const mr of this._meshRenderers) {
            // MeshRenderer は color プロパティを持たないので、sharedMaterial の色を変えるなどの方法が必要。
            // ここでは簡易的に、mainColor プロパティ(存在する場合)を変更してみる。
            const mat = mr.getSharedMaterial(0);
            if (mat && (mat as any).setProperty) {
                // 多くの標準シェーダでは "mainColor" か "albedo" が色プロパティとして使われます。
                // プロジェクトのシェーダに合わせて適宜変更してください。
                try {
                    (mat as any).setProperty('mainColor', color);
                } catch (e) {
                    // 失敗してもゲームが止まらないように握りつぶす
                    if (this.debugLog) {
                        console.warn('[DayNightTint] MeshRenderer のマテリアルに mainColor プロパティが見つかりませんでした。', mr.node.name);
                    }
                }
            }
        }
    }

    /**
     * timeOfDay に応じて 4点 (night - dawn - day - dusk - night) を補間して Color を決定する。
     */
    private _evaluateColor(t: number): Color {
        // 区間の境界
        const dawnStart = 0.0;
        const dawnEnd = 0.25;
        const dayEnd = 0.5;
        const duskEnd = 0.75;
        const nightEnd = 1.0;

        let fromColor: Color;
        let toColor: Color;
        let localT: number;

        if (t <= dawnEnd) {
            // 夜(0) → 朝(0.25)
            fromColor = this.nightColor;
            toColor = this.dawnColor;
            localT = this._remap(t, dawnStart, dawnEnd);
        } else if (t <= dayEnd) {
            // 朝(0.25) → 昼(0.5)
            fromColor = this.dawnColor;
            toColor = this.dayColor;
            localT = this._remap(t, dawnEnd, dayEnd);
        } else if (t <= duskEnd) {
            // 昼(0.5) → 夕(0.75)
            fromColor = this.dayColor;
            toColor = this.duskColor;
            localT = this._remap(t, dayEnd, duskEnd);
        } else {
            // 夕(0.75) → 夜(1.0)
            fromColor = this.duskColor;
            toColor = this.nightColor;
            localT = this._remap(t, duskEnd, nightEnd);
        }

        // カーブ適用
        localT = this._applyCurve(localT);

        // Color.lerp は out 引数が必要なので、一時オブジェクトを使う
        const out = new Color();
        Color.lerp(out, fromColor, toColor, localT);
        return out;
    }

    /**
     * [min, max] を [0,1] に正規化
     */
    private _remap(value: number, min: number, max: number): number {
        if (max - min <= 0.00001) {
            return 0;
        }
        return math.clamp01((value - min) / (max - min));
    }

    /**
     * 設定されたカーブタイプに応じて t を変形する。
     */
    private _applyCurve(t: number): number {
        t = math.clamp01(t);
        switch (this.brightnessCurve) {
            case BrightnessCurveType.LINEAR:
                return t;
            case BrightnessCurveType.SMOOTH_STEP:
                // Smoothstep: 3t^2 - 2t^3
                return t * t * (3 - 2 * t);
            case BrightnessCurveType.SHARP:
                // 少し急峻な S 字カーブ
                // ここでは簡易的に SmoothStep を2回適用
                let s = t * t * (3 - 2 * t);
                return s * s * (3 - 2 * s);
            default:
                return t;
        }
    }

    /**
     * このノードおよび子ノードから UIRenderer / MeshRenderer を収集してキャッシュする。
     */
    private _cacheRenderers() {
        this._uiRenderers.length = 0;
        this._meshRenderers.length = 0;

        if (this.applyToChildren) {
            this.node.getComponentsInChildren(UIRenderer, this._uiRenderers);
            this.node.getComponentsInChildren(MeshRenderer, this._meshRenderers);
        } else {
            const ui = this.node.getComponent(UIRenderer);
            if (ui) {
                this._uiRenderers.push(ui);
            }
            const mr = this.node.getComponent(MeshRenderer);
            if (mr) {
                this._meshRenderers.push(mr);
            }
        }

        if (this.debugLog) {
            console.log('[DayNightTint] _cacheRenderers():', {
                applyToChildren: this.applyToChildren,
                uiCount: this._uiRenderers.length,
                meshCount: this._meshRenderers.length,
            }, this.node);
        }
    }
}

コードのポイント解説

  • onLoad()
    – 対象ノード(および子ノード)から UIRenderer / MeshRenderer を収集してキャッシュ。
    – 見つからない場合は console.warn で警告を出しつつ、ゲーム自体は動作し続ける防御的実装。
    – 初期状態の timeOfDay に応じた色を一度適用。
  • update(deltaTime)
    autoUpdateTime が ON の場合のみ、dayLengthSeconds に基づいて timeOfDay を進める。
    timeOfDay は 0〜1 の範囲でループさせる。
    – 毎フレーム this._applyTint() を呼び出して色を更新。
  • setTimeOfDay()
    – 外部スクリプトから timeOfDay を明示的に設定したい場合に使える public メソッド。
    – 値を 0〜1 にクランプし、即座に色を更新。
  • _evaluateColor()
    timeOfDay を 4 区間(夜→朝→昼→夕→夜)に分割し、
    nightColor, dawnColor, dayColor, duskColor の間を補間して最終的な色を算出。
    – 区間内の補間係数 localT に対して _applyCurve() でカーブ補正を行い、自然な明暗変化を実現。
  • _cacheRenderers()
    applyToChildren に応じて、対象ノードのみ/子孫ノードすべてから Renderer を収集。
    – 収集は onLoad() で 1 回だけ行い、update() では毎フレーム走査しないため軽量。
  • MeshRenderer への適用
    – 汎用的な対応として、マテリアルの mainColor プロパティに色をセットする例を記述。
    – プロジェクトのシェーダ設定によってはプロパティ名が異なるため、必要に応じて “albedo” などに変更してください。

使用手順と動作確認

1. スクリプトファイルの作成

  1. エディタの Assets パネルで、スクリプトを置きたいフォルダ(例: assets/scripts)を右クリックします。
  2. Create → TypeScript を選択し、ファイル名を DayNightTint.ts として作成します。
  3. 作成された DayNightTint.ts をダブルクリックしてエディタで開き、
    既存のテンプレートコードをすべて削除して、前述の TypeScript コードを貼り付けて保存します。

2. テスト用ノードの作成(2D UI の例)

  1. Hierarchy パネルで右クリック → Create → UI → Canvas を選択し、Canvas を作成します(既にある場合は流用してOK)。
  2. Canvas を選択した状態で、右クリック → Create → UI → Sprite を選択し、テスト用の Sprite ノードを作成します。
    ノード名は分かりやすく BackgroundSprite などにしておくと良いです。
  3. Inspector で Sprite の SpriteFrame に、任意の画像(背景用のテクスチャなど)を設定します。

3. DayNightTint コンポーネントのアタッチ

  1. Hierarchy で BackgroundSprite ノードを選択します。
  2. Inspector 下部の Add Component ボタンをクリックします。
  3. メニューから Custom → DayNightTint を選択してアタッチします。

4. Inspector プロパティの設定例

Inspector 上で、DayNightTint の各プロパティを次のように設定してみてください。

  • autoUpdateTime: チェック ON(自動進行させる)
  • timeOfDay: 0.25(朝) ※autoUpdateTime ON の場合は起動後に自動で変化します。
  • dayLengthSeconds: 30(30秒で 1日が一周)
  • dayColor: (255, 255, 255, 255)
  • nightColor: (60, 60, 120, 255)
  • duskColor: (255, 170, 130, 255)
  • dawnColor: (220, 210, 255, 255)
  • brightnessCurve: SMOOTH_STEP
  • applyToChildren: チェック ON(Canvas 以下に背景などを複数置く場合に便利)
  • targetGroupTag: “Background” など任意
  • debugLog: 初回は ON にしてログを確認しても良いです

5. プレビューで動作確認

  1. エディタ右上の Play ボタン(ブラウザ or シミュレータ)をクリックしてゲームを実行します。
  2. 数十秒間観察すると、背景 Sprite の色が「夜 → 朝 → 昼 → 夕 → 夜」とゆっくり変化していくのが分かります。
  3. 変化が遅すぎる/速すぎる場合は、dayLengthSeconds を調整してください。
    例: 10秒で一周させたい場合は dayLengthSeconds = 10 にします。

6. 時間を手動で動かして確認する(autoUpdateTime OFF)

色の変化を細かく確認したい場合は、以下のようにします。

  1. Inspector で autoUpdateTime のチェックを OFF にします。
  2. ゲームを再生(Play)します。
  3. ゲーム実行中に、Inspector の timeOfDay スライダー を 0.0〜1.0 の間で動かしてみてください。
    それに合わせて即座に色が変化するはずです。

7. MeshRenderer(3D オブジェクト)での使用例

3D シーンで、建物や地形の色を昼夜で変えたい場合も同様に使えます。

  1. Hierarchy で 3D オブジェクト(例: Ground ノード)を作成し、MeshRenderer を付与します(3D オブジェクトのプリセットには最初から付いていることが多い)。
  2. そのノードに対して Add Component → Custom → DayNightTint を追加します。
  3. Inspector で applyToChildren を ON にしておくと、子オブジェクトの MeshRenderer にも色が適用されます。
  4. 使用しているマテリアルのシェーダに mainColor という Color プロパティが存在することを確認してください。
    存在しない場合は、DayNightTint.tssetProperty('mainColor', color) の部分を、
    プロジェクトのシェーダで使用しているプロパティ名(例: 'albedo')に変更してください。

まとめ

DayNightTint コンポーネントは、

  • 外部の GameManager などに一切依存せず、単体で「昼夜サイクルに応じた色補正」を実現できる。
  • Inspector から 時間の進行速度・色・変化カーブ を調整できるため、デザイナーがノーコードで世界の雰囲気を作り込める
  • UI / 2D / 3D いずれにも適用可能で、背景・キャラクター・UI などを一括で昼夜対応させられる。
  • タグ文字列(targetGroupTag)を使えば、将来的に「背景だけ昼夜変化」「キャラは一定の明るさ」などの制御も容易。

このように、アタッチするだけで完結する汎用コンポーネントとして実装しておくと、

  • プロトタイプ段階で素早く「時間帯の雰囲気」を試せる。
  • 別プロジェクトにも DayNightTint.ts をそのままコピーして使い回せる。
  • 将来的に「本格的な時間管理システム」を導入した際も、setTimeOfDay() を呼ぶだけで連携できる。

という利点があり、ゲーム開発の効率を大きく高めることができます。
まずはシンプルなシーンに導入して色の変化を体感し、自分のゲームに合った色・スピード・カーブを探ってみてください。

Godot 4ゲーム制作 実践ドリル 100本ノック

新品価格
¥1,250から
(2025/12/13 21:27時点)

Godot4& GDScriptではじめる 2Dゲーム開発レシピ

新品価格
¥590から
(2025/12/13 21:46時点)

Unity 6 C#スクリプト 100本ノック

新品価格
¥1,230から
(2025/12/29 10:28時点)

Cocos Creator100本ノックTypeScriptで書く!

新品価格
¥1,250から
(2025/12/29 10:32時点)

URLをコピーしました!