0
\$\begingroup\$

I'm working on playing a replay on every strike. For that I'm recording the positions and rotation of the sphere in the list individually. I now have the values reside in the list. How do i move my sphere with the defined pos and rot. Below is what I've tried so far.

Replay.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;


public class Replay : MonoBehaviour
{

    BallRecorder script;

    public List<Vector3> position;

    public List<Quaternion> rotation;




    // Use this for initialization
    void Start()
    {
        script = GetComponent<BallRecorder>();

        position = new List<Vector3>();
        rotation = new List<Quaternion>();

        position = script.position;   // Now contains all the values of the sphere(positions) 
        rotation = script.rotation;   // Now contains all the values of the sphere(rotation) 



        foreach (Vector3 av in position)
        {
            transform.position = av;  //transferring position to the sphere
        }




     }

BallRecorder.cs

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

public class BallRecorder : MonoBehaviour {

    // Use this for initialization

  public  List<Vector3> position;
  public  List<Quaternion> rotation;
    static int count = 50;


    void Start () {

        position = new List<Vector3>();

        rotation = new List<Quaternion>();

    }

    // Update is called once per frame
    void Update()
    {

        count = count - 1;

        if (count < 1)
        {
            position.Add(transform.position);
            rotation.Add(transform.rotation);

        }
    }
}
\$\endgroup\$
4
  • \$\begingroup\$ Do you have timestamps representing when each position/rotation snapshot was recorded? Or were they recorded in FixedUpdate to keep the timestep between them consistent? \$\endgroup\$
    – DMGregory
    May 20, 2017 at 15:41
  • \$\begingroup\$ Yes they were recorded in FixedUpdate method \$\endgroup\$ May 20, 2017 at 15:42
  • \$\begingroup\$ ...your newly added BallRecorder code has the recording in Update, not FixedUpdate. Did you change it for some reason? \$\endgroup\$
    – DMGregory
    May 20, 2017 at 18:11
  • \$\begingroup\$ Could you tell me if you have solution for this problem? I am trying to build a replay from previously saved position and my code is very similar for your initial code. If you have solution could you post? Thanks \$\endgroup\$ Mar 14, 2018 at 22:04

2 Answers 2

2
\$\begingroup\$

I'd be inclined to do this as a Coroutine like so, letting us start the playback by calling StartCoroutine(PlayFrom(0f));

IEenumerator PlayFrom(float time = 0f) {
    // As long as there's still more recording to play, 
    // update our transform to match the replay data at this moment.
    while(Playback(time)) {
        // Wait until next frame.
        yield return null;

        // Advance the playhead by deltaTime and loop again.
        // You can set Time.timeScale to get slow-motion replays this way.
        time += Time.deltaTime;
    }
}

bool Playback(float time) {
    // Convert the playback time to a position in our sample array.
    // (Here I'm assuming the current fixedDeltaTime is the same one used to record)
    float sample = time / Time.fixedDeltaTime;

    // Round down to the position & rotation sample just before this moment.
    // (Here I'm assuming time is non-negative)
    int previousIndex = (int)(sample);

    int last = positions.Count - 1;
    if(previousIndex < last) {
        // We have another snapshot ahead, so we'll interpolate smoothly between them.
        int nextIndex = previousIndex + 1;
        float interpolation = sample - previousIndex;

        transform.position = Vector3.Lerp(
                                position[previousIndex],
                                position[nextIndex],
                                interpolation);

        // Slerp is technically a more consistent option here, since it keeps
        // the rate of change even over the course of the interpolation.
        // But each step is usually too small for this difference to be visible.
        transform.rotation = Quaternion.Lerp(
                                rotation[previousIndex],
                                rotation[nextIndex].
                                interpolation);

        // Signal that there's still more replay past here.
        return true;
    }

    // Here we're off the end of the timeline, so we'll hold on the last snapshot.
    transform.position = position[last];
    transform.rotation = rotation[last];

    // Signal that we're at the end of the replay.
    return false;
}

Pulling out Playback as its own method also lets you call it separately if you want to seek to a particular freezeframe in the replay, or reverse time and rewind the recording, or use a shared playhead controller to scrub through the timeline, things like that.

\$\endgroup\$
7
  • \$\begingroup\$ Is the algorithm recording with snapshots? or it is the forward algorithm for what I've done so far(i.e by saving positions and rotations in a list)? \$\endgroup\$ May 20, 2017 at 17:05
  • \$\begingroup\$ I'm not sure what you're asking. You can see the code above uses the position and rotation lists defined in your question. (BTW, you don't need to create a new list a la position = new List<Vector3>() if you're just going to assign an existing list into that slot like position = script.position - this overwrites the reference to that new List, so you can skip ever creating it if you're not going to use it) \$\endgroup\$
    – DMGregory
    May 20, 2017 at 17:29
  • \$\begingroup\$ Beauty! It's working. But just a little confused. It's playing back twice. \$\endgroup\$ May 20, 2017 at 17:51
  • \$\begingroup\$ You mean it plays to the end, then repeats again from the beginning, after a single call to StartCoroutine(PlayFrom(0f))? This should only happen if your position list contains the same data twice. You can Debug.Log the time or previousIndex variables to double-check where you are in the playback. \$\endgroup\$
    – DMGregory
    May 20, 2017 at 17:56
  • \$\begingroup\$ Yes you are right Sir! It's happening because i didn't apply any stop to my recording positions from ball. Can you please suggest me how would i stop recording. I tried using Timer technique, but the timer starts when the game is executed. I edited my question. \$\endgroup\$ May 20, 2017 at 18:04
0
\$\begingroup\$

Your Start method doesn't make much sense.

    foreach (Vector3 av in position)
    {
        transform.position = av;  //transferring position to the sphere
    }

This replaces the position of the object with those from the list one after another. But the problem with this is that it does all that during the start-method. So when Start is over, the object ends up with the last position and the player has no chance to see those between. What you need to do is have a counter-variable which says at which point in the recording you are currently and then transfer the positions during the Update method, one after another.

int current = 0;

void Update() {
     if (current < position.Count) {
         transform.position = position[current];
         transform.rotation = rotation[current];
         current++;
     }
}

By the way, there are also some more improvements you could do to your Replay class:

  1. You don't need to keep a reference to the BallRecorder around when you've already copied its positions.
  2. There is no point in initializing position and rotation to empty List's and then throw those empty lists away and assign an existing list to them. You can declare and assign a variable in one line.
  3. According to your comments, you seem to misunderstand what position = script.position; actually does. It does not copy all the positions. It makes position point to the list in script.position. In this case this doesn't make that much of a difference, but you should be aware that this means that when the BallRecorder changes its list of positions, those changes will immediately be visible in Replay. If you want to create a new List which is a copy of everything in an existing List, use List<Vector3> copiedList = new List<Vector3>(existingList);

To sumarize, here is how I would write that class (note: Untested!)

public class Replay : MonoBehaviour
{

    private List<Vector3> positions;
    private List<Quaternion> rotations;  

    private int current = 0;      

    void Start()
    {
        BallRecorder recorder = GetComponent<BallRecorder>();
        positions = recorder.positions;
        rotations = recorder.rotations;
    }

    void Update() {
         if (current < positions.Count) {
             transform.positions = positions[current];
             transform.rotations = rotations[current];
             current++;
         }
    }
}
\$\endgroup\$
7
  • \$\begingroup\$ Bear in mind that advancing the current counter by 1 every update can cause the replay to play back at a different speed than the recorded action, since Update runs on a variable framerate. You'll also want your test to be if(current < positions.Count) since lists don't have a length property and the last entry has index positions.Count - 1. \$\endgroup\$
    – DMGregory
    May 20, 2017 at 19:36
  • \$\begingroup\$ @DMGregory Fixed the Count thing. I wrote too much JavaScript recently. Regarding Update and FixedUpdate: The recording used Update, so also using Update for playback will likely give better results. If the OP should use FixedUpdate, they should also do that during the recording. \$\endgroup\$
    – Philipp
    May 20, 2017 at 19:42
  • \$\begingroup\$ Can't transform position and rotation on Update function, as it keeps on replaying. \$\endgroup\$ May 21, 2017 at 7:14
  • \$\begingroup\$ @user100020 You might also want to add some methods to the Replay class to start and stop the replay (which can be done by setting a bool variable) or reset the replay (simply resetting current to 0). \$\endgroup\$
    – Philipp
    May 21, 2017 at 11:05
  • \$\begingroup\$ @Philipp, this time when I'm replaying for the pins, like same done for ball. It says the code is unreachable due to its protection level. I wonder it is because you asked me to individually assign recorder script to each gameObject. How would i access the position of the pins to the replayer script. I tried using with GameObject tag yet no result what so ever. \$\endgroup\$ May 21, 2017 at 12:08

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .