Need Help with time rewind and playback scripts

Hello, I am a beginner coder, I have been trying to prototype a system where a character can rewind back in time, and then a clone of the character would perform the same actions the initial characters did. I found a plugin called ReTime that can handle the rewinding part. I intended to utilise the code from this plugin to create the playback system.

7fSTtd8adkAPw38lJg
I got it working such that it works as expected with a cube.
Hzhbo9fuQvXKT6Sg0D
however using it on a copy of the player character ( with the retime script removed, and the clone player script added) it is entirely erratic.

I have used the Unity Starter assets 3D character controller.

here are the prefab details for the player clone.

here are the details related to the original player.

here are the scripts related to the rewind and playback.

using System.Collections;
using System.Collections.Generic;
using StarterAssets;
using UnityEngine;

public class ReTime : MonoBehaviour {
    //flagging to enable or disable rewind
    [HideInInspector]
    public bool isRewinding = false;

    //use a linked list data structure for better performance of accessing previous positions and rotations;
    public LinkedList<PointInTime> PointsInTime;

    //the key that triggers the rewind
    [Tooltip("Type the letter or name of the key you want to trigger the rewind with. Check the PDF guide for more information.")]
    public string KeyTrigger;

    [HideInInspector]
    public bool UseInputTrigger;

    private bool hasAnimator = false;
    private bool hasRb = false;
    public Animator animator;
    private ThirdPersonController TPC;

    [HideInInspector]
    public float RewindSeconds = 5;

    [HideInInspector]
    public float RewindSpeed = 1;
    private bool isFeeding = true;
    private ParticleSystem Particles;
    public LinkedList<PointInTime> removedPointsInTime;

    [HideInInspector]
    public bool PauseEnd;


    // Use this for initialization
    void Start () {
      
        PointsInTime = new LinkedList<PointInTime> ();

        removedPointsInTime = new LinkedList<PointInTime> ();

        TPC = GetComponent<ThirdPersonController>();

        //if contains particle system, then cache and add comp.
        if (GetComponent<ParticleSystem> ())
            Particles = GetComponent<ParticleSystem> ();


      
        //if going to use keyboard input, cache the key result and transform to lower case to avoid errors
        if(UseInputTrigger)
            KeyTrigger = KeyTrigger.ToLower ();

        //cache if has animator
        if (GetComponent<Animator> ()) {
            hasAnimator = true;
            animator = GetComponent<Animator> ();
        }

        //has rigidbody or not
        if (GetComponent<Rigidbody> ())
            hasRb = true;

        //Add the time rewind script to all children - Bubbling
        foreach(Transform child in transform){
            child.gameObject.AddComponent<ReTime> ();
            child.GetComponent<ReTime> ().UseInputTrigger = UseInputTrigger;
            child.GetComponent<ReTime> ().KeyTrigger = KeyTrigger;
            child.GetComponent<ReTime> ().RewindSeconds = RewindSeconds;
            child.GetComponent<ReTime> ().RewindSpeed = RewindSpeed;
            child.GetComponent<ReTime> ().PauseEnd = PauseEnd;
        }
    }

    void Update () {
        if (TPC != null && TPC.inStart == true)
        {
            Debug.Log("in Start");
        }
        //when specific input is triggered then start rewind else, stop
        if (UseInputTrigger)
            if (Input.GetKey (KeyTrigger))
                StartRewind ();
              
          
    }

    void FixedUpdate(){
        ChangeTimeScale (RewindSpeed);
        //if true then run the rewind method, else record the events
      
        if (isRewinding) {
            Rewind ();
            removedPointsInTime.AddFirst (new PointInTime (transform.position, transform.rotation));
        }else{
            Time.timeScale = 1f;
            if(isFeeding)
                Record ();
        }
      
    }

    //The Rewind method
    void Rewind(){
        if (PointsInTime.Count > 0 ) {
            PointInTime PointInTime = PointsInTime.First.Value;
            transform.position = PointInTime.position;
            transform.rotation = PointInTime.rotation;
          
          
            PointsInTime.RemoveFirst();
        } else {
            if(PauseEnd)
                Time.timeScale = 0;
            else
                StopRewind ();
        }
    }

