Compare commits
10 Commits
726d97720e
...
master
Author | SHA1 | Date | |
---|---|---|---|
44e93e5c6d | |||
bc3ef31728 | |||
e0ebef2a27 | |||
7cf0bfbdbd | |||
7493713529 | |||
0f4a403edb | |||
b682ad199c | |||
ae92c56957 | |||
135f7d8a41 | |||
c13c752716 |
BIN
assets/imgs/b31.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
assets/imgs/b32.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
assets/imgs/b33.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
assets/imgs/ball.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
assets/imgs/bar.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
assets/imgs/bg01.jpg
Normal file
After Width: | Height: | Size: 360 KiB |
BIN
assets/imgs/breakout_sprites.png
Normal file
After Width: | Height: | Size: 161 KiB |
183
assets/js/Ball.js
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
class Ball {
|
||||||
|
constructor() {
|
||||||
|
this.size = 20;
|
||||||
|
|
||||||
|
|
||||||
|
this.moving = false;
|
||||||
|
this.speed = 7;
|
||||||
|
// this.angle = 90;
|
||||||
|
this.setAngle(180 + 60, 360 - 60);
|
||||||
|
|
||||||
|
this.color = 'red';
|
||||||
|
this.limits = null;
|
||||||
|
|
||||||
|
this.angleTR = this.g2r(360);
|
||||||
|
this.angleBR = this.g2r(90);
|
||||||
|
this.angleBL = this.g2r(180);
|
||||||
|
this.angleTL = this.g2r(270);
|
||||||
|
|
||||||
|
this.img = resources.get('ball');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.moving = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(ctx, x, y) {
|
||||||
|
this.limits ??= {
|
||||||
|
l: 0,
|
||||||
|
t: 0,
|
||||||
|
r: ctx.canvas.width - this.size,
|
||||||
|
b: ctx.canvas.height
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.move(x, y)) {
|
||||||
|
this.draw(ctx);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
draw(ctx) {
|
||||||
|
/*
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI, false);
|
||||||
|
ctx.fillStyle = this.color;
|
||||||
|
ctx.fill();
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.strokeStyle = '#003300';
|
||||||
|
ctx.stroke();
|
||||||
|
*/
|
||||||
|
// ctx.drawImage(this.img,0,0,this.img.width,this.img.height,this.x,this.y,20,20);
|
||||||
|
this.drawImage(ctx, this.img, this.x, this.y, this.size,this.size, this.angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
drawImage(ctx, image, x, y, w,h, rotation){
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(x+w/2, y+h/2);
|
||||||
|
ctx.rotate(rotation);
|
||||||
|
ctx.translate(-x-w/2, -y-h/2);
|
||||||
|
ctx.drawImage(image, x, y, w, h);
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
move(x, y) {
|
||||||
|
if (this.moving) {
|
||||||
|
this.x += this.speed * Math.cos(this.angle);
|
||||||
|
this.y += this.speed * Math.sin(this.angle);
|
||||||
|
|
||||||
|
// Escaped from the pad
|
||||||
|
if (this.y > this.limits.b) {
|
||||||
|
this.moving = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.collideWalls(this.limits.l, this.limits.t, this.limits.r, this.limits.b);
|
||||||
|
} else {
|
||||||
|
this.x = x - 10;
|
||||||
|
// this.y = y - this.size - 1;
|
||||||
|
this.y = y - 20;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bounceL(r) {
|
||||||
|
if (this.angle <= this.angleBL)
|
||||||
|
this.setAngle(0 + r, 90 - r);
|
||||||
|
else
|
||||||
|
this.setAngle(270 + r, 360 - r);
|
||||||
|
}
|
||||||
|
bounceR(r) {
|
||||||
|
if (this.angle <= this.angleBR)
|
||||||
|
this.setAngle(90 + r, 180 - r);
|
||||||
|
else
|
||||||
|
this.setAngle(180 + r, 270 - r);
|
||||||
|
}
|
||||||
|
bounceT(r) {
|
||||||
|
if (this.angle <= this.angleTL)
|
||||||
|
this.setAngle(90 + r, 180 - r);
|
||||||
|
else
|
||||||
|
this.setAngle(0 + r, 90 - r);
|
||||||
|
}
|
||||||
|
bounceB(r) {
|
||||||
|
if (this.angle <= this.angleBR)
|
||||||
|
this.setAngle(270 + r, 360 - r);
|
||||||
|
else
|
||||||
|
this.setAngle(180 + r, 270 - r);
|
||||||
|
}
|
||||||
|
|
||||||
|
collideWalls(x0, y0, x1, y1) {
|
||||||
|
let r = 20;
|
||||||
|
if (this.x <= x0) {
|
||||||
|
this.bounceL(r);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this.x >= x1) {
|
||||||
|
this.bounceR(r);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this.y <= y0) {
|
||||||
|
this.bounceT(r);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this.y >= y1) {
|
||||||
|
this.bounceB(r);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
TL 270 TR
|
||||||
|
180 0
|
||||||
|
BL 90 BR
|
||||||
|
|
||||||
|
|
||||||
|
B
|
||||||
|
---------
|
||||||
|
R | | L
|
||||||
|
---------
|
||||||
|
T
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
collide(x0, y0, x1, y1) { // 0 = hit Left/Right, 1 = hit Up/Down
|
||||||
|
let r = 20;
|
||||||
|
if (this.x >= x0 && this.x <= x1) {
|
||||||
|
if ((this.y + this.size) >= y0 && (this.y + this.size) < y1) {
|
||||||
|
this.bounceB(r);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this.y <= y1 && this.y > y0) {
|
||||||
|
this.bounceT(r);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.y >= y0 && this.y <= y1) {
|
||||||
|
if ((this.x + this.size) >= x0 && (this.x + this.size) < x1) {
|
||||||
|
this.bounceR(r);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this.x <= x1 && this.x > x0) {
|
||||||
|
this.bounceL(r);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setAngle(min, max) {
|
||||||
|
this.angle = this.g2r(Math.floor(Math.random() * (max - min)) + min);
|
||||||
|
}
|
||||||
|
|
||||||
|
g2r(deg) {
|
||||||
|
return (((360 + deg) % 360) * Math.PI) / 180.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
r2g(rad) {
|
||||||
|
return rad * 180 / Math.PI;
|
||||||
|
}
|
||||||
|
}
|
60
assets/js/Bar.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
class Bar {
|
||||||
|
constructor(ctx, key) {
|
||||||
|
this.ctx = ctx;
|
||||||
|
this.key = key;
|
||||||
|
|
||||||
|
this.w = 80;
|
||||||
|
this.h = 15;
|
||||||
|
this.speed = 10; // Target Speed
|
||||||
|
this._speed = 0; // Current Speed and direction
|
||||||
|
|
||||||
|
this.xLimit = (ctx.canvas.width - this.w);
|
||||||
|
this.img = resources.get('bar');
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.x = (this.ctx.canvas.width - this.w) / 2;
|
||||||
|
this._y = (this.ctx.canvas.height - this.h * 2);
|
||||||
|
|
||||||
|
this.y = this.ctx.canvas.height + 10;
|
||||||
|
}
|
||||||
|
update() {
|
||||||
|
this.move();
|
||||||
|
this.draw();
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
this._speed = 0;
|
||||||
|
}
|
||||||
|
left() {
|
||||||
|
if (this._speed >= 0) this._speed = -this.speed;
|
||||||
|
this.x += this._speed;
|
||||||
|
if (this.x < 0) this.x = 0;
|
||||||
|
this._speed -= 0.5;
|
||||||
|
}
|
||||||
|
right() {
|
||||||
|
if (this._speed <= 0) this._speed = this.speed;
|
||||||
|
this.x += this._speed;
|
||||||
|
if (this.x > this.xLimit) this.x = this.xLimit;
|
||||||
|
this._speed += 0.5;
|
||||||
|
}
|
||||||
|
move() {
|
||||||
|
if (this.key.isDown('ArrowLeft')) this.left();
|
||||||
|
else
|
||||||
|
if (this.key.isDown('ArrowRight')) this.right();
|
||||||
|
else
|
||||||
|
this.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
draw() {
|
||||||
|
if (this.y != this._y) this.y--;
|
||||||
|
if (this.y < this.ctx.canvas.height) {
|
||||||
|
/*
|
||||||
|
this.ctx.fillStyle = 'black';
|
||||||
|
this.ctx.fillRect(this.x, this.y, this.w, this.h);
|
||||||
|
*/
|
||||||
|
this.ctx.drawImage(this.img,this.x,this.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
59
assets/js/Board.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
class Board {
|
||||||
|
constructor(ctx, key) {
|
||||||
|
this.controls = {};
|
||||||
|
this.key = key;
|
||||||
|
this.stop = false;
|
||||||
|
this.ctx = ctx;
|
||||||
|
this.x = ctx.canvas.width;
|
||||||
|
this.y = ctx.canvas.height / 2 - 48;
|
||||||
|
|
||||||
|
this.w = this.ctx.canvas.width;
|
||||||
|
this.h = this.ctx.canvas.height;
|
||||||
|
|
||||||
|
this.img = resources.get('bg01');
|
||||||
|
}
|
||||||
|
|
||||||
|
run() {
|
||||||
|
let _this = this;
|
||||||
|
this.stop = false;
|
||||||
|
this.key.setKeydown( e => {
|
||||||
|
let code = e.code;
|
||||||
|
for(var key in this.controls) {
|
||||||
|
if(key==code) this.controls[key]();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
_this.resolve = resolve;
|
||||||
|
_this.reject = reject;
|
||||||
|
_this.loop();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
next(nextStage) {
|
||||||
|
this.loopStop();
|
||||||
|
this.resolve(nextStage);
|
||||||
|
}
|
||||||
|
|
||||||
|
loopStop() {
|
||||||
|
this.stop = true;
|
||||||
|
if (this.requestID) {
|
||||||
|
cancelAnimationFrame(this.requestID);
|
||||||
|
this.requestID = null;
|
||||||
|
}
|
||||||
|
this.ctx.clearRect(0, 0, this.w, this.h);
|
||||||
|
}
|
||||||
|
|
||||||
|
loop() {
|
||||||
|
if (this.stop) return;
|
||||||
|
|
||||||
|
// this.ctx.clearRect(0, 0, this.w, this.h);
|
||||||
|
this.ctx.drawImage(this.img, 0, 0);
|
||||||
|
this.update();
|
||||||
|
this.requestID = requestAnimationFrame( ()=>this.loop() );
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
44
assets/js/Bricks.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
class Brick {
|
||||||
|
constructor(type, column, row) {
|
||||||
|
this.type = type;
|
||||||
|
this.row = row;
|
||||||
|
this.column = column;
|
||||||
|
|
||||||
|
this.vspace = 2;
|
||||||
|
this.hspace = 2;
|
||||||
|
|
||||||
|
this.w = (32) - this.hspace;
|
||||||
|
this.h = (32) - this.vspace;
|
||||||
|
this.x = 2 + (this.w + this.hspace) * column;
|
||||||
|
this.y = 80 + (this.h + this.vspace) * row;
|
||||||
|
|
||||||
|
this.img = [];
|
||||||
|
switch (type) {
|
||||||
|
case 2: this.lives = 2; this.img = [ resources.get('b31'),resources.get('b32') ]; break;
|
||||||
|
case 3: this.lives = 3; this.img = [ resources.get('b31'),resources.get('b32'), resources.get('b33') ]; break;
|
||||||
|
default: this.lives = 1; this.img = [ resources.get('b31') ]; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
crack() {
|
||||||
|
this.lives--;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(ctx) {
|
||||||
|
if (this.lives == 0) return false;
|
||||||
|
|
||||||
|
switch (this.lives) {
|
||||||
|
case 1:
|
||||||
|
ctx.drawImage(this.img[0], this.x + 1, this.y);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
ctx.drawImage(this.img[1], this.x + 1, this.y);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
ctx.drawImage(this.img[2], this.x + 1, this.y);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
18
assets/js/GameIntro.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
class GameIntro extends Board {
|
||||||
|
constructor(ctx, key) {
|
||||||
|
super(ctx, key);
|
||||||
|
this.controls = {
|
||||||
|
'Space': ()=>this.next(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
update() {
|
||||||
|
this.centerText('BreakOut', this.y, '48px', 'Consolas', 'Black');
|
||||||
|
this.centerText('JDG', this.y + 50, '24px', 'Consolas', 'Black');
|
||||||
|
}
|
||||||
|
centerText(txt, y, s, f, c) {
|
||||||
|
this.ctx.font = s + ' ' + f;
|
||||||
|
this.ctx.fillStyle = 'Black';
|
||||||
|
let x = (this.ctx.canvas.width - this.ctx.measureText(txt).width) / 2;
|
||||||
|
this.ctx.fillText(txt, x, y);
|
||||||
|
}
|
||||||
|
}
|
25
assets/js/GameOver.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
class GameOver {
|
||||||
|
constructor() {
|
||||||
|
this.w = 240;
|
||||||
|
this.h = 120;
|
||||||
|
|
||||||
|
this.cx = 360 / 2;
|
||||||
|
this.cy = 640 / 2;
|
||||||
|
|
||||||
|
this.x = this.cx - this.w/2;
|
||||||
|
this.y = this.cy - this.h/2;
|
||||||
|
}
|
||||||
|
update(ctx) {
|
||||||
|
ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
|
||||||
|
ctx.fillRect(this.x, this.y, this.w, this.h);
|
||||||
|
|
||||||
|
this.centerText(ctx, 'GAME OVER', this.cy, '48px', 'Consolas', 'Black');
|
||||||
|
this.centerText(ctx, '(Press "N" to start)', this.cy + 48, '24px', 'Consolas', 'Black');
|
||||||
|
}
|
||||||
|
centerText(ctx, txt, y, s, f, c) {
|
||||||
|
ctx.font = s + ' ' + f;
|
||||||
|
ctx.fillStyle = 'Black';
|
||||||
|
let x = (ctx.canvas.width - ctx.measureText(txt).width) / 2;
|
||||||
|
ctx.fillText(txt, x, y);
|
||||||
|
}
|
||||||
|
}
|
79
assets/js/GamePlay.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
class GamePlay extends Board {
|
||||||
|
constructor(ctx, key) {
|
||||||
|
super(ctx, key);
|
||||||
|
|
||||||
|
this.controls = {
|
||||||
|
'KeyS': ()=>{
|
||||||
|
this.nextLevel(++this.level);
|
||||||
|
},
|
||||||
|
'KeyX': ()=>{
|
||||||
|
let b = new Ball();
|
||||||
|
b.update(this.ctx, this.bar.x + this.bar.w/2, this.bar.y);
|
||||||
|
|
||||||
|
this.balls.push(b);
|
||||||
|
this.balls[this.balls.length - 1].start();
|
||||||
|
},
|
||||||
|
'Space': ()=>{
|
||||||
|
this.balls[0].moving = true;
|
||||||
|
},
|
||||||
|
'KeyN': ()=>{
|
||||||
|
if(this.lives.get()==0) this.next(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.gameOver = new GameOver();
|
||||||
|
this.score = new Score();
|
||||||
|
this.lives = new Lives();
|
||||||
|
this.bar = new Bar(ctx, key);
|
||||||
|
this.levels = new Levels();
|
||||||
|
this.newGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
newGame() {
|
||||||
|
this.lives.reset();
|
||||||
|
this.score.reset();
|
||||||
|
this.nextLevel(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextLevel(lvl) {
|
||||||
|
this.level = lvl;
|
||||||
|
this.bricks = this.levels.load(lvl);
|
||||||
|
this.bar.reset();
|
||||||
|
this.balls = [];
|
||||||
|
this.balls.push(new Ball());
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
if(this.lives.get()==0) {
|
||||||
|
this.loopStop();
|
||||||
|
this.gameOver.update(this.ctx);
|
||||||
|
// this.next(2);
|
||||||
|
} else {
|
||||||
|
this.balls = this.balls.filter(ball => {
|
||||||
|
let r = ball.update(this.ctx, this.bar.x + this.bar.w/2, this.bar.y);
|
||||||
|
ball.collide( this.bar.x, this.bar.y, this.bar.x + this.bar.w, this.bar.y + this.bar.h );
|
||||||
|
this.bricks.forEach(b=>{
|
||||||
|
if(b.lives>0){
|
||||||
|
if ( ball.collide(b.x,b.y,b.x+b.w,b.y+b.h) ) {
|
||||||
|
this.score.add(1);
|
||||||
|
b.crack();
|
||||||
|
}
|
||||||
|
|
||||||
|
}});
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (this.bricks.length==0) {
|
||||||
|
this.nextLevel(++this.level);
|
||||||
|
}
|
||||||
|
if (this.balls.length==0) {
|
||||||
|
if ( !this.lives.lost() ) this.balls.push(new Ball());
|
||||||
|
}
|
||||||
|
this.bricks = this.bricks.filter(brick => brick.update(this.ctx));
|
||||||
|
// if ( this.bricks.length == 0 ) this.nextLevel();
|
||||||
|
this.bar.update();
|
||||||
|
}
|
||||||
|
this.score.update(this.ctx);
|
||||||
|
this.lives.update(this.ctx);
|
||||||
|
}
|
||||||
|
}
|
72
assets/js/Keyboard.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
class Keyboard {
|
||||||
|
constructor(onKeydown) {
|
||||||
|
this._pressed = {};
|
||||||
|
this.cb_onKeydown = onKeydown;
|
||||||
|
|
||||||
|
window.addEventListener('keydown', e => this.onKeydown(e));
|
||||||
|
window.addEventListener('keyup', e => this.onKeyup(e));
|
||||||
|
|
||||||
|
window.addEventListener('touchstart', e => this.onTouchStart(e));
|
||||||
|
window.addEventListener('touchmove', e => this.onTouchMove(e));
|
||||||
|
window.addEventListener('touchend', e => this.onTouchEnd(e));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
setKeydown(fn) {
|
||||||
|
this.cb_onKeydown = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
isDown(keyCode) {
|
||||||
|
return this._pressed[keyCode];
|
||||||
|
}
|
||||||
|
onKeydown(event) {
|
||||||
|
this._pressed[event.code] = true;
|
||||||
|
if (this.cb_onKeydown) this.cb_onKeydown(event);
|
||||||
|
}
|
||||||
|
onKeyup(event) {
|
||||||
|
delete this._pressed[event.code];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onTouchStart(e) {
|
||||||
|
var touchobj = e.changedTouches[0] // reference first touch point (ie: first finger)
|
||||||
|
this.touchX = parseInt(touchobj.clientX) // get x position of touch point relative to left edge of browser
|
||||||
|
this.touchY = parseInt(touchobj.clientY) // get x position of touch point relative to left edge of browser
|
||||||
|
window.dispatchEvent(new KeyboardEvent('keydown',{'code':'Space'}));
|
||||||
|
window.dispatchEvent(new KeyboardEvent('keydown',{'code':'KeyN'}));
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
onTouchMove(e) {
|
||||||
|
var touchobj = e.changedTouches[0] // reference first touch point for this event
|
||||||
|
var dist = parseInt(touchobj.clientX) - this.touchX
|
||||||
|
if (dist>0) {
|
||||||
|
delete this._pressed['ArrowLeft'];
|
||||||
|
this._pressed['ArrowRight'] = true;
|
||||||
|
} else if (dist<0) {
|
||||||
|
delete this._pressed['ArrowRight'];
|
||||||
|
this._pressed['ArrowLeft'] = true;
|
||||||
|
} else {
|
||||||
|
delete this._pressed['ArrowLeft'];
|
||||||
|
delete this._pressed['ArrowRight'];
|
||||||
|
}
|
||||||
|
|
||||||
|
dist = parseInt(touchobj.clientY) - this.touchY
|
||||||
|
if (dist>0) {
|
||||||
|
delete this._pressed['ArrowUp'];
|
||||||
|
this._pressed['ArrowDown'] = true;
|
||||||
|
} else if (dist<0) {
|
||||||
|
delete this._pressed['ArrowDown'];
|
||||||
|
this._pressed['ArrowUp'] = true;
|
||||||
|
} else {
|
||||||
|
delete this._pressed['ArrowUp'];
|
||||||
|
delete this._pressed['ArrowDown'];
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
onTouchEnd(e) {
|
||||||
|
this._pressed = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
48
assets/js/Levels.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
class Levels {
|
||||||
|
load(lvl) {
|
||||||
|
let map = [];
|
||||||
|
switch (+lvl) {
|
||||||
|
case 1:
|
||||||
|
map = [].concat(
|
||||||
|
this.row(0, [0, 0, 1, 0, 1, 0, 0, 1, 0, 1]),
|
||||||
|
this.row(1, [0, 0, 1, 1, 1, 1, 1, 1, 1, 1]),
|
||||||
|
this.row(3, [0, 0, 0, 1, 1, 1, 1, 1, 1, 0]),
|
||||||
|
this.row(4, [0, 0, 1, 1, 1, 1, 1, 1, 1, 1])
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
map = [].concat(
|
||||||
|
this.row(0, [0, 0, 3, 3, 3, 0, 3, 3, 0, 0]),
|
||||||
|
this.row(1, [0, 0, 0, 0, 3, 0, 3, 0, 3, 0]),
|
||||||
|
this.row(2, [0, 0, 0, 0, 3, 0, 3, 0, 3, 0]),
|
||||||
|
this.row(3, [0, 0, 3, 0, 3, 0, 3, 0, 3, 0]),
|
||||||
|
this.row(4, [0, 0, 0, 3, 3, 0, 3, 3, 0, 0])
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
map = [].concat(
|
||||||
|
this.row(0, [2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 2]),
|
||||||
|
this.row(1, [2, 3, 1, 3, 1, 3, 1, 3, 1, 3, 2]),
|
||||||
|
this.row(2, [2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 2]),
|
||||||
|
this.row(3, [2, 3, 1, 3, 1, 3, 1, 3, 1, 3, 2]),
|
||||||
|
this.row(4, [2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 2])
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return this.toBricks(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
row(r, bricksTypes) {
|
||||||
|
let row = [];
|
||||||
|
for (var i = 0; i < bricksTypes.length; i++) row.push([bricksTypes[i], i, r]);
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
toBricks(map) {
|
||||||
|
let bricks = [];
|
||||||
|
map.forEach(b => {
|
||||||
|
if (b[0] > 0) bricks.push(new Brick(b[0], b[1], b[2]));
|
||||||
|
});
|
||||||
|
return bricks;
|
||||||
|
}
|
||||||
|
}
|
27
assets/js/Lives.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
class Lives {
|
||||||
|
constructor() {
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
reset() {
|
||||||
|
this.lives = 3;
|
||||||
|
this.y = 10;
|
||||||
|
}
|
||||||
|
lost() {
|
||||||
|
this.lives--;
|
||||||
|
return this.lives==0;
|
||||||
|
}
|
||||||
|
get() {
|
||||||
|
return this.lives;
|
||||||
|
}
|
||||||
|
update(ctx) {
|
||||||
|
if (this.y != 48) this.y++;
|
||||||
|
if (this.y > 0) {
|
||||||
|
let txt = (String.fromCharCode(parseInt('26A1', 16))+" ").repeat(this.lives);
|
||||||
|
|
||||||
|
ctx.font = "18px Consolas";
|
||||||
|
ctx.fillStyle = 'Green';
|
||||||
|
this.x = ctx.canvas.width - ctx.measureText(txt).width;
|
||||||
|
ctx.fillText(txt, this.x, this.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
assets/js/Resources.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
class Resources {
|
||||||
|
constructor() {
|
||||||
|
this.total = 0;
|
||||||
|
this.loading = 0;
|
||||||
|
this.resources = {};
|
||||||
|
|
||||||
|
this.load('bg01', 'jpg');
|
||||||
|
this.load('ball');
|
||||||
|
this.load('bar');
|
||||||
|
this.load('b31');
|
||||||
|
this.load('b32');
|
||||||
|
this.load('b33');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
load(res, ext='png') {
|
||||||
|
let _this = this;
|
||||||
|
this.total++;
|
||||||
|
this.loading++;
|
||||||
|
this.resources[res] = new Image();
|
||||||
|
this.resources[res].onload = function () {
|
||||||
|
_this.loading--;
|
||||||
|
}
|
||||||
|
this.resources[res].src = 'assets/imgs/' + res + '.'+ext;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(res) {
|
||||||
|
return this.resources[res];
|
||||||
|
}
|
||||||
|
};
|
21
assets/js/Score.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
class Score {
|
||||||
|
constructor() {
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
reset() {
|
||||||
|
this.points = 0;
|
||||||
|
this.x = 235;
|
||||||
|
this.y = -10;
|
||||||
|
}
|
||||||
|
add(x) {
|
||||||
|
this.points += x;
|
||||||
|
}
|
||||||
|
update(ctx) {
|
||||||
|
if (this.y != 20) this.y++;
|
||||||
|
if (this.y > 0) {
|
||||||
|
ctx.font = "20px Consolas";
|
||||||
|
ctx.fillStyle = 'Black';
|
||||||
|
ctx.fillText('Score: ' + this.points, this.x, this.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
assets/js/index.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
"use strict";
|
||||||
|
// import {Intro as game_intro} from "./Intro";
|
||||||
|
// import game_play from "./game.js";
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
|
|
||||||
|
|
||||||
|
let resources = new Resources();
|
||||||
|
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
let ctx, canvas = document.createElement("canvas");
|
||||||
|
canvas.width = 360; // window.innerWidth
|
||||||
|
canvas.height = 640; // window.innerHeight
|
||||||
|
ctx = canvas.getContext('2d');
|
||||||
|
document.body.insertBefore(canvas, document.body.childNodes[0]);
|
||||||
|
let container = document.querySelector("body");
|
||||||
|
let resize = (e) => {
|
||||||
|
container.clientWidth / container.clientHeight > 1
|
||||||
|
? (canvas.style.height = "100vh") && (canvas.style.width = "auto")
|
||||||
|
: (canvas.style.height = "auto") && (canvas.style.width = "100vw");
|
||||||
|
};
|
||||||
|
resize();
|
||||||
|
container.onresize = resize;
|
||||||
|
|
||||||
|
|
||||||
|
let key = new Keyboard(), board;
|
||||||
|
function runBoard(stage) {
|
||||||
|
switch (stage) {
|
||||||
|
case 1: board = new GamePlay(ctx, key); break;
|
||||||
|
default: board = new GameIntro(ctx, key); break;
|
||||||
|
}
|
||||||
|
board
|
||||||
|
.run()
|
||||||
|
.then(stage => runBoard(stage), e => { });;
|
||||||
|
}
|
||||||
|
runBoard(0);
|
||||||
|
}
|
25
index.html
@ -5,9 +5,32 @@
|
|||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
border: 1px solid black;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script src="index.js"></script>
|
<!-- ------------------------------------------ -->
|
||||||
|
<script src="assets/js/Resources.js"></script>
|
||||||
|
<script src="assets/js/Keyboard.js"></script>
|
||||||
|
<script src="assets/js/Board.js"></script>
|
||||||
|
<!-- ------------------------------------------ -->
|
||||||
|
<script src="assets/js/GameIntro.js"></script>
|
||||||
|
<!-- ------------------------------------------ -->
|
||||||
|
<script src="assets/js/GameOver.js"></script>
|
||||||
|
<script src="assets/js/Bricks.js"></script>
|
||||||
|
<script src="assets/js/Levels.js"></script>
|
||||||
|
<script src="assets/js/Score.js"></script>
|
||||||
|
<script src="assets/js/Lives.js"></script>
|
||||||
|
<script src="assets/js/Bar.js"></script>
|
||||||
|
<script src="assets/js/Ball.js"></script>
|
||||||
|
<script src="assets/js/GamePlay.js"></script>
|
||||||
|
<!-- ------------------------------------------ -->
|
||||||
|
<script src="assets/js/index.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
203
index.js
@ -1,203 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', init);
|
|
||||||
|
|
||||||
let Key = {
|
|
||||||
_pressed: {},
|
|
||||||
isDown: function (keyCode) {
|
|
||||||
return this._pressed[keyCode];
|
|
||||||
},
|
|
||||||
onKeydown: function (event) {
|
|
||||||
this._pressed[event.code] = true;
|
|
||||||
},
|
|
||||||
onKeyup: function (event) {
|
|
||||||
delete this._pressed[event.code];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let ctx, canvas = document.createElement("canvas");
|
|
||||||
function init() {
|
|
||||||
canvas.width = window.innerWidth
|
|
||||||
canvas.height = window.innerHeight
|
|
||||||
ctx = canvas.getContext('2d');
|
|
||||||
document.body.insertBefore(canvas, document.body.childNodes[0]);
|
|
||||||
window.addEventListener('keydown', (e) => Key.onKeydown(e));
|
|
||||||
window.addEventListener('keyup', (e) => Key.onKeyup(e));
|
|
||||||
|
|
||||||
function run() {
|
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
||||||
refresh();
|
|
||||||
requestAnimationFrame(run);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
|
||||||
let gstate = 1;
|
|
||||||
let intro = new Intro();
|
|
||||||
let score = new Score();
|
|
||||||
let bar = new Bar();
|
|
||||||
|
|
||||||
function joy_fire() {
|
|
||||||
switch (gstate) {
|
|
||||||
case 0: // Waiting to start
|
|
||||||
newGame();
|
|
||||||
break;
|
|
||||||
case 1: // Playing...
|
|
||||||
break;
|
|
||||||
case 2: // Game Over
|
|
||||||
newGame();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function newGame() {
|
|
||||||
gstate = 1;
|
|
||||||
score.reset();
|
|
||||||
bar.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
function refresh() {
|
|
||||||
switch (gstate) {
|
|
||||||
case 0: // Waiting to start
|
|
||||||
intro.update();
|
|
||||||
break;
|
|
||||||
case 1: // Playing...
|
|
||||||
bar.update();
|
|
||||||
score.update();
|
|
||||||
break;
|
|
||||||
case 2: // Game Over
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
|
|
||||||
class Intro {
|
|
||||||
constructor() {
|
|
||||||
this.x = canvas.width;
|
|
||||||
this.y = canvas.height / 2 - 48;
|
|
||||||
}
|
|
||||||
update() {
|
|
||||||
this.centerText('BreakOut', this.y, '48px', 'Consolas', 'Black');
|
|
||||||
this.centerText('JDG', this.y + 50, '24px', 'Consolas', 'Black');
|
|
||||||
}
|
|
||||||
centerText(txt, y, s, f, c) {
|
|
||||||
ctx.font = s + ' ' + f;
|
|
||||||
ctx.fillStyle = 'Black';
|
|
||||||
let x = (canvas.width - ctx.measureText(txt).width) / 2;
|
|
||||||
ctx.fillText(txt, x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Bar {
|
|
||||||
constructor() {
|
|
||||||
this.w = 100;
|
|
||||||
this.h = 20;
|
|
||||||
this.speed = 10; // Target Speed
|
|
||||||
this._speed = 0; // Current Speed and direction
|
|
||||||
|
|
||||||
this.xLimit = (canvas.width - this.w);
|
|
||||||
this.reset();
|
|
||||||
}
|
|
||||||
reset() {
|
|
||||||
this.x = (canvas.width - this.w) / 2;
|
|
||||||
this.y = (canvas.height - this.h * 2);
|
|
||||||
|
|
||||||
this._y = canvas.height + 10;
|
|
||||||
}
|
|
||||||
update() {
|
|
||||||
this.move();
|
|
||||||
this.draw();
|
|
||||||
}
|
|
||||||
stop() {
|
|
||||||
this._speed = 0;
|
|
||||||
}
|
|
||||||
left() {
|
|
||||||
if (this._speed >= 0) this._speed = -this.speed;
|
|
||||||
this.x += this._speed;
|
|
||||||
if (this.x < 0) this.x = 0;
|
|
||||||
this._speed -= 0.5;
|
|
||||||
}
|
|
||||||
right() {
|
|
||||||
if (this._speed <= 0) this._speed = this.speed;
|
|
||||||
this.x += this._speed;
|
|
||||||
if (this.x > this.xLimit) this.x = this.xLimit;
|
|
||||||
this._speed += 0.5;
|
|
||||||
}
|
|
||||||
move() {
|
|
||||||
if (Key.isDown('ArrowLeft')) this.left();
|
|
||||||
else
|
|
||||||
if (Key.isDown('ArrowRight')) this.right();
|
|
||||||
else
|
|
||||||
this.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
draw() {
|
|
||||||
if (this._y != this.y) this._y--;
|
|
||||||
if (this._y < canvas.height) {
|
|
||||||
ctx.fillStyle = 'black';
|
|
||||||
ctx.fillRect(this.x, this._y, this.w, this.h);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Score {
|
|
||||||
constructor() {
|
|
||||||
this.reset();
|
|
||||||
|
|
||||||
ctx.font = "30px Consolas";
|
|
||||||
let m = ctx.measureText('Score: 00000');
|
|
||||||
this.x = canvas.width - m.width;
|
|
||||||
this.y = -10;
|
|
||||||
}
|
|
||||||
reset() {
|
|
||||||
this.points = 0;
|
|
||||||
}
|
|
||||||
add(x) {
|
|
||||||
this.points += x;
|
|
||||||
}
|
|
||||||
update() {
|
|
||||||
if (this.y != 30) this.y++;
|
|
||||||
if (this.y > 0) {
|
|
||||||
ctx.font = "30px Consolas";
|
|
||||||
ctx.fillStyle = 'Black';
|
|
||||||
ctx.fillText('Score: ' + this.points, this.x, this.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ball {
|
|
||||||
constructor(x, y, speed, angle) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.speed = speed;
|
|
||||||
this.angle = angle;
|
|
||||||
this.color = 'black';
|
|
||||||
this.size = 20;
|
|
||||||
}
|
|
||||||
|
|
||||||
update() {
|
|
||||||
this.move();
|
|
||||||
this.draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
draw() {
|
|
||||||
ctx.fillStyle = this.color;
|
|
||||||
ctx.fillRect(this.x, this.y, this.size, this.size);
|
|
||||||
}
|
|
||||||
|
|
||||||
move() {
|
|
||||||
this.x += this.speed * Math.cos(this.angle);
|
|
||||||
this.y += this.speed * Math.sin(this.angle);
|
|
||||||
|
|
||||||
if (this.x < 0 || this.x > canvas.width || this.y < 0 || this.y > canvas.height)
|
|
||||||
this.bounce();
|
|
||||||
}
|
|
||||||
|
|
||||||
bounce() {
|
|
||||||
this.angle += 180;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|