application.targetframerate didn’t help (and I turned off vsync and confirmed I was getting approx the target rate, tried 10, 20, and 30 and adjusted my replay save rate to match).
I don’t think the problem is my replay code, because the live game play and also the replay by themselves are both smooth. It’s just when I try to combine the 2 on the screen at the same time, that one jitters (whichever the camera isn’t locked on).
In case it helps, or someone else wants to use this code in their own project, here is my ReplyManager class. Globals.RaceTime just returns Time.timeSinceLevelLoad (later I’m going to modify it to return actual race start time, once I add a “3, 2, 1, go” countdown).
Also, even with 10 FPS as I have in the code below, the replay is smooth, so I don’t think it’s that my capture rate is too slow. As you can see I’m adjusting for time/frame-rate on playback.
using UnityEngine;
using System.IO;
using System.Collections;
using System.Collections.Generic;
public class ReplayManager : MonoBehaviour
{
class FrameInfo
{
public float X;
public float Y;
public float Rotation;
public void SaveToStream(BinaryWriter bw)
{
bw.Write(X);
bw.Write(Y);
bw.Write(Rotation);
}
public void LoadFromStream(BinaryReader br)
{
X = br.ReadSingle();
Y = br.ReadSingle();
Rotation = br.ReadSingle();
}
public void FromTransform(Transform t, bool local)
{
if (local)
{
X = t.localPosition.x;
Y = t.localPosition.y;
Rotation = t.localEulerAngles.z;
}
else
{
X = t.position.x;
Y = t.position.y;
Rotation = t.eulerAngles.z;
}
}
public void UpdateTransform(Transform t, bool local, float perc)
{
if (local)
{
Vector3 v3 = t.localPosition;
v3.x = Mathf.Lerp(v3.x, X, perc);
v3.y = Mathf.Lerp(v3.y, Y, perc);
t.localPosition = v3;
v3 = t.localEulerAngles;
v3.z = Mathf.LerpAngle(v3.z, Rotation, perc);
t.localEulerAngles = v3;
}
else
{
Vector3 v3 = t.position;
v3.x = Mathf.Lerp(v3.x, X, perc);
v3.y = Mathf.Lerp(v3.y, Y, perc);
t.position = v3;
v3 = t.eulerAngles;
v3.z = Mathf.LerpAngle(v3.z, Rotation, perc);
t.eulerAngles = v3;
}
}
}
class Frame
{
public float Time;
public FrameInfo Car = new FrameInfo();
public FrameInfo Head = new FrameInfo();
public FrameInfo[] Wheels;
public void SaveToStream(BinaryWriter bw)
{
bw.Write(Time);
Car.SaveToStream(bw);
Head.SaveToStream(bw);
foreach (FrameInfo w in Wheels)
w.SaveToStream(bw);
}
public void LoadFromStream(BinaryReader br)
{
Time = br.ReadSingle();
Car.LoadFromStream(br);
Head.LoadFromStream(br);
for (int i = 0; i < Wheels.Length; i++)
{
FrameInfo w = new FrameInfo();
Wheels[i] = w;
w.LoadFromStream(br);
}
}
}
public Car ReplayCar;
public Car SaveCar;
List<Frame> ReplayFrames = new List<Frame>();
List<Frame> SaveFrames = new List<Frame>();
float LastSaveFrameRaceTime = 0;
float SaveFrameRate = 0.1f;
int CurrentReplayFrameIndex = 0;
float LastReplayUpdateRaceTime = 0;
bool ReplayLoaded = false;
void UpdateReplay()
{
if (!ReplayCar.enabled)
return;
if (!ReplayLoaded)
{
ReplayLoaded = true;
MemoryStream ms = new MemoryStream(File.ReadAllBytes(Application.persistentDataPath + "/replay.dat"));
BinaryReader br = new BinaryReader(ms);
LoadFromStream(br);
}
if (CurrentReplayFrameIndex == ReplayFrames.Count)
return;
Frame currentFrame = ReplayFrames[CurrentReplayFrameIndex];
while (currentFrame.Time < Globals.RaceTime)
{
if (++CurrentReplayFrameIndex == ReplayFrames.Count)
return;
currentFrame = ReplayFrames[CurrentReplayFrameIndex];
}
if (CurrentReplayFrameIndex == 0)
return;
float totalTimeBetweenCurrentLocationAndEndOfFrame = currentFrame.Time - LastReplayUpdateRaceTime;
float timeSinceLastUpdate = Globals.RaceTime - LastReplayUpdateRaceTime;
float framePerc = timeSinceLastUpdate / totalTimeBetweenCurrentLocationAndEndOfFrame;
Debug.Log(totalTimeBetweenCurrentLocationAndEndOfFrame + " " + timeSinceLastUpdate + " " + framePerc + " " + CurrentReplayFrameIndex);
currentFrame.Car.UpdateTransform(ReplayCar.transform, true, framePerc);
currentFrame.Head.UpdateTransform(ReplayCar.HeadTransform, true, framePerc);
for (int i = 0; i < currentFrame.Wheels.Length; i++)
{
FrameInfo w = currentFrame.Wheels[i];
Transform wheel = ReplayCar.Wheels[i];
w.UpdateTransform(wheel, true, framePerc);
}
LastReplayUpdateRaceTime = Globals.RaceTime;
}
void UpdateSave()
{
if (!SaveCar.enabled)
return;
if (SaveFrames.Count != 0 && Globals.RaceTime < LastSaveFrameRaceTime + SaveFrameRate)
return;
LastSaveFrameRaceTime = Globals.RaceTime;
Frame frame = new Frame();
SaveFrames.Add(frame);
frame.Time = Globals.RaceTime;
frame.Car.FromTransform(SaveCar.transform, true);
frame.Head.FromTransform(SaveCar.HeadTransform, true);
frame.Wheels = new FrameInfo[SaveCar.Wheels.Length];
for (int i = 0; i < SaveCar.Wheels.Length; i++)
{
Transform wheel = SaveCar.Wheels[i];
FrameInfo w = new FrameInfo();
frame.Wheels[i] = w;
w.FromTransform(wheel, true);
}
}
void Update()
{
UpdateReplay();
UpdateSave();
}
void OnDisable()
{
if (SaveFrames.Count == 0)
return;
MemoryStream ms = new MemoryStream();
BinaryWriter bw = new BinaryWriter(ms);
SaveToStream(bw);
File.WriteAllBytes(Application.persistentDataPath + "/replay.dat", ms.ToArray());
}
void SaveToStream(BinaryWriter bw)
{
bw.Write(SaveFrames.Count);
foreach (Frame frame in SaveFrames)
frame.SaveToStream(bw);
}
void LoadFromStream(BinaryReader br)
{
int frameCount = br.ReadInt32();
while (frameCount-- > 0)
{
Frame frame = new Frame();
frame.Wheels = new FrameInfo[ReplayCar.Wheels.Length];
frame.LoadFromStream(br);
ReplayFrames.Add(frame);
}
}
}