Jittery movement/game objects, and using interpolate only adds more problems.

Update : I tried removing everything, even the player, and there is still a lag ! It seems like half the problem comes from the looping background, it’s as if on some irregular number of seconds, there is a jerky movement as if the background pauses and generates again.

Hello everybody,

I’ve been scratching my head over this problem of general jitteriness/latency all over my minimal project (2d, mobile). At first – as seen in the first video – there is just a player with camera moving to simulate a sort of runner game, there is some jitteriness there but it’s still minimal, the moment other game objects are introduced – cats, as seen in the second video – the game become more jittery, both the game objects and the background.

When i try to add interpolate to game objects, it just makes the player jump become inconsistent without solving or improving the latency/jitteriness. So i suspect it may have something to do with the way framerates are processed, in another phone there is less lag – but still more than enough to ruin the game -

I know the problem is complicated for a beginner, having to do with Unity Game Engine/Frame Rates and i don’t expect some magical 1 paragraph solution, but i’ve been searching the whole of the internet just looking for a hint, an idea of where/what i have to look for and i still can’t find any. So i’m asking for the community generosity in directing me towards any hint/direction/rough draft of where to go.

Those videos are from a test on mobile. (In the first 5/6 seconds there is a jitteriness from the recording app, don’t mind that)

Video 1 :

Video 2 (with GameObjects) :

Video 3 (with a static GameObject, to see if the problem comes from the animator/animation) :

Code and more elements/pictures :

