【Unity】カメラのクロスフェード演出を作る方法

こじゃらこじゃら

次のようなクロスフェード演出を作りたいけど、作り方が全然分からないの~

このはこのは

少々面倒だけど、RenderTextureスクリプトを駆使すれば実現はできるわ。

動画で示したような複数カメラの映像をクロスフェード(クロスディゾルブ)で切り替える演出の作り方を紹介する記事です。

やり方は一通りではありませんが、本記事ではRenderTextureとuGUIのRawImageを用いる方法を紹介します。

この方法には、次のメリットとデメリットが存在します。

  • メリット
    • 実装が比較的シンプル
    • クロスフェード演出中以外は余計な処理負荷が発生しない
  • デメリット
    • uGUIのCanvasとRawImageが必要
    • アルファ付きのカメラ描画の場合、正しくアルファブレンドされない

デメリットの詳細については後述しますが、これらを許容できれば様々な場面で応用できるでしょう。

本記事では、複数カメラ映像をクロスフェードで切り替える方法を解説します。具体的なサンプルスクリプトとデモ動画も交えて解説していきます。

この作品はユニティちゃんライセンス条項の元に提供されています

動作環境
  • Unity2021.1.12f1

スポンサーリンク

前提条件

複数のカメラが予めシーン上に配置されているものとします。

これらのカメラをキーボードで切り替えられるようにする事を目標とします。

基本的な考え方

Unityでは、2つのカメラ映像をアルファブレンドで直接合成する手段は提供されていません。

代わりに、片方のカメラ映像をRenderTextureに出力し、このRenderTextureの内容を半透明画像として表示することで疑似的なアルファブレンドが可能です。

画像を画面全体に表示する方法はいくつか存在しますが、本記事ではuGUIのRawImageを用いることとします。RawImageを用いる理由は、画面全体のオーバーレイ表示が簡単に設定できるためです。

オーバーレイ表示できれば問題ないため、オブジェクトの前後関係に気を付ければMeshでも代用可能です。 [1]

実装手順

以上を踏まえて、具体的な実装手順を解説していきます。

RawImageの配置

uGUIのCanvas配下に演出用のRawImageオブジェクトを配置します。
例では、Canvasがシーンにないため、Canvasの新規作成から行うことにします。

RawImageは、Canvas配下に配置し、Stretch指定で上下左右の余白を0にして全画面表示します。

これでuGUI側の準備は完了です。テクスチャは、後述するスクリプトから動的に指定するため、何も指定しないでおきます。

カメラ切り替えスクリプトの適用

クロスフェードを行うためのスクリプトを適用し、クロスフェード演出の設定を行います。

以下、数字キーを押すと、対応したカメラにクロスフェードで切り替えるサンプルスクリプトです。ゲーム中の解像度変更への対応処理は敢えて外しております。(後述)

CameraCrossFade.cs
using System.Collections;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// カメラのクロスフェード演出
/// </summary>
public class CameraCrossFade : MonoBehaviour
{
    // 切り替え対象カメラ
    [SerializeField] private Camera[] _cameras;

    // クロスフェード演出を表示するためのRawImage
    [SerializeField] private RawImage _crossFadeImage;

    // フェード時間
    [SerializeField] private float _fadeDuration = 1;

    private RenderTexture _renderTexture;
    private int _currentIndex;
    private Coroutine _fadeCoroutine;

    // 指定されたパラメータは有効かどうか
    private bool IsValid => _cameras.Length >= 2 && _crossFadeImage != null;

    // フェード中かどうか
    private bool IsChanging => _fadeCoroutine != null;

    // 初期化
    private void Awake()
    {
        if (!IsValid) return;

        // クロスフェード用のRenderTexture作成
        _renderTexture = new RenderTexture(Screen.width, Screen.height, 0);

        // RawImage初期化
        _crossFadeImage.texture = _renderTexture;
        _crossFadeImage.gameObject.SetActive(false);

        // カメラ初期化
        for (var i = 0; i < _cameras.Length; ++i)
        {
            // 最初のカメラだけ有効にする
            _cameras[i].enabled = i == _currentIndex;
        }
    }

    // キー入力チェック・カメラ変更
    private void Update()
    {
        if (!IsValid || IsChanging || !Input.anyKeyDown) return;

        // 数字キー入力チェック
        if (!int.TryParse(Input.inputString, out var cameraNo))
            return;

        // 押されたカメラ番号に対応したカメラに切り替え
        ChangeCamera(cameraNo - 1);
    }

    // 指定されたインデックスのカメラにクロスフェードしながら切り替える
    public void ChangeCamera(int index)
    {
        if (!IsValid || IsChanging)
            return;

        if (index < 0 || index >= _cameras.Length)
            return;

        if (index == _currentIndex)
            return;

        // クロスフェード演出開始
        _fadeCoroutine = StartCoroutine(CrossFadeCoroutine(index));
    }

    // クロスフェード演出コルーチン
    private IEnumerator CrossFadeCoroutine(int index)
    {
        // フェード用のRawImage表示
        _crossFadeImage.gameObject.SetActive(true);

        // フェード中のみ、切り替え後カメラの描画先をRenderTextureに設定
        var nextCamera = _cameras[index];
        nextCamera.enabled = true;
        nextCamera.targetTexture = _renderTexture;

        // RawImageのα値を徐々に変更(フェード)
        var startTime = Time.time;

        while (true)
        {
            var time = Time.time - startTime;
            if (time > _fadeDuration)
                break;

            var alpha = time / _fadeDuration;
            _crossFadeImage.color = new Color(1, 1, 1, alpha);

            yield return null;
        }

        // 切り替え後カメラを有効化
        nextCamera.targetTexture = null;
        _cameras[_currentIndex].enabled = false;

        // フェード用のRawImage非表示
        _crossFadeImage.gameObject.SetActive(false);

        _currentIndex = index;
        _fadeCoroutine = null;
    }
}

