Search Unity

How do you make an object follow your exact movement, but delayed?

Discussion in 'Scripting' started by bakuganvlad, Jan 15, 2018.

  1. bakuganvlad

    bakuganvlad

    Joined:
    Dec 2, 2017
    Posts:
    103
    I am trying to create a mechanic for my game, where if you "collect" an object, that object will start following you. The trick is, I don't want it to follow my exact movements, in the moment I perform them, but make it so they "follow my steps", if it makes any sense. To make it a little more clear, I'll give an example:

    Imagine this scenario: You have your object following you and you come across an obstacle which you must jump over. When you jump over it, the object that follows you, doesn't jump at the same time you did, since it is at an offset behind you, but when it reaches the same position as you did when you performed the jump!

    I hope I made my idea clear :p
     
  2. johne5

    johne5

    Joined:
    Dec 4, 2011
    Posts:
    1,091
    I might store the players position every frame in a List<Vextor3>

    have a statement that checks for how many frames are stored.
    something like
    if(players has 100 stored frames)
    {
    move to frame 0;
    delete frame 0
    }
     
    Lysander and Kiwasi like this.
  3. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    6,348
    I like this idea as well. The only thing I would add is a check for if the player is moving or not, and don't record frames where the player is not moving (otherwise when the player stops for 100 frames in this example, the following object would move on top of the player).

    You could also instead of a static number of frames to record, just record frames until the player gets a certain distance away from the object, and then have the object start playing back frames.
     
    bakuganvlad and Kiwasi like this.
  4. bakuganvlad

    bakuganvlad

    Joined:
    Dec 2, 2017
    Posts:
    103
    Thank you for the replies, but I can't seem to figure out how to achieve the tips you gave me through code. Can you help me?
     
  5. bakuganvlad

    bakuganvlad

    Joined:
    Dec 2, 2017
    Posts:
    103
    Ok, so I found this guy's code and works, except for the fact that it doesn't have an offset so the object is constantly trying to get into the player. The problem is, I don't really understand his code
     
  6. johne5

    johne5

    Joined:
    Dec 4, 2011
    Posts:
    1,091
    here you go, try this code. Sorry for any code errors. i coded on the fly. didn't test it.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class FollowPlayer : MonoBehaviour
    6. {
    7. //place this script on the player gameobject
    8.  
    9.     public GameObject followingMe; // in the inspector drag the gameobject the will be following the player to this field
    10.     public int followDistance;
    11.     private List<Vector3> storedPositions;
    12.  
    13.  
    14.     void Awake()
    15.     {
    16.         storedPositions = new List<Vector3>(); //create a blank list
    17.      
    18.         if(!followingMe)
    19.         {
    20.             Debug.Log("The FollowingMe gameobject was not set");
    21.         }      
    22.      
    23.         if(followDistance == 0)
    24.         {
    25.             Debug.Log("Please set distance higher then 0");
    26.         }
    27.     }
    28.  
    29.     void Start ()
    30.     {
    31.  
    32.     }
    33.  
    34.     void Update()
    35.     {
    36.         storedPositions.Add(transform.position); //store the position every frame
    37.      
    38.         if(storedPositions.Count > followDistance)
    39.         {
    40.             followingMe.transform.position = storedPositions[0]; //move the player
    41.             storedPositions.RemoveAt (0); //delete the position that player just move to
    42.         }
    43.     }
    44. }
     
    Last edited: Jan 16, 2018
  7. bakuganvlad

    bakuganvlad

    Joined:
    Dec 2, 2017
    Posts:
    103
    I modified your code a little, so it is attached to the object that is following the play, instead of the player, but your code is using a delay in frames and not one in distance. I am now trying to figure out a way to make the object stay "behind" my player at an offset. Will keep you updated if I manage to figure it out
     
  8. johne5

    johne5

    Joined:
    Dec 4, 2011
    Posts:
    1,091
    just curious, what what are you setting followDistance to? this should act like an offset.
     
  9. bakuganvlad

    bakuganvlad

    Joined:
    Dec 2, 2017
    Posts:
    103
    I tried values between 5 and 30, but the problem is that the object is trying to reach the exact position of the player, not follow behind him. In my game, I am trying to make these objects stack up as a trial behind you as you collect them and follow you around
     
  10. johne5

    johne5

    Joined:
    Dec 4, 2011
    Posts:
    1,091
    updated to be used on the follower. tested in unity. it works now
    i have my followDistance set to 200

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class FollowPlayer : MonoBehaviour
    6. {
    7.     //place this script on the player gameobject
    8.  
    9.     public GameObject player; // in the inspector drag the gameobject the will be following the player to this field
    10.     public int followDistance;
    11.     private List<Vector3> storedPositions;
    12.  
    13.  
    14.     void Awake()
    15.     {
    16.         storedPositions = new List<Vector3>(); //create a blank list
    17.  
    18.         if (!player)
    19.         {
    20.             Debug.Log("The FollowingMe gameobject was not set");
    21.         }
    22.  
    23.         if (followDistance == 0)
    24.         {
    25.             Debug.Log("Please set distance higher then 0");
    26.         }
    27.     }
    28.  
    29.     void Start()
    30.     {
    31.  
    32.     }
    33.  
    34.     void Update()
    35.     {
    36.         if(storedPositions.Count == 0)
    37.         {
    38.             Debug.Log("blank list");
    39.             storedPositions.Add(player.transform.position); //store the players currect position
    40.             return;
    41.         }else if (storedPositions[storedPositions.Count -1] != player.transform.position)
    42.         {
    43.             //Debug.Log("Add to list");
    44.             storedPositions.Add(player.transform.position); //store the position every frame
    45.         }
    46.  
    47.         if (storedPositions.Count > followDistance)
    48.         {
    49.             transform.position = storedPositions[0]; //move
    50.             storedPositions.RemoveAt(0); //delete the position that player just move to
    51.         }
    52.     }
    53. }
    54.  
     
  11. johne5

    johne5

    Joined:
    Dec 4, 2011
    Posts:
    1,091
    if you need both position and rotation, you need to change the Vector3 to a Transform
     
  12. bakuganvlad

    bakuganvlad

    Joined:
    Dec 2, 2017
    Posts:
    103
    I tried something similar by myself. The code works fine at first glance, but it has the same bugs I encountered.

    1. Try moving the player little by little and you'll see that the object slowly begins to "catch up" with the player
    2. In a game where the player has physics, there are times when after the player jumps and reaches the ground, the object still remains airborne until the player moves
     
  13. johne5

    johne5

    Joined:
    Dec 4, 2011
    Posts:
    1,091
    Doing this does not work because of the delayed jump that you're after. Is that correct?
    transform.position = player.transform.position + offset;
     
  14. bakuganvlad

    bakuganvlad

    Joined:
    Dec 2, 2017
    Posts:
    103
    Yes. I want the delayed jump AND an offset. I don't think it's possible to have a Vector3 offset, so I assume I need just an X axis offset, that also works when I turn around(the player flips). I've been trying all evening to make this work, but no success. Let's hope tomorrow brings new ideas :D
     
  15. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,713
    I have followed this thread a bit, silently. Just out of curiousity, what kind of game is this? 2D / 3D? How important (or necessary) is it to jump. I guess that question is also asking if it's the "mimic" or the staying in range, as a pet, the more important part (in case you had to choose).
    Do you use navmesh at all? Could you use it?

    Do you use physics? From one of your answers, I think maybe you do, but just checking.

    Just some questions that came to mind from reading over things..
     
    Lysander likes this.
  16. johne5

    johne5

    Joined:
    Dec 4, 2011
    Posts:
    1,091
    I was going to ask if you are using a rigidbody or charatorController?
     
  17. Lysander

    Lysander

    Joined:
    Feb 24, 2013
    Posts:
    1,652
    I'm with methos5k, the specific use-case here is really important. If you only need a sequence of waypoints, any guide on pathfinding is going to be directly applicable. Instead of generating the path using the NavMesh (or whatever system you're using), you instead just generate and retrieve a list of previous positions (sampled every few frames) from the "followed" character and use that the same way.

    If, however, you need to repeat specific actions in sequence with a high level of accuracy, then it may be best to generate a series of character "action events", the same way an Undo system generally works. For instance, "MoveToNewLocationEvent", "JumpEvent", "AttackEvent"- most of these having additional information stored about the specifics of the events necessary to recreate them accurately (locations, facing directions, timings, etc). The final system will depend heavily on how accurately you need to mimic these actions.

    Looking into Replay systems (as in "Action Replay", like football replays) in guides and on the UAS may help you to shortcut some of this if you need a high level of precision. Nothing says that the object "replaying" actions needs to be the original object that generated them.
     
  18. bakuganvlad

    bakuganvlad

    Joined:
    Dec 2, 2017
    Posts:
    103
    Wow, thanks for taking interest in my problem! I am trying to create a level-based 2D platformer game that uses physics. The player has to reach the end of the level while trying to find/collect as many "allies" as possible. Once you find and touch an ally, it starts following you through the level. Of course, this mechanic will also raise the problem of the ally being stuck mid-air after the player jumps over a gap, if I were to set up a basic coordinate offset, but that is something I will try to figure out later. Right now, I need to make the ally follow me at a constant delay behind me. I think a frame delay would be optimal, but johne5's code isn't what I am looking for, as the ally is always trying to get "inside" the player, not stay behind him. While this bug is not visible at first glance, if you try to move the character little by little, it is pretty visible that the object is not moving at the player's speed. Also, the object stops following completely if you stop moving, no matter how far away it is. I want the ally to try to stay at a constant frame distance away from me.
     
  19. bakuganvlad

    bakuganvlad

    Joined:
    Dec 2, 2017
    Posts:
    103
    ok, it seems I have misunderstood the concept of a frame delay. In that case, my main problem right now is the fact that, if I were to jump over a gap, there is a chance the ally will get stuck mid-air. How could I fix that?
     
  20. bakuganvlad

    bakuganvlad

    Joined:
    Dec 2, 2017
    Posts:
    103
    I have found a solution and it all works great, except for one part: when the object is moving along the y axis: it is super glitchy and shaky. Can you figure out why?

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections.Generic;
    3.  
    4. public class FollowKnuckle : MonoBehaviour
    5. {
    6.  
    7.     [SerializeField] private GameObject player;
    8.     [SerializeField] private int followDistance;
    9.     private List<Vector3> storedPositions;
    10.  
    11.  
    12.     void Awake()
    13.     {
    14.         storedPositions = new List<Vector3>(); //create a blank list
    15.     }
    16.  
    17.     void LateUpdate()
    18.     {
    19.         Vector3 playerPos = player.transform.position;
    20.  
    21.         if (storedPositions.Count == 0) //check if the list is empty
    22.         {
    23.             storedPositions.Add(playerPos); //store the players currect position
    24.             return;
    25.         }
    26.         else if (storedPositions[storedPositions.Count - 1] != playerPos)
    27.         {
    28.             storedPositions.Add(playerPos); //store the position every frame
    29.         }
    30.  
    31.         else if(transform.position.y != playerPos.y) //check if the ally is airborne and correct it if necessary
    32.         {
    33.             storedPositions.Add(playerPos);
    34.         }
    35.  
    36.         if (storedPositions.Count > followDistance)
    37.         {
    38.             transform.position = storedPositions[0]; //move
    39.             storedPositions.RemoveAt(0); //delete the position that player just move to
    40.         }
    41.     }
    42. }
     
  21. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,713
    I'm not sure, something about the positions added while in the air?

    I wonder if it's feasible to just try to stay at an offset, until if/when an obstacle is present, and then perform a jump to get over whatever it is, and then try to keep a certain offset on the other side, again. lol
     
  22. Fido789

    Fido789

    Joined:
    Feb 26, 2013
    Posts:
    331
    This is really hard problem to solve. Probably the best option for the smooth movement will be:

    1. Calculate the circle, that is the follower capable to reach this frame. You will have to take into account deltaTime and follower's speed.
    2. Find the intersection between this circle and imaginary polyline* created by connecting recorded player positions. If there is no intersecion, you will have to find the point laying at the circle and closest to the polyline. This will be your target position.
    3. Move the follower to this position.

    Every other solution will be either shaky or the follower will not be following the player's path accuratelly.

    * Actuall lines must be shortened to match the minimal required distance (what is another circle and line intersection thing)
     
    Last edited: Jan 18, 2018
  23. DominoM

    DominoM

    Joined:
    Nov 24, 2016
    Posts:
    436
    On the follower disable the distance check if isGrounded is false. Adjust the followers speed to back off or catch up as appropriate with a ranged follow when grounded. This could also handle the player standing still after a jump - the minimum range should slow the follower to a stop before the jump, so max jump has to be less than minimum follow (maybe a temporary increase while player is jumping).
     
    Last edited: Jan 18, 2018
  24. bakuganvlad

    bakuganvlad

    Joined:
    Dec 2, 2017
    Posts:
    103
    What to you mean by "calculate the circle"? Could you try to show me a brief code of what you are trying to achieve?
     
  25. bakuganvlad

    bakuganvlad

    Joined:
    Dec 2, 2017
    Posts:
    103
    Interesting idea, but implementing a mechanic that makes the follower back off might cause him to fall/float above the gap I jumped over, if the player is not ahead enough
     
  26. bakuganvlad

    bakuganvlad

    Joined:
    Dec 2, 2017
    Posts:
    103
    I tried your idea with the ground check, but it has a problem. When the object is not grounded, it tries to reach the player's position, completely ignoring the delay. Thus, the follower is shaky and looks weird when not grounded
     
  27. Fido789

    Fido789

    Joined:
    Feb 26, 2013
    Posts:
    331
    The code is pretty complex, but I can give you a picture:

    follower.png

    Green dots are recorded hero's positions. Yellow circle around hero is the distance you want your followers to be behind. The brown circle around the follower is the distance where the follower must land, its radius is the follower's speed multiplied by deltaTime. And finally the magenta dot is where you want to move your follower.
     
    bakuganvlad likes this.
  28. bakuganvlad

    bakuganvlad

    Joined:
    Dec 2, 2017
    Posts:
    103
    Guys, last night I have found a solution that offers me exactly what I am looking for. If the follower's y pos is not equal to the player's y pos, you keep adding positions. The problem is that the follower gets shaky when airborne this way and I do not understand why. If we find out why, then this whole thread would come to an end.

    This line of code is the faulty one:
    Code (CSharp):
    1. else if(transform.position.y != playerPos.y) //check if the ally is airborne and correct it if necessary
    2.         {
    3.             storedPositions.Add(playerPos);
    4.         }
    Can you help me figure out why this particular statement is faulty, while all the others that add positions are not?
     
  29. bakuganvlad

    bakuganvlad

    Joined:
    Dec 2, 2017
    Posts:
    103
    Thanks for the visual explanation :)
     
  30. Fido789

    Fido789

    Joined:
    Feb 26, 2013
    Posts:
    331
    Well, it is just a matter of luck if this script doesn't produce a jittery movement constantly. This idea "keep the follower N frames behind" can't work. On the system with 30 fps the follower will be twice as farther from the hero compared to the 60 fps system.
     
  31. bakuganvlad

    bakuganvlad

    Joined:
    Dec 2, 2017
    Posts:
    103
    Yeah, I get the fps difference can be a problem, but at this point I just want to know why the follower is jittery when I add positions if it is not at the same level with the player, but not when it is adding positions normally
     
  32. DominoM

    DominoM

    Joined:
    Nov 24, 2016
    Posts:
    436
    Back off as in slow down to increase distance, not go into reverse :)

    You'd need to record the player's IsGrounded on the path points, not use the followers current one. That way the distance check should only be disabled during the jump / fall, once player hits ground it becomes a point where the follower would wait if they are too close to player. You need to follow at normal speed during the jump, only alter speed faster or slower when grounded.

    Jumping off a building and standing still would still let the follower 'fall' into the player, so you might still need an in air minimum distance check. Glowing balls following might not look wrong going through the player but for other cases you need to adjust for the style. If it's a squad member / pet / enemy following, then maybe having to get out of the way so they have a clear landing could be a feature and physics collisions would be used. If it's floating treasure chests then magic is at work so having a trajectory adjustment to ignore the path and land behind the player might be best option..

    Remember frame rate can be different for the follower than when the player used that part of the path, even if their speed is the same, so you should be lerping between stored positions not using them as is.
     
  33. bakuganvlad

    bakuganvlad

    Joined:
    Dec 2, 2017
    Posts:
    103
    Should the player's position be recorded every frame?
     
  34. bakuganvlad

    bakuganvlad

    Joined:
    Dec 2, 2017
    Posts:
    103
    I have no idea how to implement this in code :p. Also, what is the difference between Vector3.Lerp() and transform.position = value, if the position is stored every frame? Have I misunderstood the purpose of lerping?
     
  35. Fido789

    Fido789

    Joined:
    Feb 26, 2013
    Posts:
    331
    Well, for the best results it should be recorded as often as possible, but my solution is pretty robust to possible outages in recording.
     
  36. DominoM

    DominoM

    Joined:
    Nov 24, 2016
    Posts:
    436
    Say there's a 10% variance in your frame rate as the game runs. The follower might getting 90fps when it crosses points captured when the player was running at 110fps. This difference from the variable frame rates causes shaky looking movement if you just move directly between recorded points. For smooth movement, the next point is from a lerp between two recorded positions based on followers speed, position timestamps and delta time.

    The lerp part would look something like this:
    Code (csharp):
    1.  
    2.             while (deltaTimeXSpeed >= storedDeltas[0]) {
    3.                 transform.position = storedPositions[0];
    4.                 deltaTimeXSpeed -= storedDeltas[0];
    5.                 storedDeltas.RemoveAt(0);
    6.                 storedPositions.RemoveAt(0);
    7.             }
    8.             if (deltaTimeXSpeed > 0) {
    9.                 transform.position = Vector3.Lerp(transform.position, storedPositions[0], deltaTimeXSpeed);
    10.                 storedDelta[0] -= deltaTimeXSpeed;
    11.             }
    12.  
     
    Last edited: Jan 18, 2018
  37. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,713
    This thread is getting more & more complex, each time I see an update lol :)
     
    johne5 likes this.
  38. DominoM

    DominoM

    Joined:
    Nov 24, 2016
    Posts:
    436
    Wait until there's a bunch of followers and we want to remove one in the middle :eek:
     
  39. johne5

    johne5

    Joined:
    Dec 4, 2011
    Posts:
    1,091
    my updated script. I'm using this in my side scroller. the issue is still with jumping. If the player jumps over something. and then immediately stops moving and waits for the player to "catch up". the follower stops when x distance from the player. So that means the follower will stop in mid air. My game also does not allow change in direction. IE move fwd then move backwards.

    I've update the code using Coroutines, verses stored positions like the first script. There is just a few extra things in my script that you don't need. IE the Animator stuff and Viewable() Coroutine.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class FollowPlayer : MonoBehaviour {
    6.  
    7.     private Rigidbody2D player;
    8.     public float followDistance;
    9.     public float distCheck;
    10.     private SpriteRenderer sRenderer;
    11.     private Animator anim;
    12.     private Rigidbody2D rb;
    13.  
    14.  
    15.     void Awake()
    16.     {
    17.         rb = GetComponent<Rigidbody2D>();
    18.         sRenderer = GetComponent<SpriteRenderer>();
    19.         player = GameObject.Find("Player").GetComponent<Rigidbody2D>();
    20.         if (!player)
    21.         {
    22.             Debug.Log("The player gameobject was not set");
    23.         }
    24.  
    25.         anim = GetComponent<Animator>();
    26.         if (!anim)
    27.         {
    28.             Debug.Log("Animator is Null for : " + name);
    29.         }
    30.         else
    31.         {
    32.             anim.SetBool("isPlaying", true);
    33.         }
    34.  
    35.         if (!sRenderer)
    36.         {
    37.             Debug.Log("sRenderer was not set");
    38.         }
    39.         else
    40.         {
    41.             sRenderer.enabled = false;
    42.         }
    43.  
    44.         if (followDistance == 0)
    45.         {
    46.             Debug.Log("Please set distance higher then 0");
    47.         }
    48.     }
    49.  
    50.     void Start()
    51.     {
    52.         StartCoroutine(Viewable()); //enable the image
    53.     }
    54.  
    55.     void Update()
    56.     {
    57.         float dist = Vector2.Distance(rb.position, player.position); //distance between player and follower
    58.        
    59.         if(dist < distCheck)
    60.         {
    61.             //follower is to close to the player
    62.             //Debug.Log(dist);
    63.             StopAllCoroutines(); //stop future movements
    64.             rb.velocity = Vector2.zero; //stop in place
    65.         }
    66.         else
    67.         {
    68.             StartCoroutine(ReplaceRigidBody(player.position)); //move closer to player
    69.         }
    70.        
    71.     }
    72.  
    73.     IEnumerator ReplaceRigidBody(Vector2 setTo)
    74.     {
    75.         yield return new WaitForSeconds(followDistance);
    76.         sRenderer.enabled = true;
    77.         rb.position = setTo;
    78.     }
    79.  
    80.     IEnumerator Viewable()
    81.     {
    82.         yield return new WaitForSeconds(followDistance);
    83.         sRenderer.enabled = true;
    84.     }
    85. }
    86.  
     
  40. whileBreak

    whileBreak

    Joined:
    Aug 28, 2014
    Posts:
    287
    Sorry I didn't read all but I'm wanted to point the obvious thing that i saw missing on the first 10 or so comments. Just put an empty object behind the player for the distance offset. Put the follow script on him, and attach the follower object to this script. Put another object with the script on every follower... and you have a train! a delay train! :D with offsets.

    If the following feels mechanic due to the offsetobject make it "bounce" pointing in the direction where the player was before (just rotate it around the player)
     
  41. johne5

    johne5

    Joined:
    Dec 4, 2011
    Posts:
    1,091
    How does that fix the issue with jumping delay? That would be that same as this
    transform.position = player.transform.position + offset;
    He needs following object to do everything the hero does but with a delay. so the follower will jump at the same point on the level.
     
  42. whileBreak

    whileBreak

    Joined:
    Aug 28, 2014
    Posts:
    287
    Then just stop following in x,z when distance(without y)<offset (so it keeps falling)

    Code (CSharp):
    1. followPosThis = transform.position;
    2. followPosThat = otherTransform.position;
    3. followPosThis.y=0;
    4. followPosThat.y=0;
    5. if(Vector3.Distance(followPosThis,followPosThat)>offset)
    6.     Follow();
    Edit: Track the Y in a different way or it will stop falling becouse it's too close. the other problem would be that if distance<offset then the object wont follow you until you move away, hence the offset object i suggested
     
    Last edited: Jan 18, 2018
  43. whileBreak

    whileBreak

    Joined:
    Aug 28, 2014
    Posts:
    287
    And with the object offset it is supposed to be a child of the player so it would do the same, with an offset to the players back. Use the saved positions that I read at the beginning.
     
  44. bakuganvlad

    bakuganvlad

    Joined:
    Dec 2, 2017
    Posts:
    103
    My head is about to explode from all the suggestions and lack of programming experience in Unity :D
     
  45. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,713
    I hear ya. I know you said this is 2D, right? What kind of 2D (sorry if I missed it). Is it a flat world with a few obstacles? A side scroller? Lots of jumps in general or just some?
    Just curious at ways to minimize your grief, while still maintaining more or less your goals lol :)
     
  46. bakuganvlad

    bakuganvlad

    Joined:
    Dec 2, 2017
    Posts:
    103
    2D level-based platformer with physics and obstacles
     
  47. andyRogerKats

    andyRogerKats

    Joined:
    Oct 3, 2016
    Posts:
    12
    Here is a scene with a delayed movement script, I deleted my old post because it used O(n) time (because of List.RemoveAt()) and this new one is in constant time.
     

    Attached Files:

    Nubees and h00man like this.
  48. h00man

    h00man

    Joined:
    Apr 17, 2017
    Posts:
    48
    this one works for me.i just wanna know if there is any way to control the follower speed? how can i tell the follower object to follow the leader always at a constant speed