[SOLVED] Profiler huge spike on Physics2D.FixedUpdate

Hello guys. I’m currently in the process of debugging my game to make it as polished as possible when I released it. However, I soon find out I’m getting a major drop in frame rate which is quite a big problem for me.

The game works perfectly fine in like the first 30 levels, the frame rate is consistent and playable. In the last several levels of my game, I deemed some of the levels in the game as being unplayable. So out of all the levels available, maybe only 3-5 levels are having major frame rate drops. So I tested a level that I noticed to be lagging the most and here’s the result:

I’m kind of new with using the Profiler but I do know it’s a good tool to optimize my game as much as possible. In the screenshot, you can see that Physics2D.FixedUpdate is getting huge spikes. I’m not sure why that’s the case but my theories are that because it’s a shoot em up game, the cause must be either the bullets instantiated in the game or the enemies active in the scene. I’ve already rewrote my whole game so that it’s using as less Physics as I can but the issue is still happening with no improvement.

All my enemies are pretty much the same when it comes to behaviour. They have a Rigidbody2D (Is Kinematic set to true) and a Polygon Collider2D. The script pretty much looks like this:

using UnityEngine;

public class PursuerController : MonoBehaviour
{
    public StatusIndicator status;
    public GameObject basicNeon;
    public GameObject explosion;

    private DataManager data;
    private GameManager GM;
    private SFXManager SFXManager;
    private Rigidbody2D pursuer;
    private EnemyStatsController stats;
    private ShieldController shieldControl;
    private Transform target;
    private bool isCollided;

    void Start()
    {
        data = GameObject.FindWithTag("PlayManager").GetComponent<DataManager>();
        GM = GameObject.FindWithTag("GameManager").GetComponent<GameManager>();
        SFXManager = GameObject.FindWithTag("GameManager").transform.GetChild(1).GetComponent<SFXManager>();
        pursuer = GetComponent<Rigidbody2D>();
        stats = GetComponent<EnemyStatsController>();
        stats.maxHealth = (int)Random.Range(30f, 45f) * data.difficulty;
        stats.speed = Random.Range(0.9f, 1.3f) * data.difficulty;
        stats.collisionDamage = Random.Range(11f, 17f);
        stats.CurHealth = stats.maxHealth;
        shieldControl = GameObject.FindWithTag("Shield").GetComponent<ShieldController>();
        target = GameObject.FindWithTag("Player").transform;
    }

    void Update()
    {
        // Sets health bar
        status.SetHealth(stats.CurHealth, stats.maxHealth);

        if (stats.CurHealth <= 0f)
        {
            GM.totalEnemiesDestroyed++;
            float killReward = Random.Range(stats.minKillReward, stats.maxKillReward) / data.difficulty;
            if (!isCollided)
            {
                // Instantiate neons
                for (int i = 0; i < Random.Range(stats.neonControl.basicMinAmount, stats.neonControl.basicMaxAmount); i++)
                    Instantiate(basicNeon, transform.position, Quaternion.identity);
            }

            // Set neon display and rewards
            data.lastNeon = data.displayedNeon;
            if (!isCollided)
            {
                GM.totalNeonsCollected += killReward;
                data.curNeon += killReward;
            }
            else
            {
                GM.totalNeonsCollected += killReward / 2;
                data.curNeon += killReward / 2;
            }
            data.neonTimer = 0f;

            // Destroys enemy
            Destroy(gameObject);
            Destroy(transform.parent.gameObject);
        }
    }

    void FixedUpdate()
    {
        if (!stats.freeze)
        {
            // Unfreeze
            pursuer.constraints = RigidbodyConstraints2D.None;
            if (Vector3.Distance(target.position, transform.position) > 0f)
            {
                // Direction vector from enemy to target
                Vector3 dir = target.position - transform.position;

                // Rotation to always face player
                float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
                transform.rotation = Quaternion.AngleAxis(angle - 90, Vector3.forward);

                // Move the enemy into the direction
                transform.position = Vector3.MoveTowards(transform.position, target.position, stats.speed * Time.deltaTime);
            }
        }
        else
            // Freeze
            pursuer.constraints = RigidbodyConstraints2D.FreezeAll;
    }

    void OnTriggerEnter2D(Collider2D other)
    {
        if (!shieldControl.isActivated)
        {
            // Collide if shield deactivated
            if (other.gameObject.tag == "Player")
            {
                isCollided = true;
                // Explosion effect
                GameObject explosionInstance = (GameObject)Instantiate(explosion, transform.position, Quaternion.identity);
                Destroy(explosionInstance, 0.5f);

                SFXManager.PlaySound("Explosion", true);

                // Kills off enemy
                stats.CurHealth -= stats.maxHealth;
            }
        }
    }
}

