in
Javascript
Let's make a video game
In the year of the rat 1972, atari released one of the earliest arcade games. The tennis-themed Pong.
The structure for this project is
- an entrypoint, app.js
- a ball, ball.js
- two paddles, paddle.js
- the game, pong.js
The ball
The ball needs to tell us the position, if it hits a given coorinate. We need an api for changing the direction and angle.
// ball.js
export default class Ball
{
constructor(left, top, width, speed = 14, color = 'white')
{
this.left = left;
this.top = top;
this.speed = speed;
this.angle = 0;
this.direction = 'left';
this.canvasWidth = width;
this.color = color;
}
draw(context)
{
if (this.direction === 'left') {
this.left -= this.speed;
} else {
this.left += this.speed;
}
this.top += this.angle;
context.beginPath();
context.arc(this.left, this.top, 20, 0, 2 * Math.PI);
context.fillStyle = this.color;
context.fill();
}
hits(paddle) {
return (
(this.xStart() <= paddle.xEnd() && this.xStart() >= paddle.xStart())
|| (this.xEnd() >= paddle.xStart() && this.xEnd() <= paddle.xEnd())
) && (
(this.yStart() >= paddle.yStart() && this.yStart() <= paddle.yEnd())
|| (this.yEnd() <= paddle.yEnd() && this.yEnd() >= paddle.yStart())
);
}
changeDirection() {
if (this.direction === 'left') {
this.direction = 'right';
} else {
this.direction = 'left';
}
}
changeAngle(angle = 0) {
this.angle = angle;
}
respawn() {
this.changeDirection();
this.changeAngle(0);
this.left = this.canvasWidth / 2;
}
yStart() {
return this.top - 20;
}
yEnd() {
return this.top + 20;
}
xStart() {
return this.left - 20;
}
xEnd() {
return this.left + 20;
}
}
The paddle
We need to be able to move the paddle and know if it hit's the edges of our game.
// paddle.js
export default class Paddle
{
constructor(left, top, width = 20, height = 100, speed = 1, canvasHeight, color = 'white')
{
this.left = left;
this.top = top;
this.speed = speed;
this.width = width;
this.height = height;
this.moving = null;
this.canvasHeight = canvasHeight;
this.color = color;
}
draw(context)
{
if (this.moving === 'up') {
this.moveUp()
} else if (this.moving === 'down') {
this.moveDown(context.canvas.height)
}
context.beginPath();
context.rect(this.left, this.top - (this.height / 2), this.width, this.height);
context.fillStyle = this.color;
context.fill();
}
moveUp()
{
this.top -= this.speed;
if (this.isOverCanvas()) {
this.moveToStart();
}
}
moveDown()
{
this.top += this.speed;
if (this.isBelowCanvas()) {
this.moveToEnd();
}
}
angle(top) {
let center = this.top + (this.height / 2);
return ((center - top) / (this.height / 2)) * 2;
}
moveY(position) {
let center = this.top + (this.height / 2);
if (position > center) {
this.top += this.speed;
} else if (position < center) {
this.top -= this.speed;
}
if (this.isOverCanvas()) {
this.moveToStart();
} else if (this.isBelowCanvas()) {
this.moveToEnd();
}
}
isBelowCanvas() {
return this.top > this.canvasHeight - (this.height / 2);
}
moveToEnd() {
this.top = this.canvasHeight - (this.height / 2);
}
isOverCanvas() {
return (this.top - (this.height / 2)) < 0;
}
moveToStart() {
this.top = this.height / 2;
}
yStart() {
return this.top - (this.height / 2);
}
yEnd() {
return this.top + (this.height / 2);
}
xStart() {
return this.left;
}
xEnd() {
return this.left + this.width;
}
}
The game
The game is responsible for all the game calculations, spawning and moving the ball and paddles, keeping track of the score and the state of the game.
// pong.js
import Ball from "./ball";
import Paddle from "./paddle";
export class Pong
{
constructor(context)
{
this.score = {
user: 0,
computer: 0
}
this.paused = false;
this.context = context;
this.ball = new Ball(
this.context.canvas.width / 2,
this.context.canvas.height / 2,
this.context.canvas.width,
12,
'#fff'
);
this.paddle = new Paddle(
10,
this.context.canvas.height / 2,
20,
100,
6,
this.context.canvas.height,
'#e77936'
);
this.computer = new Paddle(
this.context.canvas.width - 30,
this.context.canvas.height / 2,
20,
100,
1,
this.context.canvas.height,
'#e77936'
);
addEventListener('keydown', e => {
if (e.key === 'ArrowUp') {
this.paddle.moving = 'up';
} else if (e.key === 'ArrowDown') {
this.paddle.moving = 'down';
} else if (e.key === 'Escape') {
if (this.paused) {
this.paused = false;
this.run();
} else {
this.paused = true;
// Set pause screen
// Set speed
// Set theme
// Save game
// Set computer speed
// Scoreboard
}
}
});
addEventListener('keyup', e => {
if (e.key === 'ArrowUp') {
this.paddle.moving = null;
} else if (e.key === 'ArrowDown') {
this.paddle.moving = null;
}
});
window.requestAnimationFrame(() => this.run());
}
run()
{
if (this.paused) {
return;
}
this.computer.moveY(this.ball.top);
if (this.ball.hits(this.paddle)) {
this.ball.changeDirection();
this.ball.changeAngle(-this.paddle.angle(this.ball.top));
} else if (this.ball.hits(this.computer)) {
this.ball.changeDirection();
this.ball.changeAngle(-this.computer.angle(this.ball.top));
}
if (this.ball.top <= 0) {
this.ball.angle -= this.ball.angle * 2;
} else if (this.ball.top >= this.context.canvas.height) {
this.ball.angle -= this.ball.angle * 2;
}
// is out to the left of canvas
if (this.isRightOfCanvas(this.ball.xStart())) {
this.score.user++;
this.ball.respawn();
} else if (this.isLeftOfCanvas(this.ball.xEnd())) {
this.score.computer++;
this.ball.respawn();
}
this.context.clearRect(
0, 0, this.context.canvas.width, this.context.canvas.height
);
this.paddle.draw(this.context);
this.ball.draw(this.context);
this.computer.draw(this.context);
// Draw score
// this.context.fontSize = '200px';
this.context.font = '40px Arial';
this.context.fillStyle = 'rgba(255, 255, 255, .4)';
this.context.fillText(
`${this.score.user} - ${this.score.computer}`,
(this.context.canvas.width / 2) - 60,
40
);
window.requestAnimationFrame(() => this.run());
}
isLeftOfCanvas(x) {
return x <= 0;
}
isRightOfCanvas(x) {
return x >= this.context.canvas.width;
}
}
The entrypoint
This is where we start our game.
// app.js
import {Pong} from "./pong";
new Pong(document.getElementById('game').getContext('2d'));
Have fun
Related articles
Writing alpine.js syntax that validates
Alpine.js is a fantastic javascript framework for performing simple tasks. By default it is not valid HTML syntax. In this post we take a look on how to make it valid.
Read more
Saving a html canvas element to your server
Saving user-generated content from an HTML canvas element to a PHP backend can be a useful way to store and process canvas-based graphics and images.
Read more