changed ball and bar for a sprite

This commit is contained in:
2021-11-16 22:01:43 +01:00
parent e0ebef2a27
commit bc3ef31728
17 changed files with 77 additions and 24 deletions

183
assets/js/Ball.js Normal file
View 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
View 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);
}
}
}

56
assets/js/Board.js Normal file
View File

@ -0,0 +1,56 @@
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
}
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.update();
this.requestID = requestAnimationFrame( ()=>this.loop() );
}
update() {
}
}

46
assets/js/Bricks.js Normal file
View File

@ -0,0 +1,46 @@
class Brick {
constructor(type, column, row) {
this.type = type;
this.row = row;
this.column = column;
this.vspace = 2;
this.hspace = 2;
this.w = (360 / 8) - this.hspace;
this.h = (20) - this.vspace;
this.x = (this.w + this.hspace) * column;
this.y = 80 + (this.h + this.vspace) * row;
switch (type) {
case 2: this.lives = 2; break;
case 3: this.lives = 3; break;
default: this.lives = 1; break;
}
}
crack() {
this.lives--;
}
update(ctx) {
if (this.lives == 0) return false;
switch (this.lives) {
case 1:
ctx.fillStyle = 'blue';
ctx.fillRect(this.x + 1, this.y, this.w, this.h);
break;
case 2:
ctx.fillStyle = 'orange';
ctx.fillRect(this.x + 1, this.y, this.w, this.h);
break;
case 3:
ctx.fillStyle = 'red';
ctx.fillRect(this.x + 1, this.y, this.w, this.h);
break;
}
return true;
}
}

18
assets/js/GameIntro.js Normal file
View 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
View 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
View 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);
}
}

24
assets/js/Keyboard.js Normal file
View File

@ -0,0 +1,24 @@
class Keyboard {
constructor(onKeydown) {
this._pressed = {};
this.cb_onKeydown = onKeydown;
window.addEventListener('keydown', e => this.onKeydown(e));
window.addEventListener('keyup', e => this.onKeyup(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];
}
}

48
assets/js/Levels.js Normal file
View File

@ -0,0 +1,48 @@
class Levels {
load(lvl) {
let map = [];
switch (+lvl) {
case 1:
map = [].concat(
this.row(0, [1, 0, 1, 0, 0, 1, 0, 1]),
this.row(1, [1, 1, 1, 1, 1, 1, 1, 1]),
this.row(3, [0, 1, 1, 1, 1, 1, 1, 0]),
this.row(4, [1, 1, 1, 1, 1, 1, 1, 1])
);
break;
case 2:
map = [].concat(
this.row(0, [3, 3, 3, 0, 3, 3, 0, 0]),
this.row(1, [0, 0, 3, 0, 3, 0, 3, 0]),
this.row(2, [0, 0, 3, 0, 3, 0, 3, 0]),
this.row(3, [3, 0, 3, 0, 3, 0, 3, 0]),
this.row(4, [0, 3, 3, 0, 3, 3, 0, 0])
);
break;
default:
map = [].concat(
this.row(0, [1, 3, 1, 3, 1, 3, 1, 3]),
this.row(1, [3, 1, 3, 1, 3, 1, 3, 1]),
this.row(2, [1, 3, 1, 3, 1, 3, 1, 3]),
this.row(3, [3, 1, 3, 1, 3, 1, 3, 1]),
this.row(4, [1, 3, 1, 3, 1, 3, 1, 3])
);
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
View 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);
}
}
}

26
assets/js/Resources.js Normal file
View File

@ -0,0 +1,26 @@
class Resources {
constructor() {
this.total = 0;
this.loading = 0;
this.resources = {};
this.load('ball');
this.load('bar');
}
load(res) {
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 + '.png';
}
get(res) {
return this.resources[res];
}
};

21
assets/js/Score.js Normal file
View 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
View 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);
}