【Cocos Creator】アタッチするだけ!AutoScroll (自動スクロール)の実装方法【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】AutoScroll の実装:アタッチするだけで親 ScrollView をゆっくり自動スクロールする汎用スクリプト

ゲームのエンドロールやお知らせ画面などで、テキストをゆっくり自動スクロールさせたい場面はよくあります。本記事では、ScrollView のコンテンツやその子ノードにアタッチするだけで、親の ScrollView を一定速度で自動スクロールしてくれる「AutoScroll」コンポーネントを、Cocos Creator 3.8 / TypeScript で実装します。

外部の GameManager などには一切依存せず、インスペクタで速度やスクロール方向、ループ動作などを調整できる完全独立コンポーネントとして設計します。


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

要件整理

  • このコンポーネントをアタッチしたノードの 親階層にある ScrollView を自動でスクロールさせる。
  • 主な用途はクレジット表示なので、縦方向(上方向 or 下方向)へのゆっくりスクロールを想定。
  • スクロールが端まで到達したときに、停止・ループ(先頭に戻る)などを選べるようにする。
  • ゲームの時間スケール(TimeScale)に影響されないように、リアルタイムベースで動作させる。
  • 他のカスタムスクリプトに依存せず、ScrollView さえあれば単体で動作する。

ScrollView との関係

AutoScroll は以下のような前提で動作します。

  • AutoScroll をアタッチしたノードの 親階層を上方向にたどり、最初に見つかった ScrollView を自動スクロール対象とする。
  • ScrollView の 垂直スクロール(vertical) を利用する。
  • 水平方向のスクロールは今回の用途から外し、縦専用とする(必要なら拡張可能な設計にする)。

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

AutoScroll コンポーネントで設定できるプロパティと役割は以下の通りです。

  • scrollSpeed (number)
    • ツールチップ: 「1秒あたりのスクロール量(0.0〜1.0)。正の値で上方向、負の値で下方向にスクロールします。」
    • ScrollView の scrollToOffset ではなく、normalizedPosition.y を 0.0〜1.0 の範囲で毎フレーム加算するイメージで扱います。
    • 例: 0.05 にすると、約 20 秒で 0 → 1 までスクロール。
  • autoStart (boolean)
    • ツールチップ: 「シーン開始時に自動スクロールを開始するかどうか。」
    • オンの場合、start() で自動的にスクロール開始。
    • オフの場合、スクリプトの public startScroll() を他スクリプトから呼び出すことで開始(単体でも動作するが、柔軟性を確保)。
  • loopMode (Enum)
    • ツールチップ: 「スクロール端に到達した時の動作。」
    • 選択肢:
      • None: 端に到達したら停止。
      • Restart: 端に到達したら反対側の端に戻ってスクロールを継続(ループ)。
  • startFromEnd (boolean)
    • ツールチップ: 「スクロール開始位置を末尾(下端)からにするかどうか。」
    • クレジットを下から上に流す場合などに便利。
    • true の場合、開始時に normalizedPosition.y0 または 1 に設定(方向に応じて決定)。
  • useUnscaledTime (boolean)
    • ツールチップ: 「TimeScale の影響を受けないリアルタイムでスクロールするかどうか。」
    • true の場合、game.deltaTime ではなく director.getTotalFrames() を利用して、実時間ベースで計算(擬似的な方法)。
    • false の場合、通常の dt を使用。
  • debugLog (boolean)
    • ツールチップ: 「ScrollView が見つからない場合などに詳細なログを出力するかどうか。」
    • 開発中だけオンにしておくとデバッグが楽になります。

※ ScrollView への参照は 自動で親階層から検索するため、インスペクタで手動設定する必要はありません。見つからない場合は警告ログを出して自動スクロールを無効化します。


TypeScriptコードの実装


import { _decorator, Component, Node, ScrollView, Vec2, director, game, Enum, log, warn } from 'cc';
const { ccclass, property } = _decorator;

