- Home /
Rewind time (or problems with arrays)
Hi all,
I'm implementing a mechanic where when triggered, target objects would rewind to their initial positions/rotations/etc. The problematic part of this currently looks something like:
ArrayList Movements = new ArrayList();
ArrayList Rotations = new ArrayList();
public GameObject[] targets;
(in update when not rewinding:)
for (int i = targets.Length-1; i >= 0 ; i--)
{
Movements.Add(targets[i].transform.position);
Rotations.Add(targets[i].transform.rotation);
}
(coroutine to rewind when trigerred:)
for(int i = 0; i<targets.Length; i++)
{
targets[i].transform.position = (Vector3)Movements[MovementIndex];
Movements.RemoveAt(MovementIndex);
targets[i].transform.rotation = (Quaternion)Rotations[MovementIndex];
Rotations.RemoveAt(MovementIndex);
}
yield return new WaitForSeconds(0f);
I think I've pasted in all the relevant parts for solving what's wrong. Basically, it worked well for just one target, e.g. player, but I want to implement an open array defined in the editor (GameObject[] targets) so I can apply the rewind to any object I want. So when I went and tried it with a player and the camera, it, for some reason (possibly my lack of understand how these arrays are called), it looks like the camera jumps around as if it's using all of the positions and rotations recorded, not just the ones captured from the camera transform. I played around with order of execution in the "for" functions but to no avail..
Any ideas?
Additionally, if anyone has a suggestion on how to implement the captured parameters in a single multi-dimensional array, please share, I couldn't wrap my head around that either. Something like [(player.position1, player.rotation1),(cam.pos1, cam.rot1),....] but that'd be there just to make it neat so is not as important.
ALSO, optimization related advice would be most welcome as well. I.e. storing all of these states will be expensive if a player takes a while going through a level and I can't really delete any of the stored data as I want everything to come back to the point when the level started. I guess storing every other frame would do, but then how would I smoothly transition across two states without it looking like glitchy teleportation?
Answer by roman_sedition · Nov 17, 2016 at 06:08 PM
@erikiene OMG This was a lot of fun trying to figure this out.
I made 2 scripts, it sort of allows you allocate which objects you want to have rewind via AddComponent, this lets each object have their own position and rotation list so you don't have some gigantic multidimensional array to worry about. Clears the list after a rewind occurs. This example will allocate which objects will be able to rewind during Start(), but it's pretty easy to implement dynamically during gameplay too.
First Script (placed on an empty object in the scene, just used for allocating which objects get to rewind, you retain a reference to each object in this Object's targets List)
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
public class TimeController : MonoBehaviour {
public List<Vector3> targetPos = new List<Vector3>();
public List<Quaternion> targetRotate = new List<Quaternion>();
public GameObject[] targets;
void Start()
{
targets = GameObject.FindGameObjectsWithTag ("Target");
foreach(GameObject target in targets)
{
target.AddComponent<Rewinder> ();
}
}
}
Second Script (This one gives the Rewind class to the object, I made RigidBody a required component, may or may not want to implement that depending on your game)
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
[RequireComponent(typeof(Rigidbody))]
public class Rewinder : MonoBehaviour {
public List<Vector3> targetPos = new List<Vector3>();
public List<Quaternion> targetRotate = new List<Quaternion>();
public bool record;
void Start()
{
record = true;
}
void Update()
{
if(record)
{
targetPos.Add (transform.position);
targetRotate.Add (transform.rotation);
}
if (Input.GetKeyDown (KeyCode.Space) && record == true)
{
StartCoroutine ("Rewind");
}
}
IEnumerator Rewind()
{
record = false;
GetComponent<Rigidbody> ().isKinematic = true;
for(int i = targetPos.Count - 1; i > -1; i--)
{
transform.position = targetPos [i];
transform.rotation = targetRotate [i];
yield return null;
}
GetComponent<Rigidbody> ().isKinematic = false;
targetPos.Clear ();
targetRotate.Clear ();
record = true;
yield return new WaitForSeconds(0);
}
}
I put yield return null in the for loop of the coroutine as it makes rewind playback frame by frame the same as it was recorded. If you are rewinding to a state in which you were airborne at the start you need kinematic set to true, then revert it when it is finished or else the acceleration of gravity whilst you're airborne is still in effect and it makes the object fall faster when rewind finishes.
@roman_sedition it really is fun! Thank You for all of this, the whole of the 2 scripts look similar to what I have, I'll try the whole thing out and if it works as I want it to (which by the looks of it it will) I think I can squeeze both Your scripts into one, will let You know if it's working!
I haven't tried using Lists ins$$anonymous$$d of ArrayLists, which seems easier with them having .Add and .Clear. Also, the is$$anonymous$$inematic tip is great because for some reason ins$$anonymous$$d of doing that I turned my custom gravity on and off each time..
Now, this might be a stretch but any chance You would have ideas on how to implement animations into this whole mechanic? I'm sure I can work it out eventually but doesn't hurt to ask a person who seems to know his stuff!
EDIT: After taking a closer look at what the first script does and implementing that (never used addcomponent in such a way before) I see there's not really a way to combine them into one but there's no reason to anyway, what You have works perfectly! All I need now is to figure out how to add animations to the rewind.
Thanks once again!!
@erikiene You are welcome, about animations, no I don't think I can help you there. Logic wise it should be similar, I am guessing you would somehow store key frame references of the animations and play them back in reverse order.$$anonymous$$y current hobby is just lurking on unity.answers trying to raise reputation points and gain badges like it was a game lol I am definitely not that knowledgeable.
:D well, I can say that's a wonderful hobby to have and thank you on behalf everyone you've helped and will help in the future!
Sorry to come back to this but I just had one more problem related to it all and thought maybe You'd be able to help me.
I'm trying to slow down the rewinding at certain points (triggered by colliders) but Time.timeScale won't work on the coroutine and simply waitforseconds'ing will make it look like choppy teleportation rather than slowed down rewinding. I'm trying to implement this with vector3 and quaternion.Lerp (and move/rotateTowards) but can't seem to get my head around what t I should use and how many seconds to wait for between the calls.. When I tried using one time for the lerp/movetoward and then wait for that exact amount of seconds before looping again, the target ends up floating, moving through ground, etc...
I'm still sitting down thinking about the logic behind this but as before - doesn't hurt asking.