Jump to content

low FPS on mobile, problem with performance


Recommended Posts

Hi All,

I am trying with my friend to develop our first game using Phraser. We decided to have it in an Isometric view, after creating first simple map we tested it on PC and game was running smoothly however when we tried to run it on a mobile device (LG G2) FPS dropped down dramatically to less then 1 frame per second, to run it on mobile we tried:

1) cordova

 

2) intel xdk

 

3) cocoon

 

4) cordova+crosswalk

 

Intel xdk was the best but still far from acceptable level. The question is how to build or what to change in our code started in phaser.js to make it work? Here is our code. Any help will be highly appreciated!

 

var width = window.innerWidth;
var height = window.innerHeight;

var obstacleGroup, player;
var marker, itemGroup;
var floorGroup;
var exitMarker;
var grassGroup;

var check;

var controls;
var cN, cS, cE, cW, cSE, cNE, cSW, cNW;

var way;

var water = [];

//Initialize function
var init = function () {
    var game = new Phaser.Game(width, height, Phaser.AUTO, 'test', null, false, true);
    var keyboard = new Phaser.Keyboard(game);
    var cursors;
    var BasicGame = function (game) { };

    BasicGame.Boot = function (game) { };

    BasicGame.Boot.prototype =
    {
        preload: function () {
            game.load.image('tree', 'images/tiles/tree.png');
            game.load.image('trees', 'images/tiles/trees.png');
            game.load.image('tree2', 'images/tiles/tree2.png');

            game.load.image('gold', 'images/tiles/find1_gold.png');
            game.load.image('exit', 'images/tiles/exit.png');
            
            game.load.image('house', 'images/tiles/house.png');
            
            game.load.image('E', 'images/controls/E.png');
            game.load.image('N', 'images/controls/N.png');
            game.load.image('NE', 'images/controls/NE.png');
            game.load.image('NW', 'images/controls/NW.png');
            game.load.image('S', 'images/controls/S.png');
            game.load.image('SE', 'images/controls/SE.png');
            game.load.image('SW', 'images/controls/SW.png');
            game.load.image('W', 'images/controls/W.png');
            
            game.load.spritesheet('terrain','images/tiles/terrain.png', 64, 64);
            game.load.spritesheet('characterAnim','images/tiles/vehicle.png', 128, 128);
         
            game.time.advancedTiming = true;

            // Add the Isometric plug-in to Phaser
            game.plugins.add(new Phaser.Plugin.Isometric(game));

            // Set the world size
            game.world.setBounds(0, 0, 2048, 1024);

            // Start the physical system
            game.physics.startSystem(Phaser.Plugin.Isometric.ISOARCADE);

            // set the middle of the world in the middle of the screen
            game.iso.anchor.setTo(0.5, 0);
        },
        create: function () {
            
            // set the Background color of our game
            game.stage.backgroundColor = "0x000000";
            
            // create groups for different tiles
            floorGroup = game.add.group();
            itemGroup = game.add.group();
            grassGroup = game.add.group();
            obstacleGroup = game.add.group();
          
            // set the gravity in our game
            game.physics.isoArcade.gravity.setTo(0, 0, -500);

            var renderedPositions = new Array(32);
            for (var i = 0; i < renderedPositions.length; i++) {
                renderedPositions[i] = new Array(32);
            }

            for (var i = 0; i < 32; i++) {
                renderedPositions[31][i] = game.add.isoSprite(31*32, i*32, 0, 'terrain', 18, obstacleGroup);
                renderedPositions[31][i].anchor.set(0.5);
                game.physics.isoArcade.enable(renderedPositions[31][i]);
                renderedPositions[31][i].body.collideWorldBounds = true;
                renderedPositions[31][i].body.immovable = true;

                renderedPositions[30][i] = game.add.isoSprite(30*32, i*32, 0, 'terrain', 18, obstacleGroup);
                renderedPositions[30][i].anchor.set(0.5);
                game.physics.isoArcade.enable(renderedPositions[30][i]);
                renderedPositions[30][i].body.collideWorldBounds = true;
                renderedPositions[30][i].body.immovable = true;

                water.push(renderedPositions[30][i]);


                renderedPositions[29][i] = game.add.isoSprite(29*32, i*32, 0, 'terrain', 18, obstacleGroup);
                renderedPositions[29][i].anchor.set(0.5);
                game.physics.isoArcade.enable(renderedPositions[29][i]);
                renderedPositions[29][i].body.collideWorldBounds = true;
                renderedPositions[29][i].body.immovable = true;

                water.push(renderedPositions[29][i]);

                renderedPositions[28][i] = game.add.isoSprite(28*32, i*32, 0, 'terrain', 8, obstacleGroup);
                renderedPositions[28][i].anchor.set(0.5);
                game.physics.isoArcade.enable(renderedPositions[28][i]);
                renderedPositions[28][i].body.collideWorldBounds = true;
                renderedPositions[28][i].body.immovable = true;

            }


            // create the floor tiles
            var grasses = [2,7,12,17,22];

            for (var xt = 0; xt < 32; xt++) {
                for (var yt = 0; yt < 32; yt++) {
                    if (!renderedPositions[xt][yt]) {
                        renderedPositions[xt][yt] = game.add.isoSprite(xt*32, yt*32, 0, 'terrain', grasses[Math.floor(Math.random() * 5)], floorGroup);
                        renderedPositions[xt][yt].anchor.set(0.5);
                    }
                }
            }


            // create a house object which will be our ending point in the game
            var house = game.add.isoSprite(300, 100, 0, 'house', 0, obstacleGroup);
                house.anchor.set(0.5);
                
                game.physics.isoArcade.enable(house);
                house.body.collideWorldBounds = true;
                house.body.immovable = true;
            
            // create collectible items 
            marker = game.add.isoSprite(rndNum(300), rndNum(300), 0, 'gold', 0, itemGroup);
            game.physics.isoArcade.enable(marker);
            marker.body.collideWorldBounds = true;
            marker.anchor.set(1.0);
            
            // create the exit marker next to the house object
            exitMarker = game.add.isoSprite(400, 400, 0, 'exit', 0, itemGroup);
            game.physics.isoArcade.enable(exitMarker);
            exitMarker.body.collideWorldBounds = true;
            exitMarker.anchor.set(0.5);
            exitMarker.alpha = 0.5;
                          
            // create control button sprites on the screen   
            cNW = game.add.sprite(0, 100, 'NW');  
            cNW.fixedToCamera = true;
            cNW.inputEnabled = true;
            cNW.events.onInputDown.add(onDown, this);
            cNW.events.onInputOver.add(onDown, this);
            cNW.events.onInputUp.add(onUp, this);
            cNW.events.onInputOut.add(onUp, this);
            
            cW = game.add.sprite(0, 176, 'W');  
            cW.fixedToCamera = true;
            cW.inputEnabled = true;
            cW.events.onInputDown.add(onDown, this);
            cW.events.onInputOver.add(onDown, this);
            cW.events.onInputUp.add(onUp, this);
            cW.events.onInputOut.add(onUp, this);
            
            cSW = game.add.sprite(0, 252, 'SW');  
            cSW.fixedToCamera = true;
            cSW.inputEnabled = true;
            cSW.events.onInputDown.add(onDown, this);
            cSW.events.onInputOver.add(onDown, this);
            cSW.events.onInputUp.add(onUp, this);
            cSW.events.onInputOut.add(onUp, this);
            
            cN = game.add.sprite(76, 100, 'N');  
            cN.fixedToCamera = true;
            cN.inputEnabled = true;
            cN.events.onInputDown.add(onDown, this);
            cN.events.onInputOver.add(onDown, this);
            cN.events.onInputUp.add(onUp, this);
            cN.events.onInputOut.add(onUp, this);
            
            cS = game.add.sprite(76, 252, 'S');  
            cS.fixedToCamera = true;
            cS.inputEnabled = true;   
            cS.events.onInputDown.add(onDown, this);
            cS.events.onInputOver.add(onDown, this);
            cS.events.onInputUp.add(onUp, this);
            cS.events.onInputOut.add(onUp, this);
            
            cNE = game.add.sprite(152, 100, 'NE');
            cNE.fixedToCamera = true;
            cNE.inputEnabled = true;
            cNE.events.onInputDown.add(onDown, this);
            cNE.events.onInputOver.add(onDown, this);
            cNE.events.onInputUp.add(onUp, this);
            cNE.events.onInputOut.add(onUp, this);
            
            cE = game.add.sprite(152, 176, 'E');  
            cE.fixedToCamera = true;
            cE.inputEnabled = true;
            cE.events.onInputDown.add(onDown, this);
            cE.events.onInputOver.add(onDown, this);
            cE.events.onInputUp.add(onUp, this);
            cE.events.onInputOut.add(onUp, this);
            
            cSE = game.add.sprite(152, 252, 'SE');  
            cSE.fixedToCamera = true;
            cSE.inputEnabled = true;
            cSE.events.onInputDown.add(onDown, this);
            cSE.events.onInputOver.add(onDown, this);
            cSE.events.onInputUp.add(onUp, this);
            cSE.events.onInputOut.add(onUp, this);
            
            // create control functions for the control buttons
            function onDown(sprite, pointer) {

                switch(sprite.key) {
                    case "N":
                    case "S":
                    case "SE":
                    case "SW":
                    case "NW":
                    case "NE":
                    case "E":
                    case "W":
                        way = sprite.key;
                }
            }
            
            function onUp(sprite, pointer) {
                way = undefined;
                
            }
            
            controls = game.add.group();
            controls.add(cN);
            controls.add(cS);
            controls.add(cW);
            controls.add(cE);
            controls.add(cNE);
            controls.add(cNW);
            controls.add(cSE);
            controls.add(cSW);
            
            controls.alpha = 0.9;
            
            // Creste the player
            player = game.add.isoSprite(25*32, 3*32, 0, 'characterAnim', 0, obstacleGroup);
            
            player.alpha = 1.0;
                    
            // add the animations from the spritesheet
            player.animations.add('S', [0], 10, true);
            player.animations.add('SW', [1], 10, true);
            player.animations.add('W', [2], 10, true);
            player.animations.add('NW', [3], 10, true);
            player.animations.add('N', [4], 10, true);
            player.animations.add('NE', [5], 10, true);
            player.animations.add('E', [6], 10, true);
            player.animations.add('SE', [7], 10, true);
             
            player.anchor.set(0.5);
            
            // enable physics on the player
            game.physics.isoArcade.enable(player);
            player.body.collideWorldBounds = true;

            game.time.advancedTiming = true;
            game.debug.text(game.time.fps, 0, 0, 'red');

            game.camera.follow(player);
        },
        update: function () {
            // Move the player
            var speed = 100;
           

            if (way === 'SE')// || (cursors.down.isDown && cursors.right.isDown))
            {
                player.body.velocity.x = speed;
                player.body.velocity.y = 0;
                player.animations.play('SE');
            }
            else if (way === 'SW')// || (cursors.down.isDown && cursors.left.isDown))
            {
                player.body.velocity.y = speed;
                player.body.velocity.x = 0;
                player.animations.play('SW');
            }
            else if (way === 'NW')// || (cursors.up.isDown && cursors.left.isDown))
            {
                player.body.velocity.x = -speed;
                player.body.velocity.y = 0;
                player.animations.play('NW');
                
            }
            else if (way === 'NE')// || (cursors.up.isDown && cursors.right.isDown))
            {
                player.body.velocity.y = -speed;
                player.body.velocity.x = 0;
                player.animations.play('NE');
            }
            else if (way === 'N')// || cursors.up.isDown) 
            {
                player.body.velocity.y = -speed;
                player.body.velocity.x = -speed;
                player.animations.play('N');
            }
            else if (way === 'S')// || cursors.down.isDown)
            {
                player.body.velocity.y = speed;
                player.body.velocity.x = speed;
                player.animations.play('S');
            }
            else if (way === 'E')// || cursors.right.isDown) 
            {
                player.body.velocity.x = speed;
                player.body.velocity.y = -speed;
                player.animations.play('E');
            }
            else if (way === 'W')// || cursors.left.isDown)
            {
                player.body.velocity.x = -speed;
                player.body.velocity.y = speed;
                player.animations.play('W');
            }
            else
            {
                player.body.velocity.x = 0;
                player.body.velocity.y = 0;
                player.animations.stop();
            }
            

        
            game.physics.isoArcade.collide(obstacleGroup);
            game.physics.isoArcade.overlap(marker, player ,function(e){
                e.destroy();
            });
               
               check = game.physics.isoArcade.overlap(exitMarker, player ,function(e){
                    
            });
            
           game.iso.topologicalSort(obstacleGroup);
            
        },
        render: function () {
          
        }
    };

    game.state.add('Boot', BasicGame.Boot);
    game.state.start('Boot');
    
    // generate random number
    function rndNum(num) {        
        return Math.round(Math.random() * num);
    }
};

