UnityのUI実装でありがちなのが、Update() に「入力処理」「アニメーション」「UI更新」「サウンド再生」など、なんでもかんでも書いてしまうパターンですね。
とくにクレジットシーンやスタッフロールを作るとき、Update() に直接 ScrollRect.verticalNormalizedPosition をゴリゴリ書いてしまうと、
- クレジット以外のロジックと混ざって可読性が落ちる
- 別シーンで同じスクロールを使いたいときにコピペ地獄になる
- スクロール速度や開始タイミングの調整がしづらい
こういうときこそ、「スクロールだけを担当するコンポーネント」に分離しておくとスッキリします。
この記事では、クレジット表示などに使える「親の ScrollContainer を自動でゆっくり下にスクロールさせる」ための
「AutoScroll」コンポーネント
を作って、コンポーネント指向で綺麗に実装していきます。
【Unity】クレジットが勝手に流れる!「AutoScroll」コンポーネント
今回作る AutoScroll は、こんな役割だけに責任を絞ったコンポーネントです。
- 指定した
ScrollRect(=ScrollContainer)を一定速度で自動スクロール - 開始ディレイ・スクロール方向・ループ可否をインスペクターから調整
- スクロール完了時にイベントを呼べる(シーン遷移などに使える)
クレジット用の UI プレハブにこのコンポーネントを 1 個付けておくだけで、他のゲームロジックとは完全に分離された「自動スクロール専用の仕組み」が手に入ります。
フルコード:AutoScroll.cs
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
namespace Sample.UI
{
/// <summary>
/// ScrollRect を自動でスクロールさせるコンポーネント。
/// クレジットや自動ニュースティッカーなどに利用できます。
///
/// 責務は「ScrollRect の normalized position を時間経過で更新すること」に限定しています。
/// 入力処理やシーン遷移などは別コンポーネントに任せましょう。
/// </summary>
[RequireComponent(typeof(ScrollRect))]
public class AutoScroll : MonoBehaviour
{
/// <summary>
/// スクロールの開始を遅らせる秒数。
/// 例:3 にすると、開始から 3 秒後にスクロールが始まる。
/// </summary>
[SerializeField]
private float startDelaySeconds = 0f;
/// <summary>
/// スクロールにかける総時間(秒)。
/// 0 以下の場合は、スクロールしません。
/// </summary>
[SerializeField]
private float scrollDurationSeconds = 20f;
/// <summary>
/// スクロール方向。
/// Vertical の場合は verticalNormalizedPosition、
/// Horizontal の場合は horizontalNormalizedPosition を操作します。
/// </summary>
[SerializeField]
private ScrollDirection scrollDirection = ScrollDirection.Vertical;
/// <summary>
/// true の場合、スクロール完了後に開始位置へ戻り、ループします。
/// </summary>
[SerializeField]
private bool loop = false;
/// <summary>
/// スクロールカーブ。
/// デフォルトは線形(0→1)。ゆっくり始めたい・終わりをゆっくりにしたい場合は
/// インスペクターからイージングカーブを設定しましょう。
/// </summary>
[SerializeField]
private AnimationCurve scrollCurve = AnimationCurve.Linear(0f, 0f, 1f, 1f);
/// <summary>
/// 自動スクロールを有効にするか。
/// 再生中に外部から ON/OFF する用途も想定しています。
/// </summary>
[SerializeField]
private bool autoStart = true;
/// <summary>
/// スクロール完了時に呼ばれるイベント。
/// シーン遷移やフェードアウトなどをここに紐づけると便利です。
/// </summary>
[SerializeField]
private UnityEvent onScrollCompleted;
/// <summary>
/// 内部で操作する ScrollRect 参照。
/// RequireComponent により必ず同じ GameObject に存在する前提です。
/// </summary>
private ScrollRect scrollRect;
/// <summary>
/// スクロール開始時の normalized position(0〜1)。
/// </summary>
private float startNormalizedPosition;
/// <summary>
/// スクロール終了時の normalized position(0〜1)。
/// </summary>
private float endNormalizedPosition;
/// <summary>
/// 経過時間(startDelay を含まない、実際のスクロール時間)。
/// </summary>
private float elapsedScrollTime;
/// <summary>
/// スクロールが現在有効かどうか。
/// </summary>
private bool isScrolling;
/// <summary>
/// スクロール方向を表す列挙型。
/// </summary>
private enum ScrollDirection
{
Vertical,
Horizontal
}
private void Awake()
{
// 同じ GameObject にアタッチされた ScrollRect を取得
scrollRect = GetComponent<ScrollRect>();
// ScrollRect の設定が正しいか簡易チェック
if (!scrollRect.vertical && !scrollRect.horizontal)
{
Debug.LogWarning(
$"[{nameof(AutoScroll)}] ScrollRect の vertical / horizontal が両方 OFF です。" +
"少なくともどちらか一方を有効にしてください。",
this);
}
// デフォルトのスクロール範囲を決める
// クレジットなど「下から上へ流す」用途を想定して、
// Vertical の場合は 0 → 1 を使うことが多いです。
InitializeScrollRange();
}
private void OnEnable()
{
// 有効化時に自動でスクロールを開始するかどうか
if (autoStart)
{
RestartScroll();
}
}
private void Update()
{
// スクロール無効時は何もしない
if (!isScrolling)
{
return;
}
// ScrollDuration が無効な値なら何もしない
if (scrollDurationSeconds <= 0f)
{
return;
}
// 開始ディレイ中かどうか
if (startDelaySeconds > 0f)
{
startDelaySeconds -= Time.deltaTime;
// まだディレイ中なら、スクロールは開始しない
if (startDelaySeconds > 0f)
{
return;
}
// ディレイが終わった瞬間に、スクロール時間をリセットして開始
elapsedScrollTime = 0f;
}
// 経過時間を進める
elapsedScrollTime += Time.deltaTime;
// 0〜1 に正規化した進行度
float t = Mathf.Clamp01(elapsedScrollTime / scrollDurationSeconds);
// カーブを通して補間した値(0〜1)
float curvedT = scrollCurve.Evaluate(t);
// start から end までを補間
float current = Mathf.Lerp(startNormalizedPosition, endNormalizedPosition, curvedT);
ApplyScrollPosition(current);
// スクロールが終端に到達したかどうか
if (Mathf.Approximately(t, 1f))
{
HandleScrollCompleted();
}
}
/// <summary>
/// スクロール範囲(start / end)を初期化します。
/// デフォルトでは Vertical: 0→1, Horizontal: 0→1 としています。
/// 必要に応じてインスペクターから変更しても構いません。
/// </summary>
private void InitializeScrollRange()
{
switch (scrollDirection)
{
case ScrollDirection.Vertical:
// 通常のクレジットで「下から上へ流す」場合、
// verticalNormalizedPosition を 0 → 1 に動かすことが多いです。
startNormalizedPosition = 0f;
endNormalizedPosition = 1f;
break;
case ScrollDirection.Horizontal:
startNormalizedPosition = 0f;
endNormalizedPosition = 1f;
break;
}
}
/// <summary>
/// ScrollRect に対して現在のスクロール位置を適用します。
/// </summary>
/// <param name="value">0〜1 の normalized position</param>
private void ApplyScrollPosition(float value)
{
if (scrollRect == null)
{
return;
}
switch (scrollDirection)
{
case ScrollDirection.Vertical:
scrollRect.verticalNormalizedPosition = value;
break;
case ScrollDirection.Horizontal:
scrollRect.horizontalNormalizedPosition = value;
break;
}
}
/// <summary>
/// スクロール完了時の処理。
/// ループ設定に応じて再スタートするか、完全停止するかを決めます。
/// </summary>
private void HandleScrollCompleted()
{
// 完了イベントを発火
onScrollCompleted?.Invoke();
if (loop)
{
// ループする場合は開始位置に戻して再スタート
RestartScrollInternal();
}
else
{
// ループしない場合は停止
isScrolling = false;
}
}
/// <summary>
/// 外部からスクロールを再スタートさせるための公開メソッド。
/// 例:ボタン押下やシーン開始時に呼ぶなど。
/// </summary>
public void RestartScroll()
{
// 開始ディレイをリセットした上で内部処理を呼ぶ
// ※ startDelaySeconds はインスペクターで設定した値を保持したい場合、
// 別変数に退避させるなど調整してください。
RestartScrollInternal();
}
/// <summary>
/// 実際の再スタート処理(内部用)。
/// </summary>
private void RestartScrollInternal()
{
// 経過時間リセット
elapsedScrollTime = 0f;
// スクロール範囲を再計算(方向などが変わっている場合に備える)
InitializeScrollRange();
// 開始位置を即時反映
ApplyScrollPosition(startNormalizedPosition);
// スクロールを有効化
isScrolling = true;
}
/// <summary>
/// スクロールを一時停止します。
/// </summary>
public void PauseScroll()
{
isScrolling = false;
}
/// <summary>
/// 一時停止したスクロールを再開します。
/// </summary>
public void ResumeScroll()
{
// ScrollDuration が 0 以下の場合は再開しても意味がないので無視
if (scrollDurationSeconds <= 0f)
{
return;
}
isScrolling = true;
}
/// <summary>
/// 現在の進行度(0〜1)を取得します。
/// UI 表示などで利用できます。
/// </summary>
public float GetProgress()
{
if (scrollDurationSeconds <= 0f)
{
return 0f;
}
return Mathf.Clamp01(elapsedScrollTime / scrollDurationSeconds);
}
}
}
使い方の手順
ここからは、実際にクレジット用の自動スクロールを作る手順を見ていきましょう。
-
ScrollContainer(ScrollRect)の用意
- Hierarchy で
UI > Scroll Viewを作成します(名前をCreditScrollなどに変更)。 ScrollRectコンポーネントが付いていることを確認します。-
クレジット用テキストを配置します:
Viewport/Contentの下にText (TMP)などを追加し、スタッフ名やライセンス文を縦に並べます。- Content の高さが Viewport よりも十分大きくなるように、テキストやレイアウトを調整します。
- クレジットを「下から上へ流したい」場合は、
ScrollRectの Vertical を ON にし、Horizontal は OFF にしておきます。
- Hierarchy で
-
AutoScroll コンポーネントをアタッチ
- 上で作成した
CreditScrollオブジェクトを選択します。 Add ComponentからAutoScrollを追加します。ScrollRectはRequireComponentによって自動的に取得されるので、特別な設定は不要です。
- 上で作成した
-
スクロールパラメータの調整
Start Delay Seconds:タイトルロゴなどを先に見せたい場合は、2〜3秒程度に設定。Scroll Duration Seconds:クレジットがすべて読み切れる時間。例:30〜60秒など。Scroll Direction:通常のクレジットなら Vertical。Loop:エンドレスで流したいデモ画面などでは ON、1回きりのスタッフロールなら OFF。Scroll Curve:線形で問題なければそのまま。ゆっくり始まって加速させたいなら、イーズイン系のカーブを設定。Auto Start:シーン開始と同時にスクロールさせたいなら ON。別コンポーネントからRestartScroll()を呼びたい場合は OFF でもOK。
-
具体的な使用例
-
プレイヤー死亡後のエンドクレジット
- ゲームオーバーシーンに
CreditScrollを配置し、AutoScrollのOn Scroll Completedに「タイトルシーンへ戻る」処理を登録。 - これにより、クレジットを最後まで見終わると自動でタイトルへ戻る流れを作れます。
- ゲームオーバーシーンに
-
タイトル画面の背景クレジット
- タイトルシーンの背景に半透明のクレジット ScrollView を置き、
Loopを ON に。 - メインメニューの入力処理は別コンポーネントに任せ、AutoScroll は「背景を流すだけ」に専念させます。
- タイトルシーンの背景に半透明のクレジット ScrollView を置き、
-
敵図鑑や実績リストの自動デモスクロール
- 敵一覧や実績一覧の ScrollView に AutoScroll を付けて、ループ ON でゆっくり流す。
- プレイヤーが操作を始めたら、別コンポーネントから
PauseScroll()を呼んで停止させる、という分業もできます。
-
プレイヤー死亡後のエンドクレジット
メリットと応用
AutoScroll をコンポーネントとして分離しておくと、以下のようなメリットがあります。
-
プレハブ化しやすい
クレジット用 ScrollView + AutoScroll を 1 つのプレハブにしておけば、どのシーンにもドラッグ&ドロップで再利用できます。
スクロール速度やカーブもプレハブ側で調整しておけば、シーンごとに複雑なスクリプトを書き換える必要がありません。 -
責務がはっきりしていてテストしやすい
AutoScroll は「ScrollRect の normalized position を時間で動かすだけ」という小さな責務に限定されています。
シーン遷移・BGM 切り替え・入力受付などは別コンポーネントに分けられるので、バグが起きたときにどこを疑えばいいかが明確です。 -
レベルデザインが楽になる
レベルデザイナーや UI デザイナーが、インスペクターのパラメータだけで「どのぐらいのスピードで流すか」「ループさせるか」を調整できます。
プログラマはコンポーネントの機能追加やバグ修正に集中できるので、役割分担がしやすくなります。 -
他の UI への転用が簡単
クレジットだけでなく、ニュースティッカー・ランキングリスト・チャットログの自動スクロールなど、ScrollRect を使う UI ならほぼそのまま流用できます。
さらに、少し手を加えるだけで応用範囲を広げられます。たとえば、以下のような「スクロール速度を一時的に倍速にする」改造案を追加すると、スキップボタン付きのクレジットが簡単に作れます。
/// <summary>
/// 一時的にスクロールを倍速にする例。
/// ボタンから呼び出して「早送りクレジット」を実装できます。
/// </summary>
public void BoostScrollSpeed(float multiplier, float duration)
{
// コルーチンなどで duration 秒だけ scrollDurationSeconds を
// 一時的に短くする処理を実装するとよいでしょう。
// ここではイメージとしてのサンプルです。
if (multiplier <= 0f)
{
return;
}
float originalDuration = scrollDurationSeconds;
scrollDurationSeconds /= multiplier;
// 実際には StartCoroutine で一定時間後に元に戻す処理を書くのがおすすめです。
// StartCoroutine(RestoreDuration(originalDuration, duration));
}
このように、小さな責務のコンポーネントを積み重ねていくと、プロジェクト全体が見通しよく、拡張しやすい構成になっていきます。
クレジットや自動スクロール UI を作るときは、ぜひ AutoScroll のような専用コンポーネントに切り出してみてください。