上記スクリプトをCameraCrossFade.csというファイル名で保存し、適当なゲームオブジェクトにアタッチします。

スクリプトをアタッチしたら、インスペクターからカメラ、RawImage、フェード時間を設定します。

なお、Camerasにはカメラが2つ以上、Cross Fade ImageにはRawImageが設定されていないと動作しませんのでご注意ください。

実行結果

数字キーを押すと、対応したカメラにクロスフェード演出しながら切り替わるようになっていれば成功です。

スクリプトの解説

ここまで駆け足でクロスフェード演出をさせるための手順を解説しました。
ここからは、スクリプトの挙動を理解したい人向けに、スクリプトの要点を解説します。

RenderTextureの準備

アルファブレンドするカメラ映像の片方はRenderTextureに出力し、RawImageとして描画します。

RenderTextureの初期化は以下の処理で行っています。

// クロスフェード用のRenderTexture作成
_renderTexture = new RenderTexture(Screen.width, Screen.height, 0);

// RawImage初期化
_crossFadeImage.texture = _renderTexture;
_crossFadeImage.gameObject.SetActive(false);

RenderTextureは、ゲーム画面のピクセルサイズで生成しています。これは、異なる解像度の画面に柔軟に対応できるようにするためです。作成したRenderTextureは、RawImageのテクスチャとして指定し、RawImageは非表示にしておきます。

数字キーによるカメラ切り替え

指定された数字キーが押されたらカメラを切り替える処理は以下で行っています。

// 数字キー入力チェック
if (!int.TryParse(Input.inputString, out var cameraNo))
    return;

// 押されたカメラ番号に対応したカメラに切り替え
ChangeCamera(cameraNo - 1);

Input.inputStringプロパティに入力されたキーの文字が格納されるため、パースに成功したらカメラに切り替える処理を行うようにしています。カメラ番号は1始まりなのに対し、インデックスは0始まりなので、ここに注意しつつカメラを指定します。

クロスフェード演出のフェード処理

カメラ切り替えのクロスフェード演出が始まると、次の処理でRawImageを表示し、切り替え後カメラの描画先(Render Texture)に一時的にRenderTextureを指定します。

// フェード用のRawImage表示
_crossFadeImage.gameObject.SetActive(true);

// フェード中のみ、切り替え後カメラの描画先をRenderTextureに設定
var nextCamera = _cameras[index];
nextCamera.enabled = true;
nextCamera.targetTexture = _renderTexture;

そして、RawImageのアルファ値を0から1に徐々に変化させることで、疑似的なクロスフェードを行います。

// RawImageのα値を徐々に変更(フェード)
var startTime = Time.time;

while (true)
{
    var time = Time.time - startTime;
    if (time > _fadeDuration)
        break;

    var alpha = time / _fadeDuration;
    _crossFadeImage.color = new Color(1, 1, 1, alpha);

    yield return null;
}

例ではUnity標準のコルーチンを使っています。 [2]

クロスフェード演出が終わったら、切り替え後カメラの描画先を画面に戻し切り替え前のカメラとRawImageを非表示にして完了です。

// 切り替え後カメラを有効化
nextCamera.targetTexture = null;
_cameras[_currentIndex].enabled = false;

// フェード用のRawImage非表示
_crossFadeImage.gameObject.SetActive(false);

注意点

本記事で紹介したクロスフェード演出を扱う際の注意点についても触れておきます。

カメラのClearFlagsと背景色に注意

カメラのClear FlagsSolid Colorに設定しているとき、Backgroundのアルファ値が255未満の状態では、正しくアルファブレンドされません。

正しく動作しない例

この場合、Backgroundのアルファ値を255に設定すれば正しく表示されるようになります。

RawImageの描画順

クロスフェード演出にuGUIのRawImageを用いているため、RawImageの背後にUIを配置してしまうと、演出中のみUIが背後に隠れてしまいます。

このような場合、RawImageはどのUIよりも背後に表示させるように配置を調整すると解決します。

解像度変更への対応

本記事で紹介したサンプルは、ゲーム中の解像度が一定であることを前提としています。

スマートフォンアプリのように画面が自動回転する等、アプリ実行中に解像度が変更される可能性がある場合、次のコードのように解像度が変更されたタイミングでRenderTextureを再作成すれば良いです。

// 解像度変更された時に外部から呼ばれるメソッド
private void OnChangeResolution() {
    // 以前のテクスチャ解放
    if (_renderTexture != null)
        _renderTexture.Release();

    // クロスフェード用のRenderTexture再作成
    _renderTexture = new RenderTexture(Screen.width, Screen.height, 0);
}

さいごに

複数のカメラ映像をクロスフェード(ディゾルブ)演出で切り替える方法について解説しました。少しトリッキーに感じるかもしれませんが、確実に実現できる方法です。

環境によってはスクリプトを少し修正しないといけませんが、原理を押さえておくと、あらゆる場面で対応できるようになるでしょう。

参考サイト

スポンサーリンク