Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Timeline エディター拡張入門
Search
Yuichiro MUKAI
May 10, 2024
Programming
0
1.3k
Timeline エディター拡張入門
2024/05/10 に開催された「Unity Timeline/Playable API 完全に理解した 勉強会」の登壇資料です。
Yuichiro MUKAI
May 10, 2024
Tweet
Share
More Decks by Yuichiro MUKAI
See All by Yuichiro MUKAI
MasterMemory v3 最速確認会
yucchiy
0
1.3k
Other Decks in Programming
See All in Programming
詳しくない分野でのVibe Codingで困ったことと学び/vibe-coding-in-unfamiliar-area
shibayu36
3
4.9k
階層構造を表現するデータ構造とリファクタリング 〜1年で10倍成長したプロダクトの変化と課題〜
yuhisatoxxx
3
1k
デミカツ切り抜きで面倒くさいことはPythonにやらせよう
aokswork3
0
230
なぜあの開発者はDevRelに伴走し続けるのか / Why Does That Developer Keep Running Alongside DevRel?
nrslib
3
400
理論と実務のギャップを超える
eycjur
0
130
Building, Deploying, and Monitoring Ruby Web Applications with Falcon (Kaigi on Rails 2025)
ioquatix
4
2.1k
非同期jobをtransaction内で 呼ぶなよ!絶対に呼ぶなよ!
alstrocrack
0
880
bootcamp2025_バックエンド研修_WebAPIサーバ作成.pdf
geniee_inc
0
100
The Past, Present, and Future of Enterprise Java
ivargrimstad
0
310
10年もののAPIサーバーにおけるCI/CDの改善の奮闘
mbook
0
820
The Past, Present, and Future of Enterprise Java
ivargrimstad
0
340
TFLintカスタムプラグインで始める Terraformコード品質管理
bells17
2
160
Featured
See All Featured
Designing for humans not robots
tammielis
254
26k
How GitHub (no longer) Works
holman
315
140k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
285
14k
Raft: Consensus for Rubyists
vanstee
139
7.1k
Producing Creativity
orderedlist
PRO
347
40k
How to train your dragon (web standard)
notwaldorf
96
6.3k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
17k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
15
1.7k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
31
9.7k
The World Runs on Bad Software
bkeepers
PRO
72
11k
StorybookのUI Testing Handbookを読んだ
zakiyama
31
6.2k
Scaling GitHub
holman
463
140k
Transcript
Unity Timeline/Playable API શʹཧղͨ͠ ษڧձ 2024/05/10 Ҫ ༞Ұ Timeline エ
デ ィター拡張入門
自己紹介 向井 祐一郎 • 株式会社 サイバーエージェント / 株式会社アプリ ボ ット
• Lead Developer Experience • システム系の基盤開発・プロジェクトの開発支援 SNS・個人活動など • @yucchiy_(X) • Unity Weekly • https://blog.yucchiy.com/tags/unity-weekly/ • UniTips(社内有志で技術書典に執筆)
• タイムラインのエ デ ィター拡張について概要の紹介 • TrackEditorとClipEditor • TrackEditorとClipEditorを用いた拡張の実例の紹介 • 見た目の調整やエラーメッセージの表示
• トラックのバインドオブジェクトの制御 • 生成・ 編 集時コールバックの取得 このLTで話したいこと
TrackEditorとClipEditorについて
TrackEditorとClipEditor • Timeline 1.1.0(2019/02/14)で追加された、トラックやクリップの エ デ ィター拡張 • UnityEditor.Editorのタイムライン版、といった立ち位置 •
トラックやクリップのエ デ ィターの挙動を制御できる • 見た目の調整やエラー表示 • トラックやクリップの追加・ 編 集時への処理の差し込み • トラックのバインドオブジェクトの制御 • etc…
標準トラックにおける拡張 事例 AudioClipの音声を波形情報として クリップの背景に表示したり バインドされたコン ポ ーネントがdisableなので エラーメッセージを表示したり
SampleTrackに対するエ デ ィターを作る TrackEditorの作り方 [CustomTimelineEditor(typeof(SampleTrack))] public class SampleTrackEditor : TrackEditor
{ public override void OnCreate(TrackAsset track, TrackAsset copiedFrom) { Debug.Log($"SampleTrack͕࡞͞Ε·ͨ͠ʂ"); } } CustomTimelineEditor属性をつけ、 対象のトラックの型を指定 用途に応じてメソッドをオーバーライドして処理を書く TrackEditorを継承
SampleTrackEditorの動作確認
SampleClipに対するエ デ ィターを作る ClipEditorの作り方 [CustomTimelineEditor(typeof(SampleClip))] public class SampleClipEditor : ClipEditor
{ public override void OnClipChanged(TimelineClip clip) { Debug.Log($"ΫϦοϓ͕ฤू͞Ε·ͨ͠ʂ"); } } CustomTimelineEditor属性をつけ、 対象のクリップの型を指定 ClipEditorを継承 用途に応じてメソッドをオーバーライドして処理を書く
SampleClipEditorの動作確認
TrackEditorでオーバーライドできるメソッド一覧 https://docs.unity3d.com/Packages/
[email protected]
/api/UnityEditor.Timeline.TrackEditor.html 見た目調整 & エラー処理 トラック操作時のコールバック トラックへのバインド オブジェクトの制御
ClipEditorでオーバーライドできるメソッド一覧 https://docs.unity3d.com/Packages/
[email protected]
/api/UnityEditor.Timeline.ClipEditor.html クリップ操作時のコールバック 見た目調整 & エラー処理 クリップ下へのタイムラインネスト制御
トラック/クリップの見た目を調整する
GetTrackOptionsをオーバーライドし、独自のTrackDrawOptionsを返却する GetTrackOptionsによる見た目の調整 [CustomTimelineEditor(typeof(SampleTrack))] public class SampleTrackEditor : TrackEditor { public
override TrackDrawOptions GetTrackOptions( TrackAsset track, Object binding) { var options = base.GetTrackOptions(track, binding); options.icon = AssetPreview.GetMiniTypeThumbnail(typeof(Rigidbody)); options.trackColor = Color.yellow; return options; } }
[CustomTimelineEditor(typeof(SampleTrack))] public class SampleTrackEditor : TrackEditor { public override TrackDrawOptions
GetTrackOptions( TrackAsset track, Object binding) { var options = base.GetTrackOptions(track, binding); options.errorText = track.GetClips().Count() switch { 0 => "ΫϦοϓΛઃఆ͍ͯͩ͘͠͞ʂ", 1 => string.Empty, _ => "ΫϦοϓ1͔ͭ͠ઃఆͰ͖·ͤΜʂ" }; return options; } errorTextにテキストを埋めることで、エラーメッセージを表示できる GetTrackOptionsでのエラーテキスト表示 クリップ一覧はTrackAsset.GetClipsで取得できる bindingには、トラックにバインドされた オブジェクトが渡ってくる
GetClipOptionsをオーバーライドし、独自のClipDrawOptionsを返却する GetClipOptionsによる見た目の調整 [CustomTimelineEditor(typeof(SampleClip))] public class SampleClipEditor : ClipEditor { public
override ClipDrawOptions GetClipOptions(TimelineClip clip) { var options = base.GetClipOptions(clip); options.highlightColor = Color.yellow; options.errorText = clip.duration < 1.0 ? "ΫϦοϓͷ͞1ඵҎ্ʹ͍ͯͩ͘͠͞ʂ" : string.Empty; return options; } } 各クリップごとにGetClipOptionsが呼び出され、 クリップ情報が引数に渡される
引数にクリップとその背景の領域が渡されるので、その領域にテクスチャを描画する DrawBackgroundによるクリップの背景の調整 [CustomTimelineEditor(typeof(SampleClip))] public class SampleClipEditor : ClipEditor { public
override void DrawBackground( TimelineClip clip, ClipBackgroundRegion region) { var tex = new Texture2D(128, 1); for (var x = 0; x < tex.width; x++) { tex.SetPixel(x, 0, Color.Lerp(Color.blue, Color.green, (float)x / tex.width)); } tex.wrapMode = TextureWrapMode.Clamp; tex.Apply(); GUI.DrawTexture(region.position, tex); } } 背景の矩形情報がRectで渡ってくるので、 その位置にGUI.DrawTextureなどで描画する エディターの定期更新で呼び出されるので、 テクスチャのキャッシュ等でパフォーマンスの工夫が必要
トラックへのオ ブ ジェクトの バイン デ ィン グ 挙動を調整する
IsBindingAssignableFromによって、オ ブ ジェクトのバインド判定を独自に定義できる IsBindingAssignableFromの活用 [CustomTimelineEditor(typeof(SampleTrack))] public class SampleTrackEditor : TrackEditor
{ public override bool IsBindingAssignableFrom( Object candidate, TrackAsset track) { var go = candidate as GameObject; if (go == null) return false; return go.TryGetComponent(out ISample _); } public override Object GetBindingFrom( Object candidate, TrackAsset track) { if (candidate is GameObject go) return go.GetComponent<ISample>() as Object; return null; } } IsBindingAssignableFromでfalseを返せば、 トラックへのオブジェクトバインディングを 拒否できる 実際にどのオブジェクトを トラックへバインドするかを指定できる (通常やれない)特定のインターフェイス を実装したコン ポ ーネントのバインド、ができる
トラック/クリップの追加・ 編 集を 検知する
それ ぞ れ OnCreate・On(Track|Clip)Changedというコールバックから取得可能 トラック/クリップの生成・ 編 集タイミン グ の取得 [CustomTimelineEditor(typeof(SampleTrack))]
public class SampleTrackEditor : TrackEditor { public override void OnCreate( TrackAsset track, TrackAsset copiedFrom) => Debug.Log($"SampleTrack͕࡞͞Ε·ͨ͠ʂ"); public override void OnTrackChanged( TrackAsset track) => Debug.Log($"SampleTrack͕ߋ৽͞Ε·ͨ͠ʂ"); }
クリップの振る舞いを制御できる(サ ポ ートする機能を報告する) 余談: ITimelineClipAsset.clipCaps [Flags] public enum ClipCaps {
None = 0, Looping = 1 << 0, Extrapolation = 1 << 1, ClipIn = 1 << 2, SpeedMultiplier = 1 << 3, Blending = 1 << 4, AutoScale = 1 << 5 | SpeedMultiplier, All = ~None } クリップをブレンドやループが可能か、 スケールした際の挙動、クリップ外の挙動など をフラグとして複数指定できる。 public interface ITimelineClipAsset { ClipCaps clipCaps { get; } }
クリップを重ねた際に、クリップを ブ レンドできることを示す機能 ClipCaps.Blending public class SampleClip : PlayableAsset, ITimelineClipAsset
{ public ClipCaps clipCaps => ClipCaps.Blending; }
クリップの範囲外の挙動を設定する ClipCaps.Extrapolation public class SampleClip : PlayableAsset, ITimelineClipAsset { public
ClipCaps clipCaps => ClipCaps.Extrapolation; } public class SampleBehaviour : PlayableBehaviour { public override void ProcessFrame( Playable playable, FrameData info, object playerData) { // ͨͩ͠Playable.GetDuration()ͷऔΓѻ͍ҙ Debug.Log($"{playable.GetTime()} / {playable.GetDuration()}"); } }
Extrapolationを設定すると、クリップ範囲外でもProcessFrameが呼 び 出される ClipCaps.Extrapolation の挙動
TimelineClipをTrackAssetからClipに渡して、Behaviourから参照する ClipCaps.Extrapolationにおけるdurationの取得 - 1 public class SampleTrack : TrackAsset {
public override Playable CreateTrackMixer( PlayableGraph graph, GameObject go, int inputCount) { var playable = ScriptPlayable<SampleMixer> .Create(graph, inputCount); foreach (var timelineClip in GetClips()) { var clip = timelineClip.asset as SampleClip; clip.TimelineClip = timelineClip; } return playable; } } public class SampleClip : PlayableAsset, ITimelineClipAsset { public TimelineClip TimelineClip; public override Playable CreatePlayable( PlayableGraph graph, GameObject owner) { var playable = ScriptPlayable<SampleBehaviour> .Create(graph); var behaviour = playable.GetBehaviour(); behaviour.Clip = this; return playable; } } TimelineClip.durationには正しい(?) Durationが入っているので、それをClipに渡し… https://blog.yucchiy.com/2019/11/how-to-access-clip-timing-in-playablebehaviour-with-unitytimeline/
public class SampleBehaviour : PlayableBehaviour { public SampleClip Clip {
get; set; } public override void ProcessFrame( Playable playable, FrameData info, object playerData) { var normalizedTime = playable.GetTime() / Clip.TimelineClip.duration; Debug.Log($"t = {normalizedTime}"); } } TimelineClipをTrackAssetからClipに渡して、Behaviourから参照する ClipCaps.Extrapolationにおけるdurationの取得 - 2 public class SampleClip : PlayableAsset, ITimelineClipAsset { public TimelineClip TimelineClip; public override Playable CreatePlayable( PlayableGraph graph, GameObject owner) { var playable = ScriptPlayable<SampleBehaviour> .Create(graph); var behaviour = playable.GetBehaviour(); behaviour.Clip = this; return playable; } } さらにClipをBehaviourに引き回して、 BehaviourからTimelineClipを参照する https://blog.yucchiy.com/2019/11/how-to-access-clip-timing-in-playablebehaviour-with-unitytimeline/
正しく(?)正規化時間が算出できることが確認できた ClipCaps.Extrapolationにおけるdurationの取得 -3
まとめ
• TrackEditorやClipEditorの基本的な使い方について紹介しました • TrackEditorやClipEditorを継承したうえで、 差し込みたい処理をメソッドのオーバーライドで実装する • TrackEditorやClipEditorの活用方法について説明しました • Get(Track|Clip)Optionsによる見た目の調整やエラーメッセージ表示 •
IsBindingAssignableToによるオブジェクトバインドの挙動制御 • OnCreateやOn(Track|Clip)Changedを用いたイベントのフック まとめ