Hi all,
We would like to create a dynamic timeline at runtime via scripts. Maybe it’s better to first state the requirements to check if this is the right approach :):
We basically have a scene with multiple moving objects depending on time. The user would need to be able to rewind, play, pause and fast forward. We also have events that are triggered that are causing other objects to move or stop. We also want to control time via some sort of slider and be able to go specific points in time.
My first thought was to record every object in the scene and have some kind of integration with the timeline to rewind or go to specific points in time. However, I was unable to do this. Maybe I’m missing something but dynamically recording and cutting recordings when going back in time and re-recording when pressing play / fast forward seemed very hard to do (because events can change so we would always need to re-record when pressing play / fast forward). Also, when fast forwarding the game with timescale, causes the recording to be speed up.
So, my second thought was to create custom playables with a duration of 1 second and link them together on the timeline. This way i can rewind through the already created playables. When the player presses play again, I would need to start generating new playables from that point. I thought the best way of testing this was to create a custom PlayableBehaviour that simply moves objects depending on the begin and end positions defined in a property. However, I’m unable to pass the custom properties to the PlayableBehaviour via the PlayableAsset. In my code, the TimelineObject is always null.
Maybe I’m missing something really critical here but I already scoured a lot of timeline posts and I haven’t found any solution to my problem. I’m probably a bit confused with all the Playable / Timeline / Playable Graphs and PlayableDirector references and how to pass different properties to different playables. Any suggestions are always welcome because maybe what I’m doing is also not the right way.
Thank you very much for your feedback!
I will post my code to (maybe) make it a bit more clear
[TrackBindingType(typeof(GameObject))]
[TrackClipType(typeof(TrainControlClip))]
public class MoveableControlTrack : TrackAsset
{
}
[Serializable]
public class TrainControlBehaviour : PlayableBehaviour
{
public TimelineObject TimelineObject { get; set; }
public Transform Transform { get; set; }
public override void ProcessFrame(Playable playable, FrameData info, object playerData)
{
var gameObject = playerData as GameObject;
// Timeline object is always null
if (gameObject == null || TimelineObject == null)
return;
float ratio = (float) playable.GetTime() / (float) playable.GetDuration();
gameObject.transform.position = Vector3.Lerp(TimelineObject.beginPosition, TimelineObject.endPosition, ratio);
}
}
[Serializable]
public class TrainControlClip : PlayableAsset, ITimelineClipAsset
{
[SerializeField] public TrainControlBehaviour template = new TrainControlBehaviour();
public ExposedReference<TimelineObject> TimelineObjectReference;
public ExposedReference<Transform> TransformReference;
public override double duration
{
get { return 1; }
}
public override Playable CreatePlayable(PlayableGraph graph, GameObject owner)
{
var playable = ScriptPlayable<TrainControlBehaviour>.Create(graph);
var director = owner.GetComponent<PlayableDirector>();
TimelineObject reference = TimelineObjectReference.Resolve(director);
playable.GetBehaviour().TimelineObject = reference;
return playable;
}
// Inheritance from ITimelineClipAsset
// We don't want to blend clips together
public ClipCaps clipCaps
{
get { return ClipCaps.None; }
}
}
public class TimelineObject : MonoBehaviour
{
public Vector3 beginPosition { get; set; }
public Vector3 endPosition { get; set; }
public Quaternion beginRotation { get; set; }
public Quaternion endRotation { get; set; }
public TimelineObject(Transform transform)
{
beginPosition = transform.position;
beginRotation = transform.rotation;
}
public void SetEndValues(Transform transform)
{
endPosition = transform.position;
endRotation = transform.rotation;
}
}
TimeManager that creates the clips and ties them together (only one clip at the moment)
TrainControlClip clip = ScriptableObject.CreateInstance<TrainControlClip>();
var playable = clip.CreatePlayable(director.playableGraph, gameObject);
TimelineAsset timelineAsset = (TimelineAsset) director.playableAsset;
//create new animationTrack on timeline
var newTrack = timelineAsset.CreateTrack<MoveableControlTrack>(null, train.name);
//bind object to which the animation shall be assigned to the created animationTrack
director.SetGenericBinding(newTrack, train);
////create a timelineClip for the animationClip on the AnimationTrack
var timelineClip1 = newTrack.CreateClip<TrainControlClip>();
timelineClip1.displayName = "clip1";
TrainControlClip testPlayable = timelineClip1.asset as TrainControlClip;
TimelineObject timelineObject = new TimelineObject(train.transform);
train.transform.position += Vector3.forward;
timelineObject.SetEndValues(train.transform);
director.SetReferenceValue(testPlayable.TimelineObjectReference.exposedName, timelineObject);