// window.onload can work without <body onload="">
window.onload = init;

 

main.js

Link to post
Share on other sites
  • 2 weeks later...

There is maybe some tips, like removing the render function completely.

Using spritesheet and Cocoon in OpenGl (removing the game.debug.text and using the dev app debugger instead)

You should not make the way variable mutate to undefined type (the javascript just in time compiler won't like that), setting it to empty string would be better ''

The iso plugin maybe really expensive on mobile.

But you should remotely CPU profile your game with chrome for mobile, you'll see where are the bottleneck, it's also important to look at the timeline to check memory management.

 

 

 

 

Link to post
Share on other sites
  • 3 weeks later...

Images are not that big, we only have simple map (generated from a tilset) and couple simple graphics to go with it so it shouldn`t be a problem for the engine to display it smoothly on LG G2 phone. We also tried to correct the code as per your advise but it didn`t help. Below I attached the whole game, perhaps we are missing out on something very obvious. Can someone please have a look? We are desperate to get this game running.... 

 

Link to post
Share on other sites

The game looks like it's running fine after inspecting in chrome dev tools, I guess the phone is really struggling. 

Few things I noticed.

Using debug.text is REALLY slow on webgl. Instead use a normal text object for your debugging.

Most of your images non POT, so webgl needs to convert them before uploading them to the gpu. Make sure your images are powers of 2.

But tbh, I really would have expected the game to run fine with the CANVAS renderer. 

Have you tried viewing the Phaser demos on that phone?

Link to post
Share on other sites

Hi Nicof, game is being developed on different platforms including PC, this is why this line of code has been inserted. If this affects the performance on mobile we will remove it. 

Mcolman, thank you for your time, I appreciate every advise. We will try to run demo on mobile this week so I will let you know.

1. I am not sure what you mean by saying images non POT? Wat POT stands for? 

2. By saying the power of 2 did you mean to create tilesets with even numbers? 

3. Is this game running well on your mobile? 

Link to post
Share on other sites
  • 2 weeks later...

Hi guys, has anyone tried to run it on a mobile? Perhaps low fps is caused by isometric plugin? 

Mcolman, Nicog, all ground tiles are the POT, map is very simple so far so there must be a bottle neck somwhere ;/ We can`t move forward without solving this problem, please help. 

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...