joshgerdes

Performance issues with P2 sprite collisions

Recommended Posts

I am having some performance issues with collision groups on mobile.
 
I have 13 enemy sprites in game that collide with map polygon boundaries.  Having the enemy sprites in game degrades the fps from 60fps down to 30fps. As I reduce the number of enemies in game the fps increases. 13 seems like a really small number of sprites to have in game so I feel like I must be setting up the collision detection incorrectly.
 
I am trying to setup a base map with polygons to use for collision boundaries of the map. I want to have enemies go back and forth horizontally between the map boundaries.  They will change direction (both a change in x velocity and flipping the scale so they visual point the opposite direction) when they make contact with the boundary.
 
 
 
Is this the proper way to setup this type of collision detection? If so, any thoughts on why it is so slow on mobile devices (testing on iPhone 5)? 
 
 
 
 
Here is the code I currently have:

// Init gamevar game = new Phaser.Game(1024, 640, Phaser.CANVAS, 'test-game'); // Load the assetsgame.load.image('level01', 'assets/level01.png'); // Image 1024x4608 game.load.physics('level01_physics', 'assets/level01_physics.json'); // Physics data format is Lime + Corona (JSON)// Setup collision groups var landCG = game.physics.p2.createCollisionGroup(); var enemyCG = game.physics.p2.createCollisionGroup();// Load map background imagevar map = this.add.sprite(0, 0, 'level01');// Create sprite and load polygon physics data for the map boundaries var mapCollisionPolygons = this.add.sprite(15, 15); this.physics.p2.enableBody(mapCollisionPolygons, false); mapCollisionPolygons.fixedRotation = true; mapCollisionPolygons.body.motionState = Phaser.Physics.P2.Body.STATIC; mapCollisionPolygons.body.clearShapes(); mapCollisionPolygons.body.loadPolygon('level01_physics', 'left'); mapCollisionPolygons.body.loadPolygon('level01_physics', 'right'); mapCollisionPolygons.body.setCollisionGroup(landCG); mapCollisionPolygons.body.collides(enemyCG); // Create a new enemy and setup collision var enemy = new Enemy(game, 100, 100, 100, 100, 100); enemy.body.setCollisionGroup(enemyCG); enemy.body.collides(landCG, enemy.collided, enemy); 

The extended Sprite class called 'Enemy':

var Enemy = function (game, x, y) {	this.hasCollided = false;	this.speed = 100;	Phaser.Sprite.call(this, game, x, y, 'boat');	game.physics.p2.enable(this);	this.body.fixedRotation = true;	this.body.motionState = Phaser.Physics.P2.Body.DYNAMIC;	this.body.onEndContact.add(this.endCollided, this);	game.add.existing(this);	return this;};Enemy.prototype = Object.create(Phaser.Sprite.prototype);Enemy.prototype.constructor = Enemy;Enemy.prototype.update = function() {};Enemy.prototype.collided = function(body1, body2) {	if(!this.hasCollided) {		this.scale.x *= -1;		this.speed *= -1;		this.body.velocity.x = this.speed;		this.hasCollided = true;	}};Enemy.prototype.endCollided = function(bodyB, shapeA, shapeB) {	this.hasCollided = false;};module.exports = Enemy;

The 'assets/level01_physics.json' file:

	{ 				"left": [							{					"density": 2, "friction": 0, "bounce": 0, 					"filter": { "categoryBits": 1, "maskBits": 65535 },					"shape": [   0, 4608  ,  224, 4016  ,  384, 4056  ,  384, 4608  ]				}  ,				{					"density": 2, "friction": 0, "bounce": 0, 					"filter": { "categoryBits": 1, "maskBits": 65535 },					"shape": [   384, 264  ,  224, 303  ,  0, 0  ,  384, 0  ]				}  ,				{					"density": 2, "friction": 0, "bounce": 0, 					"filter": { "categoryBits": 1, "maskBits": 65535 },					"shape": [   224, 4016  ,  0, 4608  ,  0, 0  ,  224, 303  ]				}  		]				, 		"right": [							{					"density": 2, "friction": 0, "bounce": 0, 					"filter": { "categoryBits": 1, "maskBits": 65535 },					"shape": [   640, 4056  ,  800, 4016  ,  1024, 4608  ,  640, 4608  ]				}  ,				{					"density": 2, "friction": 0, "bounce": 0, 					"filter": { "categoryBits": 1, "maskBits": 65535 },					"shape": [   1024, 0  ,  800, 303  ,  640, 264  ,  640, 0  ]				}  ,				{					"density": 2, "friction": 0, "bounce": 0, 					"filter": { "categoryBits": 1, "maskBits": 65535 },					"shape": [   800, 4016  ,  800, 303  ,  1024, 0  ,  1024, 4608  ]				}  		]			}

