【Unity】パララックス(多重スクロール)を実装する

Unityの2Dの表現で視差の効果を使ったパララックスのスクロール(Parallax)を実装例を紹介したいと思います。かつてレトロゲームの背景スクロールでよくありましたね。

この表現方法は、スクロールの速度が奥のほうがゆっくりで手前ほど早くスクロールすることで立体感を出す手法です。

Twitter のほうが動画が奇麗に見えるかも。

Unityだとカメラをパースペクティブに設定していれば別にこんなことせずとも自動で視差がついて遠近が出せますが、2Dで作っててカメラの設定が Orthographic の場合に使用できます。

作成環境

  • Unity 2021.3.14f1
  • VisualStudio2022
  • Windows11

エディター上で動作を確認しています。

考え方

基本的に冒頭の動画通り以下しようとします。

  • 視差効果を出すためにレイヤーごとに移動速度を変える
    • 手前のレイヤーほどカメラに追従しない
    • 奥側のレイヤーほどカメラに追従する
  • 左右に無限にスクロールできる

アセットとシーン

まず、使用するアセットは以下を使用しています。

edermunizz.itch.io

シーンは移動速度のグループごとにレイヤーを作成します。今回はアセットが6レイヤー分なので6個作成します。

そして各レイヤー内には子要素に同じ画像を3枚ずつ配置します。

同じ画像を左右に均等に3枚並べて左右均等に画像のつなぎ目が目立たない位置に配置します。

配置したあと Scene ビューでこんな感じになってるとよさそうです。

スクリプト

レイヤーに設定するスクリプト

各レイヤーに設定するスクリプトです。

指定したカメラにどれくらいレイヤーが追従するかを制御します。

// 視差スクロール用のレイヤーに設定するスクリプト
public class ParallaxCameraFlowLayer : MonoBehaviour
{
    // 追従対象のカメラ
    [SerializeField] Transform _cameraTransfrom;
    // カメラに追従する程度(1: カメラと同じ移動量 0: 移動しない)
    [SerializeField] float _followFactor;

    Vector3 _previousCameraPos;

    private void Update()
    {
        Vector3 currentPos = _cameraTransfrom.position;
        var deltaPos = currentPos - _previousCameraPos;
        _previousCameraPos = currentPos;
        var calcedPos = deltaPos * _followFactor;
        transform.AddLocalPos((Vector2)calcedPos);
    }
}

// ユーティリティ
public static class ParallaxCameraFlowLayerExtensions
{
    public static void AddLocalPos(this Transform self, in Vector2 pos)
    {
        Vector3 vec = self.localPosition;
        vec.x += pos.x;
        vec.y += pos.y;
        self.localPosition = vec;
    }
}

_cameraTransfrom にカメラを設定します。2Dなのでメインカメラを手で設定するようにしていますが、Awake で Camera.main を設定してもいいと思います。

_followFactor ですがカメラに追従する度合いを表します。0で完全に追従しない~1でカメラの動きと完全に同じとなります。

Layer Value
Layer_01 [Far] 0.8
Layer_02 0.6
Layer_03 0.4
Layer_04 0.2
Layer_05 0
Layer_06 [Near] -0.2

0.2ずつ増減させて奥に行くほどゆっくり移動するように見えるようになります。

マイナスに設定するとカメラと同じ方向により早く動くようになります。

各画像に設定するスクリプト

無限にスクロールしているように見せたいので、レイヤーの子要素に以下の画像を設定します。

このスクリプトでカメラから画像が一定距離離れると画像が自動的に左右どちらかにローテーションして無限にスクロールしてるように見せることができるようになります。

public class HorizontalDynamicImageRotation : MonoBehaviour
{
    [SerializeField] Transform _cameraTransform;
    // ローテーションするときの1枚の画像の幅
    [SerializeField] float _imageWidth;

    Transform _parentLayer;
    Transform _transformCache;

    private void Awake()
    {
        _parentLayer = this.GetParent().transform;
        _transformCache = transform;

        if (!_cameraTransform)
        {
            Log.Warn("カメラが設定されていません。", this);
            this.enabled = false;
        }
    }

    private void Update()
    {
        // 3枚でローテーションするのでカメラの描画範囲から1.5枚分ずれたらその方向に移動する
        var distance = _cameraTransform.position - _transformCache.position;
        if (Mathf.Abs(distance.x) > _imageWidth * 1.5f)
        {
            float amount = _imageWidth * 3 * (distance.x < 0 ? -1.0f : 1.0f);
            _transformCache.AddLocalPosX(amount);
        }
    }
}

// ユーティリティ
public static class HorizontalDynamicImageRotationExtensions
{
    // 親を取得
    public static GameObject GetParent(this Component self)
    {
        return self.transform.parent.gameObject;
    }
    // X方向に値を足す
    public static void AddLocalPosX(this Transform self, float x)
    {
        Vector3 vec3 = self.localPosition;
        vec3.x += x;
        self.localPosition = vec3;
    }
}

_cameraTransform は画像との距離をとるカメラです。インスペクターに手で設定するようにしていますが、Camera.main でもいいかもしれません。

_imageWidth は画像の幅です。カメラとの距離を Update で各インして一定距離以上、離れたらカメラに近い位置に画像が移動するようになります。

インスペクターに色々と手で配置して制限を付けてるためかなり短いコードでも表現できると思います。これで、Canvasにボタンなどを配置して水平方向の左右にカメラを移動すればパララックスのスクロールがされるようになりました。

以上