Mobile performance

I tested my game on an iPhone X and a somewhat older Samsung A5. Everything works smoothly on the iPhone, but not at all on the Samsung. My question now is, how do I make my game less heavy so that it runs smoothly on most devices?

Hi @albertos,

You will have to profile your game (Playcanvas provides a robust profiler) and see what are the main bottlenecks (draw calls, polycount, resolution/render time, physics etc). From there you can start implementing strategies to address those issues:

  • If it’s draw calls, leverage static and dynamic batching.
  • If it’s polycount, lower the polygons on your models or add a LOD (level of detail) system
  • If it’s resolution/render time, try using less complex materials, lower the detail of your shadows or avoid using realtime shadows at all. As a last resort disable device pixel ratio on your project settings (this can make everything look blurry).
  • If it’s physics, check the number of dynamic objects and raycasts per frame executing in your code.

Usually on older mobile devices with high resolution displays the render time is where 3D apps suffer.

1 Like

Did you thoroughly read this section in the User Manual?

https://developer.playcanvas.com/en/user-manual/optimization/

2 Likes

Oké, thanks for your response. I’m going to work with it.

Is there already a tutorial to add pathfinding? Since I now use a lot of raycasts for the sensors which is not optimal.

This is the profiler of my game.
Can someone help me with these numbers?
I don’t know what is normal and what not.

The update time is very high compared to the render time. This indicates that your game’s scripts are doing something quite expensive. I would recommend running a CPU profile from the Performance tab in Chrome Dev Tools to ascertain where time is being spent in your scripts.

Because no navmesh is possible in PlayCanvas, every enemy has many sensors (raycasts) that detect where the enemy can walk and where not. This is done in the update. Is there an alternative to that?

Hi @albertos, that’s expensive indeed, to increase performance with that system you could try running your raycasts less often. Instead of per frame, 20 or even 10 times per second.

You can try and see if that affects the gameplay in a significant way or you can get away with a somewhat laggier response for the NPCs.

Oke, i can try if this is an option. How do i do that?

I now also realize that enemies that are stationary do not have to use the sensors. Does it help to add this?

Enemy.prototype.updateSensors = function (dt) {
    if (this.isAlive && this.isMoving) {
        // update all sensors
    }
};

It looks like the update time stays the same.

Here is a script that will update a method on a fixed frame rate (e.g. 10 times a second):

var FixedFps = pc.createScript('fixedFps');

FixedFps.attributes.add('fps', { type: 'number', default: 10 });

// initialize code called once per entity
FixedFps.prototype.initialize = function() {
    
    // --- variables
    this.now = 0;
    this.then = 0;
};

// update code called every frame
FixedFps.prototype.update = function(dt) {
    
    this.now = Date.now();
    var elapsed = this.now - this.then;

    // if enough time has elapsed, update
    if (elapsed > 1000 / this.fps) {

        this.fixedUpdate();
        
        this.then = this.now - (elapsed % this.fps);
    }    
};

FixedFps.prototype.fixedUpdate = function() {
    console.log('updated');
};

You need to profile your pathfinding method, and any other “heavy” method to understand where is your bottleneck.

A quick way is to use console.time to check how much time a method takes to run:

console.time('profile');
myMethod();
console.timeEnd('profile');

This will print in the console the time it takes to run myMethod each time it executes (provided it’s a synchronous method).

Enemy.prototype.updateSensors = function (dt) {
    if (this.isAlive && this.isMoving) {
        // update all sensors
    }
};

What I said is not true. With this, the update time has dropped from 17 to 10. I’m also going to try adding your other solution and investigate where I can disable unnecessary updates. Thanks!

1 Like

The biggest fault in my script was using

this.allTargets = this.app.root.findByTag("EnemyTarget");

in the update.

1 Like

Aha. OK, yeah, it’s normally a good idea to query once in the initialize function (if entities with that tag are not dynamically created and/or destroyed). We could potentially optimize that function though (I’m not super-familiar with how it works internally myself).

It sounds like you’re making great progress to speed things up! :smiley: