はじめに
こちらの記事はUnityゲーム開発者ギルド Advent Calendar 2022の11日目の記事です。
今回はUnityのTimelineを用いてMV制作を行った話について書こうと思います。
NaTsu Beats
今年の1月ごろから妻が趣味で楽曲制作を始めました。
是非チャンネル登録をお願いします!!
制作を始めたきっかけについては妻のnoteをご覧ください。
ビートメイキングを始めた話|NaTsu Beats|note
妻はイラストを描くことも好きなので、イメージイラストやちょっとしたアニメーションも作成してYouTubeにアップロードしています。
私はMV制作のお手伝いをしていて、 先日公開された "A Brand New Day!" という楽曲ではUnityのTimelineを用いて作成しました。
TimelineでのMV制作
MV制作をTimelineでしよう!となった理由は単純にTimelineの勉強がしたかったからです笑
バージョンは以下のものを使用しました。
- Unity:
2021.3.3f1
基本的に絵素材は妻が作成し、実装を私が担当しました。
動画の出力にはUnity Recorderを使用しました。
全体の流れ
MVの流れとしては、NA-CHANとアザ...セイウチのCHILL-BALLが1日かけて街や公園、森を巡るものになっています。

曲は既に出来上がった状態でのMV制作だったため、 どのタイミングで何を出すかといった指示メモを書いてもらいました。

場面の転換
今回場面としては大きく分けて4つありました。

NA-CHANとCHILL-BALLが歩くアニメーションを再生しながら、 背景スクロールで場面転換を表現しました。
背景スクロールについて
背景用のSpriteRendererを持ったGameObjectを2つ用意して、
スクロールさせつつ、前後を入れ替えることで実装しています。


スクロール処理はBackgroundMoverが処理しています。
LoopTimeで指定した時間でTransferDistだけ進み、後方の位置へ戻ります。
ロケット鉛筆の要領ですね!

背景スクロール用のTimeline拡張
Timelineウィンドウでシークバーをスクラブした場合にも再生中と同様に背景スクロールさせたかったため、拡張機能を実装しました。
今回のMV制作では曲の進行に合わせて場面を切り替えたり、スクロールを止める場面があったため、各クリップでの細かいタイミング調整が必要でした。 スクラブでの確認ができないと編集を行うたびに先頭から再生することになるので地獄を見るところでした…

こちらの記事を参考に以下のTimeline拡張を行いました。
- BackgroundTimelineTrack:
BackgroundMoverをバインドする背景スクロール用の拡張トラック - BackgroundTimelineClip: スクロールの状態を設定する拡張クリップ
- BackgroundTimelineBehaviour:
BackgroundMoverのスクロール処理を呼び出すPlayableBehaviour派生クラス
ドキュメントによると
PlayableBehaviourのProcessFrame()はPlayableGraphのProcessFrameフェーズで実行されるメソッド
のようです。
参照:Unity - Scripting API: Playables.PlayableBehaviour.ProcessFrame
これは再生時だけでなく、Timelineを再生していないEditorモードでも実行されるとのことでした。つまり、シークバーをスクラブする際にも実行されるようです。
参照:How to get Playable.GetTime() when not in play mode ? - Unity Forum

BackgroundTimelineBehaviourではProcessFrame()内で背景スクロールの処理であるBackgroundMover.ScrollOnFixedFrameRate()を実行しています。

BackgroundMover.ScrollOnFixedFrameRate()
信号の場面などで背景スクロールを停止している部分ではIsStopフラグがtrueになったクリップを割り当てています。

Timeline再生時のスクロール
Timeline再生中はMonoBehaviourのUpdate()で呼び出すときのように、FrameData.deltaTimeを元にスクロール位置を計算しています。
Timeline停止時のスクロール
Editor上でスクラブをするようなTimelineが再生されていない状態ではFrameData.deltaTimeが0.0fになるため、そのままFrameData.deltaTimeは使用できませんでした。
Timeline停止中はProcessFrame()内で取得したクリップにおける再生位置(フレーム)を元にスクロール位置を計算しています。
また、BackgroundTimelineClipに現在のクリップまでにスクロールさせた分の経過時間(フレーム)を持たせることで、
スクロール停止クリップを挟んだ場合にも対応させています。

背景スプライトの切り替え
スクラブで背景がスクロールできるようになったので、 次は各場面でスプライトを切り替えるための拡張を作成しました。
- BackgroundSceneTrack:
SpriteRenderをバインドするスプライト切り替え用の拡張トラック - BackgroundSceneClip:切り替えるスプライトを指定する拡張クリップ
- BackgroundSceneBehaviour:バインドした
SpriteRenderにクリップで指定したスプライトを割り当てるPlayableBehaviour派生クラス

FrontとBack用のBackgroundSceneTrackを用意し、BackgroundSceneClipで指定したスプライトを各SpriteRendererに割り当てています。
FrontとBackの境目が画面に映らないようにフレーム送りでの細かい調整を行なったため、スクロール拡張がかなり役立ちました。