    //use the constructor to add the new data
    void Record(){
        if(PointsInTime.Count > Mathf.Round(RewindSeconds / Time.fixedDeltaTime)){
            PointsInTime.RemoveLast ();
        }
        PointsInTime.AddFirst (new PointInTime (transform.position, transform.rotation));
      
      
      
      
      
        if (Particles)
        if (Particles.isPaused) {
            Particles.Play ();
        }
    }

    void StartRewind(){
        isRewinding = true;
        if(hasAnimator)
            animator.enabled = false;

        if (hasRb)
            GetComponent<Rigidbody> ().isKinematic = true;
    }

    void StopRewind(){
        Time.timeScale = 1;
        isRewinding = false;
        if(hasAnimator)
            animator.enabled = true;

        if (hasRb)
            GetComponent<Rigidbody> ().isKinematic = false;
    }
      
    void ChangeTimeScale(float speed){
        Time.timeScale = speed;
        if (speed > 1)
            Time.fixedDeltaTime = 0.02f / speed;
        else
            Time.fixedDeltaTime = speed * 0.02f;
    }

    //exposed method to enable rewind
    public void StartTimeRewind(){
        isRewinding = true;

        if(hasAnimator)
            animator.enabled = false;

        if (hasRb)
            GetComponent<Rigidbody> ().isKinematic = true;
      
        if(transform.childCount > 0){
            foreach (Transform child in transform)
                child.GetComponent<ReTime> ().StartRewind ();
        }
    }

    //exposed method to disable rewind
    public void StopTimeRewind(){
        isRewinding = false;
        Time.timeScale = 1;
        if(hasAnimator)
            animator.enabled = true;

        if (hasRb)
            GetComponent<Rigidbody> ().isKinematic = false;

        if(transform.childCount > 0){
            foreach (Transform child in transform) {
                child.GetComponent<ReTime> ().StopTimeRewind ();
            }
        }
    }

    //Check point end for parent obect
    public void StopFeeding(){
        isFeeding = false;

        if(transform.childCount > 0){
            foreach (Transform child in transform) {
                child.GetComponent<ReTime> ().StopFeeding ();
              
            }
        }
    }

    //Check point start for parent obect
    public void StartFeeding(){
        isFeeding = true;

        if(transform.childCount > 0){
            foreach (Transform child in transform) {
                child.GetComponent<ReTime> ().StartFeeding ();
            }
        }
    }

    //on adding ReTime component, also add the particles script to all objects that are PS
    void Reset(){
        if(GetComponent<ParticleSystem>())
            gameObject.AddComponent<ReTimeParticles> ();
      
        //Add the particles script to all children that are PS - Bubbling
        foreach (Transform child in transform) {
            if(child.GetComponent<ParticleSystem>())
                child.gameObject.AddComponent<ReTimeParticles> ();
        }
    }
    public LinkedList<PointInTime> GetRecordedData() {
        return PointsInTime;
    }

}
using UnityEngine;

public class PointInTime {
    //save position and rotation
    public Vector3 position;
    public Quaternion rotation;

