2019-01-04に投稿

Unity+宴で動画を再生しようとしたらつまづいた話

Unity+宴で動画を再生しようとしたらつまづいた話

宴というノベルゲームエンジンがあります。UnityのAsset Storeで提供されていて、機能も豊富で大変すばらしいのですが、
動画を再生しようとしたらしんどかった話をします。

執筆時の環境
Unity: 2017.4.5f1
宴: 3.4.7

普通にVideoコマンドを使う

宴にはVideoコマンドが用意されていて、それを使えばムービーが再生されます。
しかし、音が出ません。
まあ音が出ないこと自体はドキュメントに描いてある通りなので、どうやって音を出すか考えました。

宴3.4.7が出る

宴3.4.7のリリースノートにVideoコマンド音声対応みたいに書いてある!やったー!やってみよう!

↓↓結果↓↓

だめでした。
2018.2 では確かに音が出るようになっているのですが、2017.4 ではダメなようです。

2017.4.5f1でそこそこ開発が進んだ後だったので、2018へのアップデートはしたくありません。
ドキュメントにはBgmコマンドと組み合わせるべし、と書いてありますが、
その場合は自分で忘れずにStopBgmしなければいけませんし、動画と音声の同期にも不安が残ります。
そもそも音が出ないのはUnity側のVideoPlayerの仕様が不安定なせいですので、
2017.4.5用のWorkaroundを書くことでどうにかしようと考えました。

宴のVideoPlayer再生処理を書き換える

Qiitaの記事などを参考にして試したところ、
2017.4.5のVideoPlayerで音を出すにはDirectではダメで、VideoPlayerのAudioTrackにAudioSourceを設定してあげないといけないようでした。

実際にVideoPlayerを使って再生処理をしているファイルを書き換えます。

Utage/Scripts/ADV/Graphic/Video/AdvVideoManager.cs

internal void Play(string label, string cameraName, VideoClip clip, bool loop, bool cancel)
{
    VideoInfo info = new VideoInfo() { Cancel = cancel, };
    Videos.Add(label, info);
    GameObject go = this.transform.AddChildGameObject(label);
    VideoPlayer videoPlayer = go.AddComponent<VideoPlayer>();
    float volume = Engine.SoundManager.BgmVolume * Engine.SoundManager.MasterVolume;
    videoPlayer.SetDirectAudioVolume(0, volume);
    videoPlayer.isLooping = loop;

    // Unity 2017.4.5f1 workaround
    AudioSource audioSource = go.AddComponent<AudioSource>();
    float volume = Engine.SoundManager.BgmVolume * Engine.SoundManager.MasterVolume;
    audioSource.volume = volume;

    videoPlayer.audioOutputMode = VideoAudioOutputMode.AudioSource;
    videoPlayer.EnableAudioTrack(0, true);
    videoPlayer.SetTargetAudioSource(0, audioSource);
    // Unity 2017.4.5f1 workaround end

    videoPlayer.isLooping = loop;
    videoPlayer.clip = clip;
    videoPlayer.targetCamera = Engine.EffectManager.FindTarget(AdvEffectManager.TargetType.Camera, cameraName).GetComponentInChildren<Camera>();
    videoPlayer.renderMode = VideoRenderMode.CameraNearPlane;
    videoPlayer.aspectRatio = VideoAspectRatio.FitInside;
    videoPlayer.Play();
    videoPlayer.started += (x => OnStarted(info));
    info.Player = videoPlayer;
}

private void Update()
{
    if (Videos.Count <= 0) return;

    foreach (var keyValue in Videos)
    {
    var player = keyValue.Value.Player;
    if (player == null || !player.isPlaying) continue;

    float volume = Engine.SoundManager.BgmVolume * Engine.SoundManager.MasterVolume;
    // Unity 2017.4.5f1 workaround
    player.GetTargetAudioSource(0).volume = volume;
}

再生されない

で、エディタ上で再生してみました。

↓↓結果↓↓

だめでした。
……なんで?
Edittor上でAudioSourceの参照を設定した場合はちゃんと再生されてるのに……スクリプト経由ではダメなのでしょうか?
と、とりあえず、ビルドして確認してみましょう……

↓↓結果↓↓

音が鳴りました。
……なんで? 
かなり腑に落ちませんが、ともかくビルドした実行ファイルでは正常に再生されているのでとりあえずこれで行くことにします。

中断から復帰できない

もう一つ、別の問題がありました。
ムービー再生中にゲームウィンドウを非アクティブにしてゲームを中断状態にすると、
そこから復帰するときにムービー再生が強制中断されてしまうのです。
こちらの原因はIsEndPlay()にありました。
VideoPlayer.isPlayingで再生中かどうかを判別しているのですが、このプロパティが復帰直後にはfalseになるようなのです。

internal bool IsEndPlay(string label)
{
    if (!Videos.ContainsKey(label)) return true;

    //キャンセル済み
    if (Videos[label].Canceled) return true;
    //まだロード終ってないなら
    if (!Videos[label].Started) return false;

    //最初の0フレームで呼ばれることがある模様
    return Videos[label].Player.time > 0 && !Videos[label].Player.isPlaying;
    //          return !Videos[label].Player.isPlaying;
}

こちらに関しては、
https://forum.unity.com/threads/how-to-know-video-player-is-finished-playing-video.483935/
を参考にして終了判定をVideoPlayer.loopPointReachedイベントを利用したものに書きかえました。

--- a/Assets/Utage/Scripts/ADV/Graphic/Video/AdvVideoManager.cs
+++ b/Assets/Utage/Scripts/ADV/Graphic/Video/AdvVideoManager.cs
@@ -28,7 +28,8 @@ namespace Utage
            public bool Cancel { get; set; }
            public bool Started { get; set; }
            public bool Canceled { get; set; }
-           public VideoPlayer Player { get; set; }
+           public bool Loop { get; set; }
+           public bool Ended { get; set; }
+           public VideoPlayer Player { get; set; }
        }

        Dictionary<string, VideoInfo> Videos { get { return videos; } }