Here is a link to a related post with some of my performance testing with different options for displaying the background map and boundaries in case it helps. 
http://www.html5gamedevs.com/topic/9677-vertical-scrolling-background-with-collisions-in-p2/

Share this post


Link to post
Share on other sites

Maybe you could provide a source code of your example? It's hard to debug just by watching at snipets. In theory, if your tilemap has many tiles (bodyObjects.length) then adding a new enemy with many polygons would increase the number of calculations significantly. Another problem might be that canvas mode has a hard time dealing with many polygons, have you tested it with iOS8 webgl?

Share this post


Link to post
Share on other sites

Sorry, I thought the extra basic game setup code would just get in the way of looking at the issue.  I have created a basic example with all the code in the attached zip. 

 

And just to be clear, I am not using tiles at the moment.  Just 2 sprites (one for the background image and other for the polygon-based collision layer). The collision layer uses 6 polygons and I have tested with WEBGL which runs at 15fps which much worse than the 32fps I'm getting with CANVAS.

 

 

Here's the main code:

var game = new Phaser.Game(1024, 640, Phaser.WEBGL, 'test-game', { preload: preload, create: create, update: update, render: render });var NUMBER_OF_ENEMIES = 13;var cursors = null;var player = null;var MAX_SPEED = 150;var DEFAULT_SPEED = this.MAX_SPEED / 2;var MIN_SPEED = this.DEFAULT_SPEED / 2;var enemies = null;function preload() {	game.input.maxPointers = 2;	game.input.multiInputOverride = Phaser.Input.TOUCH_OVERRIDES_MOUSE;	game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;	game.scale.pageAlignHorizontally = true;	if (game.device.desktop)	{		game.scale.pageAlignVertically = true;	}	else {		game.scale.forceLandscape = true;			game.scale.startFullScreen(false);	}	game.scale.setScreenSize(true);	game.scale.refresh();	game.load.image('level01', 'level01.png');	game.load.physics('level01_physics', 'level01.json');	game.load.image('player', 'player.png');	game.load.image('enemy', 'enemy.png');};function create() {	game.time.advancedTiming = true;	game.physics.startSystem(Phaser.Physics.P2JS);	game.physics.p2.setImpactEvents(true);	game.physics.p2.restitution = 0.8;	game.stage.backgroundColor = '#333333';	game.scale.setResizeCallback(resize, game);	// Setup collision groups 	var landCG = game.physics.p2.createCollisionGroup(); 	var enemyCG = game.physics.p2.createCollisionGroup();	var playerCG = game.physics.p2.createCollisionGroup();		// Load map background image	var map = game.add.sprite(0, 0, 'level01');	game.world.setBounds(0, 0, 1024, 4608);	// Create sprite and load polygon physics data for the map boundaries 	var mapCollisionPolygons = game.add.sprite(15, 15); 	game.physics.p2.enableBody(mapCollisionPolygons, false); 	mapCollisionPolygons.fixedRotation = true; 	mapCollisionPolygons.body.motionState = Phaser.Physics.P2.Body.STATIC; 	mapCollisionPolygons.body.clearShapes(); 	mapCollisionPolygons.body.loadPolygon('level01_physics', 'left'); 	mapCollisionPolygons.body.loadPolygon('level01_physics', 'right'); 	mapCollisionPolygons.body.setCollisionGroup(landCG); 	mapCollisionPolygons.body.collides(enemyCG); 	 	enemies = game.add.group();	for(var i = 0; i <= NUMBER_OF_ENEMIES; i++) {		// Create a new enemy and setup collision 		var enemy = new Enemy(game, map.width / 2, map.height - (680 + i * 200)); 		enemy.body.setCollisionGroup(enemyCG); 		enemy.body.collides(landCG, enemy.collided, enemy);		enemy.body.collides(playerCG, diePlayer, this);		enemies.add(enemy);	}	// Create a new player and setup collision	player = new Player(game, map.width / 2, map.height - 380);	player.body.setCollisionGroup(playerCG);	player.body.collides(landCG, diePlayer, game);	player.body.collides(enemyCG, diePlayer, game);	game.physics.p2.updateBoundsCollisionGroup();	game.camera.follow(player);	cursors = this.input.keyboard.createCursorKeys();};function update() {	if (cursors.up.isDown) {		player.body.moveUp(MAX_SPEED)	}	else if (cursors.down.isDown) {		player.body.velocity.y = -MIN_SPEED;	}	else {		player.body.moveUp(DEFAULT_SPEED);	}	if (cursors.left.isDown) {		player.body.velocity.x = -MAX_SPEED;	}	else if (cursors.right.isDown) {		player.body.moveRight(MAX_SPEED);	}	else {		player.body.velocity.x = 0;	}	enemies.forEachAlive(function(item) {		if (!item.started && Phaser.Point.distance(player.body, item.body, true) < item.range) {			item.startMove();		}	}, this);};function render() {	game.debug.text(this.time.fps || '--', this.camera.width-50, 18, "#00ff00");};function diePlayer(body1, body2) {	console.log('die!');};function resize() {	game.scale.setScreenSize();	game.scale.refresh();};////////////var Enemy = function (game, x, y) {	this.hasCollided = false;	this.started = false;	this.speed = 100;	this.range = 200;	Phaser.Sprite.call(this, game, x, y, 'enemy');	game.physics.p2.enable(this);	this.body.fixedRotation = true;	this.body.motionState = Phaser.Physics.P2.Body.DYNAMIC;	this.body.onEndContact.add(this.endCollided, this); 	game.add.existing(this); 	return this;}; Enemy.prototype = Object.create(Phaser.Sprite.prototype);Enemy.prototype.constructor = Enemy;Enemy.prototype.update = function() {}; Enemy.prototype.collided = function(body1, body2) {	if(!this.hasCollided) {		this.scale.x *= -1;		this.speed *= -1;		this.body.velocity.x = this.speed;		this.hasCollided = true;	}}; Enemy.prototype.endCollided = function(bodyB, shapeA, shapeB) {	this.hasCollided = false;};Enemy.prototype.startMove = function() {	this.started = true;	this.body.velocity.x = this.speed;};////////////var Player = function (game, x, y) {	this.startX = x;	this.startY = y;	Phaser.Sprite.call(this, game, x, y, 'player');	game.physics.p2.enable(this);	this.body.fixedRotation = true;	this.body.motionState = Phaser.Physics.P2.Body.DYNAMIC;	this.events.onKilled.add(this.killed, this);	game.add.existing(this);		return this;};Player.prototype = Object.create(Phaser.Sprite.prototype);Player.prototype.constructor = Player;Player.prototype.update = function() {};Player.prototype.killed = function(obj) {	this.reset(this.startX, this.startY);};

test-game.zip

Share this post


Link to post
Share on other sites

hmmm... thats really difficult..   first thing i noticed is that your background is huuuuge...  it doesn't even load on my nexus 7 2013 tablet..  most mobile browsers will not load something that's bigger than 4096 px  - you definitely should reduce the size or think about a totally different approach..  like flappy birds.. create an almost infinite world by reusing the same sprites and placing them infront of the camera..

 

  the game world is quite big too...  you could reduce the game world to 800x480  or something like that..   but maybe this isn't necessary - without the huge background image i get 60fs on my tablet ..

Share this post


Link to post
Share on other sites

Weird, I tested without the background image sprite (map) and only get a 2-3fps increase. Actually when I remove the collide related lines on 62 and 70 I get 60fps. That is why I originally thought it had to do with the actual collision code. Perhaps it is just because the collision polygons are too big? So the collision calculation takes too long to check?

 

And I assume you meant that the dimension of the image and the bounds it too large.  Just in case I optimized the png files and re-uploaded the zip file.  The background is now 11kb. 

 

The game size (1024x640) was set based on to my first attempt of using tilemaps.  The map is based on a 32x32 tile system and had dimensions of 1024x4608. I am unsure how to scale that down to fit a small game size but I have noticed if I reduce the game size the fps increases.  Does anybody have an example of how to reduce the scale of the sprite objects in this test game to fit in a smaller game size? Or do I have to adjust all my all my images and position logic to fit a smaller game size?

 

As for approaches, I am open to anything.  I just have defined levels with specific layouts but how those are displayed does not really matter to me.  I was planning on doing that flappy bird approach adding levels in front of the camera but maybe I need to be more fine grained at than that.

 

This is my first game, and first use of Phaser, so it is definitely a learning experience for me.

 

Thanks for the help.

Share this post


Link to post
Share on other sites

I created a similar game for the last Ludum Dare. And I had no problem running +200 asteroids with arcade physics. Phaser has an annoying feature for scrolling games https://github.com/photonstorm/phaser/issues/1175 To turn it off:

    game.camera.roundPx = false;

Also, you should not use textures that are bigger than 2046, LG G2 did not load 1024x4608 in a default browser. After texture and camera rounding fix, your game maintains a stable 60 fps. I guess I am missing the part that I should test. Should I include more enemies or add a tilemap collisions?

I would probably listen to valueerror and take the flappy birds approach to building levels.

 

Just to be really annoying and nitpicky:

Use player.update, instead of the state.update for player movement changes.

"return this;" does nothing in Constructors called with new;

Share this post


Link to post
Share on other sites

Thanks for taking the time to look at this @UgnisKarolis.  I added the roundPx line and removed the background sprite all together and saw only a 5fps improvement. I'm testing on an iPhone 5.

 

But if I remove line 62:

mapCollisionPolygons.body.collides(enemyCG);

then I get 60fps even with the large background image sprite loaded. Do you think the slowdown has to due with the size of the polygon objects used for the collision detection?

 

What exactly did you do for the texture fix? I just want to see if I can reproduce what you are seeing.

 

And no worries about being nitpicky, this test code is just a quick demo of the basics of what I have in my game. Also, I was planning to cleanup and refactor a lot of the code into the proper classes once I got the base logic working.

 

Thanks again for your help.

Share this post


Link to post
Share on other sites

I finally freed up some time to work on my game again.  I have resolved my performance issues by taking the suggestions given to me earlier.  I am doing a flappy bird approach where I have 2 'land' objects that I am vertically scrolling.  Once one tile leaves the camera viewable area I reset its values and move it to the top.  The object consists of a sprite which is home to an image and some polygon data for collisions. I sliced up the background so each 'land' image is 1024x1152 and I create the polygon json data with a slick little tool called PhysicsEditor

 

Now I am on to all the quirks that come with trying to run a game with cocoonjs in canvas+. Thanks everyone for the comments and help.

 

Happy coding!

Share this post


Link to post
Share on other sites

hey joshgerdes, I have similar issues using a polygon physics map and p2 with phaser.

 

My problem is I wanted to create a small map, and a really small player sprite, and have the camera zoom in so it seems like it's a lot bigger.

Trouble is Phaser Cameras, unlike all other cameras on the planet, cannot zoom.  So I had to try a new approach where the physics data

contains much larger (bigger distances) shapes.

 

I'm also using PhysicsEditor for the map's physics data json.

 

I am going to try to only apply the physics data for the immediate area around the player, and have the engine update the map's physics data as the player scrolls around the map.. is that the same thing you did with success?

Share this post


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.