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.