はじめに
Timelineを使用していると、デフォルトのトラックでは機能が足りないことがあると思います。
そんなときは、自分でコードを書くことでカスタムトラックを作成することができます。
カスタムトラックの作成方法
カスタムトラックを作成する方法はPlayableTrackを自作する方法もありますが、今回はPlayableMixerを使用する方法でいこうと思います。
PlayableTrackを使用すると、処理落ちなどでTimelineの時間が一気に飛んでしまった場合の考慮がされていないためです。(詳しくは参考資料にある動画をみてください)
カスタムトラックを作成するには、TrackAsset、PlayableAsset、PlayableMixer、PlayableBehaviourの4つのコードを作成する必要があります。
TrackAsset
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
[TrackBindingType( typeof( Camera ) )]
[TrackColor( 0.93f, 0.1f, 0.24f )]
[TrackClipType( typeof( CameraPlayableAsset ) )]
public class CameraTrack : TrackAsset
{
/// <summary>
/// トラックミキサーの作成
/// </summary>
///
/// <param name="graph"> PlayableGraph </param>
/// <param name="go"> GameObject </param>
/// <param name="inputCount"> 入力回数 </param>
///
/// <returns>
/// 作成したミキサー
/// </returns>
public override Playable CreateTrackMixer( PlayableGraph graph, GameObject go, int inputCount )
{
var mixer = ScriptPlayable<CameraPlayableMixer>.Create( graph, inputCount );
var director = go.GetComponent<PlayableDirector>();
if( director != null )
{
var outputGo = director.GetGenericBinding( this ) as Camera;
CameraPlayableMixer behaviour = mixer.GetBehaviour();
behaviour.clips = GetClips();
if( outputGo != null )
{
behaviour.SetGameObject( outputGo );
}
behaviour.playableDirector = director;
}
return mixer;
}
/// <summary>
/// タイムラインを離れたときにパラメータを戻す処理
/// </summary>
///
/// <param name="director"> PlayableDirector </param>
/// <param name="driver"> IPropertyCollector </param>
public override void GatherProperties( PlayableDirector director, IPropertyCollector driver )
{
#if UNITY_EDITOR
Camera trackBinding = director.GetGenericBinding( this ) as Camera;
if( trackBinding == null )
{
return;
}
var serializedObject = new UnityEditor.SerializedObject( trackBinding );
var iterator = serializedObject.GetIterator();
while( iterator.NextVisible( true ) )
{
if( iterator.hasVisibleChildren )
{
continue;
}
driver.AddFromName<Camera>( trackBinding.gameObject, iterator.propertyPath );
}
#endif
base.GatherProperties( director, driver );
}
}
TrackBindingTypeは、Trackにバインドするクラスを指定します。
TrackColorは、TimelineEditor上のTrackの色を指定します。
TrackClipTypeには、この後説明するPlayableAssetを指定します。
CreateTrackMixerでPlayableMixerを作成し、Mixerに必要な情報を流し込みます。
また、GatherPropertiesでタイムライン再生中にバインドされたオブジェクトに変更がかかっても元のパラメータに戻す処理を書いています。
PlayableAsset
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
[System.Serializable]
public class CameraPlayableAsset : PlayableAsset, ITimelineClipAsset
{
/// <summary>
/// PlayableBehaviour
/// </summary>
public CameraPlayableBehaviour behaviour = new CameraPlayableBehaviour();
/// <summary>
/// ClipCapsの取得(Unity側のインターフェースで定義されているもの)
/// </summary>
public ClipCaps clipCaps
{
get
{
return ClipCaps.All;
}
}
/// <summary>
/// プレイアブルの作成
/// </summary>
///
/// <param name="graph"> プレイアブルグラフ </param>
/// <param name="go"> ゲームオブジェクト </param>
public override Playable CreatePlayable( PlayableGraph graph, GameObject go )
{
var playable = ScriptPlayable<CameraOffsetPlayableBehaviour>.Create( graph, m_Behaviour );
return playable;
}
}
PlayableBehaviourを作成します。
注意点として、classの上に[System.Serialize]属性をつけてください。
PlayableBehaviour
using UnityEngine.Playables;
[System.Serializable]
public class CameraPlayableBehaviour : PlayableBehaviour
{
// ここに必要なパラメータを追加していきます。今回はFieldOfViewを変更するための変数を追加してみました。
/// <summary>
/// Fov
/// </summary>
public float fieldOfView;
}
こちらに、Trackに必要なパラメータを定義して下さい。今回はテストでFieldOfViewを変更する変数を追加しました。
こちらも注意点として、classの上に[System.Serialize]属性をつけてください。
PlayableMixer
using UnityEngine.Playables;
using System.Collections.Generic;
using UnityEngine.Timeline;
public class CameraPlayableMixer : PlayableBehaviour
{
/// <summary>
/// プレイアブルディレクター
/// </summary>
internal PlayableDirector payableDirector;
/// <summary>
/// タイムラインクリップ
/// </summary>
internal IEnumerable<TimelineClip> clips;
/// <summary>
/// バインドしているバトルカメラ
/// </summary>
private GameObject boundObject;
/// <summary>
/// 前フレームのアセット
/// </summary>
private CameraPlayableBehaviour oldBehaviour;
/// <summary>
/// 初期化したかどうか
/// </summary>
private bool isInitialize = false;
/// <summary>
/// バウンドしているテキストの取得
/// </summary>
public BattleCamera GetBoundBattleCamera()
{
return m_BoundBattleCamera;
}
/// <summary>
/// バウンドしているカメラのセット
/// </summary>
///
/// <param name="battleCamera"> バトルカメラコンポーネント </param>
public void SetBattleCamera( BattleCamera battleCamera )
{
m_BoundBattleCamera = battleCamera;
}
/// <summary>
/// OnGraphStart
/// </summary>
///
/// <param name="playable"> Playable </param>
public override void OnGraphStart( Playable playable )
{
if( m_BoundBattleCamera != null )
{
m_BackupOffset = m_BoundBattleCamera.GetCameraOffsetController().GetCameraOffset();
}
}
/// <summary>
/// OnGraphStop
/// </summary>
///
/// <param name="playable"> Playable </param>
public override void OnGraphStop( Playable playable )
{
}
/// <summary>
/// ProcessFrame
/// </summary>
///
/// <param name="playable"> Playable </param>
/// <param name="info"> FrameData </param>
/// <param name="playerData"> PlayerData </param>
public override void ProcessFrame( Playable playable, FrameData info, object playerData )
{
int inputCount = playable.GetInputCount<Playable>();
if( inputCount == 0 || m_BoundBattleCamera == null )
{
return;
}
if( !m_IsInitialize )
{
m_OldBehaviour = null;
m_IsInitialize = true;
}
var time = m_PlayableDirector.time;
var enumulator = m_Clips.GetEnumerator();
enumulator.MoveNext();
for( int i = 0; i < inputCount; i++, enumulator.MoveNext() )
{
var clip = enumulator.Current;
float inputWeight = playable.GetInputWeight( i );
ScriptPlayable<CameraOffsetPlayableBehaviour> inputPlayable = ( ScriptPlayable<CameraOffsetPlayableBehaviour> )playable.GetInput( i );
CameraOffsetPlayableBehaviour inputBehaviour = inputPlayable.GetBehaviour();
if( time >= clip.start && time <= clip.end )
{
// 以前と違うビヘイビアになっていたら、リセットする
if( m_OldBehaviour == null || m_OldBehaviour != inputBehaviour )
{
if( m_BoundBattleCamera && inputBehaviour.m_CameraOffsetParameter != null )
{
switch( inputBehaviour.m_OffsetType )
{
case CameraOffsetPlayableBehaviour.E_OFFSET_TYPE.DEFAULT:
m_BoundBattleCamera.GetCameraOffsetController().SetTargetOffset( inputBehaviour.m_CameraOffsetParameter );
break;
case CameraOffsetPlayableBehaviour.E_OFFSET_TYPE.BACKUP_OFFSET:
if( inputBehaviour.m_TimeSpan > 0.0f )
{
CameraOffsetParameter cameraOffsetParameter = new CameraOffsetParameter( m_BackupOffset );
cameraOffsetParameter.m_EnablePosition = true;
cameraOffsetParameter.m_EnableRotation = true;
cameraOffsetParameter.m_EnableWidthRatio = true;
cameraOffsetParameter.m_EnableHeightLimit = true;
cameraOffsetParameter.m_EaseType = inputBehaviour.m_EaseType;
cameraOffsetParameter.m_TimeSpan = inputBehaviour.m_TimeSpan;
m_BoundBattleCamera.GetCameraOffsetController().SetTargetOffset( cameraOffsetParameter );
}
else
{
m_BoundBattleCamera.GetCameraOffsetController().OverrideOffsetParameter( m_BackupOffset );
}
break;
default:
break;
}
}
m_OldBehaviour = inputBehaviour;
}
}
}
}
}
if( time >= clip.start && time <= clip.end )内に実際の挙動を書きます。
コメント