3
\$\begingroup\$

I am making somewhat of a flappy bird clone (mostly for practice) and everything works fine, but it is just really slow with a low FPS of 13 and a max FPS of 28. Why would this be?

So I ran a check with the unity profiler and it turns out that my MoveLeft script which I am using to reuse my obstacles is taking up 73% of the CPU. I have it attached to each of my 6 prefabs, which include 2 long obstacles and a token (to collect) in middle each. The game works by moving the prefabs along the camera, then moving them back after they pass the camera.

Here is the script:

using UnityEngine;
using System.Collections;

public class MoveLeft : MonoBehaviour
{

    [SerializeField]
    private float _speed = 5f;
    // Update is called once per frame
    void Update()
    {
         transform.Translate(Vector3.left * Time.deltaTime * _speed);
         if (transform.position.x < -15)
         {
             float randomYPosition = UnityEngine.Random.Range(-3, 3);
             transform.position = new Vector3(15, randomYPosition, 0);
         }
     } 
}

I really want to fix the lag. What can I do? I am sure this is the script causing it, but without this script my game does not work. Thank you! All help is appreciated!

Hierarchy (objects that I am moving with MoveLeft script Hierarchy (objects that I am moving with MoveLeft script. Script only attached to tree parents)

And a scene view that displays colliders found in prefabs Scene View displaying prefab colliders

Profiler with information regarding my MoveLeft script Profiler with information regarding my MoveLeft script

\$\endgroup\$
  • 3
    \$\begingroup\$ This script looks quite harmless to me, except for the fact that it is generally recommended to put game mechanics into FixedUpdate(), not Update(). \$\endgroup\$ – Philipp Dec 1 '15 at 8:38
  • 3
    \$\begingroup\$ The comment about FixedUpdate is not true. The only time you need to use FixedUpdate is when updating your physics objects. In your case, you are directly affecting the transform position, and does not need to be put into FixedUpdate. \$\endgroup\$ – jgallant Dec 1 '15 at 11:15
  • \$\begingroup\$ The script could be bad if the object has a very large number of child objects. Translating a game object means that all its children need to be translated too. \$\endgroup\$ – Philipp Dec 1 '15 at 11:31
  • 1
    \$\begingroup\$ Although it's not so performance affecting, usine new Vector3 every time is not a good practice. Instead, declare a Vector3 along with other global variables, then newPosition.set(15,randomYPosition,0) . But like others have pointed out, the script shouldn't consume resources too much unless it has too many child objects \$\endgroup\$ – SanSolo Dec 1 '15 at 13:05
  • 5
    \$\begingroup\$ @SanSolo that's true for reference objects like classes which need a heap allocation for every new, but Vector3 is a struct and can be allocated on the stack, so this won't be the cause of the slowdown here. To the asker: do these objects have large trigger colliders by any chance? I've experienced performance hits from repeatedly repositioning large triggers in the past. In any case, showing us your hierarchy/inspectors will help us diagnose the issue. \$\endgroup\$ – DMGregory Dec 1 '15 at 15:04
5
\$\begingroup\$

Performance problems are tricky to diagnose remotely, but the fact that your palm trees turn almost solid green when selected is a probable clue.

If you started with a nice high-resolution image, Unity probably generated PolygonCollider2D data like what you see on the left: trying to capture each frond in exacting detail.

Varying levels of collision detail

But if you're making a flappy bird clone, that detail is wasted. If a player ever ends up between those fronds or below the crown they're screwed anyway, so you don't need pixel-perfect collisions there. You could manually tweak the points to get something like the one in the middle, or replace the PolygonCollider2D with a simple EdgeCollider2D chain like the example on the right, for virtually identical gameplay.

Why does the polygon count matter so much?

When it comes to physics and collision tests, checking against a mesh of polygons is one of the most expensive things you can do. Circles/spheres and other primitives like boxes are generally a lot faster than similarly-shaped meshes, so you'll want to avoid polygon meshes whenever you don't need vertex-for-vertex fidelity, or at least try to keep the number of polygons/edges as low as possible.

But wait, why is the game even doing detailed collision calculations for these trees when the player is nowhere near them?

Because of this line:

 transform.Translate(Vector3.left * Time.deltaTime * _speed);

Physics objects, including colliders, are happiest when they're moving using physics (like Rigidbody2D forces & velocities). That lets the physics engine make good predictions about where they're going and what they might hit along the way, so it only checks for the minimum number of collisions each step.

When you Translate a physics object like a Collider, from the perspective of the physics engine it's just teleported, and could potentially be anywhere in the scene, intersecting with any other body. So suddenly its predictions are out the window and it needs to do more exhaustive checks and updates to make sure it safely handles even the worst teleportation cases. Any clever acceleration structures it had for speeding up checks against this object are now invalid too and need to be rebuilt - this is especially expensive for static colliders (ones with no Rigidbody/Rigidbody2D) as the physics system assumes these will never move. All that work is what's spiking time in your update function.

This gets compounded if the trees are in a physics layer that's allowed to collide with itself - because now the engine has to check not just each tree against the player, but each tree against every other tree.

So, your best bet fixes, with the biggest performance wins first:

  1. Don't move the trees every frame. Move the camera and the player avatar. Then you only need to reposition a tree when it scrolls off-screen, and then only one tree at a time. Huge win.

  2. Simplify your colliders to the minimum of what you need for your gameplay, using EdgeCollider2D or simpler primitives if possible. If you need to depart from the visible shape, err toward the inside - players feel much better about living when it looked like they were going to die than they do about dying when it looked like they should've lived. ;)

  3. If you need objects to move every frame, do it with a Rigidbody2D and velocity. That way the physics engine keeps control of their locations and has fewer surprises thrown its way, so it can work at peak efficiency. You can also set the body to Kinematic if you need to override the physics without incurring the cost of moving static colliders.

  4. Keep the obstacles in a physics layer that only collides with the player. That lets the physics engine focus only on the collisions that could possibly matter for gameplay.

\$\endgroup\$
  • \$\begingroup\$ Glad to hear it, @AbeY ! May I ask which fix(es) you decided to use? \$\endgroup\$ – DMGregory Dec 4 '15 at 3:26
  • 2
    \$\begingroup\$ First I edited my polygon collider and achieved 100 FPS (which was awesome) and then I changed that to an edge collider which I manually edited to get a nice outline of the outsides of the tree. This got me to 200 FPS, which is a whole lot better than the 25 FPS I was getting before. I may implement your other suggestions soon to further optimize the game, but for now I am happy! Thanks again! \$\endgroup\$ – Abe Y Dec 4 '15 at 4:22
0
\$\begingroup\$

If I understand correctly, I believe the transform function calls a GetComponent with it every time, which could spike CPU in an Update function. Is it attached deep in a parented hierarchy on the prefabs? (i.e. a child, in a child, in a child and the scripts are attached there?)

Try something like this and see if it helps

Transform myTransform;
private float _speed = 5f;

void Start()
    {
         myTransform = transform;
    }
void Update()
    {
         myTransform.Translate(Vector3.left * Time.deltaTime * _speed);
         if (myTransform.position.x < -15)
         {
             float randomYPosition = UnityEngine.Random.Range(-3, 3);
             myTransform.position = new Vector3(15, randomYPosition, 0);
         }
     } 
\$\endgroup\$
  • \$\begingroup\$ thanks but this did not solve the problem. I am thinking now that the problem is not the script, but the objects I am moving. \$\endgroup\$ – Abe Y Dec 1 '15 at 22:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.