I’ve been using PhaserJS for the development of The Botanist. Phaser has been generally great to work with, is rapid to develop on top of, and has excellent performance on desktop. Phaser is also a mobile-conscious framework, and I do often get 60FPS on my Nexus 5’s Chrome browser.

One major weakness of Phaser’s mobile performance, though, is its default camera following mode. The camera follow mode pans the camera smoothly to follow the character, which is beautiful visually but also forces a full redraw on every frame. This works fine on desktop, but mobile browsers tend to struggle, especially given patchy support for WebGL.

My solution is to write a more efficient following mode for mobile devices. Rather than redrawing on every frame with smooth scrolling, we should only redraw once if the player is at the edge of the screen.

I tend to avoid premature optimization, but The Botanist is ultimately intended for mobile and this seems like a low hanging fruit to grab!

First you’ll need to decide exactly how you’d like the camera to follow the player. Legend of Zelda, for instance, uses a purely orthogonal camera pan (only left/right, up/down), and only pans when the player exits the screen. That’s a great mode especially for Zelda, where the world is meticulously designed and moving from screen to screen is part of the game play itself. The Botanist, however, already uses a smoothly panning camera on desktop, so I’d like the mobile camera mode to be flexible.

In particular, I’d like to pan the camera when the player gets close to the edge rather than off-screen, and I’d also like the camera to pan in the direction of the player’s movement. The camera should pan such that the player ends up at the diagonal edge of the screen after the pan.

Let’s illustrate the intention. Before:

Before Camera Pan

After:

After Camera Pan

This is pretty easy to do mathematically with vectors! We want to find the vector from the player to the center of the screen, and multiply that vector by something slightly less than 2 and pan the camera by that vector in the opposite direction.

Now that we know the math, the implementation is easy. We’ll do some setup in the scene’s create method to define the deadzone and to set the camera mode based on device.

The deadzone implementation is inspired by Phaser’s own, except I define a deadzone with a constant-width border of 100 pixels rather than the percentage approach that Phaser takes.

var edge = 100;
this.cameraDeadzone = new Phaser.Rectangle(edge, edge, this.game.camera.width - (edge * 2), this.game.camera.height - (edge * 2));
this.game.camera.focusOn(this.player);

if (this.game.device.desktop) {
	// Only autofollow if we're on desktop.
	this.game.camera.follow(this.player, Phaser.Camera.FOLLOW_TOPDOWN_TIGHT)
}

And finally, this code in the update method checks the device and runs our efficient algorithm if we’re on a mobile device.

The camera object’s x and y coordinates correspond to the top-left corner of the screen, so we need to do some work to find the center of the screen. I did this manually, but you could also use Phaser’s rectangle class.

Once we have the screen’s center point, we can use Phaser.Point to subtract the two points and find the vector between the player and the screen center. After that, we can just update the camera coordinates by multiplying the components of the vector by 1.8. This leaves the player slightly farther from the edge after the pan, but avoids panning back and forth too quickly if the player is in motion (the player is farther inside the deadzone).

if (!this.game.device.desktop) {
	var cam = this.game.camera;
	var player = this.player;

	var hEdge = player.x - cam.x;
	var vEdge = player.y - cam.y;

	if (hEdge < this.cameraDeadzone.left || hEdge > this.cameraDeadzone.right || vEdge < this.cameraDeadzone.top || vEdge > this.cameraDeadzone.bottom) {
		var camCenter = { x: cam.x + (cam.width / 2), y: cam.y + (cam.height / 2) };
		var diff = Phaser.Point.subtract(player, camCenter);
		cam.x += diff.x * 1.8;
		cam.y += diff.y * 1.8;
	}
}

The end result? The camera pans once only, places the player on the opposite corner of the screen, and works great on mobile because it only causes one full redraw.

The performance gain is pretty huge:

  • Desktop shows no difference and runs smoothly at 60 FPS on both modes.
  • My Nexus 5 used to run between 30-60 FPS, now it runs at 55-60 FPS.
  • My 2012 Nexus 7 used to be unplayable at 1 FPS, now it runs reasonably at 30-45 FPS.

Here’s a special build snapshot that forces the camera mode to mobile on all devices, so you can check it out from your desktop.