Welcome back for another day of adventure! Today we will be adding a paddle to our game so we can smack the ball around more. We will also implement resetting the ball if it falls below the paddle. If you don't remember our last adventure (Adventures in TypeScript 02), we got the ball on the screen and bounce it around in the canvas.
You can see the final outcome of the Breakout clone here. As well as see all the code in my GitHub repo.
In our last adventure, we created a nice interface for all of our objects to implement called iEntity. Our paddle class will also implement this interface. Let's start a basic class that implements the interface.
// class for the paddle entity (aka, the player)
class cPaddle implements iEntity {
public x: number = 0;
public y: number = 0;
public velX: number = 0;
public velY: number = 0;
public draw = (): void => {
}
public update = (): void => {
}
}
Like the ball, our paddle will need more properties. We should know what color to draw it, as well as how big. Also, since this is being controlled by the player, we should have booleans to know whether we are moving it left or right. Also, let's add a nice constructor with the basic items we need.
public width: number = 0;
public height: number = 0;
public color: string = "white";
public strokeColor: string = "#363636";
public moveRight: boolean = false;
public moveLeft: boolean = false;
constructor(x: number, y: number, width: number, height: number, color: string = "white")
{
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.color = color;
}
Now that we have most of the properties ready, let's flesh out the draw function. I added a strokeColor so we have a nice border around the paddle, to give it some flair. We will be filling in a rectangle, then adding a small strokeRect around it.
public draw = (): void => {
// draw the nice little paddle!
ctx.save();
ctx.beginPath();
ctx.rect(this.x, this.y, this.width, this.height);
ctx.fillStyle = this.color;
ctx.fill();
ctx.strokeStyle = this.strokeColor;
ctx.lineWidth = 1;
ctx.strokeRect(this.x,this.y,this.width, this.height);
ctx.closePath();
}
Ok, the paddle will draw a nice little rectangle when we call the draw function. Now let's cover the update function. We added a moveLeft and moveRight properties to our cPaddle class. If we press the left or right arrow, we want to set the corresponding property to true, and when we let go of the left or right arrow, we want to set them to false. For now, we are just going to flesh out the update function, and we will set the moveLeft and moveRight in a different area of the code.
public update = (): void => {
// are we moving right or left?
if(this.moveRight) {
this.x += this.velX;
}else if(this.moveLeft) {
this.x -= this.velX;
}
// make sure the user can't go off the X axis bounds.
if(this.x > canvas.width - this.width) {
this.x = canvas.width - this.width;
}
if(this.x < 0) {
this.x = 0;
}
}
Now let's compile and run. We still don't see our paddle. Whoops, we need to declare a variable like we did for our ball, and initialize a new paddle. After we do that, we should probably add to the gameLoop function to call the paddle's update and draw functions. Let's do that.
var paddle: cPaddle = new cPaddle(215, 300, 50, 10, "gray");
function gameLoop()
{
// lets keep the game loop going!
requestAnimationFrame(gameLoop);
// fill to black!
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ball.update();
ball.draw();
paddle.update();
paddle.draw();
}
Now let's compile and run again. We see our paddle now on the canvas! But wait, what's this? We can't move it. We need to hook into the keyUp and keyDown events and set the paddle's moveLeft/moveRight properties. And we don't want to forget to give the paddle a velocity.
function keyDownHandler(e: KeyboardEvent) {
// right arrow == 39
// left arrow == 37
if(e.keyCode == 39) {
paddle.moveRight = true;
}else if(e.keyCode == 37) {
paddle.moveLeft = true;
}
}
function keyUpHandler(e: KeyboardEvent) {
if(e.keyCode == 39) {
paddle.moveRight = false;
}else if(e.keyCode == 37) {
paddle.moveLeft = false;
}
}
window.onload = () => {
// grab our canvas, and get a 2d context
canvas = <HTMLCanvasElement>document.getElementById('gameCanvas');
ctx = canvas.getContext("2d");
// let's handle the user input
document.addEventListener('keydown', keyDownHandler);
document.addEventListener('keyup', keyUpHandler);
ball.velX = 3;
ball.velY = 3;
paddle.velX = 5;
// run the game!
gameLoop();
}
Now that is looking better. We can now move the paddle left and right. There is one thing left to do and that is to check for collision with the ball and paddle. Right now the ball will just go thru the paddle and keep on bouncing. Let's change the ball's update function to check for collision with the paddle as well as the bottom of the canvas. We want to reset the ball if it hits the bottom of the canvas, and later in the series, we will setup a lives system to keep track to know when it is game over.
public update = (): void => {
// update the balls position
this.x += this.velX;
this.y += this.velY;
// did we hit the left or right bounds?
// if so, lets bounce back the opposite direction.
if(this.x > canvas.width - this.radius || this.x < this.radius) {
this.velX = -this.velX;
}
// did we hit the ceiling?
// if so, let's bounce back down.
if(this.y < this.radius) {
this.velY = -this.velY;
}else if(this.y > paddle.y - this.radius) {
// check to see if the ball is near the height of the paddle
// if so, let's check to see if it is in the same area on the X axis as the paddle,
// and check to see if the ball is still above the paddle.
if(this.y < paddle.y && this.x + this.radius > paddle.x && this.x - this.radius < paddle.x + paddle.width) {
// bounce the ball back up!
// if already going down (if hits paddle at odd angle, and gets stuck, we just want it to go up,
// and not bounce back n forth until out of paddle.)
if(this.velY > 0)
this.velY = -this.velY;
}else if(this.y > canvas.height - this.radius) {
// reset the ball back to start.
this.x = 240;
this.y = 290;
}
}
}
Now when the ball comes in contact with the paddle, it should bounce off of it. If the ball happens to dodge the paddle, it gets reset back to a starting position and continues on. This pretty much wraps up this tutorial, and here is what the game.ts should look like right now.
var canvas: HTMLCanvasElement;
var ctx: CanvasRenderingContext2D;
// base interface for our entities that we want to draw on the screen
// and handle in the game.
interface iEntity {
draw(): void;
update(): void;
x: number;
y: number;
velX: number;
velY: number;
}
// class for the ball entity
class cBall implements iEntity {
public x: number = 0;
public y: number = 0;
public velX: number = 0;
public velY: number = 0;
public radius: number = 0;
public color: string = "white";
constructor(x: number, y: number, radius: number, color: string = "white")
{
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
}
public draw = (): void => {
// draw a nice circle!
ctx.save();
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2);
ctx.fillStyle = this.color;
ctx.fill();
ctx.closePath();
}
public update = (): void => {
// update the balls position
this.x += this.velX;
this.y += this.velY;
// did we hit the left or right bounds?
// if so, lets bounce back the opposite direction.
if(this.x > canvas.width - this.radius || this.x < this.radius) {
this.velX = -this.velX;
}
// did we hit the ceiling?
// if so, let's bounce back down.
if(this.y < this.radius) {
this.velY = -this.velY;
}else if(this.y > paddle.y - this.radius) {
// check to see if the ball is near the height of the paddle
// if so, let's check to see if it is in the same area on the X axis as the paddle,
// and check to see if the ball is still above the paddle.
if(this.y < paddle.y && this.x + this.radius > paddle.x && this.x - this.radius < paddle.x + paddle.width) {
// bounce the ball back up!
// if already going down (if hits paddle at odd angle, and gets stuck, we just want it to go up,
// and not bounce back n forth until out of paddle.)
if(this.velY > 0)
this.velY = -this.velY;
}else if(this.y > canvas.height - this.radius) {
// reset the ball back to start.
this.x = 240;
this.y = 290;
}
}
}
}
// class for the paddle entity (aka, the player)
class cPaddle implements iEntity {
public x: number = 0;
public y: number = 0;
public velX: number = 0;
public velY: number = 0;
public width: number = 0;
public height: number = 0;
public color: string = "white";
public strokeColor: string = "#363636";
public moveRight: boolean = false;
public moveLeft: boolean = false;
constructor(x: number, y: number, width: number, height: number, color: string = "white")
{
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.color = color;
}
public draw = (): void => {
// draw the nice little paddle!
ctx.save();
ctx.beginPath();
ctx.rect(this.x, this.y, this.width, this.height);
ctx.fillStyle = this.color;
ctx.fill();
ctx.strokeStyle = this.strokeColor;
ctx.lineWidth = 1;
ctx.strokeRect(this.x,this.y,this.width, this.height);
ctx.closePath();
}
public update = (): void => {
// are we moving right or left?
if(this.moveRight) {
this.x += this.velX;
}else if(this.moveLeft) {
this.x -= this.velX;
}
// make sure the user can't go off the X axis bounds.
if(this.x > canvas.width - this.width) {
this.x = canvas.width - this.width;
}
if(this.x < 0) {
this.x = 0;
}
}
}
var ball: cBall = new cBall(240, 290, 5);
var paddle: cPaddle = new cPaddle(215, 300, 50, 10, "gray");
function gameLoop()
{
// lets keep the game loop going!
requestAnimationFrame(gameLoop);
// fill to black!
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ball.update();
ball.draw();
paddle.update();
paddle.draw();
}
function keyDownHandler(e: KeyboardEvent) {
// right arrow == 39
// left arrow == 37
if(e.keyCode == 39) {
paddle.moveRight = true;
}else if(e.keyCode == 37) {
paddle.moveLeft = true;
}
}
function keyUpHandler(e: KeyboardEvent) {
if(e.keyCode == 39) {
paddle.moveRight = false;
}else if(e.keyCode == 37) {
paddle.moveLeft = false;
}
}
window.onload = () => {
// grab our canvas, and get a 2d context
canvas = <HTMLCanvasElement>document.getElementById('gameCanvas');
ctx = canvas.getContext("2d");
// let's handle the user input
document.addEventListener('keydown', keyDownHandler);
document.addEventListener('keyup', keyUpHandler);
ball.velX = 3;
ball.velY = 3;
paddle.velX = 5;
// run the game!
gameLoop();
}
In the next tutorial, I will go over creating the bricks that the ball will smash into.