components and new tower

This commit is contained in:
Evert Prants 2017-08-22 15:39:45 +03:00
parent 87aea4bd6f
commit c00dacd4cb
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
1 changed files with 323 additions and 94 deletions

417
index.js
View File

@ -6,6 +6,9 @@ window.onload = function () {
* I use Object.assign to copy objects in order to eliminate references when spawning enemies * I use Object.assign to copy objects in order to eliminate references when spawning enemies
and to get the ability to modify them individually and to get the ability to modify them individually
* Components such as buttons or selections within the canvas are classes * Components such as buttons or selections within the canvas are classes
* if, function and else have spaces between both '(' and '{'
`if (thing) {}` not `if(thing){}`
* Keep all variables local to their scope (in other words, use `let` instead of `var`)
*/ */
let canvas = document.getElementById('canvas') let canvas = document.getElementById('canvas')
@ -36,6 +39,7 @@ window.onload = function () {
waveTimer: 0, waveTimer: 0,
tower: 'simple', tower: 'simple',
towerSel: null, towerSel: null,
debug: false,
sellRatio: .8 sellRatio: .8
} }
@ -67,7 +71,7 @@ window.onload = function () {
tough: { tough: {
speed: 5, speed: 5,
node: 1, node: 1,
health: 100, health: 150,
reward: 20, reward: 20,
frequency: 1000, frequency: 1000,
icon: '#f40' icon: '#f40'
@ -80,30 +84,44 @@ window.onload = function () {
damage: 15, // damage to deal to enemies when hit damage: 15, // damage to deal to enemies when hit
rate: 20, // rate of fire, higher - slower rate: 20, // rate of fire, higher - slower
name: 'Simple', // name of the tower name: 'Simple', // name of the tower
description: 'Basic tower', description: 'Medium rate and damage',
speed: 30, // bullet speed, higher - faster speed: 30, // bullet speed, higher - faster
cost: 50, // cost to place cost: 50, // cost to place
icon: '#333' // currently color icon: '#333', // currently color
bullet: 1 // The type of bullet. 1: damage, 2: slow down by damage, 3: instant kill
}, },
rapid: { rapid: {
range: 3, range: 3,
damage: 5, damage: 5,
rate: 5, rate: 5,
name: 'Rapid', name: 'Rapid',
description: 'Rapid-firing tower', description: 'Rapid-firing but low damage',
speed: 30, speed: 30,
cost: 250, cost: 250,
icon: '#303' icon: '#303',
bullet: 1
}, },
snipe: { sticky: {
range: 5, range: 3,
damage: 150, damage: 10,
rate: 100, rate: 30,
name: 'Sniper', name: 'Sticky',
description: 'Slow but powerful shots', description: 'Slow down enemies by damage',
speed: 50, speed: 50,
cost: 500, cost: 500,
icon: '#4f3' icon: '#4f3',
bullet: 2
},
snipe: {
range: 10,
damage: 1500,
rate: 100,
name: 'Sniper',
description: 'Slow firing but always kills',
speed: 50,
cost: 1000,
icon: '#4f3',
bullet: 1
} }
} }
@ -157,12 +175,114 @@ window.onload = function () {
class Component { class Component {
constructor (x, y) { constructor (x, y) {
this.visible = true this.visible = true
this.elements = []
this.x = x this.x = x
this.y = y this.y = y
} }
draw () {} elDraw() {
update() {} for (let i in this.elements) {
let elem = this.elements[i]
if (elem instanceof Component) {
elem.draw()
}
}
}
elUpdate() {
for (let i in this.elements) {
let elem = this.elements[i]
if (elem instanceof Component) {
elem.update()
}
}
}
addElement (el) {
if (!(el instanceof Component)) return
this.elements.push(el)
}
draw () {
this.elDraw()
}
update() {
this.elUpdate()
}
}
class Tooltip extends Component {
constructor () {
super(0, 0)
this.font = '20px Helvetica'
this.text = ''
this.w = 0
this.h = 24
this.components = []
this.visible = false
}
static assign (tooltip, component, text) {
tooltip.addComponent(component, text)
}
setText (str) {
if (this.text === str) return
this.text = str
if (this.font) ctx.font = this.font
this.w = ctx.measureText(this.text).width + this.h
}
draw () {
if (!this.visible) return
if (this.text === '') return
if (this.font) ctx.font = this.font
let aX = this.x
let aY = this.y
if (aX + this.w > canvas.width) {
aX -= this.w + 5
}
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)'
ctx.fillRect(aX, aY, this.w, this.h)
ctx.fillStyle = '#000'
ctx.fillText(this.text, aX + this.h / 2, aY + this.h / 2 + 5)
}
setPosition (x, y) {
this.x = x
this.y = y
}
update () {
if (this.components.length) {
let cmps = false
for (let i in this.components) {
let cmp = this.components[i]
if (mXr > cmp.x && mYr > cmp.y &&
mXr < cmp.x + cmp.w && mYr < cmp.y + cmp.h) {
this.setPosition(mXr, mYr)
this.setText(cmp.text)
cmps = true
}
}
this.visible = cmps
}
}
addComponent (component, text) {
this.components.push({
x: component.x,
y: component.y,
w: component.w,
h: component.h,
text: text
})
}
} }
class ButtonComponent extends Component { class ButtonComponent extends Component {
@ -197,9 +317,19 @@ window.onload = function () {
ctx.fillStyle = 'rgba(0, 0, 0, 0.45)' ctx.fillStyle = 'rgba(0, 0, 0, 0.45)'
ctx.fillRect(this.x, this.y, this.w, this.h) ctx.fillRect(this.x, this.y, this.w, this.h)
} else if (this.hovered) { } else if (this.hovered) {
ctx.fillStyle = 'rgba(255, 255, 255, 0.25)' ctx.fillStyle = 'rgba(255, 255, 255, 0.15)'
ctx.fillRect(this.x, this.y, this.w, this.h) ctx.fillRect(this.x, this.y, this.w, this.h)
} }
this.elDraw()
}
update () {
if (!this.visible || this.disabled) return
if (mXr > this.x && mYr > this.y && mXr < this.x + this.w && mYr < this.y + this.h) {
this.hovered = true
} else if (this.hovered) {
this.hovered = false
}
} }
setDisabled (disable) { setDisabled (disable) {
@ -222,13 +352,23 @@ window.onload = function () {
this.textColor = '#fff' this.textColor = '#fff'
this.color = '#995522' this.color = '#995522'
this.fn = this.select this.fn = this.select
this.font = '14px Helvetica'
} }
select () { select () {
Game.tower = this.tower Game.tower = this.tower
} }
addTooltip () {
Tooltip.assign(Components.tooltip, this, this.towerObj.description)
}
update () {
super.update()
this.disabled = this.towerObj.cost > Game.money
this.active = Game.tower === this.tower
this.elUpdate()
}
draw () { draw () {
if (!this.visible) return if (!this.visible) return
if (this.active) { if (this.active) {
@ -261,26 +401,113 @@ window.onload = function () {
ctx.fillStyle = 'rgba(0, 0, 0, 0.45)' ctx.fillStyle = 'rgba(0, 0, 0, 0.45)'
ctx.fillRect(this.x, this.y, this.w, this.h) ctx.fillRect(this.x, this.y, this.w, this.h)
} else if (this.hovered) { } else if (this.hovered) {
ctx.fillStyle = 'rgba(255, 255, 255, 0.25)' ctx.fillStyle = 'rgba(255, 255, 255, 0.15)'
ctx.fillRect(this.x, this.y, this.w, this.h) ctx.fillRect(this.x, this.y, this.w, this.h)
} }
this.elDraw()
} }
} }
function clickBtn () { class InfoDialog extends Component {
constructor () {
super()
this.x = 0
this.y = (Maps.height - 5) * Maps.tile
this.w = Maps.width * Maps.tile
this.h = 5 * Maps.tile
this.createButton()
}
createButton () {
let btn = new ButtonComponent('Sell Tower', '#fff', '#f11', 490, 590, 140, 40, () => {
if (Game.towerSel) {
sellTower(Game.towerSel.x, Game.towerSel.y)
}
})
btn.update = function () {
this.visible = Game.towerSel !== null
if (!this.visible) return
if (mXr > this.x && mYr > this.y && mXr < this.x + this.w && mYr < this.y + this.h) {
this.hovered = true
} else if (this.hovered) {
this.hovered = false
}
}
this.addElement(btn)
}
draw () {
if (Game.towerSel) {
let ts = Game.towerSel
ctx.fillStyle = 'rgba(0, 0, 0, 0.45)'
ctx.fillRect(this.x, this.y, this.w, this.h)
ctx.fillStyle = '#fff'
ctx.font = '25px Helvetica'
ctx.fillText(ts.name + ' Tower', 5, this.y + 25)
ctx.font = '15px Helvetica'
ctx.fillText(ts.description, 5, this.y + 42)
ctx.fillText('Range: ' + ts.range + ' tiles', 5, this.y + 70)
ctx.fillText('Damage: ' + ts.damage + ' HP', 5, this.y + 85)
ctx.fillText('Fire Rate: ' + ts.rate, 5, this.y + 100)
ctx.fillText('Kills: ' + ts.killcount, 5, this.y + 115)
ctx.fillText('Fired ' + ts.fires + ' times', 5, this.y + 130)
}
this.elDraw()
}
}
function clickBtn (cmp) {
let click = false let click = false
for (let i in Components) { let compList = cmp != null && cmp instanceof Component ? cmp.elements : null
let btn = Components[i] if (cmp == null && compList == null) {
if (!(btn instanceof ButtonComponent)) continue compList = Components
if (btn.disabled || !btn.visible) continue }
if (mXr > btn.x && mYr > btn.y && mXr < btn.x + btn.w && mYr < btn.y + btn.h) {
btn.fn() for (let i in compList) {
click = true let btn = compList[i]
if (!(btn instanceof ButtonComponent)) {
// Loop through sub-components of components
if (btn.elements.length) {
click = clickBtn(btn)
} else {
continue
}
} else {
// Click the button if its in bounds, visible and not disabled
if (btn.disabled || !btn.visible) continue
if (mXr > btn.x && mYr > btn.y && mXr < btn.x + btn.w && mYr < btn.y + btn.h && btn) {
btn.fn()
click = true
}
} }
} }
return click return click
} }
function updateComponents (cmp) {
// Determine which object of components to update
let compList = cmp != null && cmp instanceof Component ? cmp.elements : null
if (cmp == null && compList == null) {
compList = Components
}
for (let i in compList) {
let component = compList[i]
component.update()
if (component.elements.length) {
updateComponents(component)
}
}
}
// Use this function to spawn enemies depending on round // Use this function to spawn enemies depending on round
function nextWave () { function nextWave () {
Game.wave++ Game.wave++
@ -303,7 +530,7 @@ window.onload = function () {
// Use this function to modify the enemies spawned each round // Use this function to modify the enemies spawned each round
function waveEnemyModifer (enemy, round) { function waveEnemyModifer (enemy, round) {
// Reduce the time between enemy spawns // Reduce the time between enemy spawns
let fr = enemy.frequency - 2 * round let fr = enemy.frequency - 5 * round
if (fr < 100) { if (fr < 100) {
fr = 100 fr = 100
} }
@ -400,6 +627,8 @@ window.onload = function () {
target = enemiesProxima[enemiesProxima.length - 1] target = enemiesProxima[enemiesProxima.length - 1]
} }
tower.fires++
Game.particles.push({ Game.particles.push({
x: tower.x, x: tower.x,
y: tower.y, y: tower.y,
@ -408,9 +637,9 @@ window.onload = function () {
velY: (target.y - tower.y) / target.dist * 1.24, velY: (target.y - tower.y) / target.dist * 1.24,
dmg: tower.damage, dmg: tower.damage,
speed: tower.speed, speed: tower.speed,
life: 100 type: tower.bullet || 1,
life: 30
}) })
tower.fires++
} }
function tickTowers () { function tickTowers () {
@ -446,15 +675,22 @@ window.onload = function () {
if (parti.x >= enemy.x - 0.25 && parti.y >= enemy.y - 0.25 && if (parti.x >= enemy.x - 0.25 && parti.y >= enemy.y - 0.25 &&
parti.x <= enemy.x + 0.5 && parti.y <= enemy.y + 0.5) { parti.x <= enemy.x + 0.5 && parti.y <= enemy.y + 0.5) {
// damage enemy // damage enemy
enemy.dmg -= parti.dmg if (parti.type === 1) {
enemy.dmg -= parti.dmg
if (enemy.dmg <= 0) { if (enemy.dmg <= 0) {
Game.enemies.splice(j, 1) Game.enemies.splice(j, 1)
Game.money += 10 Game.money += 10
let tower = getTowerAt(parti.tower.x, parti.tower.y) let tower = getTowerAt(parti.tower.x, parti.tower.y)
if (tower) { if (tower) {
tower.killcount++ tower.killcount++
}
}
} else if (parti.type === 2) {
enemy.speed -= parti.dmg
if (enemy.speed < 2) {
enemy.speed = 2
} }
} }
@ -576,7 +812,7 @@ window.onload = function () {
function sellTower (x, y) { function sellTower (x, y) {
let tower = getTowerAt(x, y) let tower = getTowerAt(x, y)
if(tower) { if (tower) {
let amount = tower.cost * Game.sellRatio let amount = tower.cost * Game.sellRatio
Game.money += amount Game.money += amount
Game.selltext.push({ Game.selltext.push({
@ -591,12 +827,13 @@ window.onload = function () {
} }
return Game.towers.splice(Game.towers.indexOf(tower), 1) return Game.towers.splice(Game.towers.indexOf(tower), 1)
}else{ } else {
return null return null
} }
} }
function update (dt) { function update (dt) {
// Update FPS count for drawing (Don't render a new number every frame, it changes too fast)
fpsCount++ fpsCount++
fpsCount %= 20 fpsCount %= 20
if (fpsCount === 0) { if (fpsCount === 0) {
@ -608,32 +845,25 @@ window.onload = function () {
tickTowers() tickTowers()
} }
// Move enemies
updateEnemyMovements() updateEnemyMovements()
// Move bullets
tickParticles() tickParticles()
// Move sell texts
tickSellText() tickSellText()
for (let i in Components) { // Update all components (eg buttons)
let btn = Components[i] updateComponents()
if (btn instanceof ButtonComponent) {
if (mXr > btn.x && mYr > btn.y && mXr < btn.x + btn.w && mYr < btn.y + btn.h) {
btn.hovered = true
} else if (btn.hovered) {
btn.hovered = false
}
if (btn instanceof TowerButton) {
btn.disabled = btn.towerObj.cost > Game.money
btn.active = Game.tower === i
}
}
}
// Set the state
updateGameState() updateGameState()
// Increment game clock
if (Game.state === 1) { if (Game.state === 1) {
Game.waveTimer++ Game.waveTimer++
} }
Components.sell.visible = Game.towerSel !== null
} }
function render () { function render () {
@ -641,6 +871,7 @@ window.onload = function () {
ctx.fillStyle = '#0fa' ctx.fillStyle = '#0fa'
ctx.fillRect(0, 0, canvas.width, canvas.height) ctx.fillRect(0, 0, canvas.width, canvas.height)
// Draw the map
for (let i in Game.map.tiles) { for (let i in Game.map.tiles) {
let tile = Game.map.tiles[i] let tile = Game.map.tiles[i]
let index = parseInt(i) let index = parseInt(i)
@ -658,28 +889,34 @@ window.onload = function () {
draw_tile = false draw_tile = false
} }
if(draw_tile) { if (draw_tile) {
ctx.fillRect(x * mt, y * mt, mt, mt) ctx.fillRect(x * mt, y * mt, mt, mt)
} }
if(Game.state == 2 && tile == 0 && !getTowerAt(x, y) && !canPlaceTowerAt(x, y)) { // Draw obstructed tiles
if (Game.state == 2 && tile == 0 && !getTowerAt(x, y) && !canPlaceTowerAt(x, y)) {
ctx.fillStyle = 'rgba(255, 0, 0, 0.45)' ctx.fillStyle = 'rgba(255, 0, 0, 0.45)'
ctx.fillRect(x * mt, y * mt, mt, mt) ctx.fillRect(x * mt, y * mt, mt, mt)
} }
} }
/*
for (let i in Game.map.pathgen) { // Show the enemy movement path
let node = Game.map.pathgen[i] if (Game.debug) {
ctx.fillStyle = '#00f' for (let i in Game.map.pathgen) {
ctx.fillRect((node.x * mt) + mt / 3, (node.y * mt) + mt / 3, 8, 8) let node = Game.map.pathgen[i]
ctx.fillStyle = '#00f'
ctx.fillRect((node.x * mt) + mt / 3, (node.y * mt) + mt / 3, 8, 8)
}
} }
*/
// Draw towers
for (let i in Game.towers) { for (let i in Game.towers) {
let tower = Game.towers[i] let tower = Game.towers[i]
ctx.fillStyle = tower.icon ctx.fillStyle = tower.icon
ctx.fillRect(tower.x * mt + 2, tower.y * mt + 2, 28, 28) ctx.fillRect(tower.x * mt + 2, tower.y * mt + 2, 28, 28)
} }
// Draw enemies
for (let i in Game.enemies) { for (let i in Game.enemies) {
let enemy = Game.enemies[i] let enemy = Game.enemies[i]
let rx = (enemy.x * mt) + mt / 8 let rx = (enemy.x * mt) + mt / 8
@ -697,13 +934,14 @@ window.onload = function () {
ctx.fillRect(hx, hy, (16 + 12) * enemy.dmg / enemy.health, 5) ctx.fillRect(hx, hy, (16 + 12) * enemy.dmg / enemy.health, 5)
} }
// Draw bullets
for (let i in Game.particles) { for (let i in Game.particles) {
let tower = Game.particles[i] let tower = Game.particles[i]
ctx.fillStyle = '#f33' ctx.fillStyle = '#f33'
ctx.fillRect(tower.x * mt + mt / 16, tower.y * mt + mt / 16, 8, 8) ctx.fillRect(tower.x * mt + mt / 16, tower.y * mt + mt / 16, 8, 8)
} }
// tower range visualization // Tower range visualization
let towerData = Towers[Game.tower] let towerData = Towers[Game.tower]
let vX = null let vX = null
let vY = null let vY = null
@ -730,6 +968,7 @@ window.onload = function () {
ctx.closePath() ctx.closePath()
} }
// Render sell text
for (let i in Game.selltext) { for (let i in Game.selltext) {
let txt = Game.selltext[i] let txt = Game.selltext[i]
ctx.font = '12px Helvetica' ctx.font = '12px Helvetica'
@ -737,9 +976,11 @@ window.onload = function () {
ctx.fillText('+ $' + txt.amount, txt.x * mt, txt.y * mt) ctx.fillText('+ $' + txt.amount, txt.x * mt, txt.y * mt)
} }
// Render sidebar background
ctx.fillStyle = '#996633' ctx.fillStyle = '#996633'
ctx.fillRect(640, 0, 240, 640) ctx.fillRect(640, 0, 240, 640)
// Render sidebar text
ctx.font = '20px Helvetica' ctx.font = '20px Helvetica'
ctx.fillStyle = '#fff' ctx.fillStyle = '#fff'
ctx.fillText('FPS: ' + fpsDraw.toFixed(2), 0, 20) ctx.fillText('FPS: ' + fpsDraw.toFixed(2), 0, 20)
@ -747,43 +988,24 @@ window.onload = function () {
ctx.fillText('Health: ' + Game.health, 645, 45) ctx.fillText('Health: ' + Game.health, 645, 45)
ctx.fillText('Money: ' + Game.money, 645, 65) ctx.fillText('Money: ' + Game.money, 645, 65)
// Game Over text
if (Game.state === -1) { if (Game.state === -1) {
ctx.font = '80px Helvetica' ctx.font = '80px Helvetica'
ctx.fillStyle = '#f00' ctx.fillStyle = '#f00'
ctx.fillText('Game Over', 100, canvas.height / 2 - 80 / 2) ctx.fillText('Game Over', 100, canvas.height / 2 - 80 / 2)
} }
// Draw mouse cursor
if (mX < Maps.width && mY < Maps.height) { if (mX < Maps.width && mY < Maps.height) {
ctx.fillStyle = 'rgba(255, 0, 0, 0.24)' ctx.fillStyle = 'rgba(255, 0, 0, 0.24)'
ctx.fillRect(mX * mt, mY * mt, mt, mt) ctx.fillRect(mX * mt, mY * mt, mt, mt)
} }
// Render a selection information box // Draw all components
// TODO: component
if (Game.towerSel) {
let by = (Maps.height - 5) * Maps.tile
let ts = Game.towerSel
ctx.fillStyle = 'rgba(0, 0, 0, 0.45)'
ctx.fillRect(0, by, Maps.width * Maps.tile, 5 * Maps.tile)
ctx.fillStyle = '#fff'
ctx.font = '25px Helvetica'
ctx.fillText(ts.name + ' Tower', 5, by + 25)
ctx.font = '15px Helvetica'
ctx.fillText(ts.description, 5, by + 42)
ctx.fillText('Range: ' + ts.range + ' tiles', 5, by + 70)
ctx.fillText('Damage: ' + ts.damage + ' HP', 5, by + 85)
ctx.fillText('Fire Rate: ' + ts.rate, 5, by + 100)
ctx.fillText('Kills: ' + ts.killcount, 5, by + 115)
ctx.fillText('Fired ' + ts.fires + ' times', 5, by + 130)
}
for (let i in Components) { for (let i in Components) {
let btn = Components[i] let cmp = Components[i]
btn.draw() if (!(cmp) instanceof Component) continue
cmp.draw()
} }
} }
@ -795,6 +1017,7 @@ window.onload = function () {
update() update()
render() render()
// Update FPS
let cfps = 1000 / ((now = new Date) - lastTime) let cfps = 1000 / ((now = new Date) - lastTime)
if (now != lastTime) { if (now != lastTime) {
fps += (cfps - fps) / fpsRes fps += (cfps - fps) / fpsRes
@ -812,19 +1035,25 @@ window.onload = function () {
updateGameState(1) updateGameState(1)
}) })
// Tower sell button // Tower information box
Components.sell = new ButtonComponent('Sell Tower', '#fff', '#f11', 490, 590, 140, 40, () => { Components.info = new InfoDialog()
if (Game.towerSel) {
sellTower(Game.towerSel.x, Game.towerSel.y)
}
})
// Add buy buttons to every tower
let index = 0 let index = 0
for (let i in Towers) { for (let i in Towers) {
Components[i] = new TowerButton(i, index) Components[i] = new TowerButton(i, index)
index++ index++
} }
// Tooltip
Components.tooltip = new Tooltip()
for (let i in Towers) {
let cmp = Components[i]
if (!cmp) continue
Components.tooltip.addComponent(cmp, cmp.towerObj.description)
}
// Start the game
gameLoop() gameLoop()
} }