また制作序盤ではMarkerを使って通知を送ってシーンの切り替えを行おうとしていました。 しかし、スクラブなどを考慮するとトラックにしてしまった方が良いと思い、最終的には拡張トラックを作成しました。
今回は使用しませんでしたが、SceneSetNotificationはその名残です。
マーカー実装時には以下を参考にしました。
- [Unity] 分かった気になる! Timeline Signals / Timeline Signals Tutorial - Speaker Deck by 青木とと(lycoris102)さん
- カスタム Timeline Marker の作成方法 | Unity Blog

地面のつながりを表現するシェーダー
地面の表現としては上段・中段・下段用のスプライトを用意し、スクロールさせずにシェーダーで実装しました。
素材がドット絵風なテイストだったので、2色のグラデーションに対してポスタライズ処理を使ったシェーダーをShaderGraphで作成しました。


Gradient.shadergraph
パラメーターは以下の通りです。
- ColorRight:右端の色
- ColorLeft:左端の色
- T:ポスタライズ処理領域の中心 [0→1]
- Width:ポスタライズ処理領域の幅
- PosterizePower:ポスタライズ処理の度合い
このシェーダーを各スプライトに割り当て、背景スクロールに合わせてTを変化させるトラックを作成しました。


- GroundColor:上中下段のマテリアルに対して色情報をセットするクラス
- BackgroundColorTrack:
GroundColorClipとChangeColorClip(後述)用の拡張トラック - GroundColorClip:地面用のスプライトにセットする色の指定や
Tを変化させる拡張クリップ - BackgroundColorBehaviour:地面の色の変更処理や空フィルターの色を変更する(後述)ための
PlayableBehaviour派生クラス
GroundColorClipで指定する色情報はGradientとして持たせていますが、内部的にはGradient.Evaluate()で取得した左端(time = 0)と右端(time = 1)の色のみを使用しています。
Tの変化に関してはBackgroundColorBehaviour.ProcessFrame内で以下のように計算してそのまま渡すことができました。


曲のリズムに合わせたキャラクター、小物の動き
MVなので途中に出てくるキャラクターや小物の動きを曲に合わせる点についてもこだわりました。 是非キャラクターたちの動きにも注目してみてください!

時間変化に合わせた空と色調補正について
ここでは1日の時間に合わせて情景の変化をさせるために、 空の色や色調補正をかけるための拡張クリップを作成しました。
- ChangeColorClip:
Typeに応じて空の色変化や色調補正を行う拡張クリップ
TypeとしてはSKY、COLOR_ADDITIVE、SPRITE_ALPHAの3種類作成しました。

空の表現について
空については時間変化に応じて
カメラのbackgroundColorプロパティを変更しグラデーションさせています。

地面シェーダーで使用したクリップ長を1としたときのクリップ位置tもここでも使用しました。
クリップの再生に合わせてグラデーションさせるために、
ChangeColorClipで設定されたGradientに対してGradient.Evaluate()でColorを取得する際のtとして渡しています。
色調補正について
ある程度MVの流れができ、最終的に色調補正をかけたくなりました。
はじめはURPのPost-processingに含まれるColorAdjustmentsを使用しようと思い、設定を進めていました。

しかし、ポストプロセス(Post-processing)の名前の通り、背景色が描画された後でしか適用できず、背景色を色調補正から除外することはできませんでした。

せっかく設定した空の色が変わってしまう
空(背景)の色については先程のグラデーション機能で色を決めているため、
「空(背景色)以外の描画結果に対して、Timeline経由で色調補正をかける」
がここで目指すべきゴールでした。
ただ、全てのスプライトを拡張トラックなどで指定して調整するのは途方もない作業かつ、スプライトが増えた場合への拡張性がありません。
最終的にRenderTextureを使った方法を採用することにしました。

スプライトの描画結果をRenderTextureに出力するRenderTextureCameraを用意し、MainCameraで出力されたRenderTextureと背景色を描画します。

色調補正ではChangeColorClipのType.COLOR_ADDITVEを使用して時間変化に合わせて、クリップで指定されたグラデーションを用いてRawImageのcolorプロパティにセットしています。
まとめ
MVを作る上でこうしたい!という表現がたくさん出てきてそれに合わせる形でいろんな機能拡張に挑戦しました。

初めてのTimeline拡張で冗長なことをしている部分も多々あったとは思いますが、 最終的に満足のいくMVを公開することができました。
また、かなり参考にさせていただいたTimeline拡張の記事を執筆されている凹みさんも先日Unityを用いてムービー作成をされた際の記事を公開されています。 (Happy Wedding!!🎉)
"A Brand New Day"のMV作成後、そこまで動きの多くないMV(Good Old Times / relaxing lofi beat - YouTube)をAffter Effectsで作ってみました。 他の方法でのMV作成も試してみて、凹みさんも書かれているように、サクッと簡単な動画を作るのであれば動画編集ソフトがやはり使いやすいなと感じました。
ただ、無い機能を自分で拡張して作ること自体は学びが多く、 いろんな小さな課題に対して解決策を考えていく過程はとても楽しい時間でした! チャレンジした分だけ得られるものはあったと感じています。
今回は"A Brand New Day"のMVのためのTimeline拡張の話だったので、限定的なものも多いかとは思いますが、 この記事がまたTimeline拡張へ挑戦する方の一助となれば幸いです。
それでは!