@@ -41,20 +42,33 @@ namespace Utage

        internal void Play(string label, string cameraName, VideoClip clip, bool loop, bool cancel)
        {
-           VideoInfo info = new VideoInfo() { Cancel = cancel, };
+           VideoInfo info = new VideoInfo() { Cancel = cancel, Loop = loop, Ended = false};
            Videos.Add(label, info);
            GameObject go = this.transform.AddChildGameObject(label);
            VideoPlayer videoPlayer = go.AddComponent<VideoPlayer>();
-           float volume = Engine.SoundManager.BgmVolume * Engine.SoundManager.MasterVolume;
-           videoPlayer.SetDirectAudioVolume(0, volume);
-           videoPlayer.isLooping = loop;
+
+           // Unity 2017.4.5f1 workaround
+           AudioSource audioSource = go.AddComponent<AudioSource>();
+           float volume = Engine.SoundManager.BgmVolume * Engine.SoundManager.MasterVolume;
+           audioSource.volume = volume;
+
+           videoPlayer.audioOutputMode = VideoAudioOutputMode.AudioSource;
+           videoPlayer.EnableAudioTrack(0, true);
+           videoPlayer.SetTargetAudioSource(0, audioSource);
+           // Unity 2017.4.5f1 workaround end
+
+           videoPlayer.isLooping = loop;
            videoPlayer.clip = clip;
            videoPlayer.targetCamera = Engine.EffectManager.FindTarget(AdvEffectManager.TargetType.Camera, cameraName).GetComponentInChildren<Camera>();
            videoPlayer.renderMode = VideoRenderMode.CameraNearPlane;
            videoPlayer.aspectRatio = VideoAspectRatio.FitInside;
            videoPlayer.Play();
            videoPlayer.started += (x => OnStarted(info));
-           info.Player = videoPlayer;
+
+           // 終わったらフラグを立てる
+           videoPlayer.loopPointReached += (x => OnEnd(info));
+
+           info.Player = videoPlayer;
        }

        void OnStarted(VideoInfo info)
@@ -62,7 +76,13 @@ namespace Utage
            info.Started = true;
        }


+       // 終わったらフラグを立てる
+       void OnEnd(VideoInfo info)
+       {
+           info.Ended = true;
+       }
+
        internal void Cancel(string label)
        {
            if (!Videos[label].Cancel)
            {
@@ -81,9 +101,12 @@ namespace Utage
            //まだロード終ってないなら
            if (!Videos[label].Started) return false;

-           //最初の0フレームで呼ばれることがある模様
-           return Videos[label].Player.time > 0 && !Videos[label].Player.isPlaying;
-//         return !Videos[label].Player.isPlaying;
+           // 終わってたら
+           return !Videos[label].Loop && Videos[label].Ended;
        }

        //終了処理
@@ -106,7 +129,9 @@ namespace Utage
                if (player == null || !player.isPlaying) continue;

                float volume = Engine.SoundManager.BgmVolume * Engine.SoundManager.MasterVolume;
-               player.SetDirectAudioVolume(0, volume);
+               // Unity 2017.4.5f1 workaround
+               player.GetTargetAudioSource(0).volume = volume;
            }
        }
 #else

結論

Unity2018.2を使いましょう。何もしなくても音が出ます。


ドッグ

C++er見習い。でしたが最近はC#にほだされつつある

Crieitは個人で開発中です。 興味がある方は是非記事の投稿をお願いします! どんな軽い内容でも嬉しいです。
なぜCrieitを作ろうと思ったか

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください!

ボードとは?

コメント