How to improve perfomance?

My game is quite lagy. So I’ve started reducing lights and shadows and add a LoD. So it’s still lame I’ve started making batch groups for static things like walls and grounds. But I’ve to do more…
I change color for ground tiles at runtime, for example if go from a snowy terrain into a desert like. I change material and color of the character at runtime two. Maybe to create an opponent.

  • should I batch those into batch groups although?
  • is there any tool to visualize batch groups?
  • are there any best practices for the size of batch group?. default is 100, this will catch maybe 2 x 2 tiles
  • must I add layer ‘world’ to each, to get it work?

thanx :cucumber:

Before going down this route, use the profiler to see what is actually causing the FPS drops/laggyness in the game.

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

1 Like

FPS = 2 :grimacing: my intention, to do something was right :slight_smile:

With the profiler, you can see if that is due to the CPU or the GPU.

What numbers are you seeing in the profiler? https://developer.playcanvas.com/en/user-manual/optimization/profiler/

image
image
image
everything else is = -

Start with the biggest number, the update time is 424.22 ms and batching won’t fix that.

Record the runtime performance with https://developer.chrome.com/docs/devtools/evaluate-performance/ and that will tell you where time is being spent in update

3 Likes

@Gurki batching helps with reducing the draw calls, how many are there? Check the profiler for that.

But yes, the script update times are what is killing your app.

with the performance tool, I noticed that my character animation causes the long script times:

MoveWalk.prototype.update = function(dt) {
    
    Walkstep = Walkstep +1;
    
    if(Walkstep == 1){
        this.entity.parent.parent.findByName('MoveFLL').enabled = true;
        this.entity.parent.parent.findByName('MoveFLLKnee').enabled = true;
        this.entity.parent.parent.findByName('MoveHLR').enabled = true;
        this.entity.parent.parent.findByName('MoveHLRKnee').enabled = true;
    }
    if(Walkstep == 10){
        this.entity.parent.parent.findByName('MoveFLR').enabled = true;
        this.entity.parent.parent.findByName('MoveFLRKnee').enabled = true;
        this.entity.parent.parent.findByName('MoveHLL').enabled = true;
        this.entity.parent.parent.findByName('MoveHLLKnee').enabled = true;
    }
    if(Walkstep == 20){
        Walkstep = 0;
        if(this.app.root.findByName('rootWorld')){
            this.app.root.findByName('rootAction').findByName('CameraMenue').findByName('CameraHerbi1').findByName('CamHerbi1On').script.herbi1CamMove.setCamera(3);
        }
    }
    this.walk();
};

MoveWalk.prototype.walk = function (){
        
        WalkSpeed = this.entity.parent.parent.script.herbiBuild.Speed;
        if(this.app.root.findByName('rootAction')){
            if(this.app.root.findByName('rootAction').findByName('Property').findByName('activePotion').findByName('SpeedPotion').enabled){
                WalkSpeed = WalkSpeed + 10;
            }
        }
        if(this.app.root.findByName('Herbis')){
            if(this.app.root.findByName('Herbis').findByName('Herbivore').findByTag('Rollerskates')[0].enabled){
                WalkSpeed = WalkSpeed + 25;
            }
            if(this.app.root.findByName('Herbis').findByName('Herbivore').findByTag('Skateboard')[0].enabled){
                WalkSpeed = WalkSpeed * 35;
            }
        }
        WalkMass  = this.entity.parent.parent.rigidbody.mass;    
        WalkSpeed = 35 + (WalkMass * WalkSpeed / 10); 
        
        if(this.app.root.findByName('rootWorld')){
            this.app.root.findByName('Core').rotateLocal(0,0,-WalkSpeed/100);
        }
        Vector0 = new pc.Vec3(0,0,0);
        Vector1 = this.entity.parent.parent.findByName('Body').findByName('Torso').findByName('Head').findByName('NeckAnkle').findByName('NeckAnkle2').findByName('Skull').findByName('Skull2').findByName('Vector1').getPosition();
        Vector2 = this.entity.parent.parent.findByName('Body').findByName('Torso').findByName('Head').findByName('NeckAnkle').findByName('NeckAnkle2').findByName('Skull').findByName('Skull2').findByName('Vector2').getPosition();
    
        Vector0.sub2(Vector2,Vector1);
        Vector0.normalize();
        Vector0.scale(WalkSpeed);
        
        //alert(Vector0);
    
        this.entity.parent.parent.rigidbody.applyForce(Vector0);  
};

these calls function like this:

HeadTurnLeft.prototype.update = function(dt) {
    if(MoveFor17 < 15){
        this.entity.parent.parent.findByName('Body').findByName('Torso').findByName('Head').rotateLocal (0.5,0,0);
        MoveFor17 = MoveFor17 +1;
    } else {
        if(Toogle17){
            MoveBack17 = 15;
            Toogle17 = false;
        }
    }
  
  if(MoveBack17>1){
    this.entity.parent.parent.findByName('Body').findByName('Torso').findByName('Head').rotateLocal (-0.5,0,0);
    MoveBack17 = MoveBack17 -1;
    }
    if(MoveBack17==1){
        MoveFor17 = 1;
        MoveBack17 = 0;
        Toogle17 = true;
        this.entity.enabled = false;
    }  
};

