The third weekend of January was the coldest that Poland has been all winter, so far. Temperatures in Poznań hit -11°C during the day. In such conditions, the best way to spend free time was to stay at home and enjoy the advantages of a comfortable and warm bed ;). But this weekend was special for the game makers from all over the world. The Global Game Jam was taking place and I couldn't miss it!
Poznań Global Game Jam edition took place in Politechnika Poznańska. Over forty enthusiastic participants had 48 hours to make glorious games. The main theme of this year's jam, was 'We don't see things as they are, we see them as we are'. We found this out a few hours after we already started implementing our ideas, so the better solution was to focus on delivering what we had already started.
My team (me & @lukaszwojciak) decided to learn something new and related to circle geometry, so we got the idea to implement a game called Planet Defender, which is somehow similar to the classic Space Invaders, but with a planet behind the main character, so he/she can protect it. We had some problems with defining the main character, but at the end we came to the conclusion, that the easiest to implement and animate would be a simple pyramid, which would imitate the Egyptian God Ra.
Similar to Purrfect, we used Spine and Pixi.js to animate and render graphic assets. In this article I would like to focus mainly on circle geometry and all of the mathematics which is necessary to understand the rotation of the planet and collision between the planet and meteors.
Rotation of the planet
Lets start with revision of standard equations of the circle. Let me help myself with a nice and clear explanation form the Wikipedia. In a Cortesian coordinate system circle is a set of all points (x,y) such that
(x-a)2 + (y-b)2 = r2
where (a,b) are the center coordinates and r is a radius.
The same equation can be written using the trigonometric functions as:
x = a + r cos t,
y = b + r sin t
where t is a variable in the range 0 to 2π, interpreted geometrically as the angle that the ray from (a, b) to (x, y) makes with the x-axis.
Circle equations
Knowing this, we can now move to the aspect of rendering and rotating our planet. Similar to the Purrfect game, we used Pixi.js to help us render assets on canvas
element.
First we need to create a container, which will represent a collection of displayed objects. Pixi.js implemented a helpful object called DisplayObjectContainer(), which allows us to simply add, remove and manage other objects which it contains.
camera = new PIXI.DisplayObjectContainer();
//place x position in the center of x axis
camera.position.x = window.innerWidth / 2;
//place the camera a little lower on the y axis so we can see only the top of the planet
camera.position.y = window.innerHeight + 250;
//at the beginning camera is not moving
camera.velocity = 0;
After creating a container, we set its x and y coordinates to fit the position of our planet. Speaking of the planet, we can easily render it as followed:
var texture = PIXI.Texture.fromImage('planet.jpg'),
sprite = new PIXI.Sprite(texture);
//position related to the camera container
sprite.position.x = 0;
sprite.position.y = 0;
//anchor will be needed to calculate the axis of rotation
sprite.anchor.x = 0.5;
sprite.anchor.y = 0.5;
//radius will be very important to detect the collision
sprite.radius = 400;
//render sprite and add it to the camera container at a specific index
camera.addChildAt(sprite, 0);
The planet is attached to the camera, so its position relates to that aforementioned container. Now we want to add a rotation of the planet in such a way, that its direction will be dependent on the player's actions.
window.addEventListener('keydown', function (e) {
var key = e.keyCode;
if (key === 37) {
camera.velocity = 0.02;
player.walkLeft();
}
if (key === 39) {
camera.velocity = -0.02;
player.walkRight();
}
}
//in a game loop
camera.rotation += camera.velocity;
After detecting which key has been pressed, the velocity of the camera is being updated. In a main game loop, rotation of the camera will be changed according to the new velocity.
For the sake of simplicity, I don't want to describe in detail how the player object works. In general it's just a Spine object, with walkLeft
, walkRight
, idle
and fire
animation states.
The movement of meteors and collision with the planet
At the beginning of the game, we created only one meteor (enemy) which will start its movement from the position directly above the player.
//Create a Spine animation
var spine = new PIXI.Spine('rock/skeleton.json');
//position related to the camera container
spine.position.x = 0;
spine.position.y = 0;
spine.rotation = 0;
//this will be needed to calculate the pivot value
spine.radius = 400;
//pivot will be needed to detect the collision with the planet
//this is a distance from the center of the planet
spine.pivot.y = spine.radius + window.innerHeight / 2 + Math.random() * 400;
//render sprite and add it to the camera container
camera.addChild(spine);
//multiplier value will be needed to calculate the next meteor's rotation
var multiplier = 1;
After destroying the first meteor, a few more are being created but with slightly different pivot and rotation values.
//after destroying a meteor
multiplier += 0.1;
//each of the incoming meteors will have different pivot and rotation values
spine.pivot.y = spine.radius + window.innerHeight / 2 + Math.random() * 400;
spine.rotation = Math.random() * ((Math.PI / 16) * multiplier)
- ((Math.PI / 16) * multiplier / 2);
When the player rotates the planet, all of the existing meteors have to update their positions.
//meteor is an enemy here
enemy.position.y += enemy.velocity * Math.cos(enemy.rotation);
enemy.position.x -= enemy.velocity * Math.sin(enemy.rotation);
//update the velocity so each meteor goes faster when it is nearer to the player
enemy.velocity *= 1.008 - (0.003 * enemy.rotation) / Math.PI;
As you can see, there is no magic here. We just used commonly known trigonometric functions of the equations of the circle. When the planet will rotate, all of the meteors will change their positions according to the rotation of the planet. Their velocities also change a little, so they go faster when they are closer to the player.
Now let's focus on detecting collisions between the planet and meteors. To calculate it we will use a Pythagorean theorem. This theorem will help us calculate the needed distance between the meteor and the center of the planet.
var enemyDistanceFromCenter = this.pivot.y - Math.sqrt(this.position.y * this.position.y
+ this.position.x * this.position.x);
if (enemyDistanceFromCenter < radius + 30) {
//collision detected
}
It is apparent in the situation, when the meteor is straight above the player, collision detection is very easy. All we need to do is calculate a difference between a starting point and an actual position of the meteor. Calculated distance just needs to be compared with the planet's radius, which needs to be extended by the radius of single meteor. The only impediment in the situation where there is an added rotation, is that to calculate distance, we need to help ourselves with the mentioned Pythagorean theorem.
Bullets and collision with meteors
To create a bullet we will use almost the same technique as with the meteors. The only difference is that weapons will be fired always in the same position and its initial rotation will depend on the camera's rotation. A meteor's position was related to the camera, bullets have to follow that camera and change their rotation in the opposite direction of that container.
//when the user presses space bar
var texture = PIXI.Texture.fromImage('bullet.png'),
sprite = new PIXI.Sprite(texture);
//position related to the camera container
sprite.position.x = 0;
sprite.position.y = 0;
//anchor will be needed to calculate the axis of rotation
sprite.anchor.x = 0.5;
sprite.anchor.y = 0.5;
//radius will be very important to detect the collision
sprite.radius = 400;
//each bullet has to follow the camera and rotate in the complete opposite direction
sprite.rotation = -camera.rotation;
sprite.pivot.y = sprite.radius + 80;
//render sprite and add it to the camera container at a specific index
camera.addChildAt(sprite, 2);
When we rotate the planet, positions of the bullets will also be changed:
bullet.position.y -= velocity * Math.cos(bullet.rotation);
bullet.position.x += velocity * Math.sin(bullet.rotation);
Calculating collision between the weapons and meteors is rather similar to the previous example. First, we have to ensure that the bullet and the meteor have the same angle:
var bulletRotation = Math.sin(bullet.rotation),
enemyRotation = Math.sin(enemy.rotation);
if ((bulletRotation > enemyRotation - 0.05 && bulletRotation < enemyRotation + 0.05) {
//compute the distance between the meteor and the bullet
}
The distance between the meteor and the bullet should be calculated as follows:
var bulletDistance = Math.abs(Math.sqrt(bullet.position.y * bullet.position.y
+ bullet.position.x * bullet.position.x)),
enemyDistanceFromCenter = enemy.pivot.y - Math.sqrt(enemy.position.y
* enemy.position.y + enemy.position.x * enemy.position.x),
bulletDistanceFromCenter = bulletDistance + planet.radius + bullet.height;
if (bulletDistanceFromCenter - enemyDistanceFromCenter < 0
&& bulletDistanceFromCenter - enemyDistanceFromCenter > -bullet.height) {
//collision detected
}
Collision with the bullet
We used Pythagorean theorem once again to calculate the positions of the bullet and meteor. When we know those values, checking the distance between those two objects is just a matter of proper subtraction and comparison with the bullet's height.
Conclusion
Learning and coding Planet Defender game was a real joy! We managed to calculate and detect all of the necessary values without any game framework and physics engine. It looked a little scary at the beginning, but with a little math and ubiquitously known theorems, it wasn't that difficult.
It was a really enjoyable and profitable weekend. This was my second hackathon in which I participated but this time, I spent much more time sleeping and I don't regret that ;). Exhaustion can heavily slow down our thinking process. It is very important to, especially during the events like this, spend some time on mental regeneration.
Among eleven completed games, there were only two made in html5 and JavaScript. The Planet Defender won second prize and I happily returned home with my new 'Angry Birds' mascot :). The only think I regret form that weekend, is that I missed a visitation of a local TV station. What I've seen during the news clip, contestants had some real fun during that time ;).
If you are interested in our messy code, here is the github repo. Feel free to comment on or contribute to our work. Any helpful action will be appreciated.