Unityを触り始めた頃は、つい「とりあえず全部 Update に書く」実装をしがちですよね。
プレイヤーの移動、カメラ追従、UI更新、背景スクロール…全部を1つの巨大スクリプトに詰め込むと、最初は動いてもだんだん手が付けられなくなります。

とくに2Dゲームや横スクロールゲームでよくある「パララックス背景(遠景ほどゆっくり動く背景)」を、プレイヤー制御スクリプトやカメラ制御スクリプトの中に直書きしてしまうと、

  • 背景の動きだけ調整したいのに、プレイヤーコードを開く必要がある
  • 別シーンで同じパララックス処理をしたいとき、コピペ地獄になる
  • デザイナーがパラメータを触りづらい(どこにあるか分からない)

といった問題が出てきます。

そこで今回は、背景レイヤー1枚ごとにアタッチできる小さなコンポーネントとして、カメラの動きに合わせて少し遅れて動く「ParallaxLayerMover」を用意して、きれいに責務分割していきましょう。

【Unity】カメラ追従で奥行き演出!「ParallaxLayerMover」コンポーネント

このコンポーネントは、

  • カメラの移動量を監視
  • 「どれくらい遅れて動くか」を係数で指定
  • 背景レイヤー(Sprite、UIのTextureRect など)をゆっくり追従させる

という単機能に特化しています。
プレイヤー制御やカメラ制御とは完全に分離されているので、どの背景オブジェクトにもポン付けで再利用できるのがポイントです。

フルコード:ParallaxLayerMover.cs


using UnityEngine;

namespace ParallaxSample
{
    /// <summary>
    /// カメラの移動に応じて、背景レイヤーを少し遅れて動かすコンポーネント。
    /// - 横スクロール / 縦スクロール 両対応
    /// - SpriteRenderer, Tilemap, UI など Transform を持つものなら何でもOK
    /// </summary>
    [DisallowMultipleComponent]
    public class ParallaxLayerMover : MonoBehaviour
    {
        // 追従させるカメラ。未指定の場合は MainCamera を自動取得
        [SerializeField]
        private Camera targetCamera;

        // カメラの移動量に対して、どれくらい動くかの係数(0 = 固定, 1 = カメラと同じ)
        // 遠景ほど小さく、手前ほど大きくする
        [SerializeField, Tooltip("カメラ移動量に対する追従係数(0=動かない, 1=カメラと同じ)")]
        private Vector2 parallaxFactor = new Vector2(0.3f, 0.3f);

        // パララックスを適用する軸の設定
        [SerializeField, Tooltip("X軸方向にパララックスを適用するか")]
        private bool affectX = true;

        [SerializeField, Tooltip("Y軸方向にパララックスを適用するか")]
        private bool affectY = false;

        // カメラの初期位置
        private Vector3 _cameraStartPosition;

        // このレイヤーの初期位置
        private Vector3 _layerStartPosition;

        // LateUpdate で動かすかどうか(カメラが Update で動く場合にズレを防ぐ)
        [SerializeField, Tooltip("LateUpdateで更新する(カメラ追従系と相性がよい)")]
        private bool useLateUpdate = true;

        // カメラが見つからない場合に警告を一度だけ出すためのフラグ
        private bool _cameraWarningShown = false;

        private void Awake()
        {
            // カメラ未指定なら MainCamera を自動取得
            if (targetCamera == null)
            {
                targetCamera = Camera.main;
            }

            // 初期位置を記録
            _layerStartPosition = transform.position;

            if (targetCamera != null)
            {
                _cameraStartPosition = targetCamera.transform.position;
            }
        }

        private void Start()
        {
            // Awake 時点では Camera.main がまだ見つからないケースへの保険
            if (targetCamera == null)
            {
                targetCamera = Camera.main;
            }

            if (targetCamera != null)
            {
                _cameraStartPosition = targetCamera.transform.position;
            }
        }

        private void Update()
        {
            // LateUpdate を使う設定なら、Update では何もしない
            if (!useLateUpdate)
            {
                ApplyParallax();
            }
        }

        private void LateUpdate()
        {
            // LateUpdate を使う設定のときだけ実行
            if (useLateUpdate)
            {
                ApplyParallax();
            }
        }

        /// <summary>
        /// カメラ位置に応じてパララックスを適用するメイン処理
        /// </summary>
        private void ApplyParallax()
        {
            if (targetCamera == null)
            {
                // カメラが見つからないときは一度だけ警告を出す
                if (!_cameraWarningShown)
                {
                    Debug.LogWarning(
                        $"[ParallaxLayerMover] targetCamera が設定されていません。{name} ではパララックスが無効です。",
                        this
                    );
                    _cameraWarningShown = true;
                }
                return;
            }

            // カメラの現在位置
            Vector3 cameraPos = targetCamera.transform.position;

            // カメラの初期位置からの移動量(デルタ)
            Vector3 cameraDelta = cameraPos - _cameraStartPosition;

            // パララックス係数を適用した移動量
            // X, Y のみ扱い、Z は元のままにする
            float offsetX = affectX ? cameraDelta.x * parallaxFactor.x : 0f;
            float offsetY = affectY ? cameraDelta.y * parallaxFactor.y : 0f;

            // 新しい位置 = 初期位置 + オフセット
            Vector3 newPos = _layerStartPosition;
            newPos.x += offsetX;
            newPos.y += offsetY;

            transform.position = newPos;
        }

