Created
July 3, 2021 21:54
-
-
Save jeffersonmourak/301f6fc9806e2f368a3c9172cfc7b357 to your computer and use it in GitHub Desktop.
Space Inverders Game
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Game Engine | |
| */ | |
| class Canvas { | |
| /** | |
| * Stores the DOM elements that will be used on this code. | |
| */ | |
| #elements = {}; | |
| /** | |
| * Game Settings. | |
| */ | |
| #settings = {}; | |
| /** | |
| * Runtime Variables. | |
| */ | |
| #runtime = { | |
| key: null, | |
| enemies: [], | |
| player: null, | |
| time: { | |
| start: Date.now(), | |
| lastFrame: Date.now(), | |
| delta: 0, | |
| }, | |
| }; | |
| /** | |
| * Contructor. | |
| * | |
| * @param {HTMLElement} el Parent element that will receive the game. | |
| */ | |
| constructor({ | |
| el = document.body, | |
| width = 600, | |
| height = 600, | |
| fps = 30, | |
| } = {}) { | |
| this.#elements.body = el; | |
| this.#settings = { | |
| width, | |
| height, | |
| fps, | |
| fpsInterval: 1000 / fps, | |
| pallete: ["transparent", "white", "red", "green"], | |
| }; | |
| this.#elements.canvas = this.#createCanvasEl(); | |
| this.context = this.#elements.canvas.getContext("2d"); | |
| this.#elements.body.appendChild(this.#elements.canvas); | |
| this.#setup(); | |
| this.#listenKeyboard(); | |
| this.render(); | |
| } | |
| /** | |
| * Renders the frames for the game. | |
| */ | |
| render() { | |
| window.requestAnimationFrame(() => { | |
| this.render(); | |
| }); | |
| const now = Date.now(); | |
| this.#runtime.time.delta = now - this.#runtime.time.lastFrame; | |
| if (this.#runtime.time.delta > this.#settings.fpsInterval) { | |
| this.context.clearRect(0, 0, this.#settings.width, this.#settings.height); | |
| this.#draw(); | |
| this.#runtime.time.lastFrame = | |
| now - (this.#runtime.time.delta % this.#settings.fpsInterval); | |
| } | |
| } | |
| /** | |
| * Setup Gameobjects before start drawing. | |
| */ | |
| #setup() { | |
| const { enemies } = this.#runtime; | |
| const enemyW = Enemy.bitMap[0].length; | |
| const enemyH = Enemy.bitMap.length; | |
| const enemiesPerRow = 7; | |
| this.#runtime.player = new Player(this.context, { ...this.#settings }, {}); | |
| for (let i = 0; i < enemiesPerRow * 4; i++) { | |
| let x = Enemy.toPixel(enemyW * 2 + 2) * (i % enemiesPerRow); | |
| let y = | |
| Enemy.toPixel(enemyH + 10) * Math.floor(i / enemiesPerRow) + | |
| Enemy.toPixel(10); | |
| enemies.push( | |
| new Enemy( | |
| this.context, | |
| { ...this.#settings }, | |
| { x, y, speed: 1, rtl: true } | |
| ) | |
| ); | |
| } | |
| } | |
| /** | |
| * Draw a frame for the game. | |
| */ | |
| #draw() { | |
| this.drawBackground(); | |
| this.drawEnemies(); | |
| this.drawPlayer(); | |
| } | |
| /** | |
| * Listen Keyboard entries. | |
| */ | |
| #listenKeyboard() { | |
| document.addEventListener("keydown", (event) => | |
| this.controller(event.key, false) | |
| ); | |
| document.addEventListener("keyup", (event) => | |
| this.controller(event.key, true) | |
| ); | |
| } | |
| /** | |
| * Creates Canvas element to be manipulated. | |
| * @returns {HTMLElement} | |
| */ | |
| #createCanvasEl() { | |
| const element = document.createElement("canvas"); | |
| element.width = this.#settings.width; | |
| element.height = this.#settings.height; | |
| element.id = `canvas-${Math.random().toString(36).substring(7)}`; | |
| return element; | |
| } | |
| // PUBLIC | |
| /** | |
| * Draw the background. | |
| */ | |
| drawBackground() { | |
| this.context.fillStyle = "black"; | |
| this.context.fillRect(0, 0, this.#settings.width, this.#settings.height); | |
| } | |
| /** | |
| * Draw Enemies entities. | |
| */ | |
| drawEnemies() { | |
| const { enemies } = this.#runtime; | |
| for (const enemy of enemies) { | |
| enemy.runtime.delta = this.#runtime.time.delta; | |
| if ( | |
| enemies[0].runtime.onWall || | |
| enemies[enemies.length - 1].runtime.onWall | |
| ) { | |
| enemy.runtime.rtl = !enemy.runtime.rtl; | |
| } | |
| if ( | |
| enemy.runtime.bullet && | |
| enemy.runtime.bullet.collides(this.#runtime.player) | |
| ) { | |
| this.#runtime.player.destroy(); | |
| enemy.runtime.bullet.destroy(); | |
| } | |
| enemy.draw(); | |
| } | |
| } | |
| /** | |
| * Draws Player Entity. | |
| */ | |
| drawPlayer() { | |
| const { player } = this.#runtime; | |
| switch (this.#runtime.key) { | |
| case "ArrowRight": | |
| player.runtime.x += Character.toPixel(3); | |
| break; | |
| case "ArrowLeft": | |
| player.runtime.x -= Character.toPixel(3); | |
| break; | |
| case " ": | |
| player.shoot(); | |
| } | |
| if (player) { | |
| player.draw(); | |
| } | |
| for (let bI = 0; bI < player.runtime.bullets.length; bI++) { | |
| const bullet = player.runtime.bullets[bI]; | |
| for (let eI = 0; eI < this.#runtime.enemies.length; eI++) { | |
| if ( | |
| !bullet.runtime.destroyed && | |
| bullet.collides(this.#runtime.enemies[eI]) | |
| ) { | |
| this.#runtime.enemies[eI].destroy(); | |
| bullet.destroy(); | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Saves What key is being pressed. | |
| * | |
| * @param {String} key Key Pressed | |
| * @param {Boolean} released Is the key released. | |
| */ | |
| controller(key, released) { | |
| this.#runtime.key = !released ? key : null; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Character | |
| * @extends GameObject. | |
| */ | |
| class Character extends GameObject { | |
| /** | |
| * How to draw the character. | |
| */ | |
| static bitMap = []; | |
| static pixelSize = 3; | |
| /** | |
| * Calculates the pixel size of a given number. | |
| * | |
| * @param {Number} size Size to be calculated | |
| * @returns {Number} pixel perfect size. | |
| */ | |
| static toPixel(size) { | |
| return size * Character.pixelSize; | |
| } | |
| drawBitMap(Entity) { | |
| let { x, y } = this.runtime; | |
| let { pallete } = this.settings; | |
| for (let by = 0; by < Entity.bitMap.length; by++) { | |
| for (let bx = 0; bx < Entity.bitMap[by].length; bx++) { | |
| this.context.fillStyle = pallete[Entity.bitMap[by][bx]]; | |
| this.context.fillRect( | |
| x + bx * Entity.pixelSize, | |
| y + by * Entity.pixelSize, | |
| Entity.pixelSize, | |
| Entity.pixelSize | |
| ); | |
| } | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class EnemyBullet extends Character { | |
| static bitMap = [ | |
| [0, 2, 0], | |
| [0, 2, 0], | |
| [0, 2, 0], | |
| [2, 2, 2], | |
| ]; | |
| constructor(context, settings, runtime) { | |
| super(context, settings, runtime); | |
| this.runtime = { | |
| ...runtime, | |
| w: EnemyBullet.bitMap[0].length * EnemyBullet.pixelSize, | |
| h: EnemyBullet.bitMap.length * EnemyBullet.pixelSize, | |
| }; | |
| } | |
| draw() { | |
| this.drawBitMap(EnemyBullet); | |
| this.runtime.y += Character.toPixel(1); | |
| } | |
| } | |
| class Enemy extends Character { | |
| static bitMap = [ | |
| [0, 0, 2, 0, 0, 0, 0, 0, 2, 0, 0], | |
| [0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0], | |
| [0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 0], | |
| [0, 2, 2, 0, 2, 2, 2, 0, 2, 2, 0], | |
| [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2], | |
| [2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2], | |
| [2, 0, 2, 0, 0, 0, 0, 0, 2, 0, 2], | |
| [0, 0, 0, 2, 2, 0, 2, 2, 0, 0, 0], | |
| ]; | |
| constructor(context, settings, runtime) { | |
| super(context, settings, runtime); | |
| this.runtime = { | |
| ...runtime, | |
| x: runtime.x + 30, | |
| offset: 10 * Enemy.pixelSize, | |
| w: Enemy.bitMap[0].length * Enemy.pixelSize, | |
| h: Enemy.bitMap.length * Enemy.pixelSize, | |
| bullet: null, | |
| destroyed: false, | |
| }; | |
| } | |
| draw() { | |
| let { x, y, speed, rtl, offset, bullet, destroyed } = this.runtime; | |
| const { | |
| tl: [tlX], | |
| tr: [trX], | |
| } = this.getVertex(); | |
| const { width, height } = this.settings; | |
| if (!destroyed) { | |
| this.drawBitMap(Enemy); | |
| } | |
| let onWall = (tlX <= offset && !rtl) || (trX > width - offset && rtl); | |
| rtl = tlX <= offset && !rtl ? !rtl : rtl; | |
| if (!bullet && Math.random() * 100 > 99.85) { | |
| bullet = new EnemyBullet(this.context, this.settings, { x, y }); | |
| } | |
| if (bullet && bullet.runtime.y > height) { | |
| bullet = null; | |
| } | |
| this.runtime = { | |
| ...this.runtime, | |
| onWall, | |
| bullet, | |
| x: x + speed * Enemy.pixelSize * (rtl ? 1 : -1), | |
| }; | |
| if (bullet) { | |
| bullet.draw(); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Game Object | |
| */ | |
| class GameObject { | |
| runtime = {}; | |
| /** | |
| * Initializes the Gameobject. | |
| * | |
| * @param {Canvas2DContext} context Canvas Context. | |
| * @param {GameSettings} settings Game Settings. | |
| * @param {RuntimeObj} runtime Initial Runtime Variables. | |
| */ | |
| constructor(context, settings, runtime) { | |
| this.context = context; | |
| this.settings = settings; | |
| this.runtime = { | |
| ...this.runtime, | |
| ...runtime, | |
| }; | |
| } | |
| /** | |
| * Draws the game object into canvas | |
| */ | |
| draw() {} | |
| /** | |
| * Calculates the vertexes of an object. | |
| * | |
| * @returns {VertexObject} | |
| */ | |
| getVertex() { | |
| const { x, y, w, h } = this.runtime; | |
| return { | |
| tl: [x, y], | |
| tr: [x + w, y], | |
| bl: [x, y + h], | |
| br: [x + w, y + h], | |
| }; | |
| } | |
| /** | |
| * Calculates if the objects are colliding. | |
| * | |
| * @param {GameObject} gameObject Target Gameobject. | |
| * @returns {Boolean} | |
| */ | |
| collides(gameObject) { | |
| const { x, y, w, h } = this.runtime; | |
| const { x: gX, y: gY, w: gW, h: gH } = gameObject.runtime; | |
| if (this.runtime.destroyed || gameObject.runtime.destroyed) { | |
| return false; | |
| } | |
| return x < gX + gW && x + w > gX && y < gY + gH && y + h > gY; | |
| } | |
| /** | |
| * Destroy the object. | |
| */ | |
| destroy() { | |
| this.runtime.destroyed = true; | |
| } | |
| } | |
| /** | |
| * Character | |
| * @extends GameObject. | |
| */ | |
| class Character extends GameObject { | |
| /** | |
| * How to draw the character. | |
| */ | |
| static bitMap = []; | |
| static pixelSize = 3; | |
| /** | |
| * Calculates the pixel size of a given number. | |
| * | |
| * @param {Number} size Size to be calculated | |
| * @returns {Number} pixel perfect size. | |
| */ | |
| static toPixel(size) { | |
| return size * Character.pixelSize; | |
| } | |
| drawBitMap(Entity) { | |
| let { x, y } = this.runtime; | |
| let { pallete } = this.settings; | |
| for (let by = 0; by < Entity.bitMap.length; by++) { | |
| for (let bx = 0; bx < Entity.bitMap[by].length; bx++) { | |
| this.context.fillStyle = pallete[Entity.bitMap[by][bx]]; | |
| this.context.fillRect( | |
| x + bx * Entity.pixelSize, | |
| y + by * Entity.pixelSize, | |
| Entity.pixelSize, | |
| Entity.pixelSize | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| class EnemyBullet extends Character { | |
| static bitMap = [ | |
| [0, 2, 0], | |
| [0, 2, 0], | |
| [0, 2, 0], | |
| [2, 2, 2], | |
| ]; | |
| constructor(context, settings, runtime) { | |
| super(context, settings, runtime); | |
| this.runtime = { | |
| ...runtime, | |
| w: EnemyBullet.bitMap[0].length * EnemyBullet.pixelSize, | |
| h: EnemyBullet.bitMap.length * EnemyBullet.pixelSize, | |
| }; | |
| } | |
| draw() { | |
| this.drawBitMap(EnemyBullet); | |
| this.runtime.y += Character.toPixel(1); | |
| } | |
| } | |
| class Enemy extends Character { | |
| static bitMap = [ | |
| [0, 0, 2, 0, 0, 0, 0, 0, 2, 0, 0], | |
| [0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0], | |
| [0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 0], | |
| [0, 2, 2, 0, 2, 2, 2, 0, 2, 2, 0], | |
| [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2], | |
| [2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2], | |
| [2, 0, 2, 0, 0, 0, 0, 0, 2, 0, 2], | |
| [0, 0, 0, 2, 2, 0, 2, 2, 0, 0, 0], | |
| ]; | |
| constructor(context, settings, runtime) { | |
| super(context, settings, runtime); | |
| this.runtime = { | |
| ...runtime, | |
| x: runtime.x + 30, | |
| offset: 10 * Enemy.pixelSize, | |
| w: Enemy.bitMap[0].length * Enemy.pixelSize, | |
| h: Enemy.bitMap.length * Enemy.pixelSize, | |
| bullet: null, | |
| destroyed: false, | |
| }; | |
| } | |
| draw() { | |
| let { x, y, speed, rtl, offset, bullet, destroyed } = this.runtime; | |
| const { | |
| tl: [tlX], | |
| tr: [trX], | |
| } = this.getVertex(); | |
| const { width, height } = this.settings; | |
| if (!destroyed) { | |
| this.drawBitMap(Enemy); | |
| } | |
| let onWall = (tlX <= offset && !rtl) || (trX > width - offset && rtl); | |
| rtl = tlX <= offset && !rtl ? !rtl : rtl; | |
| if (!bullet && Math.random() * 100 > 99.85) { | |
| bullet = new EnemyBullet(this.context, this.settings, { x, y }); | |
| } | |
| if (bullet && bullet.runtime.y > height) { | |
| bullet = null; | |
| } | |
| this.runtime = { | |
| ...this.runtime, | |
| onWall, | |
| bullet, | |
| x: x + speed * Enemy.pixelSize * (rtl ? 1 : -1), | |
| }; | |
| if (bullet) { | |
| bullet.draw(); | |
| } | |
| } | |
| } | |
| class PlayerBullet extends Character { | |
| static bitMap = [ | |
| [3, 3, 3], | |
| [0, 3, 0], | |
| [0, 3, 0], | |
| [0, 3, 0], | |
| ]; | |
| constructor(context, settings, runtime) { | |
| super(context, settings, runtime); | |
| this.runtime = { | |
| ...runtime, | |
| w: PlayerBullet.bitMap[0].length * PlayerBullet.pixelSize, | |
| h: PlayerBullet.bitMap.length * PlayerBullet.pixelSize, | |
| }; | |
| } | |
| draw() { | |
| let { destroyed } = this.runtime; | |
| if (!destroyed) { | |
| this.drawBitMap(PlayerBullet); | |
| } | |
| this.runtime.y -= Character.toPixel(1); | |
| } | |
| } | |
| class Player extends Character { | |
| static bitMap = [ | |
| [0, 0, 0, 3, 0, 0, 0], | |
| [0, 0, 3, 3, 3, 0, 0], | |
| [3, 3, 3, 3, 3, 3, 3], | |
| [3, 3, 3, 3, 3, 3, 3], | |
| ]; | |
| constructor(context, settings, runtime) { | |
| super(context, settings, runtime); | |
| const w = Character.toPixel(Player.bitMap[0].length); | |
| const h = Character.toPixel(Player.bitMap.length); | |
| this.runtime = { | |
| ...runtime, | |
| delta: Date.now(), | |
| bullets: [], | |
| w, | |
| h, | |
| y: settings.height - h - Character.toPixel(2), | |
| x: Math.round((settings.width - w) / 2), | |
| }; | |
| } | |
| /** | |
| * Shoots a bullet upwards. | |
| */ | |
| shoot() { | |
| const { delta, x, y } = this.runtime; | |
| if (Date.now() - delta > 1000) { | |
| this.runtime.bullets.push( | |
| new PlayerBullet(this.context, this.settings, { x, y }) | |
| ); | |
| this.runtime.delta = Date.now(); | |
| } | |
| } | |
| destroy() { | |
| this.runtime.destroyed = true; | |
| this.runtime.destroyedAt = Date.now(); | |
| } | |
| draw() { | |
| let { x, y, bullets, destroyed, destroyedAt } = this.runtime; | |
| if (destroyed && Date.now() - destroyedAt < 3000) { | |
| return; | |
| } | |
| this.drawBitMap(Player); | |
| this.runtime.bullets = bullets.filter((bullet) => bullet.runtime.y > 0); | |
| for (let i = 0; i < this.runtime.bullets.length; i++) { | |
| bullets[i].draw(); | |
| } | |
| } | |
| } | |
| /** | |
| * Game Engine | |
| */ | |
| class Canvas { | |
| /** | |
| * Stores the DOM elements that will be used on this code. | |
| */ | |
| #elements = {}; | |
| /** | |
| * Game Settings. | |
| */ | |
| #settings = {}; | |
| /** | |
| * Runtime Variables. | |
| */ | |
| #runtime = { | |
| key: null, | |
| enemies: [], | |
| player: null, | |
| time: { | |
| start: Date.now(), | |
| lastFrame: Date.now(), | |
| delta: 0, | |
| }, | |
| }; | |
| /** | |
| * Contructor. | |
| * | |
| * @param {HTMLElement} el Parent element that will receive the game. | |
| */ | |
| constructor({ | |
| el = document.body, | |
| width = 600, | |
| height = 600, | |
| fps = 30, | |
| } = {}) { | |
| this.#elements.body = el; | |
| this.#settings = { | |
| width, | |
| height, | |
| fps, | |
| fpsInterval: 1000 / fps, | |
| pallete: ["transparent", "white", "red", "green"], | |
| }; | |
| this.#elements.canvas = this.#createCanvasEl(); | |
| this.context = this.#elements.canvas.getContext("2d"); | |
| this.#elements.body.appendChild(this.#elements.canvas); | |
| this.#setup(); | |
| this.#listenKeyboard(); | |
| this.render(); | |
| } | |
| /** | |
| * Renders the frames for the game. | |
| */ | |
| render() { | |
| window.requestAnimationFrame(() => { | |
| this.render(); | |
| }); | |
| const now = Date.now(); | |
| this.#runtime.time.delta = now - this.#runtime.time.lastFrame; | |
| if (this.#runtime.time.delta > this.#settings.fpsInterval) { | |
| this.context.clearRect(0, 0, this.#settings.width, this.#settings.height); | |
| this.#draw(); | |
| this.#runtime.time.lastFrame = | |
| now - (this.#runtime.time.delta % this.#settings.fpsInterval); | |
| } | |
| } | |
| /** | |
| * Setup Gameobjects before start drawing. | |
| */ | |
| #setup() { | |
| const { enemies } = this.#runtime; | |
| const enemyW = Enemy.bitMap[0].length; | |
| const enemyH = Enemy.bitMap.length; | |
| const enemiesPerRow = 7; | |
| this.#runtime.player = new Player(this.context, { ...this.#settings }, {}); | |
| for (let i = 0; i < enemiesPerRow * 4; i++) { | |
| let x = Enemy.toPixel(enemyW * 2 + 2) * (i % enemiesPerRow); | |
| let y = | |
| Enemy.toPixel(enemyH + 10) * Math.floor(i / enemiesPerRow) + | |
| Enemy.toPixel(10); | |
| enemies.push( | |
| new Enemy( | |
| this.context, | |
| { ...this.#settings }, | |
| { x, y, speed: 1, rtl: true } | |
| ) | |
| ); | |
| } | |
| } | |
| /** | |
| * Draw a frame for the game. | |
| */ | |
| #draw() { | |
| this.drawBackground(); | |
| this.drawEnemies(); | |
| this.drawPlayer(); | |
| } | |
| /** | |
| * Listen Keyboard entries. | |
| */ | |
| #listenKeyboard() { | |
| document.addEventListener("keydown", (event) => | |
| this.controller(event.key, false) | |
| ); | |
| document.addEventListener("keyup", (event) => | |
| this.controller(event.key, true) | |
| ); | |
| } | |
| /** | |
| * Creates Canvas element to be manipulated. | |
| * @returns {HTMLElement} | |
| */ | |
| #createCanvasEl() { | |
| const element = document.createElement("canvas"); | |
| element.width = this.#settings.width; | |
| element.height = this.#settings.height; | |
| element.id = `canvas-${Math.random().toString(36).substring(7)}`; | |
| return element; | |
| } | |
| // PUBLIC | |
| /** | |
| * Draw the background. | |
| */ | |
| drawBackground() { | |
| this.context.fillStyle = "black"; | |
| this.context.fillRect(0, 0, this.#settings.width, this.#settings.height); | |
| } | |
| /** | |
| * Draw Enemies entities. | |
| */ | |
| drawEnemies() { | |
| const { enemies } = this.#runtime; | |
| for (const enemy of enemies) { | |
| enemy.runtime.delta = this.#runtime.time.delta; | |
| if ( | |
| enemies[0].runtime.onWall || | |
| enemies[enemies.length - 1].runtime.onWall | |
| ) { | |
| enemy.runtime.rtl = !enemy.runtime.rtl; | |
| } | |
| if ( | |
| enemy.runtime.bullet && | |
| enemy.runtime.bullet.collides(this.#runtime.player) | |
| ) { | |
| this.#runtime.player.destroy(); | |
| enemy.runtime.bullet.destroy(); | |
| } | |
| enemy.draw(); | |
| } | |
| } | |
| /** | |
| * Draws Player Entity. | |
| */ | |
| drawPlayer() { | |
| const { player } = this.#runtime; | |
| switch (this.#runtime.key) { | |
| case "ArrowRight": | |
| player.runtime.x += Character.toPixel(3); | |
| break; | |
| case "ArrowLeft": | |
| player.runtime.x -= Character.toPixel(3); | |
| break; | |
| case " ": | |
| player.shoot(); | |
| } | |
| if (player) { | |
| player.draw(); | |
| } | |
| for (let bI = 0; bI < player.runtime.bullets.length; bI++) { | |
| const bullet = player.runtime.bullets[bI]; | |
| for (let eI = 0; eI < this.#runtime.enemies.length; eI++) { | |
| if ( | |
| !bullet.runtime.destroyed && | |
| bullet.collides(this.#runtime.enemies[eI]) | |
| ) { | |
| this.#runtime.enemies[eI].destroy(); | |
| bullet.destroy(); | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Saves What key is being pressed. | |
| * | |
| * @param {String} key Key Pressed | |
| * @param {Boolean} released Is the key released. | |
| */ | |
| controller(key, released) { | |
| this.#runtime.key = !released ? key : null; | |
| } | |
| } | |
| window.canvas = new Canvas({ | |
| fps: 60, | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Game Object | |
| */ | |
| class GameObject { | |
| runtime = {}; | |
| /** | |
| * Initializes the Gameobject. | |
| * | |
| * @param {Canvas2DContext} context Canvas Context. | |
| * @param {GameSettings} settings Game Settings. | |
| * @param {RuntimeObj} runtime Initial Runtime Variables. | |
| */ | |
| constructor(context, settings, runtime) { | |
| this.context = context; | |
| this.settings = settings; | |
| this.runtime = { | |
| ...this.runtime, | |
| ...runtime, | |
| }; | |
| } | |
| /** | |
| * Draws the game object into canvas | |
| */ | |
| draw() {} | |
| /** | |
| * Calculates the vertexes of an object. | |
| * | |
| * @returns {VertexObject} | |
| */ | |
| getVertex() { | |
| const { x, y, w, h } = this.runtime; | |
| return { | |
| tl: [x, y], | |
| tr: [x + w, y], | |
| bl: [x, y + h], | |
| br: [x + w, y + h], | |
| }; | |
| } | |
| /** | |
| * Calculates if the objects are colliding. | |
| * | |
| * @param {GameObject} gameObject Target Gameobject. | |
| * @returns {Boolean} | |
| */ | |
| collides(gameObject) { | |
| const { x, y, w, h } = this.runtime; | |
| const { x: gX, y: gY, w: gW, h: gH } = gameObject.runtime; | |
| if (this.runtime.destroyed || gameObject.runtime.destroyed) { | |
| return false; | |
| } | |
| return x < gX + gW && x + w > gX && y < gY + gH && y + h > gY; | |
| } | |
| /** | |
| * Destroy the object. | |
| */ | |
| destroy() { | |
| this.runtime.destroyed = true; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class PlayerBullet extends Character { | |
| static bitMap = [ | |
| [3, 3, 3], | |
| [0, 3, 0], | |
| [0, 3, 0], | |
| [0, 3, 0], | |
| ]; | |
| constructor(context, settings, runtime) { | |
| super(context, settings, runtime); | |
| this.runtime = { | |
| ...runtime, | |
| w: PlayerBullet.bitMap[0].length * PlayerBullet.pixelSize, | |
| h: PlayerBullet.bitMap.length * PlayerBullet.pixelSize, | |
| }; | |
| } | |
| draw() { | |
| let { destroyed } = this.runtime; | |
| if (!destroyed) { | |
| this.drawBitMap(PlayerBullet); | |
| } | |
| this.runtime.y -= Character.toPixel(1); | |
| } | |
| } | |
| class Player extends Character { | |
| static bitMap = [ | |
| [0, 0, 0, 3, 0, 0, 0], | |
| [0, 0, 3, 3, 3, 0, 0], | |
| [3, 3, 3, 3, 3, 3, 3], | |
| [3, 3, 3, 3, 3, 3, 3], | |
| ]; | |
| constructor(context, settings, runtime) { | |
| super(context, settings, runtime); | |
| const w = Character.toPixel(Player.bitMap[0].length); | |
| const h = Character.toPixel(Player.bitMap.length); | |
| this.runtime = { | |
| ...runtime, | |
| delta: Date.now(), | |
| bullets: [], | |
| w, | |
| h, | |
| y: settings.height - h - Character.toPixel(2), | |
| x: Math.round((settings.width - w) / 2), | |
| }; | |
| } | |
| /** | |
| * Shoots a bullet upwards. | |
| */ | |
| shoot() { | |
| const { delta, x, y } = this.runtime; | |
| if (Date.now() - delta > 1000) { | |
| this.runtime.bullets.push( | |
| new PlayerBullet(this.context, this.settings, { x, y }) | |
| ); | |
| this.runtime.delta = Date.now(); | |
| } | |
| } | |
| destroy() { | |
| this.runtime.destroyed = true; | |
| this.runtime.destroyedAt = Date.now(); | |
| } | |
| draw() { | |
| let { x, y, bullets, destroyed, destroyedAt } = this.runtime; | |
| if (destroyed && Date.now() - destroyedAt < 3000) { | |
| return; | |
| } | |
| this.drawBitMap(Player); | |
| this.runtime.bullets = bullets.filter((bullet) => bullet.runtime.y > 0); | |
| for (let i = 0; i < this.runtime.bullets.length; i++) { | |
| bullets[i].draw(); | |
| } | |
| } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
TO DO.