Player GameObject Code :
(
What i’m trying to do with the Player Code as it may seem – and it is – convoluted. Player moves once (or twice if double tap under a certain time constraint) top or bottom. With a constraint on top and a constraint on bottom.
)
```csharp
**using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class Player : MonoBehaviour
{

public float playerSpeed;

public Animator animator;

private Rigidbody2D rb;
private Vector3 playerPosition;
// private Vector3 playerDirection;
public float directionY;

private Rect top;
private Rect bottom;

private bool isJumping;
// public bool AnJumping;

float vertical = 0;
float timer;
// float moveLimiter = 2f;
float waitTime = 0.2f;

float speed = 2f;
float jumpCount = 0f;
float jumpLimit = 10f;
public static bool paused;

static float tapCount = 0f; 

bool isSecondJumping = false;
bool once = true;
static bool pauseButton;
float jumpTimer = 0.5f;
float secondJumpTimer = 0.25f;

[SerializeField] private AudioSource jumpSoundEffect;

Touch touch;
public TextMeshProUGUI StartText;

void Awake()
{
    StartText = GetComponent<TextMeshProUGUI>();
}

// Start is called before the first frame update
void Start()
{

     rb = GetComponent<Rigidbody2D>();    
    waitTime = 3f;
    vertical = 0;

}

void tryJump(string position)
{
    tapCount++;
    Debug.Log("Display Number Taps : " + tapCount);

    if (tapCount > 1) // If player tapped more than once
    {
        Debug.Log("Display Number Taps : Yes You Here");            
    }

    if (position == "Top")
    {
      Debug.Log("Movement Activated : Top");
        vertical = speed;
        jumpCount = 0f;
    }

    else if (position == "Bottom")
    {
        Debug.Log("Movement Activated : Bottom");
        vertical = -speed;
        jumpCount = 0f;
    }    

}

// Update is called once per frame
void Update()
{
playerPosition = this.gameObject.transform.position;

      bottom = new Rect(0, 0, Screen.width, Screen.height / 4.5f);
    top = new Rect(0, Screen.height / 4, Screen.width, Screen.height / 1.50f);

 
    if (jumpTimer > 0 && isJumping) 

// If the player is Jumping and the JumpTimer is still over 0, we decrease the value of
// the jumpTimer.
{
jumpTimer -= Time.deltaTime;
}

    if (jumpTimer <= 0) 

// Once the Jump Timer value is inferior to 0 (jump timer over), the jump is Over, we assign false to isJumping.

    {
        isJumping = false;
        jumpTimer = 0.20f;
        tapCount = 0;
    }

    if (secondJumpTimer <= 0) 

// Once the secondJump timer value is inferior to 0 (second jump timer over), we reset the value of
// secondJumpTimer, a player can make the second jump during this interval (0.15f)

    {
      once = true;
        secondJumpTimer = 0.15f;
        isSecondJumping = false;
    }


    if (secondJumpTimer > 0 && isSecondJumping) 

// If the player is still in the interval of the second jump, we decrease the value of
// the secondjumpTimer.

    {
        secondJumpTimer -= Time.deltaTime;
    }

    if (!isJumping | tapCount == 1)  

// If it’s either the first or second Jump during the second Jump Intervall.

    {

        if (Input.touchCount > 0)  // If player wants to Jump
        {

             isSecondJumping = true;
            Debug.Log("Display if Click Detected");

            if (!isJumping) { isJumping = true; }

          touch = Input.GetTouch(0);
           

            if (top.Contains(touch.position) && playerPosition.y < 0.98f)
            {
                if (once) 

// To make the code generate only once as to avoid moving up ad infinitum
{
once = false;
tryJump(“Top”); // Player move up.

                }
               
            }

            else if (bottom.Contains(touch.position) && playerPosition.y > -0.64f)
            {
                if (once) 

// To make the code generate only once as to avoid moving down ad infinitum
{
once = false;
tryJump(“Bottom”); // PLayer move down.
}

            }

        }

    }


}


void FixedUpdate()
{

    if (vertical != 0) { jumpCount += 1f; }

// We increase the JumpCount by 1 unless vertical = 0 (which mean jump is over).

    if (jumpCount >= jumpLimit)  // We reached the end of the jumpCount, we set vertical to 0.
    {
        vertical = 0f;
    }

    rb.velocity = new Vector2(0, (vertical * 2));

}}**

** **Cat GameObject Code :** **csharp
**[/U]using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BlackCat : MonoBehaviour
{

public Animator animator;
public static float finalScore = 0;

public GameObject soundEffects;
// public Animator animator;
private GameObject player;
  private Vector3 positionPlayer;

public Vector3 positionBlackCat;
public float LastPosition;
public string TypeLastPosition;
public float directionYinitial = 1;  
private Rigidbody2D rb;

 

private SpriteRenderer renderer;

[SerializeField] private AudioSource meowSoundEffect;

// Start is called before the first frame update
void Start()
{
finalScore = 0;
player = GameObject.FindGameObjectWithTag(“Player”);
rb = GetComponent();
renderer = GetComponent();

}

private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.tag == “sideBorder”)
{
Destroy(this.gameObject);
}

    else if (collision.tag == "Player") // If there is a collusion with white cat
    {
        // Destroy(player.gameObject);               
       }
       
    else

        {                           
             meowSoundEffect.Play();                 
            //Debug.Log("No Collusion Here White Cat");      
         }

    }

}

void Update()
{

    ScoreManager.score += finalScore;
    finalScore = 0;

   if (GameObject.FindGameObjectWithTag("Player") != null)
            {
    positionPlayer = player.gameObject.transform.position;
    }        

}

void FixedUpdate()
{

}

}**
** **SpawnGame Objects Code :** **csharp
**using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class spawnAllCats : MonoBehaviour
{

public float blackCatScore;
public bool isGenerated;
public GameObject WhiteCats;
public GameObject BlackCats;


public float maxX;
public float minX;
public float maxY;
public float minY;
private float timeBetweenSpawn = 0.1f;
public float spawnTime;
private float randomNumber = 0; 

float randInterval;
float actualHorizontal = 0f;
static int catNumber = 0;
private float randomX;
public static bool isGeneratingCat;
string generateType = "White";
float timer;
float waitTime;
float randomY;   


// Zones where game objects will be placed
float verticallevel1 = 2.6f;
float verticallevel2 = 1.8f;
float verticallevel3 = 1.16f;
float verticallevel4 = -1.5f;
float verticallevel5 = -2.25f;

private bool oneTime = false;

public static List<string> lastAllCats = new List<string>();
public static List<float> lastAllCatsLocation = new List<float>();



void Start()
{

    isGeneratingCat = false;
    randomX = 0;
    waitTime = 1.5f;
    timer = 0f;
    randomY = 0;
    spawnTime = 0;

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

  
  

    if (!isGeneratingCat && Time.time > spawnTime && !Player.paused) // If the process of generating cat isn't ongoing and if
   // enough time has lapsed
    {    
    
        if (randomNumber == 0)  // End of instanciation, we start a new one.
        {
            randomNumber = Random.Range(1, 3); 

// We generate a random number to generate either a white or black cat.

        }

        if (randomNumber == 1)

        // We'll generate a white cat here
        {

            generateType = "White";              

// We will generate a white cat

            // 1 : We generate an X axis, and a Y axis.

            float[] arrayVerticals = new float[] { verticallevel1, verticallevel2, verticallevel5, verticallevel3, verticallevel4, verticallevel5 }; 

// We create an array containing the possible values of the Y axis where
// the cats will be placed.

            // float[] arrayVerticals = new float[] { 3.4f, -1.7f, -1.7f,- 3.7f }

            int randNumber = Random.Range(0, 6); // We create a random number that will serve as the index of the arrayVerticals array.
            randomY = arrayVerticals[randNumber];
            randomX = Random.Range(minX, maxX);

        actualHorizontal = randomX + transform.position.x;
            // We get the horizontal value that will serve to generate the cat
          
         }     // We set isGenerating Cat is true, so that this process of cat generation isn't engaged again untill / waitTime has lapsed

      isGeneratingCat = true;
    }


        else if (randomNumber == 2)
        {

            generateType = "Black";

            // We will generate a black/orange cat

            // We create an array containing the possible values of the Y axis that will
            // serve to spawn the cat

            float[] arrayVerticals = new float[] { verticallevel1, verticallevel2, verticallevel2, verticallevel3, verticallevel3, verticallevel4, verticallevel5 }; // We create an array containing the possible values of the Y axis where  the cats will be placed              

        int randNumber = Random.Range(0, 7);

   // We create a random number that will serve as the index of the arrayVerticals array.

randomY = arrayVerticals[randNumber];
randomX = Random.Range(minX, maxX);

actualHorizontal = randomX + transform.position.x; ; // We save the position of the last

cat in the actualHorizontal variable

  // We set isGenerating Cat is true, so that this process of cat generation isn't engaged again untill / waitTime has lapsed

        isGeneratingCat = true;

    }


    if (isGeneratingCat) // If isGenerating Cat is true (we have our x/y axis and our waittime and isGenerating cat is set to true)

    {
        timer += Time.fixedDeltaTime;   // We run a timer, this time will serve to generate a cat once it reaches the relevant waitTime.
        Debug.Log("Debug 1, Timer now shows " + timer);
    }

    if (timer >= waitTime) // if the time of WaitTime has elapsed.
    {
        timer = 0f; // We reset the timer once it has reached its end

        if (generateType == "White") // We genenerate a white cat
        {
            SpawnWhiteCat(randomX, randomY);
        }

        else if (generateType == "Black") // We generate a black cat

        {
            SpawnBlackCat(randomX, randomY);
        }

  }


}

void SpawnWhiteCat(float randomX, float randomY)
{              

// We generate a white cat using the coordinates we received as parameters of the function.

    Instantiate(WhiteCats, transform.position + new Vector3(randomX, randomY, 0), transform.rotation);

          // We set isGeneratingCat to false, so that the cat generating process can re-start again.

    isGeneratingCat = false;
    randomNumber = 0;
    oneTime = false;
    spawnTime = Time.time + timeBetweenSpawn;


}


void SpawnBlackCat(float randomX, float randomY)
{
    Instantiate(BlackCats, transform.position + new Vector3(randomX, randomY, 0), transform.rotation);


             // We set isGeneratingCat to false, so that the cat generating process can re-start again.

    isGeneratingCat = false;
    randomNumber = 0;
    oneTime = false;
    spawnTime = Time.time + timeBetweenSpawn;[/B]

}

}**
** **Looping Background Code :** **csharp
**using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class loopingBackground : MonoBehaviour
{
public float backgroundSpeed;
public Renderer backgroundRenderer;

// Start is called before the first frame update


// Update is called once per frame
void Update()
{
    backgroundRenderer.material.mainTextureOffset += new Vector2(backgroundSpeed * Time.deltaTime, 0f);
}

}**
** **CameraMovement Code :** **csharp
**using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class cameraMovement : MonoBehaviour
{

public GameObject RunTimerPanel;
[SerializeField] TextMeshProUGUI RunText;
public static float cameraSpeed;


 [SerializeField] private AudioSource runSoundEffect;

  
void Start()
{
 
    cameraSpeed = 12f;          }


void Update()
{

   transform.position += new Vector3(cameraSpeed * Time.deltaTime, 0, 0);

}

}**
```

