Skip to content

Commit ac2beb6

Browse files
committed
feat(TimelineCallback): add first version of TimelineCallback package
1 parent 264482f commit ac2beb6

File tree

76 files changed

+4491
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+4491
-0
lines changed

.github/workflows/main.yml

+3
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ jobs:
3737
3838
git subtree split -P "ExternalAssetLoader" -b upm-external-asset-loader
3939
git push -u origin upm-external-asset-loader --force
40+
41+
git subtree split -P "TimelineCallback" -b upm-timeline-callback
42+
git push -u origin upm-timeline-callback --force
4043
4144
git subtree split -P "General" -b upm-youneiti-toolbox
4245
git push -u origin upm-youneiti-toolbox --force

TimelineCallback/TimelineCallback.meta

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

TimelineCallback/TimelineCallback/Editor.meta

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "FortySevenE.TimelineCallback.Editor",
3+
"rootNamespace": "",
4+
"references": [
5+
"GUID:de45cafea229e5d42a379beb9526f89b",
6+
"GUID:02f771204943f4a40949438e873e3eff",
7+
"GUID:f06555f75b070af458a003d92f9efb00",
8+
"GUID:fa350bda189d3474f9e3daebcd57b68f"
9+
],
10+
"includePlatforms": [
11+
"Editor"
12+
],
13+
"excludePlatforms": [],
14+
"allowUnsafeCode": false,
15+
"overrideReferences": false,
16+
"precompiledReferences": [],
17+
"autoReferenced": true,
18+
"defineConstraints": [],
19+
"versionDefines": [],
20+
"noEngineReferences": false
21+
}

TimelineCallback/TimelineCallback/Editor/FortySevenE.TimelineCallback.Editor.asmdef.meta

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using MS.Events;
5+
using UnityEditor;
6+
using UnityEditor.Timeline;
7+
using UnityEngine;
8+
using UnityEngine.Timeline;
9+
10+
namespace FortySevenE.TimelineCallback
11+
{
12+
[CustomTimelineEditor(typeof(TimelineCallbackAsset))]
13+
public class TimelineCallbackPlayableAssetEditor : ClipEditor
14+
{
15+
private GUIStyle _extrapolateSignGuiStyle = new GUIStyle();
16+
readonly float _extrapolateSignWidth = 16f;
17+
private readonly float _extrapolateSignHeight = 4f;
18+
19+
public override void OnClipChanged(TimelineClip clip)
20+
{
21+
base.OnClipChanged(clip);
22+
var timelineEventClip = clip.asset as TimelineCallbackAsset;
23+
if (timelineEventClip != null)
24+
{
25+
clip.displayName = timelineEventClip.GetDisplayName();
26+
}
27+
}
28+
29+
public override void DrawBackground(TimelineClip clip, ClipBackgroundRegion region)
30+
{
31+
base.DrawBackground(clip, region);
32+
33+
_extrapolateSignGuiStyle.fontStyle = FontStyle.Bold;
34+
_extrapolateSignGuiStyle.alignment = TextAnchor.UpperCenter;
35+
_extrapolateSignGuiStyle.normal.textColor = Color.white;
36+
_extrapolateSignGuiStyle.fontSize = 14;
37+
38+
if (clip.asset is TimelineCallbackAsset normTimeControlClip)
39+
{
40+
if (normTimeControlClip.callbackControl.retroactive)
41+
{
42+
var extrapolateSignRect = new Rect(region.position.max.x + 1 - _extrapolateSignWidth,
43+
region.position.min.y - _extrapolateSignHeight,
44+
_extrapolateSignWidth, _extrapolateSignHeight);
45+
EditorGUI.LabelField(extrapolateSignRect, "∞", _extrapolateSignGuiStyle);
46+
}
47+
}
48+
}
49+
50+
public override ClipDrawOptions GetClipOptions(TimelineClip clip)
51+
{
52+
var drawOptions = base.GetClipOptions(clip);
53+
var timelineEventClip = clip.asset as TimelineCallbackAsset;
54+
if (timelineEventClip != null)
55+
{
56+
switch (timelineEventClip.callbackControl.callback.callState)
57+
{
58+
case CallState.Off:
59+
drawOptions.highlightColor = new Color(0.5f, 0, 0);
60+
break;
61+
case CallState.EditorAndRuntime:
62+
drawOptions.highlightColor = new Color(0,0.5f,0f) ;
63+
break;
64+
case CallState.RuntimeOnly:
65+
drawOptions.highlightColor = new Color(0.4448276f, 0f, 1f);
66+
break;
67+
}
68+
}
69+
70+
return drawOptions;
71+
}
72+
}
73+
}

