Circle Status

Built with Phaser. Packaged with Cordova.

Background

Circle Status is an endless 2-dimensional "eating" game, inspired by the popular Fishy games. The goal of the game is to grow by collecting shapes with the same number of sides as you and avoiding those with a different number of sides.

We wanted to give the game some depth, so we based it on Edwin Abbott's Flatland universe. This is a favourite book of ours about a square living in a 2-dimensional plane who gets a visit from a 3-dimensional sphere. In Flatland, social status is determined by the number of sides on your body - the higher the number, the more power and prestige you have. Shapes with a lot of sides, who resembled circles, were at the top of the social ladder.

And that's where we come to our game, Circle Status! Starting out as a lowly triangle, you must eat shapes that look like you and climb the social ranks until you transform into a circle.

Circle Status is an HTML5 game, developed using Phaser 3 and packaged into a mobile game using Cordova. It is currently out on Google Play and the App Store. This blog will go over our learnings and present the different iterations we went through while developing this game.

Creating the polygons

The first thing we needed to figure out was a quick and consistent way to generate all of our polygons. Phaser has a Polygon game object, but we wanted to have more control over the look of the shapes. We ended up using Phaser's Graphics game object instead, which allows us to create objects based off of HTML canvas drawings. In order to draw the different polygons on the canvas, we used the following equation:

To unpack what this means: for a polygon with n sides, we can generate all of its vertices by plugging i = 0, i = 1, up to i = n - 1 into the equation above. Once we have all of the vertices, we simply connect the adjacent ones with a straight line.

You can visualize the equation in the following way. We know that a circle has 2π radians (or 360°). Based on that, we can say that each side of the polygon takes up 2π/n radians (or 360/n °) of the entire circle. If you start at 0 radians and move around the circle 2π/n radians at a time, you will trace out the polygon.

You can find a working example of the code here.

First iteration

This is what the first working version of the game looked like. Only the basic funtionality was setup. We had shapes randomly generating on the screen and are able to handle interactions between them using Phaser's Matter Collision methods. At this point, all we're doing is having the game reset when a user hits a 'bad' shape, and gradually increasing the opacity when a user hits a 'good' shape. When the user hits enough 'good' shapes, it levels up to the next polygon.

One of the most important parts we had to figure out was how the player's movement would work. We knew we wanted the player to be fixed in the centre of the screen with an endless playing field. To achieve this, we chose to have the player stay still and make the enemies and the background move in the opposite direction of the player.

We also wanted to make this game optimized for both mobile and computer use, so we setup two forms of movement. You can use the arrow keys on your computer or drag your mouse (or finger on a mobile screen).

Movement

Below are code samples of how we handled the dragging movement behaviour. We start by storing the coordinates from the pointerdown event. Then, depending on the direction and how far they drag their finger from that point during the pointermove event, we set a new direction and new speed to the background and enemy shapes.

function handleMovementFromPointer(\$this) {
moveArea.on('pointerdown', event => {
origin = {x: event.x, y: event.y};

\$this.input.on('pointermove', event => {
currentPosition = {x: event.x, y: event.y};

speed = {
x: currentPosition.x - origin.x,
y: currentPosition.y - origin.y
};
});
}, \$this);
}

function moveBackgroundAndEnemies() {
background.tilePositionX += speed.x;
background.tilePositionY += speed.y;

enemyGroup.children.iterate(enemy => {
enemy.x -= speed.x;
enemy.y -= speed.y;
});
}

Second iteration

One of the highlights from going from Phaser 2 to Phaser 3 was having the Matter physics engine integrated into Phaser. It allows customization of the physical properties on each of your objects, such as setting the friction, gravity, force, or velocity. We had a lot of fun playing around with it and started thinking of ways we could really utilize it for future games.

Because the player's body had physics, it would bounce off other shapes when colliding with them. In the previous iteration, we were resetting the player's position back to the centre after every collision. This started to get messy, so we ended up setting the player's body to Static, turning off its physical properties. This fixed any buggy behaviour, but it also made collisions less exciting, since the player lost its angular velocity.

Other changes in this iteration include changing the background image, trying out new shape colours, and making the enemies move in both the x and y directions.

Third iteration

Every iteration of our game would go through user testing with our friends. Some feedback we got from the last one was that changing the opacity wasn't enough to know how many more shapes you needed to collect before leveling up. We removed the changing opacity and added capsules at the bottom instead.

We also gave the player and enemies a rotation. The player rotates to the direction they're traveling in, which made a previously static shape feel a lot more 'alive'. Because the player has a static body, we accomplished the rotatation by giving it a Tween. We had to be careful when doing this because the player can rotate counter-clockwise or clockwise to get to the same point. We had to make sure it always chose the shortest direction.

Final version

This is what the current version of the game looks like. We've added a lot of UI elements to it, such as highscore, pause button, and power-ups. Not shown in this gif are all the other screens we created, such as the main menu, game over screen, achievements page, leader boards, and tutorial.

We also wanted to put more emphasis on the story we were telling, so we gave each level a different setting. You start off in the 'Triangle Slums', which has a grimey, dirty background and as you progress to the other levels, you'll notice the background changing and looking more refined.

You'll also notice now there are blue sparks that appear after every collision. We did this by using Phaser's ParticleEmitter.

Particle emitter

Below is what the creation of our particle emitter looks like.

function createEmitter(\$this) {

particles.createEmitter({
angle: { min: 0, max: 360, steps: 100 },
speed: { min: 150, max: 650 },
quantity: 200,
lifespan: 8000,
rotate: { min: 0, max: 360 },
alpha: { start: 1, end: 0 },
scale: { start: 1, end: 0.25 },
on: false,
});

return particles;
}

When we want to emit particles at certain a location, we call the function below.

function emitParticle(particles, position) {
particles.emitParticleAt(position.x, position.y);
}

Power-ups

We added several power-ups to the game and each came with animations we wanted to create for them. Even something seemingly simple like this electricity animation below took some time to do with our limited graphic design knowledge.

This animation was done using Phaser's AnimationManager, which takes an image with a certain number of frames and plays through each of those frames.

Packaging for release

We used Cordova to package this HTML5 game into a mobile app for iOS and Android. This is usually the part where things start breaking and we start encountering performance issues and need to refactor our code.

A surprising issue we encountered is the severe performance issues web audio was causing our game on iOS. We switched over to using native audio instead with the cordova-plugin-media plugin, which fixed the majority of our issues instantly. All of the music in our game was made by a friend of ours, Scott Teglasi. You can check out some of his other pieces on his SoundCloud here.

Another plugin we used is the cordova-plugin-ionic-webview plugin, which changes the app's webview to use the new WKWebView instead of the UIWebView. The WKWebView comes with some performance boosts and will also soon be required for releasing hybrid apps on the App Store.

We also used the cordova-plugin-purchase plugin for in-app purchases and the cordova-admob-plus plugin for AdMob ads. This AdMob plugin was the only one we found that doens't redirect a percentage of your ad traffic to the developer's AdMob account.

That's it!

Thanks for taking the time to read this and if you have any questions or feedback on the game, feel free to send us an email at support@anaphylacticapps.com. We make these games in our spare time and they're only a hobby, but hopefully there will be more to come in the future.

Published on March 7, 2020

Made by Teba Ibrahim and Greg Rychlewski — Two Toronto-based software devs.

Our other games:

Where's Balldo?

Four Factors