Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Jitter/stutter issue

Discussion in 'Editor & General Support' started by shopguy, Nov 8, 2014.

  1. shopguy

    shopguy

    Joined:
    Jan 12, 2013
    Posts:
    282
    I'm trying to add a "race your replay" or "race your best time" feature and I'm having a weird jitter/stutter issue that has me at a loss. Please help if you can, any ideas appreciated.

    The darker jeep in the back (z-order) is the replay. The jeep in the front is controlled by physics and a lot of code I wrote. At first I assumed the issue was something with my replay code, the method or rate at which I save the locations or the way I play them back. However, just by changing the target that the camera follows, changes the issue from one jeep to the other -- weird right?

    In this video the physics controlled jeep stutters, the camera is following the replay jeep:


    If I make no other changes, except have my camera follow the physics jeep instead of the replay, now the replay jeep stutters instead:


    Any ideas on what I should start looking for? I can post some of my code but not sure what area would even be helpful to see. The 2 jeeps and the camera are not parent/child of each other, they are all top level objects in the hierarchy, and I just update the camera X,Y to match one of the jeeps. Other than this one issue everything works great, for example, if I disable the replay jeep, the game runs very smoothly at 90+ FPS. I see the issue in the game window in editor and also Windows stand-alone build.
     
  2. shopguy

    shopguy

    Joined:
    Jan 12, 2013
    Posts:
    282
    Edited: delete my 2nd thought, turned out to not be the case... still at a loss.
     
  3. 0tacun

    0tacun

    Joined:
    Jun 23, 2013
    Posts:
    245
    Try to restrict your framerate to your fixed update framerate with application.targetframerate
     
  4. shopguy

    shopguy

    Joined:
    Jan 12, 2013
    Posts:
    282
    When you say "your fixed update" do you mean the rate that I save locations for my replay? Obviously that rate isn't going to be exact, since nothing is exact... but I currently save at 10 or 20 FPS (tried both), but my playback code uses Lerp and MoveTowards... I guess I should probably post that code, if it seems likely the problem. I'll do that later, away from my PC right now.
     
  5. shopguy

    shopguy

    Joined:
    Jan 12, 2013
    Posts:
    282
    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.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.IO;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6. public class ReplayManager : MonoBehaviour
    7. {
    8.     class FrameInfo
    9.     {
    10.         public float X;
    11.         public float Y;
    12.         public float Rotation;
    13.         public void SaveToStream(BinaryWriter bw)
    14.         {
    15.             bw.Write(X);
    16.             bw.Write(Y);
    17.             bw.Write(Rotation);
    18.         }
    19.         public void LoadFromStream(BinaryReader br)
    20.         {
    21.             X = br.ReadSingle();
    22.             Y = br.ReadSingle();
    23.             Rotation = br.ReadSingle();
    24.         }
    25.  
    26.         public void FromTransform(Transform t, bool local)
    27.         {
    28.             if (local)
    29.             {
    30.                 X = t.localPosition.x;
    31.                 Y = t.localPosition.y;
    32.                 Rotation = t.localEulerAngles.z;
    33.             }
    34.             else
    35.             {
    36.                 X = t.position.x;
    37.                 Y = t.position.y;
    38.                 Rotation = t.eulerAngles.z;
    39.             }
    40.         }
    41.      
    42.         public void UpdateTransform(Transform t, bool local, float perc)
    43.         {
    44.             if (local)
    45.             {
    46.                 Vector3 v3 = t.localPosition;
    47.                 v3.x = Mathf.Lerp(v3.x, X, perc);
    48.                 v3.y = Mathf.Lerp(v3.y, Y, perc);
    49.                 t.localPosition = v3;
    50.                 v3 = t.localEulerAngles;
    51.                 v3.z = Mathf.LerpAngle(v3.z, Rotation, perc);
    52.                 t.localEulerAngles = v3;
    53.             }
    54.             else
    55.             {
    56.                 Vector3 v3 = t.position;
    57.                 v3.x = Mathf.Lerp(v3.x, X, perc);
    58.                 v3.y = Mathf.Lerp(v3.y, Y, perc);
    59.                 t.position = v3;
    60.                 v3 = t.eulerAngles;
    61.                 v3.z = Mathf.LerpAngle(v3.z, Rotation, perc);
    62.                 t.eulerAngles = v3;
    63.             }
    64.         }
    65.     }
    66.  
    67.     class Frame
    68.     {
    69.         public float Time;
    70.         public FrameInfo Car = new FrameInfo();
    71.         public FrameInfo Head = new FrameInfo();
    72.         public FrameInfo[] Wheels;
    73.         public void SaveToStream(BinaryWriter bw)
    74.         {
    75.             bw.Write(Time);
    76.             Car.SaveToStream(bw);
    77.             Head.SaveToStream(bw);
    78.             foreach (FrameInfo w in Wheels)
    79.                 w.SaveToStream(bw);
    80.         }
    81.         public void LoadFromStream(BinaryReader br)
    82.         {
    83.             Time = br.ReadSingle();
    84.             Car.LoadFromStream(br);
    85.             Head.LoadFromStream(br);
    86.             for (int i = 0; i < Wheels.Length; i++)
    87.             {
    88.                 FrameInfo w = new FrameInfo();
    89.                 Wheels[i] = w;
    90.                 w.LoadFromStream(br);
    91.             }
    92.         }
    93.     }
    94.  
    95.     public Car ReplayCar;
    96.     public Car SaveCar;
    97.  
    98.     List<Frame> ReplayFrames = new List<Frame>();
    99.     List<Frame> SaveFrames = new List<Frame>();
    100.     float LastSaveFrameRaceTime = 0;
    101.     float SaveFrameRate = 0.1f;
    102.     int CurrentReplayFrameIndex = 0;
    103.     float LastReplayUpdateRaceTime = 0;
    104.     bool ReplayLoaded = false;
    105.  
    106.     void UpdateReplay()
    107.     {
    108.         if (!ReplayCar.enabled)
    109.             return;
    110.  
    111.         if (!ReplayLoaded)
    112.         {
    113.             ReplayLoaded = true;
    114.             MemoryStream ms = new MemoryStream(File.ReadAllBytes(Application.persistentDataPath + "/replay.dat"));
    115.             BinaryReader br = new BinaryReader(ms);
    116.             LoadFromStream(br);
    117.         }
    118.  
    119.         if (CurrentReplayFrameIndex == ReplayFrames.Count)
    120.             return;
    121.         Frame currentFrame = ReplayFrames[CurrentReplayFrameIndex];
    122.         while (currentFrame.Time < Globals.RaceTime)
    123.         {
    124.             if (++CurrentReplayFrameIndex == ReplayFrames.Count)
    125.                 return;
    126.             currentFrame = ReplayFrames[CurrentReplayFrameIndex];
    127.         }
    128.  
    129.         if (CurrentReplayFrameIndex == 0)
    130.             return;
    131.  
    132.         float totalTimeBetweenCurrentLocationAndEndOfFrame = currentFrame.Time - LastReplayUpdateRaceTime;
    133.         float timeSinceLastUpdate = Globals.RaceTime - LastReplayUpdateRaceTime;
    134.         float framePerc = timeSinceLastUpdate / totalTimeBetweenCurrentLocationAndEndOfFrame;
    135.         Debug.Log(totalTimeBetweenCurrentLocationAndEndOfFrame + " " + timeSinceLastUpdate + " " + framePerc + " " + CurrentReplayFrameIndex);
    136.         currentFrame.Car.UpdateTransform(ReplayCar.transform, true, framePerc);
    137.         currentFrame.Head.UpdateTransform(ReplayCar.HeadTransform, true, framePerc);
    138.         for (int i = 0; i < currentFrame.Wheels.Length; i++)
    139.         {
    140.             FrameInfo w = currentFrame.Wheels[i];
    141.             Transform wheel = ReplayCar.Wheels[i];
    142.             w.UpdateTransform(wheel, true, framePerc);
    143.         }
    144.  
    145.         LastReplayUpdateRaceTime = Globals.RaceTime;
    146.     }
    147.  
    148.     void UpdateSave()
    149.     {
    150.         if (!SaveCar.enabled)
    151.             return;
    152.  
    153.         if (SaveFrames.Count != 0 && Globals.RaceTime < LastSaveFrameRaceTime + SaveFrameRate)
    154.             return;
    155.  
    156.         LastSaveFrameRaceTime = Globals.RaceTime;
    157.  
    158.         Frame frame = new Frame();
    159.         SaveFrames.Add(frame);
    160.      
    161.         frame.Time = Globals.RaceTime;
    162.  
    163.         frame.Car.FromTransform(SaveCar.transform, true);
    164.         frame.Head.FromTransform(SaveCar.HeadTransform, true);
    165.         frame.Wheels = new FrameInfo[SaveCar.Wheels.Length];
    166.         for (int i = 0; i < SaveCar.Wheels.Length; i++)
    167.         {
    168.             Transform wheel = SaveCar.Wheels[i];
    169.             FrameInfo w = new FrameInfo();
    170.             frame.Wheels[i] = w;
    171.             w.FromTransform(wheel, true);
    172.         }
    173.     }
    174.  
    175.     void Update()
    176.     {
    177.         UpdateReplay();
    178.         UpdateSave();
    179.     }
    180.  
    181.     void OnDisable()
    182.     {
    183.         if (SaveFrames.Count == 0)
    184.             return;
    185.         MemoryStream ms = new MemoryStream();
    186.         BinaryWriter bw = new BinaryWriter(ms);
    187.         SaveToStream(bw);
    188.         File.WriteAllBytes(Application.persistentDataPath + "/replay.dat", ms.ToArray());
    189.     }
    190.  
    191.     void SaveToStream(BinaryWriter bw)
    192.     {
    193.         bw.Write(SaveFrames.Count);
    194.         foreach (Frame frame in SaveFrames)
    195.             frame.SaveToStream(bw);
    196.     }
    197.  
    198.     void LoadFromStream(BinaryReader br)
    199.     {
    200.         int frameCount = br.ReadInt32();
    201.         while (frameCount-- > 0)
    202.         {
    203.             Frame frame = new Frame();
    204.             frame.Wheels = new FrameInfo[ReplayCar.Wheels.Length];
    205.             frame.LoadFromStream(br);
    206.             ReplayFrames.Add(frame);
    207.         }
    208.     }
    209. }
    210.  
     
  6. shopguy

    shopguy

    Joined:
    Jan 12, 2013
    Posts:
    282
    I have seen a look very similar to this before when I was doing this in my code, in this exact order:

    1. Moving the camera to location of character/car

    2. Move the character/car

    ...all in the same frame/update. So then I moved the code that updates the camera location (to follow character/car) to LateUpdate instead of Update, so it would always run last, and that fixed the issue before.

    So maybe some rendering issue.. where one jeep is being rendered, then the camera moves, then the other?

    I'm going to see if I can recreate in a smaller test project, because the issue is very important to me, but I suspect it's going to be hard to help me with this being part of such a large project -- too many variables.
     
  7. shopguy

    shopguy

    Joined:
    Jan 12, 2013
    Posts:
    282
  8. shopguy

    shopguy

    Joined:
    Jan 12, 2013
    Posts:
    282
  9. Spacejet13

    Spacejet13

    Joined:
    Apr 22, 2018
    Posts:
    8