I have been struggling with the same thing myself :
I have found two different approaches - both feel like work-arounds , and I am quite new to the Timeline system, so I don’t know if I’m doing unnecessary workarounds, or if this is the way you have to do what I am trying to do.
In short - what I want to do :
While playHead is inside a clip - I want a value t in range 0 to 1 , based on clip start and clip end.
var myVal = behaviour.animCurve.Evaluate(t)*behaviour.myData;
If playHead is past the clip end, I want to grab the value as given by
var myVal = behaviour.animCurve.Evaluate(1.0)*behaviour.myData;
Here is a video with both workarounds
That far cleanest solution I have found - when it comes to MixerBehaviourCode is having access to the clip start and clip end : which I had to do by using the work-around described here :
The mixer behaviour code becomes like this :
public void ProcessFrameClipPassthroughAndPlayHeadWorkAround(Playable playable, FrameData info, object playerData)
{
ScoreTallyMaster trackBinding = playerData as ScoreTallyMaster;
if (!trackBinding)
return;
int inputCount = playable.GetInputCount ();
double speed = 1.0;
float totalEndScore = 0f;
float currentAccumulatedScore = 0f;
for (int i = 0; i < inputCount; i++)
{
ScriptPlayable<ScoreTallyBehaviour> inputPlayable = (ScriptPlayable<ScoreTallyBehaviour>)playable.GetInput(i);
ScoreTallyBehaviour input = inputPlayable.GetBehaviour ();
totalEndScore += input.GetScore();
double playHeadTime = playable.GetGraph().GetRootPlayable(0).GetTime();
double duration = input.Clip.end - input.Clip.start;
double playHeadRelativeToClip = playHeadTime - input.Clip.start;
double tForClip = playHeadRelativeToClip / duration;
float floatingScore = input.GetScore() * input.easing.Evaluate((float)tForClip);
if (playHeadTime > input.Clip.end)
{
input.ScoreTallyPart.myScore.text = $"{Mathf.Floor(input.GetScore())}";
currentAccumulatedScore += (input.GetScore());
}
else if (playHeadTime < input.Clip.start)
{
input.ScoreTallyPart.myScore.text = $"0";
}
else
{
speed = SpeedFromScore(input.EditorPreviewScore);
currentAccumulatedScore += (floatingScore);
input.ScoreTallyPart.myScore.text = $"{Mathf.Floor(floatingScore)}";
}
}
if (trackBinding.Total!=null)
{
trackBinding.PublishScore(currentAccumulatedScore,totalEndScore);
}
playable.GetGraph().GetRootPlayable(0).SetSpeed(speed);
}
The other solution I found to get this working foregoes the clip start/end workaround, but uses a
“workAroundForHold” boolean in the behaviour - this is turned on on clips that has to fill the gaps between the real clips.
The mixerBehaviour for this method is weirder by magnitudes…
public void ProcessFrameHoldWorkAround(Playable playable, FrameData info, object playerData)
{
ScoreTallyMaster trackBinding = playerData as ScoreTallyMaster;
if (!trackBinding)
return;
int inputCount = playable.GetInputCount ();
float totalWeight = 0.0f;
float floatingScore = 0f;
double speed = 1.0;
float totalEndScore = 0f;
float currentAccumulatedScore = 0f;
/*
* We are counting the clips backwards for the reasons below..
* if we encounter clip 4 with weight > 0 , we
* want all clips prior to clip 4 to be at considered as weight 1
* When we are inside a clip - we want the local time t in 0-1
* to use with our own AnimationCurve to set easing for our clips.
*/
int playHeadPastClipIndex = -1;
for (int i = inputCount-1; i >= 0; i--)
{
ScriptPlayable<ScoreTallyBehaviour> inputPlayable = (ScriptPlayable<ScoreTallyBehaviour>)playable.GetInput(i);
ScoreTallyBehaviour input = inputPlayable.GetBehaviour ();
bool pastClip = false;
float inputWeight = playable.GetInputWeight(i);
if (inputWeight > Double.Epsilon)
{
playHeadPastClipIndex = i;
}
if (i < playHeadPastClipIndex)
{
pastClip = true;
}
/* I was unable to get previous values to hold and still get a t=0-1 range with GetTime and GetDuration
* when there were gaps in the timeline track.
*
* I ended up with a pragmatic workaround where a "hold"-clip is inserted instead of gaps in the timeline
* If the playhead is at a hold clip, just continue the loop
*/
if (input.workAroundForHold)
{
continue;
}
if (inputWeight > Double.Epsilon)
{
speed = SpeedFromScore(input.EditorPreviewScore) * inputWeight;
}
totalEndScore += input.GetScore();
totalWeight += inputWeight;
double timeForInput = playable.GetInput(i).GetTime();
double durationForInput = playable.GetInput(i).GetDuration();
float t = (float) (timeForInput/durationForInput);
floatingScore = input.GetScore() * input.easing.Evaluate(t);
if (pastClip)
{
input.ScoreTallyPart.myScore.text = $"{Mathf.Floor(input.GetScore())}";
currentAccumulatedScore += (input.GetScore());
}
else if (inputWeight > Double.Epsilon)
{
currentAccumulatedScore += (floatingScore);
input.ScoreTallyPart.myScore.text = $"{Mathf.Floor(floatingScore)}";
}
else if (inputWeight < Double.Epsilon)
{
input.ScoreTallyPart.myScore.text = $"0";
}
}
if (trackBinding.Total!=null)
{
if (totalWeight > Mathf.Epsilon || playHeadPastClipIndex!=-1)
{
trackBinding.PublishScore(currentAccumulatedScore,totalEndScore);
}else if (totalWeight < Mathf.Epsilon)
{
trackBinding.PublishScore(0f,totalEndScore);
}
}
playable.GetGraph().GetRootPlayable(0).SetSpeed(speed);
}
I would love to know if there is a better way to do things?
If there isn’t a better way, I think I would go for the the Clip-passthrough technique, because that code is just so much easier to read and understand.