what should I do :cry:

Hi @Gurki,

I first thing I notice here that is going to be taking a lot of time is:

this.entity.parent.parent.findByName();

findByName functions are one of the most expensive ways to find an asset, and having to do it every frame will really slow your app down. This is true in practically all game engines. You might want to consider creating an attribute and defining these objects in the editor, or defining them in the initialize function and using those variables/objects in update so the app doesn’t have to find the asset up to 60 times a second.

4 Likes

Thanx a lot :+1: I’ve seen a findByName took about 17µs to 2 ms, but there are so many, it makes total sense, that they sum up to a big total… and with bots they multiply by four…

quick search result: I use findByName() 12,830 times in 525 files , happy editing :roll_eyes:

As @eproasim mentioned, searching the scene graph is expensive, but that doesn’t mean you should not do it. There are perfectly fine times when you can do it - when the scene loads/starts, for example. You can do all the expensive stuff in the beginning of a scene start, like loading assets, searching for entities, etc. That is when you could show some loader bar, if there is a lot of stuff to load. But then you cache them and only use the cached references at run time.

findByName() and other graph node methods that help searching the scene graph for entities are not evil :slight_smile: Don’t hesitate to use them, as they are powerful and simplify the code process. The only rule you want to follow is that you should use them only once, as mentioned and avoid using them in update methods.

I’ve tried to replace the findByName() by a static pointer.
Did this to most commonly used slow scripts (mainly the walk animotions, and for some reasons, the hide() loading screens script) about 50 scripts and 600 findByName() calls…
I get the feeling, that’s working but the values in performance test stay the same :frowning:
So instead of beeing a static pointer my variable/object seems to still call findByName():

> ShakeButt.prototype.initialize = function() {
>     this.HB = this.app.root.findByName('Herbis').findByName('Herbivore').findByTag('HerbiButt')[0];
>     this.HHL = this.app.root.findByName('Herbis').findByName('Herbivore').findByTag('HerbiHindLegs')[0];
>     this.BUT = this.app.root.findByName('Herbis').findByName('Herbivore').findByTag('HerbiButt');
> };
> 
> ShakeButt.prototype.update = function(dt) {
>   
>   if(this.BUT.length > 0){
>     if(this.ButtShaker){
>         if(this.ButtAngle<5){
>             this.ButtAngle = this.ButtAngle +1;
>             this.HB.rotateLocal(0,0.3,0);
>             this.HHL.rotateLocal(0,0.3,0);
>         } else {
>             this.ButtShaker= false;
>             this.ButtAngle = 1;
>         }
>     } else {
>         if(this.ButtAngle<5){
>             this.ButtAngle = this.ButtAngle +1;
>             this.HB.rotateLocal(0,-0.3,0);
>             this.HHL.rotateLocal(0,-0.3,0);
>         } else {
>             this.ButtShaker = true;
>             this.ButtAngle = 1;
>         }
>    }
  • Does I make mistake here ?
  • findByTag() appears to be a little bit faster, but maybe not fast enough for 500 calls a frame, or?
  • Would it change something, if I use attributes instead of a variable?

There is also an option to use findByPath. In the above script this could already be applied 3 times. I’m not sure how you can combine this the best with findByTag. I also doubt whether this is of much benefit.

this.HB = this.app.root.findByPath('Herbis/Herbivore').findByTag('HerbiButt')[0];

I recommend using the browser profiler as mentioned here to see where the time is spent Best practice for batch groups

I’ve already checked with the profiler, and it’s defently the findByName().
Every single tiny stripat the bottom is a function call:

I had some bad expierience with findByPath(). For me it’s not working resilient.
I get randomly can’t find error with those :slight_smile:

In which case, you will need to track them down from the update loops. It looks like MoveWalk script is causing a lot of issues (pink bars): https://playcanvas.com/editor/code/678788?tabs=30414531

Thats the first one I’ve reworked. I didn’t choose this as example because it’s less clear, but I did the same approach here, which didn’t works to well…

You did well on reworking it by moving almost all searching to initialize. However, you did not move all, e.g.:

Same thing for the ones inside the walk() method.

1 Like

thats because the hierarchy tree changes on runtime. In this example “root world” is simply not loaded when the init of this script runs, and I haven’t implemented something to catch this. Every Scene like “root world” fires an event when loaded, but before I change tons of code, I want to have a solution that most likely works. I have about 50 scenes and 800 scripts, so I’ve to check before starting changing big chunks of code.
So if I can increase the FPS a litlle I’ll do things right :slight_smile:

Right. Refactoring the code is something we all go through and is part of your growth as a programmer. Looking back at my own code I can clearly see things that could have been done better, now that I learned a little more. Later you will also meet situtations when you are going to say “Was it really me who has written this?..” :slight_smile: Don’t give up!