【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分外側までスクロール)
- 親の高さから start / end を自動計算(親の上下端より
- そうでない場合は
startOffsetY/endOffsetYをそのまま使用
- startRoll / pauseRoll / stopRoll
- インスペクタからだけでなく、他のスクリプトからも簡単に制御できるように、公開メソッドとして用意
- ただし本コンポーネント自体は他のスクリプトに依存していないため、このまま単体でも完結して動作
使用手順と動作確認
1. スクリプトファイルの作成
- Creator の Assets パネルで右クリック
→ Create > TypeScript を選択します。 - ファイル名を
CreditRoll.tsに変更します。 - 作成された
CreditRoll.tsをダブルクリックして開き、
先ほどの 全コード をそのまま貼り付けて保存します。
2. テキストファイル(スタッフロール)の準備
- Assets パネルで右クリック
→ Create > Text を選択します。 - ファイル名を
credits.txtなどに変更します。 - ダブルクリックして開き、例として以下のような内容を入力します。
STAFF Game Director John Doe Lead Programmer Jane Smith Art Director Alice Johnson Special Thanks All Players - 保存して閉じます。
3. スタッフロール用ノードの作成
- Hierarchy パネルで右クリック
→ Create > UI > Canvas を作成(まだなければ)。 - Canvas の子として 右クリック > Create > UI > Label を作成します。
- Label ノードを選択し、Inspector の Label コンポーネントで
- String は空で構いません(スクリプトが上書きします)。
- Horizontal Align を Center にすると見やすくなります。
- Overflow を NONE か CLAMP にしておくと、親のサイズに合わせて表示を制限できます(好みに応じて)。
4. CreditRoll コンポーネントのアタッチ
- 先ほど作成した Label ノード を選択します。
- Inspector の下部で Add Component ボタンをクリック。
- Custom カテゴリから CreditRoll を選択してアタッチします。
5. プロパティの設定
Label ノードに追加された CreditRoll コンポーネントの各プロパティを設定します。
- Text Asset:
- Assets パネルから先ほど作成した
credits.txtをドラッグ&ドロップします。
- Assets パネルから先ほど作成した
- Scroll Speed:
- 例:
50~80くらいから試すと良いです。 - 値を大きくすると速くスクロールします。
- 例:
- Use Parent Height Auto Range:
- Canvas の下に Label を置いている場合、ON のままで OK です。
- 親の UITransform 高さを使って、画面下の少し外から上の少し外まで自動でスクロールします。
- Loop Padding:
- 例:
50~100。 - 値を大きくすると、画面外からゆっくり入ってきて、画面外へ完全に消えてから終了します。
- 例:
- Start Offset Y / End Offset Y:
- Use Parent Height Auto Range を OFF にした場合のみ有効です。
- 例えば:
Start Offset Y = -300End 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. プレビューで動作確認
- エディタ右上の Play(プレビュー)ボタン を押してシーンを実行します。
- シーンが開始すると、Label に
credits.txtの内容が読み込まれ、下から上へ自動スクロールしていくのを確認できます。 - スクロール速度や範囲がイメージと違う場合は、
- Scroll Speed
- Loop Padding(または Start Offset Y / End Offset Y)
を少しずつ変更しながら、プレビューし直して調整してください。
まとめ
この CreditRoll コンポーネントは、
- TextAsset を指定して
- 任意の Label ノードにアタッチするだけで
- 自動スクロールするスタッフロールを実現できる、完全に独立した汎用スクリプトです。
他の GameManager やシングルトンに一切依存せず、インスペクタのプロパティだけで挙動を完結に制御できるため、
- タイトル画面の「スタッフロール」ボタンから遷移した先のシーン
- ゲームクリア後のエンディングシーン
- イベントシーン内のクレジット表示
など、様々な場面で簡単に再利用できます。
さらに、autoRestart や playOnStart、範囲自動計算などのオプションを組み合わせることで、ループする背景テキストや、ニュースティッカー風の演出などにも応用できます。
このコンポーネントをベースに、フォントサイズやフェードイン/フェードアウト、BGM再生トリガーなどを追加していけば、よりリッチなエンディング演出も簡単に構築できるはずです。




