Record
The first thing we need to do is to record our players movement. The easiest thing to do seems to be to record the players position and rotation (Transform). For this we create an object which will be used to hold the recorded information.RecordingData.cs
public class RecordingData
{
public float RecordedRate { get; set; }
private List<RecordData> recordedData = new List<RecordData>();
public struct RecordData
{
public Vector3 Position { get; set; }
public Quaternion Rotation { get; set; }
}
public void AddDataLine(RecordData data)
{
recordedData.Add(data);
}
public int pointer = 0;
public void MoveToStartOfData()
{
pointer = 0;
}
public RecordData? GetNextDataLine()
{
var count = recordedData.Count;
RecordData? data = null;
if (count > 0 && pointer < count && pointer >= 0) // not empty and pointer is within range and pointer not negative
{
data = recordedData[pointer];
}
pointer++;
return data;
}
}
Note
This can be extended to record more information in the future (maybe Scale ?)TakeSnapShot()
private RecordingData recordingData = new RecordingData();
public void TakeSnapshot()
{
var t = Transform;
var data = new RecordData()
{
Position = t.position,
Rotation = t.rotation
};
recordingData.AddDataLine(data);
}
Record Coroutine
public bool DoRecording = true;
void StartRecording()
{
DoRecording = true;
StartCoroutine(RecordingTimer(0.05f, 0f));
}
//RECORDING LOOP
IEnumerator RecordingTimer(float sampleRate)
{
recordingData.RecordedRate = sampleRate;
while (DoRecording) // Repeat until DoRecording is false
{
TakeSnapshot();
yield return new WaitForSeconds(recordingData.RecordedRate / Time.timeScale);
}
}
Note
DoRecording can be set to false to stop recording.Replay
Then we just need to play this data back. Again this is done with a coroutine.Playback Coroutine
public bool DoPlayback = true;
public void StartPlayback()
{
DoPlayback = true;
recordingData.MoveToStartOfData(); //Move to start of playback
StartCoroutine(PlaybackTimer());
}
Playback Loop
//PLAYBACK LOOP
IEnumerator PlaybackTimer()
{
while (DoPlayback) // Repeat Until DoPlayback is false
{
NextSnapshot();
yield return new WaitForSeconds(recordingData.RecordedRate / Time.timeScale);
}
}
NextSnapshot()
private RecordingData.RecordData? _desiredMoveLocation = null;
//MOVE DESIRED LOCATION TO NEXT LOCATION
private void NextSnapshot()
{
var data = recordingData.GetNextDataLine();
if (_desiredMoveLocation.HasValue && !data.HasValue) //If we previously had move location and now we do not
{
//END OF REPLAY - YOU MAY WANT TO REMOVE THE REPLAY OBJECT FROM SCENE NOW
}
_desiredMoveLocation = data;
}
Move Object
public void Update()
{
if (_desiredMoveLocation.HasValue)
{
var playbackRate = recordingData.RecordedRate;
var amount = Math.Abs(Time.deltaTime) / playbackRate;
var p = Vector3.Lerp(Transform.position, _desiredMoveLocation.Value.Position, amount);
var r = Quaternion.Lerp(Transform.rotation, _desiredMoveLocation.Value.Rotation, amount);
Transform.position = p;
Transform.rotation = r;
}
}
This is not the only way
There are many many ways of doing this. I am outlining how I did it.I have simplified my implementation for the purposes of this post (to outline roughly how it was done).
Any feedback or improvements are welcome :)
Have a question?
Feel free to ask me anything (I have overlooked some internal workings in this post). I also have a Unity3d Forum WIP post for this game prototype (in progress).More Info?
I have not gone into any detail on how the replay data can be saved and loaded between games (I have implemented this in my prototype).I can try and write a post outlining this if this is of interest?