TimelineCallback/TimelineCallback/Editor/TimelineCallbackPlayableAssetEditor.cs.meta

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

TimelineCallback/TimelineCallback/Runtime.meta

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "FortySevenE.TimelineCallback",
3+
"rootNamespace": "",
4+
"references": [
5+
"GUID:f06555f75b070af458a003d92f9efb00",
6+
"GUID:fa350bda189d3474f9e3daebcd57b68f"
7+
],
8+
"includePlatforms": [],
9+
"excludePlatforms": [],
10+
"allowUnsafeCode": false,
11+
"overrideReferences": false,
12+
"precompiledReferences": [],
13+
"autoReferenced": true,
14+
"defineConstraints": [],
15+
"versionDefines": [],
16+
"noEngineReferences": false
17+
}

TimelineCallback/TimelineCallback/Runtime/FortySevenE.TimelineCallback.asmdef.meta

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using System;
2+
using MS.Events;
3+
using UnityEngine;
4+
using UnityEngine.Playables;
5+
using UnityEngine.Timeline;
6+
7+
namespace FortySevenE.TimelineCallback
8+
{
9+
public class TimelineCallbackAsset : PlayableAsset, ITimelineClipAsset
10+
{
11+
public TimelineCallbackBehaviour callbackControl = default;
12+
13+
public TimelineClip TargetTimelineClip { get; set; }
14+
15+
public override Playable CreatePlayable(PlayableGraph graph, GameObject owner)
16+
{
17+
callbackControl.TargetTimelineClip = TargetTimelineClip;
18+
var playable = ScriptPlayable<TimelineCallbackBehaviour>.Create(graph, callbackControl);
19+
return playable;
20+
}
21+
22+
public string GetDisplayName()
23+
{
24+
return callbackControl.callback.funcName;
25+
}
26+
public ClipCaps clipCaps => ClipCaps.Blending;
27+
}
28+
29+
[Serializable]
30+
public class TimelineCallbackBehaviour : PlayableBehaviour
31+
{
32+
public bool retroactive;
33+
public bool onlyTriggerOnce;
34+
public TimelineSerializableCallback callback = new TimelineSerializableCallback();
35+
public Component TargetBinding { get; set; }
36+
public TimelineClip TargetTimelineClip { get; set; }
37+
public double ClipStart => TargetTimelineClip.start;
38+
public double ClipEnd => TargetTimelineClip.end;
39+
public int EventInvokeCounter { get; private set; }
40+
41+
public bool Enabled => callback.CanInvoke() && !(onlyTriggerOnce && EventInvokeCounter > 0);
42+
43+
44+
public override void OnPlayableCreate(Playable playable)
45+
{
46+
base.OnPlayableCreate(playable);
47+
callback.target = TargetBinding;
48+
}
49+
50+
public override void OnBehaviourPlay(Playable playable, FrameData info)
51+
{
52+
//Debug.Log($"FrameID{info.frameId}, Time:{playable.GetTime():0.000}, DeltaTime{info.deltaTime:0.000}");
53+
// Only invoke if time has passed to avoid invoking
54+
// repeatedly after resume
55+
if ((info.frameId == 0) || (info.deltaTime > 0) || playable.GetTime()<=0)
56+
{
57+
TriggerCallback();
58+
}
59+
}
60+
61+
public void TriggerCallback()
62+
{
63+
callback.Invoke(SerializableEvent.EMPTY_ARGS);
64+
EventInvokeCounter++;
65+
}
66+
67+
public void ResetInvokeCounter()
68+
{
69+
EventInvokeCounter = 0;
70+
}
71+
}
72+
73+
}

