From e7c9eccc4422699fe7229ea45080bd091abfde1f Mon Sep 17 00:00:00 2001 From: Nikos Katsikanis Date: Fri, 21 Oct 2016 23:23:39 +0100 Subject: [PATCH] Add shooting, collision detection and game state- part 4 --- package.json | 3 +- src/Common.ts | 30 ---- src/Game.ts | 267 +++++++++++++++++++++++++-------- src/Waves.ts | 6 +- src/app.js | 1 - src/constants/GameSettings.ts | 10 ++ src/constants/GameStates.ts | 5 + src/constants/Keycodes.ts | 12 ++ src/css/app.css | 2 +- src/gameObjects/Bullets.ts | 55 +++++++ src/gameObjects/Invaders.ts | 64 +++++--- src/gameObjects/Player.ts | 93 ++++++------ src/images/player.svg | 26 ++-- src/images/playerShip.png | Bin 0 -> 1508 bytes src/index.html | 15 +- src/util/CollisionDetection.ts | 8 + src/util/Math.ts | 14 ++ tsconfig.json | 5 +- 18 files changed, 435 insertions(+), 181 deletions(-) delete mode 100644 src/Common.ts create mode 100644 src/constants/GameSettings.ts create mode 100644 src/constants/GameStates.ts create mode 100644 src/constants/Keycodes.ts create mode 100644 src/gameObjects/Bullets.ts create mode 100644 src/images/playerShip.png create mode 100644 src/util/CollisionDetection.ts diff --git a/package.json b/package.json index cfeb04b..1667995 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,9 @@ "main": "webpack.config.js", "dependencies": {}, "devDependencies": { + "@types/node": "^6.0.45", "css-loader": "^0.25.0", - "html-webpack-plugin": "^2.22.0", + "html-webpack-plugin": "https://github.com/ampedandwired/html-webpack-plugin", "style-loader": "^0.13.1", "ts-loader": "^0.9.0", "typescript": "^2.0.3", diff --git a/src/Common.ts b/src/Common.ts deleted file mode 100644 index b831bb8..0000000 --- a/src/Common.ts +++ /dev/null @@ -1,30 +0,0 @@ -export let KEYS = { - BACKSPACE: 8, - TAB: 9, - RETURN: 13, - ESC: 27, - SPACE: 32, - LEFT: 37, - UP: 38, - RIGHT: 39, - DOWN: 40, - DELETE: 46, - HOME: 36, - END: 35, - PAGEUP: 33, - PAGEDOWN: 34, - INSERT: 45, - ZERO: 48, - ONE: 49, - TWO: 50, - A: 65, - L: 76, - P: 80, - Q: 81, - TILDA: 192 -} - -//think of these speeds as relative speeds t -export let GAME_DEFAULTS = { - GAME_SPEED: 50 // the higher the number the faster the game will run -} diff --git a/src/Game.ts b/src/Game.ts index 8ff8dec..16ed01b 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -1,8 +1,15 @@ import {Player} from "./gameObjects/Player" -import {Vector2} from "./util/Math" -import {GAME_DEFAULTS, KEYS} from "./Common" -import {Invader} from "./gameObjects/Invaders"; +import {Vector2, Vector2Normalised} from "./util/Math" +import {KEY_CODES} from "./constants/Keycodes" +import * as GameSettings from "./constants/GameSettings" +import {GAME_OVER, INITIALISING, BATTLE_MODE, YOU_WIN} from "./constants/GameStates" +import {Invader} from "./gameObjects/Invaders" +import {BasicBullet, Bullet} from "./gameObjects/Bullets" import Waves from "./Waves" +import {rectCollides} from "./util/CollisionDetection"; +import {IGameObject} from "./gameObjects/IGameObject"; +import {LARGE_FONT_SIZE} from "./constants/GameSettings"; +import {MEDIUM_FONT_SIZE} from "./constants/GameSettings"; export class Game { static ASPECT_RATIO: number = 1 // keep it square for now @@ -10,10 +17,16 @@ export class Game { static CANVAS_HEIGHT: number = Game.CANVAS_WIDTH / Game.ASPECT_RATIO + static gameState = INITIALISING + static score: number = 0 + player: Player playerOffsetHeight: number = 20 + playerBullets: Array = []; invaders: Array + invaderBullets: Array = []; + canvas: HTMLCanvasElement = document.getElementById('canvas') @@ -21,57 +34,89 @@ export class Game { spaceColor: string = "black" - //for the key events - rightDown: boolean = false - leftDown: boolean = false - upDown: boolean = false - downDown: boolean = false - space: boolean = false + keyStatus = {} + + lastFrame: number = new Date().getTime() + + /** + * Basically we figure out the best width for our canvas at start up. + */ + constructor() { + new Date().getTime() + this.context2D = this.canvas.getContext("2d") + this.canvas.width = Game.CANVAS_WIDTH + this.canvas.height = this.canvas.width / Game.ASPECT_RATIO + + //all keys are down to start + for (let code in KEY_CODES) { + this.keyStatus[KEY_CODES[code]] = false + } + + this.initGame() + } - lastFrame: number = this.timestamp() //init to current time update() { - let start = this.timestamp() + let start = new Date().getTime() let elapsedTime: number = start - this.lastFrame // get the current time as seconds then multiple by the game speed to get a sensible number for multiplying velocities per frame - let elapsedReduced: number = (elapsedTime / 1000.0) * GAME_DEFAULTS.GAME_SPEED + let elapsedReduced: number = (elapsedTime / 1000.0) * GameSettings.GAME_SPEED + + this.drawBackground() + switch (Game.gameState) { + case INITIALISING: + this.drawInit() + return + case YOU_WIN: + this.drawYouWin() + return + case GAME_OVER: + this.drawGameOver() + return + } + + //battle mode this.updatePlayer(elapsedReduced) this.updateEnemies(elapsedReduced); - this.draw() + this.updateBullets(elapsedReduced); + this.handleCollisions(); + + if (this.invaders.length === 0) { + Game.gameState = YOU_WIN + } + + this.drawBattleScene() this.lastFrame = start } - timestamp(): number { - return new Date().getTime() - } - /** - * Basically we figure out the best width for our canvas at start up. - */ - constructor() { - this.context2D = this.canvas.getContext("2d") - this.canvas.width = Game.CANVAS_WIDTH - this.canvas.height = this.canvas.width / Game.ASPECT_RATIO + drawInit() { + this.context2D.fillStyle = '#0FF'; + this.context2D.font = LARGE_FONT_SIZE + "px Verdana"; + this.context2D.fillText("Loading..", 5, 25); + Game.gameState = BATTLE_MODE + } - this.initGame() + drawGameOver() { + this.context2D.fillStyle = '#F00'; + this.context2D.font = LARGE_FONT_SIZE + "px Verdana"; + this.context2D.fillText("Game over!", 5, 25); + } + drawYouWin() { + this.context2D.fillStyle = '#FF0'; + this.context2D.font = LARGE_FONT_SIZE + "px Verdana"; + this.context2D.fillText("YOU win!", 5, 25); } onKeyDown(evt) { - if (evt.keyCode == KEYS.RIGHT) this.rightDown = true - else if (evt.keyCode == KEYS.LEFT) this.leftDown = true - else if (evt.keyCode == KEYS.UP) this.upDown = true - else if (evt.keyCode == KEYS.DOWN) this.downDown = true + this.keyStatus[evt.keyCode] = true } onKeyUp(evt) { - if (evt.keyCode == KEYS.RIGHT) this.rightDown = false - if (evt.keyCode == KEYS.LEFT) this.leftDown = false - if (evt.keyCode == KEYS.UP) this.upDown = false - if (evt.keyCode == KEYS.DOWN) this.downDown = false - if (evt.keyCode == KEYS.SPACE) this.space = false + this.keyStatus[evt.keyCode] = false } initGame() { @@ -82,58 +127,78 @@ export class Game { } drawBackground() { - let self = this - self.context2D.fillStyle = self.spaceColor - self.context2D.fillRect(0, 0, Game.CANVAS_WIDTH, Game.CANVAS_HEIGHT) + this.context2D.fillStyle = this.spaceColor + this.context2D.fillRect(0, 0, Game.CANVAS_WIDTH, Game.CANVAS_HEIGHT) } - draw() { - this.drawBackground() + drawScore() { + this.context2D.fillStyle = '#0FF'; + this.context2D.font = MEDIUM_FONT_SIZE + "px Verdana"; + this.context2D.fillText(`Score: ${Game.score}`, 2, 14); + this.context2D.fillText(`Health: ${this.player.health}`, 2, Game.CANVAS_HEIGHT - 6); + + } + + drawBattleScene() { + this.drawScore() this.player.draw(this.context2D) let self = this; - this.invaders.forEach(function (item:Invader) { - item.draw(self.context2D); + this.invaders.forEach(function (thing: Invader) { + thing.draw(self.context2D); + }); + this.playerBullets.forEach(function (thing: Bullet) { + thing.draw(self.context2D); + }); + this.invaderBullets.forEach(function (thing: Bullet) { + thing.draw(self.context2D); }); } updatePlayer(elapsedTime: number) { - if (this.leftDown) { - this.player.movmentVector.x = -this.player.DefaultMovementSpeed + if (this.keyStatus[KEY_CODES.LEFT]) { + this.player.updateDirection(new Vector2Normalised(270)) } - else if (this.rightDown) { - this.player.movmentVector.x = this.player.DefaultMovementSpeed + else if (this.keyStatus[KEY_CODES.RIGHT]) { + this.player.updateDirection(new Vector2Normalised(90)) } else { - this.player.movmentVector.x = 0 + this.player.remainStationary() + } + + if (this.keyStatus[KEY_CODES.SPACE]) { + let bullet = this.player.shootAhead() + if (bullet) { + this.playerBullets.push(bullet) + } } + this.player.update(elapsedTime) - this.player.clamp(Game.CANVAS_WIDTH, this.canvas.height) + this.clamp(this.player) } - ReverseEnemyDirectionIfOutOfBoundsAndDropDown(): boolean { - let offset = 0; - for (var i = 0; i < this.invaders.length; i++) { - if (this.invaders[i].position.x < 0) { - offset = this.invaders[i].position.x; - break; - } - else if (this.invaders[i].position.x > (Game.CANVAS_WIDTH - this.invaders[i].dimensions.width)) { - offset = this.invaders[i].position.x - (Game.CANVAS_WIDTH - this.invaders[i].dimensions.width); - break; + ReverseEnemyDirectionIfOutOfBoundsAndDropDown(): void { + let outOfBoundsBy = 0 + this.invaders.forEach(item=> { + if (item.position.x < 0) { + outOfBoundsBy = item.position.x + return + } else if (item.position.x > (Game.CANVAS_WIDTH - item.dimensions.width)) { + outOfBoundsBy = item.position.x - (Game.CANVAS_WIDTH - item.dimensions.width) + return } - } - if (offset === 0) { - return; + }) + + if (outOfBoundsBy === 0) { + return } this.invaders.forEach(function (enemy: Invader) { //moving to the right - enemy.movmentVector.x = enemy.movmentVector.x * -1; - enemy.position.x += offset * -1; - // enemy.position.y += enemy.dimensions.height; + enemy.position.x -= outOfBoundsBy + enemy.reverse() enemy.position.y += 10; }); } @@ -147,15 +212,91 @@ export class Game { self.invaders.forEach(function (enemy: Invader) { enemy.update(elapsedUnit);// this might move things out of bounds so check next + // self.clamp(enemy) }); self.ReverseEnemyDirectionIfOutOfBoundsAndDropDown(); + self.invaders.forEach(function (invader: Invader) { + + if (Math.random() < invader.probabilityOfShooting) { + var fire = invader.shootAhead(); + if (fire.hasOwnProperty("length")) { + self.invaderBullets = self.invaderBullets.concat(fire); + } else { + self.invaderBullets.push(fire); + } + } + }); + } + + + updateBullets(elapsedUnit: number) { + this.playerBullets = this.playerBullets.filter(function (bullet) { + return bullet.active; + }); + this.playerBullets.forEach(function (bullet: Bullet) { + bullet.update(elapsedUnit); + }); + + this.invaderBullets = this.invaderBullets.filter(function (bullet) { + return bullet.active; + }); + this.invaderBullets.forEach(function (bullet: Bullet) { + bullet.update(elapsedUnit); + }); + } nextWave() { this.invaders = Waves.shift()(); if (!this.invaders) { - alert("You win!! Well done."); + Game.gameState = YOU_WIN + } + } + + + handleCollisions() { + var self = this; + self.playerBullets.forEach(function (bullet: Bullet) { + self.invaders.forEach(function (invader: Invader) { + if (rectCollides(bullet, invader)) { + invader.takeHit(bullet); + bullet.active = false; + } + }); + } + ); + + self.invaderBullets.forEach(function (bullet: Bullet) { + if (rectCollides(bullet, self.player)) { + self.player.takeDamage(bullet); + var postionCopy = JSON.parse(JSON.stringify(self.player.position)) + bullet.active = false; + } + }); + } + + gameOver() { + alert("you lose!") + } + + + clamp(item: IGameObject) { + if (item.position.x < 0) { + item.position.x = 0 + return + } + else if (item.position.x > (Game.CANVAS_WIDTH - item.dimensions.width)) { + item.position.x = Game.CANVAS_WIDTH - item.dimensions.width + return + } + else if (item.position.y < 0) { + item.position.y = 0 + return + } + else if (item.position.y > (Game.CANVAS_HEIGHT - item.dimensions.height)) { + item.position.y = Game.CANVAS_HEIGHT - item.dimensions.height + return } } } diff --git a/src/Waves.ts b/src/Waves.ts index 69a5584..6a22eba 100644 --- a/src/Waves.ts +++ b/src/Waves.ts @@ -1,5 +1,5 @@ import {LightInvader, Invader} from "./gameObjects/Invaders"; -import {Vector2} from "./util/Math" +import {Vector2, Vector2Normalised} from "./util/Math" let waves: Array<()=> Array> = [] @@ -11,11 +11,11 @@ let initialYOffset = 20; waves.push(function () { for (var i = 0; i <= 7; i++) { - for (var j = 0; j <= 3; j++) { + for (var j = 0; j <= 2; j++) { let position = new Vector2(i * (Invader.DEFAULT_WIDTH + horizontalGap), (j * (Invader.DEFAULT_HEIGHT + verticalGap))) position = position.add(new Vector2(initialXOffset, initialYOffset)) let invader: Invader = new LightInvader(position); - invader.movmentVector.x = Invader.DEFAULT_HORIZONTAL_SPEED; + invader.updateDirection(new Vector2Normalised(90)) arr.push(invader); } } diff --git a/src/app.js b/src/app.js index 50543ad..a36f64c 100644 --- a/src/app.js +++ b/src/app.js @@ -13,7 +13,6 @@ function gameLoop() { requestAnimationFrame(gameLoop) // Drawing code goes here game.update() - game.draw() } gameLoop() diff --git a/src/constants/GameSettings.ts b/src/constants/GameSettings.ts new file mode 100644 index 0000000..1bf5491 --- /dev/null +++ b/src/constants/GameSettings.ts @@ -0,0 +1,10 @@ +export const GAME_SPEED: number = 50 // the higher the number the faster the game will run, all movemments are effected by this number + +export const VERY_SLOW_MOVEMENT_SPEED: number = 1; +export const SLOW_MOVEMENT_SPEED: number = 2; +export const MEDIUM_MOVEMENT_SPEED: number = 4; +export const FAST_MOVEMENT_SPEED: number = 6; +export const VERY_FAST_MOVEMENT_SPEED: number = 12; + +export const MEDIUM_FONT_SIZE:number = 14; +export const LARGE_FONT_SIZE:number = 20; diff --git a/src/constants/GameStates.ts b/src/constants/GameStates.ts new file mode 100644 index 0000000..b386b92 --- /dev/null +++ b/src/constants/GameStates.ts @@ -0,0 +1,5 @@ +export const INITIALISING = 'INITIALISING' +export const GAME_OVER = 'GAME_OVER' +export const BATTLE_MODE = 'BATTLE_MODE' +export const YOU_WIN = 'YOU_WIN' + diff --git a/src/constants/Keycodes.ts b/src/constants/Keycodes.ts new file mode 100644 index 0000000..4c376c8 --- /dev/null +++ b/src/constants/Keycodes.ts @@ -0,0 +1,12 @@ +export let KEY_CODES = { + RETURN: 13, + ESC: 27, + SPACE: 32, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, + ZERO: 48, + ONE: 49, + TWO: 50, +} diff --git a/src/css/app.css b/src/css/app.css index 36a56e4..b3a8ff9 100644 --- a/src/css/app.css +++ b/src/css/app.css @@ -1,5 +1,5 @@ #main-container { - margin: 60px auto 20px auto; + margin: 0 auto; max-width: 1200px; padding: 0 15px; overflow: hidden; diff --git a/src/gameObjects/Bullets.ts b/src/gameObjects/Bullets.ts new file mode 100644 index 0000000..39933a5 --- /dev/null +++ b/src/gameObjects/Bullets.ts @@ -0,0 +1,55 @@ +import {Game} from "../Game"; +import {Vector2, Dimensions2, Vector2Normalised} from "../util/Math" +import {IGameObject} from "./IGameObject" +import * as GameSettings from "../constants/GameSettings" + + +export abstract class Bullet implements IGameObject { + + static SMALL_SIZE: number = 3; + static LARGE_SIZE: number = 9; + + color: any; + + dimensions: Dimensions2; + damageInflicted: number; + protected directionVector: Vector2Normalised + active: boolean = true; + + constructor(public position: Vector2, directionVector: Vector2Normalised) { + this.directionVector = directionVector + } + + inBounds() { + return this.position.x >= 0 && (this.position.x - this.dimensions.width <= Game.CANVAS_WIDTH ) && + this.position.y >= 0 && (this.position.y - this.dimensions.height <= Game.CANVAS_HEIGHT); + } + + draw(canvas: CanvasRenderingContext2D) { + } + + update(elapsedUnit) { + } +} + +export class BasicBullet extends Bullet { + constructor(public position: Vector2, directionVector: Vector2) { + super(position, directionVector); + this.dimensions = new Dimensions2(Bullet.SMALL_SIZE, Bullet.SMALL_SIZE); + this.color = "white"; + this.damageInflicted = 1; + } + + draw(canvas: CanvasRenderingContext2D) { + canvas.fillStyle = this.color; + canvas.fillRect(this.position.x, this.position.y, this.dimensions.width, this.dimensions.height); + } + + update(elapsedUnit) { + this.position.x += this.directionVector.x * elapsedUnit * GameSettings.MEDIUM_MOVEMENT_SPEED + this.position.y += this.directionVector.y * elapsedUnit * GameSettings.MEDIUM_MOVEMENT_SPEED + this.active = this.active && this.inBounds(); + } + +} + diff --git a/src/gameObjects/Invaders.ts b/src/gameObjects/Invaders.ts index fedb9dc..4e6c7ff 100644 --- a/src/gameObjects/Invaders.ts +++ b/src/gameObjects/Invaders.ts @@ -1,59 +1,75 @@ -import {Vector2, Dimensions2} from "../util/Math" +import {Vector2, Dimensions2, Vector2Normalised} from "../util/Math" import {IGameObject} from "./IGameObject" -import {Game} from "../Game" +import {Bullet, BasicBullet} from "./Bullets" +import {MEDIUM_MOVEMENT_SPEED, SLOW_MOVEMENT_SPEED, VERY_SLOW_MOVEMENT_SPEED} from "../constants/GameSettings" +import {Game} from "../Game"; export abstract class Invader implements IGameObject { - health: number; + health: number - static DEFAULT_HEIGHT: number = 20; - static DEFAULT_WIDTH: number = 30; - static DEFAULT_HORIZONTAL_SPEED: number = 1; + static DEFAULT_HEIGHT: number = 20 + static DEFAULT_WIDTH: number = 30 dimensions: Dimensions2 = new Dimensions2(Invader.DEFAULT_WIDTH, Invader.DEFAULT_HEIGHT) - movmentVector: Vector2 = new Vector2(0, 0) + private directionVector: Vector2 = new Vector2(0, 0) - active: boolean = true; - probabilityOfShooting: number = 0.0005; // on each game frame + active: boolean = true + probabilityOfShooting: number = 0.0005 // on each game frame - BasicColor: string; + BasicColor: string + pointsValue:number constructor(public position:Vector2) { } draw(canvas: CanvasRenderingContext2D) { - canvas.fillStyle = this.BasicColor; - canvas.fillRect(this.position.x, this.position.y, this.dimensions.width, this.dimensions.height); + canvas.fillStyle = this.BasicColor + canvas.fillRect(this.position.x, this.position.y, this.dimensions.width, this.dimensions.height) } midpoint() { - return new Vector2(this.position.x + this.dimensions.width / 2, this.position.y + this.dimensions.height / 2); + return new Vector2(this.position.x + this.dimensions.width / 2, this.position.y + this.dimensions.height / 2) } explode() { - this.active = false; + this.active = false + Game.score += this.pointsValue + // todo boom graphic + } + reverse() { + this.directionVector.x = -this.directionVector.x + this.directionVector.y = -this.directionVector.y // todo boom graphic } + updateDirection(directionVector:Vector2Normalised){ + this.directionVector = directionVector + } update(elapsedUnit) { - this.position.x += this.movmentVector.x * elapsedUnit; + this.position.x += this.directionVector.x * elapsedUnit * VERY_SLOW_MOVEMENT_SPEED } - shoot() { + shootAhead() { + // todo Sound.play("shoot") + return new BasicBullet(this.midpoint(), new Vector2(0, 1)) + } + takeHit(bullet:Bullet) { + this.health -= bullet.damageInflicted + if (this.health <= 0) { + this.explode() + } } } export class LightInvader extends Invader { constructor(position: Vector2) { - super(position); - this.BasicColor = "#0F9"; - this.probabilityOfShooting = 0.001; - this.health = 1; - } - - shoot() { - // todo Sound.play("shoot"); + super(position) + this.BasicColor = "#0F9" + this.probabilityOfShooting = 0.001 + this.health = 1 + this.pointsValue = 10 } } diff --git a/src/gameObjects/Player.ts b/src/gameObjects/Player.ts index 349264c..a073553 100644 --- a/src/gameObjects/Player.ts +++ b/src/gameObjects/Player.ts @@ -1,72 +1,81 @@ import {IGameObject} from "./IGameObject" -import {Vector2,Dimensions2} from "../util/Math" +import {Vector2, Dimensions2, Vector2Normalised} from "../util/Math" +import {Bullet, BasicBullet} from "./Bullets"; +import {MEDIUM_MOVEMENT_SPEED} from "../constants/GameSettings"; +import {GAME_OVER} from "../constants/GameStates"; +import {Game} from "../Game"; +//import foo from 'file!../images/player.svg! '; -//var fileContent = require("raw!./images/player.svg"); +//import fileContent from 'raw!./file.txt' -export class Player implements IGameObject { +export class Player implements IGameObject { - color:string = "#FFF" + color: string = "#0FF" - position:Vector2 - dimensions:Dimensions2 = new Dimensions2(Player.DEFAULT_WIDTH, Player.DEFAULT_HEIGHT) - movmentVector:Vector2 = new Vector2(0, 0) - static DEFAULT_HEIGHT:number = 20 - static DEFAULT_WIDTH:number = 40 - DefaultMovementSpeed:number = 7 + position: Vector2 + dimensions: Dimensions2 = new Dimensions2(Player.DEFAULT_WIDTH, Player.DEFAULT_HEIGHT) + private directionVector: Vector2 = new Vector2(0, 0) + static DEFAULT_HEIGHT: number = 30 + static DEFAULT_WIDTH: number = 60 + health: number = 3 + lastShotTime: number = 0 + + fireRatePerSec = 2 constructor(position) { this.position = position } - draw(context2D:CanvasRenderingContext2D) { - context2D.fillStyle = this.color - context2D.fillRect(this.position.x, this.position.y, this.dimensions.width, this.dimensions.height) - context2D.fillRect(this.position.x, this.position.y, this.dimensions.width, this.dimensions.height) - -//todo look at this new way instead of importing svg via webpack -/* var path = new Path2D('M 100,100 h 50 v 50 h 50'); - ctx.stroke(path);*/ + draw(context2D: CanvasRenderingContext2D) { + context2D.drawImage(img, this.position.x, this.position.y); } update(elapsedUnit) { - this.position.x += this.movmentVector.x * elapsedUnit - this.position.y += this.movmentVector.y * elapsedUnit - } - - clamp(gameWidth:number, gameHeight:number) { - if (this.position.x < 0) { - this.position.x = 0 - return - } - else if (this.position.x > (gameWidth - this.dimensions.width)) { - this.position.x = gameWidth - this.dimensions.width - return - } - else if (this.position.y < 0) { - this.position.y = 0 - return - } - else if (this.position.y > (gameHeight - this.dimensions.height)) { - this.position.y = gameHeight - this.dimensions.height - return - } + this.position.x += this.directionVector.x * elapsedUnit * MEDIUM_MOVEMENT_SPEED + this.position.y += this.directionVector.y * elapsedUnit * MEDIUM_MOVEMENT_SPEED } midpoint() { return new Vector2(this.position.x + this.dimensions.width / 2, this.position.y + this.dimensions.height / 2) } - //todo explode() { - this.color = "#F00" + Game.gameState = GAME_OVER } -} + shootAhead(): Bullet { + // todo Sound.play("shoot"); + let timeDifference = new Date().getTime() - this.lastShotTime + if (timeDifference > (1000 / this.fireRatePerSec)) { + this.lastShotTime = new Date().getTime() + return new BasicBullet(this.midpoint(), new Vector2Normalised(0)) + } else { + return null + } + } + updateDirection(directionVector: Vector2Normalised) { + this.directionVector = directionVector + } + + remainStationary() { + this.directionVector.x = 0 + this.directionVector.y = 0 + } + + takeDamage(bullet: Bullet) { + this.health -= bullet.damageInflicted; + if (this.health <= 0) { + this.explode(); + } + } +} +var img = new Image(); +img.src = require('file?name=player.svg!../images/player.svg') diff --git a/src/images/player.svg b/src/images/player.svg index 4ced065..7464364 100644 --- a/src/images/player.svg +++ b/src/images/player.svg @@ -1,14 +1,20 @@ - + - background - - - - - - - Layer 1 - + Player ship + + + + + + diff --git a/src/images/playerShip.png b/src/images/playerShip.png new file mode 100644 index 0000000000000000000000000000000000000000..8d70c9f8166b1a6e4a64175ffaf13b20cf0dbf18 GIT binary patch literal 1508 zcmeAS@N?(olHy`uVBq!ia0vp^x!DajJoh?3y^w370~qErUQl>DSr z1<%~X^wgl##FWaylc_*O%u1Od5hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|81#=KlDb#X~hD#E>34K5C;EJ)Q4 zN-fSWElLJPT$(b-ssbzLqSVBa{GyQj{2W*)24v)yGj(=!b+j-yG;}q#v~)3bHZ(Ibbuu?J zb#gO>>2=9ZF3nBND}m`vLFjeDsTY(KatnYqyQCInmZhe+73JqDfW2&$iQ6p}IL(9V zO~LIJOPqT3fsWA!MJ!T8!-RmT2gHOYTObFX@Kf`Esl5o8toKIhOk-eRitu!C45_&F zX2yQ+!ww?H-#h*1VZY0z$Je_+NL_v5WQBi>M;%+%{S}sR5jf)bi@igEuPIo)TlNx9 zY)tO|$;S65l<$lA{P@z6OHT4FX47|<&)-vX|Mb%(L0(sK+u!w-SxNb)-u)+inqgmk zjpv-}e0?7JMKW8qUA{Ht{qHX6a~JcB?|mycB6W-H*ZnnmzRO=)`d`;xnfY_sHE%hV zJvs%;&)s^n>z#bYhjj-p_PzeIbaPdw1oP|rzF!kARFpj2IBCLzN0*8?D$3uhtZGO- zxO3j;oWR)QHiGiKclQV`K9#5zUlCXSvREtYtd~@%YLI%#)epKh_YTI+kB#Y0+dNZX z|0JWY9jkkGai&e}Fq~KO^vu%V&HQs-%>Pm3*jDGy*5UAK`mw+rFIIA8H%-33K4IF| zQs=2_=h!@$(y({OTJd$4PQAE!#q`+WJreVtt#~Ru(fzvii`V(AN%31Ii!Qm^cgy+C znZkLsS3WO%xvsM0tmm%q=eu|>9I@d%zIqH-03A(cu{|W>Qt+JCzW{E%|0;r zF`vmg-}JOH>4ZPip2TU3b+ns~_cH5fUz+IBv`;igu>VPQWo0(gByPzEI~{{uCz{3k zsO8+-G$Vhl=WX5-i(L8UT$-eyd#Pma3HO)Ny|2wxI(27ZlF-A8rx+J+6x#PAcvG)g e{iXd(JPdZvgJ!cI5S|ArcRgMGT-G@yGywpmzfF<= literal 0 HcmV?d00001 diff --git a/src/index.html b/src/index.html index 0e233a6..ba78fad 100644 --- a/src/index.html +++ b/src/index.html @@ -1,9 +1,5 @@
- - -

You have reached the Space Invaders game!

- -

You project has compiled successfully

+

You have reached the Space Invaders game!

Your browser doesn't appear to support the HTML5 <canvas> element. @@ -12,3 +8,12 @@

You have reached the Space Invaders game!

This code matches the last video of the youtube series here

+ + + + Fork me on GitHub + + diff --git a/src/util/CollisionDetection.ts b/src/util/CollisionDetection.ts new file mode 100644 index 0000000..e0253c9 --- /dev/null +++ b/src/util/CollisionDetection.ts @@ -0,0 +1,8 @@ +import {IGameObject} from "../gameObjects/IGameObject"; + +export function rectCollides(a: IGameObject, b: IGameObject) { + return a.position.x < b.position.x + b.dimensions.width && + a.position.x + a.dimensions.width > b.position.x && + a.position.y < b.position.y + b.dimensions.height && + a.position.y + a.dimensions.height > b.position.y; +} diff --git a/src/util/Math.ts b/src/util/Math.ts index 3fa41fa..2ab8f57 100644 --- a/src/util/Math.ts +++ b/src/util/Math.ts @@ -10,5 +10,19 @@ export class Vector2 { public add(otherVector: Vector2): Vector2 { return new Vector2(this.x + otherVector.x, this.y + otherVector.y); } + + public reverse(): Vector2 { + return new Vector2(-this.x, -this.y); + } } + +export class Vector2Normalised extends Vector2 { + constructor(degrees: number) { + super(Math.sin(degreesToRadians(degrees)), -Math.cos(degreesToRadians(degrees))); + } +} + +function degreesToRadians(degrees: number): number { + return (degrees / 360 ) * 2 * Math.PI +} diff --git a/tsconfig.json b/tsconfig.json index 876c1b6..9789a35 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,5 +3,8 @@ "target": "es5", "sourceMap": true }, - "files": [] + "files": [], + "types": [ + "node" + ] }