Background :
Backgr1 - Album on Imgur
Player : Player1 - Album on Imgur
SpawnPoints : Imgur: The magic of the Internet
BlackCat (Orange Cat) : Imgur: The magic of the Internet

Thank you in advance.

Shot in the dark, but if the camera is moving and the background is moving, try putting the logic into FixedUpdate, like you have with the player.

Not sure how you managed to underline all your code and make it hard to read.

Though camera movement should be done in LateUpdate. Potentially the same for any background scenery movement.

Mind you, for this type of game, you probably don’t even want to move the camera, and instead just move the environment to give the impression of movement.

Really sorry about that, it is corrected now.

So i’ve been tinkering a little bit with the framerate, there is less lag with 60frames per second, i’d say approximately half the latency is gone, still trying to find some solution or approach for the other half.

I tried to put the CameraMovement script in fixed update, it just made the lag worse : Don’t know if it’s visible here because the app i’m using to record add some lag of its own unfortunately :

Video with CameraMovement script on FixedUpdate :

Video with CameraMovement script on Update :

Video with all the player movement on Update :

Here i try to simply, start from scratch as i found is recommended when having problems of that type, and i still can’t figure it out, it seems there is a lag :

I’m gonna try the approach of making just the environnement move and see if it works. By moving the environement you mean moving the background ?

Thanks.

Once again, camera movement should go in LateUpdate. FixedUpdate is for physics.

Yes just move the background while keeping the player still, and use some maths to make reset its position in order to give the impression that it’s repeating.

I confused your message with @Deridealized , apologies. It did sound wrong but i’m at the stage of trying everything, no matter how absurd.

After putting it in LateUpdate (both the cameramovement and background), it did help with the latency somewhat but there is still a lag.

I removed the camera movement afterwards and added a simple line of code joined to the gameobjects

 void Update()
{

     transform.Translate(-0.1f, 0f, 0f);
}

It works better, less latency, but it’s as if after reaching the lowest hanging fruits, the lag gets reduced only so slightly.

I’d say it went from a 5/10 to a 3/10 with the framerate change, but then only to 2.75 & 2.50.

The only thing i didn’t try is removing the score (not posted here), maybe the lag comes from that script, i don’t know.

I can’t take any new video because now the latency that remains is equal to the latency added by the screen app recorder.

Thank you very much for your time and for trying to help. Much appreciated.

Update : I tried removing everything, even the player, and there is still a lag ! It seems like half the problem comes from the looping background, it’s as if on some irregular number of seconds, there is a jerky movement as if the background pauses and generates again.