TimelineCallback/TimelineCallback/Runtime/TimelineCallbackAsset.cs.meta

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
using System.Collections;
2+
using System.Collections.Generic;
3+
using UnityEngine;
4+
using UnityEngine.Playables;
5+
using UnityEngine.Timeline;
6+
7+
namespace FortySevenE.TimelineCallback
8+
{
9+
[TrackClipType(typeof(TimelineCallbackAsset))]
10+
[TrackBindingType(typeof(Component))]
11+
public class TimelineCallbackTrack : TrackAsset
12+
{
13+
public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
14+
{
15+
var bindingTarget= go.GetComponent<PlayableDirector>().GetGenericBinding(this) as Component;
16+
var trackPlayable = ScriptPlayable<TimelineCallbackMixerBehaviour>.Create(graph, inputCount);
17+
foreach (var clip in GetClips())
18+
{
19+
if (clip.asset is TimelineCallbackAsset playableAsset)
20+
{
21+
playableAsset.callbackControl.TargetBinding = bindingTarget;
22+
playableAsset.TargetTimelineClip = clip;
23+
}
24+
}
25+
26+
return trackPlayable;
27+
}
28+
}
29+
30+
public class TimelineCallbackMixerBehaviour : PlayableBehaviour
31+
{
32+
public bool DebugLogging { get; set; }
33+
public bool Initialized { get; private set; }
34+
private Dictionary<string, List<TimelineCallbackBehaviour>> _clipControlsByFunctionName;
35+
36+
void Init(Playable playable)
37+
{
38+
_clipControlsByFunctionName = new Dictionary<string, List<TimelineCallbackBehaviour>>();
39+
int inputCount = playable.GetInputCount();
40+
for (int i = 0; i < inputCount; i++)
41+
{
42+
ScriptPlayable<TimelineCallbackBehaviour> inputPlayable =
43+
(ScriptPlayable<TimelineCallbackBehaviour>)playable.GetInput(i);
44+
TimelineCallbackBehaviour input = inputPlayable.GetBehaviour();
45+
input.ResetInvokeCounter();
46+
var functionName = input.callback.funcName;
47+
if (!_clipControlsByFunctionName.ContainsKey(functionName))
48+
{
49+
_clipControlsByFunctionName.Add(functionName, new List<TimelineCallbackBehaviour>());
50+
}
51+
52+
_clipControlsByFunctionName[functionName].Add(input);
53+
}
54+
55+
foreach (var clipControlsPerType in _clipControlsByFunctionName)
56+
{
57+
clipControlsPerType.Value.Sort((x, y) => x.ClipEnd.CompareTo(y.ClipEnd));
58+
}
59+
60+
Initialized = true;
61+
}
62+
63+
public override void OnGraphStart(Playable playable)
64+
{
65+
base.OnGraphStart(playable);
66+
if(!Initialized) Init(playable);
67+
}
68+
69+
public override void ProcessFrame(Playable playable, FrameData info, object playerData)
70+
{
71+
base.ProcessFrame(playable, info, playerData);
72+
73+
double time = playable.GetTime();
74+
75+
foreach (var clipControlsPerType in _clipControlsByFunctionName)
76+
{
77+
bool inTheMiddleOfNearestClipToLeft =
78+
GetNearestInputToTheLeft(clipControlsPerType.Value, time, out var nearestLeftInput);
79+
if (nearestLeftInput is { Enabled: true, retroactive: true, EventInvokeCounter: 0 } && !inTheMiddleOfNearestClipToLeft)
80+
{
81+
if (DebugLogging)
82+
{
83+
Debug.Log(
84+
$"Retroactively trigger event: end time {nearestLeftInput}" +
85+
$", current trigger count: {nearestLeftInput.EventInvokeCounter}");
86+
}
87+
nearestLeftInput.TriggerCallback();
88+
}
89+
}
90+
}
91+
92+
private bool GetNearestInputToTheLeft(List<TimelineCallbackBehaviour> leftToRightList,
93+
double targetTime, out TimelineCallbackBehaviour nearestInput)
94+
{
95+
int? nearestIndex = null;
96+
for (int i = leftToRightList.Count - 1; i >= 0; i--)
97+
{
98+
if (leftToRightList[i].ClipStart < targetTime)
99+
{
100+
nearestIndex = i;
101+
break;
102+
}
103+
}
104+
105+
if (!nearestIndex.HasValue)
106+
{
107+
nearestInput = null;
108+
return false;
109+
}
110+
111+
nearestInput = leftToRightList[nearestIndex.Value];
112+
return leftToRightList[nearestIndex.Value].ClipEnd > targetTime;
113+
}
114+
}
115+
}

TimelineCallback/TimelineCallback/Runtime/TimelineCallbackTrack.cs.meta

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)