/**
 * スクロール端に到達した時の動作モード
 */
enum AutoScrollLoopMode {
    None = 0,      // 端で停止
    Restart = 1,   // 反対側の端に戻ってループ
}

Enum(AutoScrollLoopMode); // インスペクタに表示するための登録

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

    @property({
        tooltip: '1秒あたりのスクロール量(0.0〜1.0)。正の値で上方向、負の値で下方向にスクロールします。',
        slide: true,
        range: [-1, 1],
    })
    public scrollSpeed: number = 0.05;

    @property({
        tooltip: 'シーン開始時に自動スクロールを開始するかどうか。',
    })
    public autoStart: boolean = true;

    @property({
        type: AutoScrollLoopMode,
        tooltip: 'スクロール端に到達した時の動作。\nNone: 端で停止\nRestart: 反対側の端に戻ってループ',
    })
    public loopMode: AutoScrollLoopMode = AutoScrollLoopMode.None;

    @property({
        tooltip: 'スクロール開始位置を末尾(下端 or 上端)からにするかどうか。\nクレジットを下から上に流す場合などに使用します。',
    })
    public startFromEnd: boolean = false;

    @property({
        tooltip: 'TimeScale の影響を受けないリアルタイムでスクロールするかどうか。',
    })
    public useUnscaledTime: boolean = false;

    @property({
        tooltip: 'ScrollView が見つからない場合などに詳細なログを出力するかどうか。',
    })
    public debugLog: boolean = false;

    // 内部状態
    private _scrollView: ScrollView | null = null;
    private _isScrolling: boolean = false;
    private _lastFrame: number = 0; // useUnscaledTime 用
    private _normalizedPos: Vec2 = new Vec2(0, 1); // ScrollView.normalizedPosition をキャッシュ

    onLoad() {
        // 親階層から ScrollView を検索
        this._scrollView = this._findParentScrollView();

        if (!this._scrollView) {
            warn('[AutoScroll] 親階層に ScrollView が見つかりません。自動スクロールは無効化されます。');
            return;
        }

        // 垂直スクロールが有効かチェック
        if (!this._scrollView.vertical) {
            warn('[AutoScroll] 対象の ScrollView で vertical が無効になっています。自動スクロールを行うには vertical を有効にしてください。');
        }

        // 初期位置の設定
        this._normalizedPos = this._scrollView.getScrollOffset(); // 実際には normalizedPosition を取得したいが、API上は直接アクセス
        // ScrollView.normalizedPosition は public プロパティなので直接参照
        // @ts-ignore: プロパティ存在チェックをバイパス
        this._normalizedPos = this._scrollView.normalizedPosition.clone();

        if (this.startFromEnd) {
            this._setStartPositionByDirection();
        }

        // useUnscaledTime 用に現在のフレームを記録
        this._lastFrame = director.getTotalFrames();

        if (this.debugLog) {
            log('[AutoScroll] onLoad 完了。ScrollView を検出しました。');
        }
    }

    start() {
        if (this.autoStart) {
            this.startScroll();
        }
    }

    update(dt: number) {
        if (!this._isScrolling || !this._scrollView) {
            return;
        }

        // 有効な ScrollView か確認
        if (!this._scrollView.isValid) {
            warn('[AutoScroll] ScrollView が無効になりました。自動スクロールを停止します。');
            this._isScrolling = false;
            return;
        }

        // 実際に使用する dt を計算
        let delta = dt;
        if (this.useUnscaledTime) {
            const currentFrame = director.getTotalFrames();
            const frameDiff = currentFrame - this._lastFrame;
            this._lastFrame = currentFrame;

            // 60fps を基準に実時間を近似
            const approxDelta = frameDiff / 60;
            delta = approxDelta;
        }

        // normalizedPosition を更新
        // ScrollView.normalizedPosition.y は 0 (下端) 〜 1 (上端)
        // scrollSpeed > 0 なら上方向(y を増加)、scrollSpeed < 0 なら下方向(y を減少)
        // @ts-ignore
        this._normalizedPos = this._scrollView.normalizedPosition.clone();

        const newY = this._normalizedPos.y + this.scrollSpeed * delta;

        // 端に到達したかどうか判定
        if (this.scrollSpeed > 0) {
            // 上方向スクロール(y → 1)
            if (newY >= 1) {
                this._handleReachEnd(1);
                return;
            }
        } else if (this.scrollSpeed  0 (上方向) なら下端(0)から、scrollSpeed < 0 (下方向) なら上端(1)から開始
        let startY = 0;

        if (this.scrollSpeed > 0) {
            startY = 0; // 下端から上へ
        } else if (this.scrollSpeed < 0) {
            startY = 1; // 上端から下へ
        } else {
            // scrollSpeed == 0 の場合は特に変更しない
            // ただし、ユーザが startFromEnd を true にしている場合もあるので、
            // デフォルトで下端から開始するようにしておく
            startY = 0;
        }

        this._setNormalizedY(startY);
    }

    /**
     * ScrollView.normalizedPosition.y を設定するヘルパー
     */
    private _setNormalizedY(y: number) {
        if (!this._scrollView) {
            return;
        }

        // 範囲を 0〜1 にクランプ
        const clampedY = Math.max(0, Math.min(1, y));

        // @ts-ignore: normalizedPosition は public プロパティ
        const current = this._scrollView.normalizedPosition as Vec2;
        const newPos = new Vec2(current.x, clampedY);

        // @ts-ignore
        this._scrollView.normalizedPosition = newPos;
        this._normalizedPos = newPos;
    }
}

