├── img ├── orc.png ├── tower.png ├── gameMap.png ├── explosion.png └── projectile.png ├── .prettierrc ├── js ├── classes │ ├── Projectile.js │ ├── PlacementTile.js │ ├── Sprite.js │ ├── Building.js │ └── Enemy.js ├── waypoints.js ├── placementTilesData.js └── index.js └── index.html /img/orc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/tower-defense/HEAD/img/orc.png -------------------------------------------------------------------------------- /img/tower.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/tower-defense/HEAD/img/tower.png -------------------------------------------------------------------------------- /img/gameMap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/tower-defense/HEAD/img/gameMap.png -------------------------------------------------------------------------------- /img/explosion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/tower-defense/HEAD/img/explosion.png -------------------------------------------------------------------------------- /img/projectile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/tower-defense/HEAD/img/projectile.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": false, 4 | "trailingComma": "none", 5 | "overrides": [ 6 | { 7 | "files": "*.html", 8 | "options": { 9 | "parser": "html" 10 | } 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /js/classes/Projectile.js: -------------------------------------------------------------------------------- 1 | class Projectile extends Sprite { 2 | constructor({ position = { x: 0, y: 0 }, enemy }) { 3 | super({ position, imageSrc: 'img/projectile.png' }) 4 | this.velocity = { 5 | x: 0, 6 | y: 0 7 | } 8 | this.enemy = enemy 9 | this.radius = 10 10 | } 11 | 12 | update() { 13 | this.draw() 14 | 15 | const angle = Math.atan2( 16 | this.enemy.center.y - this.position.y, 17 | this.enemy.center.x - this.position.x 18 | ) 19 | 20 | const power = 5 21 | this.velocity.x = Math.cos(angle) * power 22 | this.velocity.y = Math.sin(angle) * power 23 | 24 | this.position.x += this.velocity.x 25 | this.position.y += this.velocity.y 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /js/classes/PlacementTile.js: -------------------------------------------------------------------------------- 1 | class PlacementTile { 2 | constructor({ position = { x: 0, y: 0 } }) { 3 | this.position = position 4 | this.size = 64 5 | this.color = 'rgba(255, 255, 255, 0.15)' 6 | this.occupied = false 7 | } 8 | 9 | draw() { 10 | c.fillStyle = this.color 11 | c.fillRect(this.position.x, this.position.y, this.size, this.size) 12 | } 13 | 14 | update(mouse) { 15 | this.draw() 16 | 17 | if ( 18 | mouse.x > this.position.x && 19 | mouse.x < this.position.x + this.size && 20 | mouse.y > this.position.y && 21 | mouse.y < this.position.y + this.size 22 | ) { 23 | this.color = 'white' 24 | } else this.color = 'rgba(255, 255, 255, 0.15)' 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /js/waypoints.js: -------------------------------------------------------------------------------- 1 | const waypoints = [ 2 | { 3 | x: -124.21331566744, 4 | y: 475.322954620735 5 | }, 6 | { 7 | x: 276.581649552832, 8 | y: 472.01059953627 9 | }, 10 | { 11 | x: 276.581649552832, 12 | y: 157.33686651209 13 | }, 14 | { 15 | x: 735.342828751242, 16 | y: 158.993044054323 17 | }, 18 | { 19 | x: 735.342828751242, 20 | y: 405.763497846969 21 | }, 22 | { 23 | x: 606.160980457105, 24 | y: 407.419675389202 25 | }, 26 | { 27 | x: 606.160980457105, 28 | y: 669.095727061941 29 | }, 30 | { 31 | x: 1046.70420669096, 32 | y: 665.783371977476 33 | }, 34 | { 35 | x: 1050.01656177542, 36 | y: 288.17489234846 37 | }, 38 | { 39 | x: 1407.75091089765, 40 | y: 286.518714806227 41 | } 42 | ] 43 | -------------------------------------------------------------------------------- /js/placementTilesData.js: -------------------------------------------------------------------------------- 1 | const placementTilesData = [ 2 | 0, 14, 0, 14, 0, 14, 0, 14, 0, 14, 0, 14, 0, 14, 0, 0, 0, 0, 0, 0, 0, 14, 0, 3 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 4 | 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5 | 14, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 14, 0, 14, 0, 0, 0, 0, 14, 0, 0, 0, 6 | 0, 0, 0, 0, 14, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 7 | 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 14, 8 | 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 9 | 0, 14, 0, 0, 0, 0, 14, 0, 0, 0, 14, 0, 14, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10 | 0, 0, 14, 0, 0, 0, 14, 0, 14, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11 | 0, 14, 0, 14, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 12 | ] 13 | -------------------------------------------------------------------------------- /js/classes/Sprite.js: -------------------------------------------------------------------------------- 1 | class Sprite { 2 | constructor({ 3 | position = { x: 0, y: 0 }, 4 | imageSrc, 5 | frames = { max: 1 }, 6 | offset = { x: 0, y: 0 } 7 | }) { 8 | this.position = position 9 | this.image = new Image() 10 | this.image.src = imageSrc 11 | this.frames = { 12 | max: frames.max, 13 | current: 0, 14 | elapsed: 0, 15 | hold: 3 16 | } 17 | this.offset = offset 18 | } 19 | 20 | draw() { 21 | const cropWidth = this.image.width / this.frames.max 22 | const crop = { 23 | position: { 24 | x: cropWidth * this.frames.current, 25 | y: 0 26 | }, 27 | width: cropWidth, 28 | height: this.image.height 29 | } 30 | c.drawImage( 31 | this.image, 32 | crop.position.x, 33 | crop.position.y, 34 | crop.width, 35 | crop.height, 36 | this.position.x + this.offset.x, 37 | this.position.y + this.offset.y, 38 | crop.width, 39 | crop.height 40 | ) 41 | } 42 | 43 | update() { 44 | // responsible for animation 45 | this.frames.elapsed++ 46 | if (this.frames.elapsed % this.frames.hold === 0) { 47 | this.frames.current++ 48 | if (this.frames.current >= this.frames.max) { 49 | this.frames.current = 0 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /js/classes/Building.js: -------------------------------------------------------------------------------- 1 | class Building extends Sprite { 2 | constructor({ position = { x: 0, y: 0 } }) { 3 | super({ 4 | position, 5 | imageSrc: './img/tower.png', 6 | frames: { 7 | max: 19 8 | }, 9 | offset: { 10 | x: 0, 11 | y: -80 12 | } 13 | }) 14 | 15 | this.width = 64 * 2 16 | this.height = 64 17 | this.center = { 18 | x: this.position.x + this.width / 2, 19 | y: this.position.y + this.height / 2 20 | } 21 | this.projectiles = [] 22 | this.radius = 250 23 | this.target 24 | } 25 | 26 | draw() { 27 | super.draw() 28 | 29 | // c.beginPath() 30 | // c.arc(this.center.x, this.center.y, this.radius, 0, Math.PI * 2) 31 | // c.fillStyle = 'rgba(0, 0, 255, 0.2)' 32 | // c.fill() 33 | } 34 | 35 | update() { 36 | this.draw() 37 | if (this.target || (!this.target && this.frames.current !== 0)) 38 | super.update() 39 | 40 | if ( 41 | this.target && 42 | this.frames.current === 6 && 43 | this.frames.elapsed % this.frames.hold === 0 44 | ) 45 | this.shoot() 46 | } 47 | 48 | shoot() { 49 | this.projectiles.push( 50 | new Projectile({ 51 | position: { 52 | x: this.center.x - 20, 53 | y: this.center.y - 110 54 | }, 55 | enemy: this.target 56 | }) 57 | ) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /js/classes/Enemy.js: -------------------------------------------------------------------------------- 1 | class Enemy extends Sprite { 2 | constructor({ position = { x: 0, y: 0 } }) { 3 | super({ 4 | position, 5 | imageSrc: 'img/orc.png', 6 | frames: { 7 | max: 7 8 | } 9 | }) 10 | this.position = position 11 | this.width = 100 12 | this.height = 100 13 | this.waypointIndex = 0 14 | this.center = { 15 | x: this.position.x + this.width / 2, 16 | y: this.position.y + this.height / 2 17 | } 18 | this.radius = 50 19 | this.health = 100 20 | this.velocity = { 21 | x: 0, 22 | y: 0 23 | } 24 | } 25 | 26 | draw() { 27 | super.draw() 28 | 29 | // health bar 30 | c.fillStyle = 'red' 31 | c.fillRect(this.position.x, this.position.y - 15, this.width, 10) 32 | 33 | c.fillStyle = 'green' 34 | c.fillRect( 35 | this.position.x, 36 | this.position.y - 15, 37 | (this.width * this.health) / 100, 38 | 10 39 | ) 40 | } 41 | 42 | update() { 43 | this.draw() 44 | super.update() 45 | 46 | const waypoint = waypoints[this.waypointIndex] 47 | const yDistance = waypoint.y - this.center.y 48 | const xDistance = waypoint.x - this.center.x 49 | const angle = Math.atan2(yDistance, xDistance) 50 | 51 | const speed = 3 52 | 53 | this.velocity.x = Math.cos(angle) * speed 54 | this.velocity.y = Math.sin(angle) * speed 55 | 56 | this.position.x += this.velocity.x 57 | this.position.y += this.velocity.y 58 | 59 | this.center = { 60 | x: this.position.x + this.width / 2, 61 | y: this.position.y + this.height / 2 62 | } 63 | 64 | if ( 65 | Math.abs(Math.round(this.center.x) - Math.round(waypoint.x)) < 66 | Math.abs(this.velocity.x) && 67 | Math.abs(Math.round(this.center.y) - Math.round(waypoint.y)) < 68 | Math.abs(this.velocity.y) && 69 | this.waypointIndex < waypoints.length - 1 70 | ) { 71 | this.waypointIndex++ 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 13 | 14 |