【Cocos Creator】アタッチするだけ!CreditRoll (スタッフロール)の実装方法【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】CreditRoll(スタッフロール)の実装:アタッチするだけでテキストファイルから自動スクロールする汎用スクリプト

このガイドでは、任意のNodeにアタッチするだけで、指定したテキストファイルを読み込み、下から上へ自動スクロールする「スタッフロール」コンポーネントを実装します。
外部のGameManagerやシングルトンに一切依存せず、インスペクタでテキストファイルとスクロール速度などを設定するだけで使えるように設計します。


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

1. 機能要件の整理

  • テキストファイル(.txt)を読み込む(プロジェクト内のTextAssetを参照)
  • 読み込んだテキストを Label で表示する
  • テキストを下から上へ一定速度でスクロールさせる
  • スクロールが終わったら
    • そのまま停止する
    • 指定時間後に自動でリスタートする(オプション)
  • スクロール開始位置・終了位置を、画面(親ノード)内で柔軟に調整可能にする
  • 外部スクリプトに依存せず、このコンポーネント単体で完結させる

2. ノード構造と依存コンポーネント

このコンポーネントは以下の前提で動作します。

  • このスクリプトをアタッチするノードには Label コンポーネントが付いていること
    • なければ onLoad 内で検出し、エラーログを出力して処理を停止
  • 親ノードのサイズ(高さ)をスクロール範囲の基準にできるよう、親ノードのUITransformを参照(なければ警告を出し、代わりに手動パラメータを使用)

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