コードのポイント解説

  • onLoad()
    • this._findParentScrollView() で、自分自身から親方向にたどって最初に見つかった ScrollView を取得します。
    • ScrollView が見つからなければ warn を出し、以降の処理はスキップ(防御的実装)。
    • startFromEnd が有効なら、_setStartPositionByDirection() で開始位置を端にセットします。
  • start()
    • autoStarttrue のときだけ startScroll() を呼び出し、自動スクロールを開始します。
  • update(dt)
    • _isScrollingtrue かつ _scrollView が存在するときだけ動作。
    • useUnscaledTimetrue の場合、director.getTotalFrames() の差分から擬似的な実時間 delta を計算します。
    • scrollSpeednormalizedPosition.y に加算し、0〜1 の範囲外に出たら _handleReachEnd() で端到達処理を行います。
  • _handleReachEnd()
    • loopMode === None なら端で停止して _isScrolling = false
    • loopMode === Restart なら 0 ↔ 1 を行き来するループ動作を行います。
  • _findParentScrollView()
    • this.node から親方向に current.parent をたどり、最初に見つかった ScrollView を返します。
    • これにより、AutoScroll を ScrollView 自身・content・その子ノードのどこに付けても動作します。

使用手順と動作確認

ここからは、実際に Cocos Creator 3.8.7 のエディタ上で AutoScroll を使ってみる手順を説明します。

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

  1. Assets パネルで任意のフォルダ(例: assets/scripts)を右クリックします。
  2. Create → TypeScript を選択します。
  3. 新しく作成されたスクリプトの名前を AutoScroll.ts に変更します。
  4. ダブルクリックしてエディタで開き、本文をすべて削除して、上記の TypeScript コードを丸ごと貼り付けて保存します。

2. ScrollView を使ったクレジット用 UI の準備

  1. Hierarchy パネルで右クリックし、Create → UI → ScrollView を選択します。
    • 自動的に ScrollView、Viewport、Content など必要なノードが作成されます。
  2. ScrollView ノードを選択し、Inspector で以下を確認・設定します。
    • ScrollView コンポーネントVerticalON になっていること。
    • クレジットを縦スクロールさせたいので、HorizontalOFF で構いません。
  3. ScrollView の Content ノード配下に、クレジット用の LabelLayout などを配置して、縦長の内容を作成します。
  4. Content の高さが ScrollView の表示領域より大きくなるように調整します(そうしないとスクロールしても見た目が変わりません)。

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