        /// <summary>
        /// エディタ上でパラメータ調整中にも結果を確認しやすくするため、
        /// 値が変わったときに初期位置をリセットする補助メソッド。
        /// (必要に応じてインスペクタから呼び出してもよい)
        /// </summary>
        public void ResetParallaxOrigin()
        {
            if (targetCamera == null)
            {
                targetCamera = Camera.main;
            }

            _layerStartPosition = transform.position;

            if (targetCamera != null)
            {
                _cameraStartPosition = targetCamera.transform.position;
            }
        }
    }
}

使い方の手順

ここでは 2D 横スクロールゲームを例に、背景レイヤーにパララックスを仕込む手順を見ていきます。

① 背景レイヤー用のオブジェクトを準備する

  • Hierarchy で GameObject > 2D Object > Sprite などから背景用オブジェクトを作成
  • 名前を BG_Far(遠景)、BG_Mid(中景)、BG_Near(手前)などに分けておくと分かりやすいです
  • それぞれに SpriteRenderer や Tilemap などを設定して、見た目を作成

UI ベースで背景を出したい場合は、Canvas 配下に RawImageImage を置き、その RectTransform を動かす形でもOKです(このスクリプトは Transform に対して動作します)。

② カメラを用意する(既存の Main Camera でOK)

  • シーンに Main Camera があることを確認します
  • カメラをプレイヤーに追従させるスクリプトなどが既にある場合でも、そのままで構いません
  • この ParallaxLayerMover は「カメラの移動量」だけを参照するので、カメラ制御とは独立して動きます

③ ParallaxLayerMover を各レイヤーにアタッチする

  1. プロジェクトに ParallaxLayerMover.cs を作成し、上記コードをそのまま貼り付けて保存
  2. 背景オブジェクト(例:BG_Far)を選択し、Add Component から ParallaxLayerMover を追加
  3. インスペクタ上で以下を設定:
    • Target Camera:空欄のままなら自動で MainCamera を参照します。複数カメラを使う場合は明示的に指定しましょう。
    • Parallax Factor
      • 遠景(BG_Far):(0.1, 0.0) など、かなり小さめ
      • 中景(BG_Mid):(0.3, 0.0)
      • 手前(BG_Near):(0.6, 0.0) など、大きめ
    • Affect X:横スクロールなら ON
    • Affect Y:縦方向にもパララックスをかけたい場合のみ ON
    • Use Late Update:カメラが LateUpdate で動く場合は ON にしておくとズレにくいです

これで、カメラを左右に動かすと、遠景ほどゆっくり、手前ほど速く動くパララックス背景が完成します。

④ 具体例:プレイヤー+カメラ+パララックス背景

簡単な構成例を挙げておきます。

  • Player
    • Rigidbody2D + 移動スクリプト(プレイヤー操作専用)
  • Main Camera
    • Player を追いかける CameraFollow スクリプト(カメラ追従専用)
  • BG_Far / BG_Mid / BG_Near
    • それぞれに ParallaxLayerMover をアタッチ

このように、

  • プレイヤーの移動は「プレイヤー用コンポーネント」
  • カメラ追従は「カメラ用コンポーネント」
  • パララックスは「背景レイヤー用コンポーネント」

と役割を分けておくと、どこを触ればどの挙動が変わるかが明確になり、保守性が一気に上がります。

メリットと応用

ParallaxLayerMover を使うことで、レベルデザインやプレハブ管理がかなり楽になります。

  • 背景レイヤーごとに独立したプレハブにできる
    BG_Far, BG_Mid, BG_Near をそれぞれプレハブ化しておけば、別シーンにもドラッグ&ドロップするだけで同じパララックス演出を再利用できます。
    カメラ制御スクリプトに一切手を入れなくてよいので、安全に流用できます。
  • デザイナーがインスペクタから直接パラメータをいじれる
    parallaxFactor をいじるだけでスピード感を調整できるので、プログラマーに頼まずに演出調整が可能です。
  • ゲームの規模が大きくなっても「背景の責務」が変わらない
    プレイヤーやカメラの仕様が変わっても、ParallaxLayerMover のコードはほとんどそのまま使い続けられます。

また、少し改造するだけで、例えば「ループ背景」や「特定方向にだけ強く動かす」などのバリエーションも作れます。

改造案:ループするパララックス背景にする

例えば横方向に無限ループする背景を作りたい場合、背景テクスチャ1枚分の幅を超えたら位置を巻き戻す処理を追加できます。
以下は、テクスチャ1枚分の幅 loopWidth ごとに X 位置をループさせる簡単な例です。


/// <summary>
/// 背景をループさせるための補助メソッドの例。
/// テクスチャ幅(world 単位)を loopWidth に設定しておく想定。
/// </summary>
[SerializeField]
private float loopWidth = 0f;

private void ApplyLoopIfNeeded()
{
    if (loopWidth <= 0f)
    {
        return;
    }

    Vector3 pos = transform.position;

    // カメラの位置との差分を見て、一定範囲を超えたらループさせる
    float deltaXFromCamera = pos.x - targetCamera.transform.position.x;

    // 右方向に離れすぎたら左に戻す
    if (deltaXFromCamera > loopWidth)
    {
        pos.x -= loopWidth;
        _layerStartPosition.x -= loopWidth;
    }
    // 左方向に離れすぎたら右に戻す
    else if (deltaXFromCamera < -loopWidth)
    {
        pos.x += loopWidth;
        _layerStartPosition.x += loopWidth;
    }

    transform.position = pos;
}

このように、小さなコンポーネントをベースにして、必要な機能だけを少しずつ足していくと、
巨大な God クラスに頼らずに、柔軟で拡張しやすいパララックスシステムを育てていけます。