    //constructor
    public PointInTime(Vector3 _position, Quaternion _rotation){
        position = _position;
        rotation = _rotation;

 
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

public class ClonePlayer : MonoBehaviour
{
    public bool isPlayingBack = false;
    public GameObject clonePrefab;
    public float playbackSpeed = 2f;
    private bool hasAnimator = false;
    public Animator animator;

    private GameObject clone;
    private LinkedList<PointInTime> removedPointsInTime;
    private GameObject playerObject;


    void Awake()
    { if (clonePrefab != null)
        {
        clone = Instantiate(clonePrefab, transform.position, transform.rotation, transform);
          }

    }
    void Start()
    {
      
        if (GetComponent<Animator> ()) {
            hasAnimator = true;
            animator = GetComponent<Animator> ();
        }
        playerObject = GameObject.FindWithTag("Player");
      
    

               GameObject objWithTag = GameObject.FindGameObjectWithTag("Player");
       if (objWithTag != null)
        {
          
            ReTime reTimeComponent = objWithTag.GetComponent<ReTime>();

          
            if (reTimeComponent != null)
            {
              
                removedPointsInTime = reTimeComponent.removedPointsInTime;
              
              
            }
            else
            {
                Debug.LogError("ReTime component not found on the GameObject with tag: Player");
            }
        }
        else
        {
            Debug.LogError("GameObject with tag: Player not found");
        }

      
      
        foreach (Transform child in transform)
        {
            child.gameObject.AddComponent<ClonePlayer> ();
          
            SkinnedMeshRenderer skinnedMeshRenderer = child.GetComponent<SkinnedMeshRenderer>();
            child.GetComponent<ClonePlayer> ().playbackSpeed = playbackSpeed;

            // If a SkinnedMeshRenderer component is found, disable it
            if (skinnedMeshRenderer != null)
            {
                skinnedMeshRenderer.enabled = false;
            }
        }
    }
void Update()
   {
    if (Input.GetKeyDown(KeyCode.T))
        {
      
            Debug.Log("p pressed");
             BeginClonePlayback();
          
            foreach (Transform child in transform)
        {
            SkinnedMeshRenderer skinnedMeshRenderer = child.GetComponent<SkinnedMeshRenderer>();
          
            if (skinnedMeshRenderer != null)
            {
                skinnedMeshRenderer.enabled = true;
            }
        }
        }
        ChangeTimeScale(playbackSpeed);
        if (isPlayingBack == true){
            clonePlayback();
        }
        else
        {
            Time.timeScale = 1f;
        }
        if (Input.GetKeyDown(KeyCode.V))
         {
             DebugRemovedPointsInTime();
         }
        if (removedPointsInTime != null)
        {
         if (removedPointsInTime.Count > 0){
             Debug.Log("data in remove");
         }
         if (removedPointsInTime.Count == 0){
              Debug.Log("no data in remove");
          }
        }
      
  }

    void ChangeTimeScale(float speed){
        Time.timeScale = speed;
        if (speed > 1)
            Time.fixedDeltaTime = 0.02f / speed;
        else
            Time.fixedDeltaTime = speed * 0.02f;
    }

    private void BeginClonePlayback()
    {
        if(hasAnimator)
            animator.enabled = false;
        isPlayingBack = true;
    }
    private void StopClonePlayback()
    {
        Time.timeScale = 1;
         if(hasAnimator)
            animator.enabled = true;
        isPlayingBack = false;
    }

    void DebugRemovedPointsInTime()
{
     if (removedPointsInTime != null){
    Debug.Log("debug act");
    foreach (var point in removedPointsInTime)
    {
         Debug.Log("Position: " + point.position + ", Rotation: " + point.rotation);
    }
     }
}
     void clonePlayback()
    {
       if (removedPointsInTime != null &&  removedPointsInTime.Count > 0 ){
        {
          Debug.Log("cloneplayback activated");
                PointInTime PointInTime = removedPointsInTime.First.Value;
                transform.SetPositionAndRotation(PointInTime.position, PointInTime.rotation);
                removedPointsInTime.RemoveFirst();
          
           }
       }
       else
       {
        StopClonePlayback();
       }
      
       }
}

I kindly request your help, please ask if any more information is needed.

Not sure how ReTime works (I guess it does store state) but I wouldn’t trust it. It has very limited options, and hasn’t been updated in 3-4 years. It doesn’t seem to be compatible with CharacterController from its description, since the CharacterController would require other variables to be recorded over time, such as velocity. Best to roll your own system so you have more control over everything.

Also … just seeing your enter playmode time feels painful to me. You might say “oh it’s only 5 seconds” but the alternative is “instantaneous” so … Enter playmode options => disable domain reload. :wink:

1 Like

ReTime has worked fine so far for its intended purpose, i am facing problems in adding my own playback system, im not really skilled enough to make my own system.

Thanks for the tip!