AutoScroll は、ScrollView 自身・Content・Content の子ノードなど、ScrollView の親階層に ScrollView が存在するノードならどこに付けても動きます。ここでは分かりやすく Content に付けます。

  1. Hierarchy で ScrollView → Viewport → Content ノードを選択します。
  2. Inspector の下部にある Add Component ボタンをクリックします。
  3. Custom → AutoScroll を選択してアタッチします。

4. プロパティの設定例

クレジットを「下から上にゆっくり流す」典型的な設定例を示します。

  • scrollSpeed: 0.03
    • 値が小さいほどゆっくりスクロールします。0.02〜0.05 くらいがクレジットにちょうど良い範囲です。
  • autoStart: true
    • シーンが再生されたら自動的にスクロール開始します。
  • loopMode: None
    • 一番上までスクロールしたら停止します。エンドロールなどでよく使うパターンです。
  • startFromEnd: true
    • 下端から上方向へ流したいので、有効にしておきます。
  • useUnscaledTime: false(まずはオフでOK)
    • ゲーム全体の TimeScale を変更する予定がなければオフで問題ありません。
  • debugLog: false(動作確認中だけ true にしてもよい)
    • ScrollView が見つからないなどの問題がある場合にログが出るので、トラブルシュートに便利です。

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

  1. エディタ右上の Preview ボタン(または ▶ ボタン)をクリックしてゲームを実行します。
  2. ゲーム画面に表示された ScrollView 内のクレジットテキストが、自動的に下から上に向かってゆっくりスクロールしていれば成功です。
  3. スクロール速度が速すぎる/遅すぎる場合は、scrollSpeed の値を少しずつ調整して、好みの速度に合わせてください。

6. よくあるつまづきポイント

  • ScrollView が見つからないと言われる
    • AutoScroll をアタッチしたノードの親階層のどこかに ScrollView があるか確認してください。
    • ScrollView より上(親)に AutoScroll を付けてしまうと、親方向にたどっても ScrollView が見つかりません。
  • スクロールしているようだが見た目が変わらない
    • Content の高さが ScrollView の表示領域より小さいと、スクロールしても見た目が変わりません。Content の高さを十分大きくしてください。
  • スクロール方向が逆
    • 下から上に流したいのに上から下に動いてしまう場合は、scrollSpeed の符号を逆にしてください(正→負、負→正)。
    • startFromEnd の設定も合わせて見直すと、意図した位置から流し始められます。

まとめ

今回実装した AutoScroll コンポーネントは、

  • 親階層から ScrollView を自動検出し、
  • インスペクタで 速度・ループモード・開始位置・時間スケール依存などを柔軟に調整でき、
  • 他のカスタムスクリプトやシングルトンに一切依存しない、完全に独立した汎用スクリプト

として設計されています。

クレジット表示だけでなく、

  • お知らせやニュースティッカーの自動スクロール
  • ランキングリストの自動デモ表示
  • チュートリアルテキストの自動送り

など、ScrollView を使うあらゆる UI で「とりあえず自動で流したい」場面にそのまま再利用できます。

プロジェクト内のどの ScrollView にも、Content かその子ノードに AutoScroll を一つアタッチするだけで自動スクロールを実現できるので、シーンごとに複雑な制御スクリプトを書く必要がなくなり、UI 実装の効率が大きく向上します。

必要に応じて、今後は水平方向の自動スクロールや、一定時間停止後に再開する機能などを追加してもよいでしょう。その際も、本記事の設計方針(外部依存をなくし、インスペクタで完結させる)をベースに拡張していくと、再利用性の高いコンポーネント群を構築できます。

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