If the problem is the bullets, I suspect the OnTriggerEnter2D checks is the reason causing the lag but I may be totally wrong. Script:

using UnityEngine;

public class EnemyBulletController : MonoBehaviour
{
    public float speed;
    public float damage;
    public GameObject playerHitEffect;
    public GameObject shieldHitEffect;
    public LayerMask playerLayer;

    private DataManager data;

    void Start()
    {
        data = GameObject.FindWithTag("PlayManager").GetComponent<DataManager>();
    }

    void Update()
    {
        // Automatically destroy bullet when out of bounds
        if (transform.position.x > data.xBound || transform.position.x < -data.xBound || transform.position.y > data.yBound || transform.position.y < -data.yBound)
            Destroy(gameObject);
    }

    void FixedUpdate()
    {
        // Bullet's movement
        transform.position += transform.up * (speed * data.difficulty);
    }

    void OnTriggerEnter2D(Collider2D other)
    {
        RaycastHit2D hit = Physics2D.Raycast(transform.position, transform.up, 100f, playerLayer);

        if (hit.collider != null)
        {
            if (other.gameObject.tag == "Shield")
            {
                // Hit effect then destroy it
                GameObject hitInstance = (GameObject)Instantiate(shieldHitEffect, hit.point, Quaternion.identity);
                hitInstance.transform.up = hit.normal;
                Destroy(hitInstance, 0.5f);
                // Destroy bullet
                Destroy(gameObject);
            }

            if (other.gameObject.tag == "Player")
            {
                // Damage the player
                other.transform.root.GetComponent<PlayerStatsController>().TakeDamage(damage);

                // Hit effect then destroy it
                GameObject hitInstance = (GameObject)Instantiate(playerHitEffect, hit.point, Quaternion.identity);
                hitInstance.transform.up = hit.normal;
                Destroy(hitInstance, 0.5f);
                // Destroy bullet
                Destroy(gameObject);
            }
        }
    }
}

More info: The level I’m testing on lags when there are more than 20 of these enemies in the scene. This may be more on other people’s computer because my laptop isn’t that powerful.

Unfold the FixedUpdate in the profiler to see whats in there.

Physics2D.Simulate was the one that used up most of my CPU when I revealed what’s below FixedUpdate.
2877412--211051--Capture.PNG
Also, what’s the difference between Time ms and Self ms? I’m new to the Profiler so I’m still kind of confused what those does.

Time ms is how long that call in total takes, including all of the things that happen under it. It’s the cost of that node and every node under it.
Time self is the cost of exactly that node, excluding everything under it.

To figure out exactly what’s happening, turn on deep profiling. It has a massive overhead, but you’ll probably get to know which part of your code’s causing Physics2D.Simulate to trigger.

I turned Deep Profiling on but everything looks the same as before for me. Where do I look for to find which part of the code is causing my Physics2D.Simulate to use so much CPU? I think I’m missing something.

On the other hand, I changed my enemy bullet’s polygon collider2d to box collider2d but I don’t think there’s any improvement. If there is one, it has to be very subtle. I also tried disabling everything not needed in the Layer Collision Matrix and there is a small improvement, but it’s definitely not enough.

A couple of obvious things jump out at me: You’re manipulating transforms directly, which is effectively like teleportation to the Physics engine even if it only moves a tiny bit, so it has to re-calculate a ton of data to account for the new (teleported) position and rotation.

2 Likes

I’m pretty sure I found the cause of my problem. It has to do with the Polygon Colliders2D of my gameobjects in the game, both the bullets and enemies. Apparently, when I edited these colliders, there was always a warning that came up regarding removed collision shapes. I’ve been ignoring it though because I didn’t think it will cause any problems. Here is what the warning looks like:
2877615--211077--Capture.PNG

So I removed all my Polygon Colliders2D and replace it with Box Colliders2D and the result was very significant. I’m able to run the game at 30fps (pretty good considering my laptop) when there are 30 enemies active in the scene. The Profiler looks like this after the change:

Definitely a lesson learned here. I’m also pretty happy this situation happened because now I’ll try to use Physics as less as possible. :stuck_out_tongue:

Looks like your code could be profiled further if you were interested in getting the game on phones/daydream etc… In my case Rendering always profiles worse than the code

( 100-500k tris on mobile had to make my code fast so the graphics guys could add as many effects as possible )

VR requires very stable 60fps