はじめに
※このサンプルはUnity2018.3.0b4
で動作確認しています
DefaultPlayablesのサンプル実装のようにトラックを独自実装する場合、用途によってはデフォルトで生成されるクリップの長さを変更したい場合があります
しかし、サッと公開されている情報を漁ってみてもどのように設定するのかイマイチ分かりません
ここは内部実装を覗いてみて何か手段がないか探ってみることにしましょう
内部実装の解析
早速クリップを生成する内部コードを掘り起こして確認すると、下記のような恐ろしい決め打ち実装がなされていて、そのままでは強制的に5秒でクリップが生成されてしまうような設計になっています
public class TimelineClip : ISerializationCallbackReceiver
{
// ...(省略)
public static readonly float kDefaultClipDurationInSeconds = 5f;
// ...(省略)
}
public abstract class TrackAsset : PlayableAsset, IPropertyPreview, ISerializationCallbackReceiver
{
// ...(省略)
internal TimelineClip CreateNewClipContainerInternal()
{
// ...(省略)
timelineClip.duration = (double) TimelineClip.kDefaultClipDurationInSeconds;
// ...(省略)
return timelineClip;
}
// ...(省略)
}
しかし、もう少し内部実装を追ってみるととある項目を見てクリップ生成時の長さの設定を上書きしている箇所を発見することができます
public abstract class TrackAsset : PlayableAsset, IPropertyPreview, ISerializationCallbackReceiver
{
// ...(省略)
private TimelineClip CreateClipFromAsset(ScriptableObject playableAsset)
{
TimelineClip containerInternal = this.CreateNewClipContainerInternal();
// ...(省略)
IPlayableAsset playableAsset1 = playableAsset as IPlayableAsset;
if (playableAsset1 != null)
{
// なんかここでIPlayaleAsset.durationを使ってTimelineClip.durationを設定しているぞ(ΦωΦ)!
double duration = playableAsset1.duration;
if (!double.IsInfinity(duration) && duration > 0.0)
containerInternal.duration = Math.Min(Math.Max(duration, TimelineClip.kMinDuration), TimelineClip.kMaxTimeValue);
}
// ...(省略)
}
// ...(省略)
}
結論
IPlayableAsset
はトラックを独自実装する際にTrackClipTypeAttribute
で設定するクラスが継承することになります
継承関係としてはIPlayableAsset
-> PlayableAsset
-> 独自PlayableAsset
という形になり、独自実装側で何もしない場合duration
プロパティはPlayableAsset
でdouble.PositiveInfinity
が定義されることになっています
よってこのduration
プロパティを独自実装側でさらにoverride
してあげて、任意のデフォルト時間を設定してあげれば、クリップが生成されたときの長さを自由に設定できるようになるということになります
最後に、実際にデフォルトのクリップの長さを変更した実装例を紹介して終わりにしたいと思います
サンプルコード
トラックの独自実装の一連の流れを下記に全て並べてみていますが、重要なのはSamplePlayableAsset
クラスでoverride
しているdouble duration
プロパティです
// クリップ単位で持つPlayableAsset
public class SamplePlayableAsset : PlayableAsset, ITimelineClipAsset
{
const double DefaultDuration = 1.0f;
[SerializeField] float num;
public float Num
{
get { return num; }
}
// IPlayableAsset.durationを実装するとその値をデフォルト値としてTimelineClipを生成してくれる
public override double duration
{
get { return DefaultDuration; }
}
public ClipCaps clipCaps
{
get { return ClipCaps.Blending; }
}
// クリップ単位のPlayableを生成する
public override Playable CreatePlayable(PlayableGraph graph, GameObject owner)
{
// 自身(PlayableAsset)の情報を持たせたBehaviourでScriptPlayableを生成する
// ※これによりProcessFrame内でPlayableからBehaviourを取り出し、クリップ単位のPlayableAsset情報にアクセスできるようになる
return ScriptPlayable<SampleBehaviour>.Create(graph, new SampleBehaviour(this));
}
}
// クリップ単位の処理する
public class SampleBehaviour : PlayableBehaviour
{
// ここではミキサー側からPlayableAsset情報を取り出すためのプロパティを定義しているだけになっている
public SamplePlayableAsset Asset { get; private set; }
public SampleBehaviour(SamplePlayableAsset asset)
{
Asset = asset;
}
public SampleBehaviour()
{
}
}
// 独自実装のトラック
[TrackClipType(typeof(SamplePlayableAsset))]
[TrackBindingType(typeof(SampleComponent))]
public class SampleTrack : TrackAsset
{
// クリップ全体を処理できるミキサーの生成
public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
{
return ScriptPlayable<SampleMixer>.Create(graph, inputCount);
}
}
// クリップ全体を処理するためのミキサー
public class SampleMixer : PlayableBehaviour
{
// 現在のフレームの処理を実装する
public override void ProcessFrame(Playable playable, FrameData info, object playerData)
{
var component = playerData as SampleComponent;
if (component == null)
{
return;
}
// ここらへんはクリップごとにミキサーがブレンド処理をしている風を装っているだけなので深い意味はない
var num = 0.0f;
var inputCount = playable.GetInputCount();
for (var i = 0; i < inputCount; ++i)
{
var weight = playable.GetInputWeight(i);
if (Mathf.Approximately(weight, 0))
{
continue;
}
var input = (ScriptPlayable<SampleBehaviour>) playable.GetInput(i);
var behaviour = input.GetBehaviour();
var asset = behaviour.Asset;
num += (asset.Num * weight);
}
component.Num = num;
}
}
// 独自トラックにBindする対象となるコンポーネント
public class SampleComponent : MonoBehaviour
{
[SerializeField] float num;
// ミキサーから設定される想定のプロパティ
public float Num
{
get { return num; }
set { num = value; }
}
void Update()
{
// とりあえzyミキサーから設定されたパラメータをデバッグログ出力している
Debug.LogFormat("num={0}", Num);
}
}