以下のプロパティを用意します。

  • textAsset : TextAsset | null
    • スタッフロールの内容を記述したテキストファイル
    • Assets 内で作成した .txt を ドラッグ&ドロップで割り当てる
  • scrollSpeed : number(デフォルト: 50)
    • 1秒あたりのスクロール速度(単位:px/秒
    • 正の値で下→上方向にスクロール
  • startOffsetY : number(デフォルト: -100)
    • スクロール開始時のY位置のオフセット
    • 親ノードの中央を基準に、どのくらい下からスタートするかを指定
    • 負の値ほど画面の下側からスタート
  • endOffsetY : number(デフォルト: 100)
    • スクロール終了とみなすY位置のオフセット
    • 親ノードの中央を基準に、どのくらい上までスクロールしたら終了とするかを指定
    • 正の値ほど画面の上側で終了
  • useParentHeightAutoRange : boolean(デフォルト: true)
    • 親ノードの高さから自動的に startOffsetY / endOffsetY を計算するかどうか
    • ON の場合:
      • 開始位置: 親の下端より少し下
      • 終了位置: 親の上端より少し上
    • 親に UITransform がない場合は自動計算せず、startOffsetY / endOffsetY の値をそのまま使用
  • autoRestart : boolean(デフォルト: false)
    • スクロール終了後に自動で最初からやり直すかどうか
  • restartDelay : number(デフォルト: 2.0)
    • autoRestart が有効な場合にのみ使用
    • スクロール終了からリスタートまでの待機時間(秒)
  • playOnStart : boolean(デフォルト: true)
    • コンポーネント有効化時(onEnable)に自動で再生を開始するかどうか
  • loopPadding : number(デフォルト: 50)
    • useParentHeightAutoRange が有効な場合に使用
    • 親の上下端よりどのくらい外側から/外側までスクロールさせるか(px)
  • debugLog : boolean(デフォルト: false)
    • 内部状態をログに出力するかどうか(デバッグ用)

これらのプロパティにより、他のノードやスクリプトに依存せず、インスペクタ上の設定だけで動作と見た目を調整できます。


TypeScriptコードの実装

以下が完成した CreditRoll.ts の全コードです。


import { _decorator, Component, Node, Label, TextAsset, UITransform, Vec3, log, warn, error } from 'cc';
const { ccclass, property } = _decorator;

/**
 * CreditRoll
 * 指定した TextAsset (.txt) の内容を Label に流し込み、
 * 下から上へ自動スクロールする汎用コンポーネント。
 *
 * 必須コンポーネント:
 *  - Label (このスクリプトをアタッチした Node に追加してください)
 */
@ccclass('CreditRoll')
export class CreditRoll extends Component {

    @property({
        type: TextAsset,
        tooltip: 'スタッフロールの内容を記述した TextAsset (.txt) を指定します。'
    })
    public textAsset: TextAsset | null = null;

    @property({
        tooltip: '1秒あたりのスクロール速度(px/秒)。正の値で下から上へスクロールします。'
    })
    public scrollSpeed: number = 50;

    @property({
        tooltip: '開始位置のYオフセット(useParentHeightAutoRange=false のときに使用)。\n親ノード中央を基準に、負の値ほど画面下側からスタートします。'
    })
    public startOffsetY: number = -100;

    @property({
        tooltip: '終了位置のYオフセット(useParentHeightAutoRange=false のときに使用)。\n親ノード中央を基準に、正の値ほど画面上側で終了します。'
    })
    public endOffsetY: number = 100;

    @property({
        tooltip: '親ノードの高さから開始位置・終了位置を自動計算するかどうか。\nON の場合、startOffsetY / endOffsetY は無視されます(親に UITransform がない場合を除く)。'
    })
    public useParentHeightAutoRange: boolean = true;

    @property({
        tooltip: '自動範囲計算時に、親ノードの上下端からどれだけ外側までスクロールさせるか(px)。'
    })
    public loopPadding: number = 50;

    @property({
        tooltip: 'スクロール終了後に自動で最初から再生し直すかどうか。'
    })
    public autoRestart: boolean = false;

    @property({
        tooltip: 'autoRestart が有効な場合、スクロール終了から再スタートまでの待機時間(秒)。'
    })
    public restartDelay: number = 2.0;

    @property({
        tooltip: 'コンポーネント有効化時に自動で再生を開始するかどうか。'
    })
    public playOnStart: boolean = true;

    @property({
        tooltip: '内部状態をログ出力するかどうか(デバッグ用)。'
    })
    public debugLog: boolean = false;

    private _label: Label | null = null;
    private _uiTransform: UITransform | null = null;
    private _parentTransform: UITransform | null = null;

    private _startY: number = 0;
    private _endY: number = 0;
    private _isPlaying: boolean = false;
    private _restartTimer: number = 0;

    onLoad() {
        // 必須コンポーネントの取得と検証
        this._label = this.getComponent(Label);
        if (!this._label) {
            error('[CreditRoll] Label コンポーネントが見つかりません。このノードに Label を追加してください。');
            return;
        }

        this._uiTransform = this.getComponent(UITransform);
        if (!this._uiTransform) {
            warn('[CreditRoll] UITransform が見つかりません。レイアウトに依存した高度な調整はできませんが、スクロール自体は動作します。');
        }

        if (this.node.parent) {
            this._parentTransform = this.node.parent.getComponent(UITransform);
            if (!this._parentTransform && this.useParentHeightAutoRange) {
                warn('[CreditRoll] 親ノードに UITransform がありません。useParentHeightAutoRange を false にして手動で startOffsetY / endOffsetY を設定してください。');
            }
        } else if (this.useParentHeightAutoRange) {
            warn('[CreditRoll] 親ノードが存在しません。useParentHeightAutoRange を false にして手動で startOffsetY / endOffsetY を設定してください。');
        }

        // テキストの初期読み込み
        this._applyTextAsset();
    }

    onEnable() {
        if (this.playOnStart) {
            this.startRoll();
        }
    }

    update(dt: number) {
        if (!this._isPlaying) {
            // 自動リスタート待機中の処理
            if (this.autoRestart && this._restartTimer > 0) {
                this._restartTimer -= dt;
                if (this._restartTimer <= 0) {
                    this.startRoll();
                }
            }
            return;
        }

        if (!this._label) {
            return;
        }

        const currentPos = this.node.position;
        const newY = currentPos.y + this.scrollSpeed * dt;
        this.node.setPosition(new Vec3(currentPos.x, newY, currentPos.z));

        // 終了位置に到達したか判定
        if (newY >= this._endY) {
            this._onReachEnd();
        }
    }

    /**
     * TextAsset の内容を Label に適用する。
     */
    private _applyTextAsset() {
        if (!this._label) {
            return;
        }

        if (!this.textAsset) {
            warn('[CreditRoll] textAsset が設定されていません。インスペクタで TextAsset (.txt) を割り当ててください。');
            this._label.string = '';
            return;
        }

        this._label.string = this.textAsset.text;
        if (this.debugLog) {
            log('[CreditRoll] TextAsset を適用しました。文字数:', this.textAsset.text.length);
        }
    }

    /**
     * スクロール範囲(開始Y・終了Y)を計算する。
     */
    private _calculateRange() {
        // デフォルトはユーザー指定値
        let startY = this.startOffsetY;
        let endY = this.endOffsetY;

        if (this.useParentHeightAutoRange && this._parentTransform) {
            const parentHeight = this._parentTransform.height;
            // 親の中央を基準に、下端/上端から少し外側までスクロールさせる
            startY = -parentHeight / 2 - this.loopPadding;
            endY = parentHeight / 2 + this.loopPadding;
            if (this.debugLog) {
                log('[CreditRoll] 親の高さから自動計算 startY:', startY, 'endY:', endY);
            }
        } else if (this.useParentHeightAutoRange && !this._parentTransform) {
            // 自動計算が有効だが親に UITransform がない場合
            warn('[CreditRoll] useParentHeightAutoRange が true ですが、親に UITransform がないため、startOffsetY / endOffsetY をそのまま使用します。');
        }

        this._startY = startY;
        this._endY = endY;
    }

    /**
     * スクロールを開始(または再開)する。
     */
    public startRoll() {
        if (!this._label) {
            error('[CreditRoll] Label が存在しないため、startRoll を実行できません。');
            return;
        }

        // 念のため毎回テキストを適用(TextAssetを差し替えた場合に対応)
        this._applyTextAsset();

        // 範囲計算
        this._calculateRange();

        // 開始位置にセット
        const pos = this.node.position;
        this.node.setPosition(new Vec3(pos.x, this._startY, pos.z));

        this._isPlaying = true;
        this._restartTimer = 0;

        if (this.debugLog) {
            log('[CreditRoll] スクロール開始: startY =', this._startY, 'endY =', this._endY);
        }
    }

    /**
     * スクロールを一時停止する。
     */
    public pauseRoll() {
        if (!this._isPlaying) return;
        this._isPlaying = false;
        if (this.debugLog) {
            log('[CreditRoll] スクロール一時停止');
        }
    }

    /**
     * スクロールを停止し、開始位置に戻す。
     */
    public stopRoll() {
        this._isPlaying = false;
        this._restartTimer = 0;
        // 開始位置へ戻す
        const pos = this.node.position;
        this.node.setPosition(new Vec3(pos.x, this._startY, pos.z));
        if (this.debugLog) {
            log('[CreditRoll] スクロール停止 & 位置リセット');
        }
    }

    /**
     * スクロール終了時の処理。
     */
    private _onReachEnd() {
        this._isPlaying = false;
        if (this.debugLog) {
            log('[CreditRoll] スクロール終了位置に到達');
        }

        if (this.autoRestart) {
            this._restartTimer = this.restartDelay;
            if (this.debugLog) {
                log('[CreditRoll] autoRestart 有効。', this.restartDelay, '秒後に再スタートします。');
            }
        }
    }
}

コードのポイント解説

  • onLoad
    • 必須コンポーネント Label を取得し、存在しなければ error を出して終了
    • UITransform / 親の UITransform を取得し、自動範囲計算の可否を判定
    • _applyTextAsset() でテキストファイルを Label に反映
  • onEnable
    • playOnStart が true の場合、自動で startRoll() を呼んでスクロール開始
  • update(dt)
    • _isPlaying が true のときだけ Y 座標を scrollSpeed * dt だけ増加
    • Y が _endY に到達したら _onReachEnd() を呼び、停止または自動リスタート
    • 停止中かつ autoRestart が true のとき、_restartTimer を減算し、0以下になったら再スタート
  • _calculateRange()
    • useParentHeightAutoRange が true かつ 親に UITransform がある場合:
      • 親の高さから start / end を自動計算(親の上下端より loopPadding 分外側までスクロール)
    • そうでない場合は startOffsetY / endOffsetY をそのまま使用
  • startRoll / pauseRoll / stopRoll
    • インスペクタからだけでなく、他のスクリプトからも簡単に制御できるように、公開メソッドとして用意
    • ただし本コンポーネント自体は他のスクリプトに依存していないため、このまま単体でも完結して動作

使用手順と動作確認

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

  1. Creator の Assets パネルで右クリック
    Create > TypeScript を選択します。
  2. ファイル名を CreditRoll.ts に変更します。
  3. 作成された CreditRoll.ts をダブルクリックして開き、
    先ほどの 全コード をそのまま貼り付けて保存します。

2. テキストファイル(スタッフロール)の準備

  1. Assets パネルで右クリック
    Create > Text を選択します。
  2. ファイル名を credits.txt などに変更します。
  3. ダブルクリックして開き、例として以下のような内容を入力します。
    STAFF
    
    Game Director
    John Doe
    
    Lead Programmer
    Jane Smith
    
    Art Director
    Alice Johnson
    
    Special Thanks
    All Players
  4. 保存して閉じます。

3. スタッフロール用ノードの作成

  1. Hierarchy パネルで右クリック
    Create > UI > Canvas を作成(まだなければ)。
  2. Canvas の子として 右クリック > Create > UI > Label を作成します。
  3. Label ノードを選択し、Inspector の Label コンポーネントで
    • String は空で構いません(スクリプトが上書きします)。
    • Horizontal AlignCenter にすると見やすくなります。
    • OverflowNONECLAMP にしておくと、親のサイズに合わせて表示を制限できます(好みに応じて)。

4. CreditRoll コンポーネントのアタッチ

  1. 先ほど作成した Label ノード を選択します。
  2. Inspector の下部で Add Component ボタンをクリック。
  3. Custom カテゴリから CreditRoll を選択してアタッチします。

5. プロパティの設定

Label ノードに追加された CreditRoll コンポーネントの各プロパティを設定します。

  • Text Asset:
    • Assets パネルから先ほど作成した credits.txt をドラッグ&ドロップします。
  • Scroll Speed:
    • 例: 5080 くらいから試すと良いです。
    • 値を大きくすると速くスクロールします。
  • Use Parent Height Auto Range:
    • Canvas の下に Label を置いている場合、ON のままで OK です。
    • 親の UITransform 高さを使って、画面下の少し外から上の少し外まで自動でスクロールします。
  • Loop Padding:
    • 例: 50100
    • 値を大きくすると、画面外からゆっくり入ってきて、画面外へ完全に消えてから終了します。
  • Start Offset Y / End Offset Y:
    • Use Parent Height Auto Range を OFF にした場合のみ有効です。
    • 例えば:
      • Start Offset Y = -300
      • End Offset Y = 300

      とすると、親ノード中央を基準に -300 から +300 までスクロールします。

  • Auto Restart:
    • スタッフロールをループ再生したい場合は ON。
  • Restart Delay:
    • Auto Restart が ON のときのみ使用されます。
    • 例: 2.0 にすると、スクロールが終わってから 2 秒後に再スタートします。
  • Play On Start:
    • シーン開始と同時に自動スクロールさせたい場合は ON(デフォルト)。
    • 別のUI操作から startRoll() を呼びたい場合は OFF にしておきます。
  • Debug Log:
    • 挙動を確認したいときだけ ON にして、Console にログを出すとデバッグしやすくなります。

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

  1. エディタ右上の Play(プレビュー)ボタン を押してシーンを実行します。
  2. シーンが開始すると、Label に credits.txt の内容が読み込まれ、下から上へ自動スクロールしていくのを確認できます。
  3. スクロール速度や範囲がイメージと違う場合は、
    • Scroll Speed
    • Loop Padding(または Start Offset Y / End Offset Y

    を少しずつ変更しながら、プレビューし直して調整してください。


まとめ

この CreditRoll コンポーネントは、

  • TextAsset を指定して
  • 任意の Label ノードにアタッチするだけで
  • 自動スクロールするスタッフロールを実現できる、完全に独立した汎用スクリプトです。

他の GameManager やシングルトンに一切依存せず、インスペクタのプロパティだけで挙動を完結に制御できるため、

  • タイトル画面の「スタッフロール」ボタンから遷移した先のシーン
  • ゲームクリア後のエンディングシーン
  • イベントシーン内のクレジット表示

など、様々な場面で簡単に再利用できます。

さらに、autoRestartplayOnStart、範囲自動計算などのオプションを組み合わせることで、ループする背景テキストや、ニュースティッカー風の演出などにも応用できます。

このコンポーネントをベースに、フォントサイズやフェードイン/フェードアウト、BGM再生トリガーなどを追加していけば、よりリッチなエンディング演出も簡単に構築できるはずです。

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をコピーしました!