There are multiple possible approaches, depending on what requirements you have. Shall the replay system allow for arbitrary back and forth scrubbing, or only forward playback?
If you only need forward playback (which I doubt), you could get away with only recording calls that have an effect on AnimationState, such as AddAnimation()
or modifying TrackEntry properties.
If you also need arbitrary positioning within the timeline, and forward as well as backward playback, recording track entries of AnimationState would be a good approach. It would also be possible to not save the AnimationState but to instead save all bone rotations and locations, but this will most likely require more storage space. If storage space is not a problem, recording all bone's locations could be easier implementation-wise. The lines below describe recording AnimationState and not how to record individual bone state.
1) What to record/serialize:
You would for each track record the list of currently enqueued TrackEntry objects (at least the first two track entries to cover transitions), with all relevant settings that deviate from the default. You need to at least record the Animation of the TrackEntry as well as the TrackTime
. The perfect approach would be to basically serialize all attributes of TrackEntry but to remove unnecessary and redundant data, as follows below.
2) How to store the data:
If you need to record long playback periods and need to save space, it would be advisable to only save what actually changed instead of the full redundant data every time. So you could save only the delta to:
1) a default TrackEntry, e.g. you don't need to save the default loop=0
all the time ("content-wise delta compression")
2) a delta to the last recording timepoint when no new TrackEntry was enqueued or removed ("time-wise delta compression"), e.g. when only TrackTime
changes, don't record the non-default but unchanged loop=1
every time.
3) When to serialize data:
Depending on your use case you might either want to serialize the state every 0.1 seconds, or every frame. When storage size does not matter and you can save every frame and don't need slow-motion playback, you could theoretically get away without interpolation between recorded timepoints. The perfect solution that support playback with playback-timepoints different from recording-timepoints would require to use an interpolated TrackEntry.TrackTime
of two recording timepoints.
4) How to apply serialized data:
Obviously, you would then need to unpack any delta-compressed saved TrackEntry states to fully filled-out TrackEntry objects (in the perfect solution also interpolating TrackEntry.TrackTime
when time lies between two entries), assign them to their respective AnimationState.tracks
and then call AnimationState.Apply()
to apply the animation state to the skeleton.
Note that when changing attachments or skins via code, these changes should also be recorded.
Please let us know if this makes sense to you or if anything is unclear.
Of course, any additional input by other users on the forum would be highly welcome, we would love to hear your ideas and solutions as well! Perhaps Timeline functionality could also be utilized successfully for such use cases.