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
and to get the ability to modify them individually
* 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')
@ -36,6 +39,7 @@ window.onload = function () {
waveTimer: 0,
tower: 'simple',
towerSel: null,
debug: false,
sellRatio: .8
}
@ -67,7 +71,7 @@ window.onload = function () {
tough: {
speed: 5,
node: 1,
health: 100,
health: 150,
reward: 20,
frequency: 1000,
icon: '#f40'
@ -80,30 +84,44 @@ window.onload = function () {
damage: 15, // damage to deal to enemies when hit
rate: 20, // rate of fire, higher - slower
name: 'Simple', // name of the tower
description: 'Basic tower',
description: 'Medium rate and damage',
speed: 30, // bullet speed, higher - faster
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: {
range: 3,
damage: 5,
rate: 5,
name: 'Rapid',
description: 'Rapid-firing tower',
description: 'Rapid-firing but low damage',
speed: 30,
cost: 250,
icon: '#303'
icon: '#303',
bullet: 1
},
snipe: {
range: 5,
damage: 150,
rate: 100,
name: 'Sniper',
description: 'Slow but powerful shots',
sticky: {
range: 3,
damage: 10,
rate: 30,
name: 'Sticky',
description: 'Slow down enemies by damage',
speed: 50,
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 {
constructor (x, y) {
this.visible = true
this.elements = []
this.x = x
this.y = y
}
draw () {}
update() {}
elDraw() {
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 {
@ -197,9 +317,19 @@ window.onload = function () {
ctx.fillStyle = 'rgba(0, 0, 0, 0.45)'
ctx.fillRect(this.x, this.y, this.w, this.h)
} 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)
}
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) {
@ -222,13 +352,23 @@ window.onload = function () {
this.textColor = '#fff'
this.color = '#995522'
this.fn = this.select
this.font = '14px Helvetica'
}
select () {
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 () {
if (!this.visible) return
if (this.active) {
@ -261,26 +401,113 @@ window.onload = function () {
ctx.fillStyle = 'rgba(0, 0, 0, 0.45)'
ctx.fillRect(this.x, this.y, this.w, this.h)
} 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)
}
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
for (let i in Components) {
let btn = Components[i]
if (!(btn instanceof ButtonComponent)) continue
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()
click = true
let compList = cmp != null && cmp instanceof Component ? cmp.elements : null
if (cmp == null && compList == null) {
compList = Components
}
for (let i in compList) {
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
}
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
function nextWave () {
Game.wave++
@ -303,7 +530,7 @@ window.onload = function () {
// Use this function to modify the enemies spawned each round
function waveEnemyModifer (enemy, round) {
// Reduce the time between enemy spawns
let fr = enemy.frequency - 2 * round
let fr = enemy.frequency - 5 * round
if (fr < 100) {
fr = 100
}
@ -400,6 +627,8 @@ window.onload = function () {
target = enemiesProxima[enemiesProxima.length - 1]
}
tower.fires++
Game.particles.push({
x: tower.x,
y: tower.y,
@ -408,9 +637,9 @@ window.onload = function () {
velY: (target.y - tower.y) / target.dist * 1.24,
dmg: tower.damage,
speed: tower.speed,
life: 100
type: tower.bullet || 1,
life: 30
})
tower.fires++
}
function tickTowers () {
@ -446,15 +675,22 @@ window.onload = function () {
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) {
// damage enemy
enemy.dmg -= parti.dmg
if (parti.type === 1) {
enemy.dmg -= parti.dmg
if (enemy.dmg <= 0) {
Game.enemies.splice(j, 1)
Game.money += 10
if (enemy.dmg <= 0) {
Game.enemies.splice(j, 1)
Game.money += 10
let tower = getTowerAt(parti.tower.x, parti.tower.y)
if (tower) {
tower.killcount++
let tower = getTowerAt(parti.tower.x, parti.tower.y)
if (tower) {
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) {
let tower = getTowerAt(x, y)
if(tower) {
if (tower) {
let amount = tower.cost * Game.sellRatio
Game.money += amount
Game.selltext.push({
@ -591,12 +827,13 @@ window.onload = function () {
}
return Game.towers.splice(Game.towers.indexOf(tower), 1)
}else{
} else {
return null
}
}
function update (dt) {
// Update FPS count for drawing (Don't render a new number every frame, it changes too fast)
fpsCount++
fpsCount %= 20
if (fpsCount === 0) {
@ -608,32 +845,25 @@ window.onload = function () {
tickTowers()
}
// Move enemies
updateEnemyMovements()
// Move bullets
tickParticles()
// Move sell texts
tickSellText()
for (let i in Components) {
let btn = Components[i]
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
}
}
}
// Update all components (eg buttons)
updateComponents()
// Set the state
updateGameState()
// Increment game clock
if (Game.state === 1) {
Game.waveTimer++
}
Components.sell.visible = Game.towerSel !== null
}
function render () {
@ -641,6 +871,7 @@ window.onload = function () {
ctx.fillStyle = '#0fa'
ctx.fillRect(0, 0, canvas.width, canvas.height)
// Draw the map
for (let i in Game.map.tiles) {
let tile = Game.map.tiles[i]
let index = parseInt(i)
@ -658,28 +889,34 @@ window.onload = function () {
draw_tile = false
}
if(draw_tile) {
if (draw_tile) {
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.fillRect(x * mt, y * mt, mt, mt)
}
}
/*
for (let i in Game.map.pathgen) {
let node = Game.map.pathgen[i]
ctx.fillStyle = '#00f'
ctx.fillRect((node.x * mt) + mt / 3, (node.y * mt) + mt / 3, 8, 8)
// Show the enemy movement path
if (Game.debug) {
for (let i in Game.map.pathgen) {
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) {
let tower = Game.towers[i]
ctx.fillStyle = tower.icon
ctx.fillRect(tower.x * mt + 2, tower.y * mt + 2, 28, 28)
}
// Draw enemies
for (let i in Game.enemies) {
let enemy = Game.enemies[i]
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)
}
// Draw bullets
for (let i in Game.particles) {
let tower = Game.particles[i]
ctx.fillStyle = '#f33'
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 vX = null
let vY = null
@ -730,6 +968,7 @@ window.onload = function () {
ctx.closePath()
}
// Render sell text
for (let i in Game.selltext) {
let txt = Game.selltext[i]
ctx.font = '12px Helvetica'
@ -737,9 +976,11 @@ window.onload = function () {
ctx.fillText('+ $' + txt.amount, txt.x * mt, txt.y * mt)
}
// Render sidebar background
ctx.fillStyle = '#996633'
ctx.fillRect(640, 0, 240, 640)
// Render sidebar text
ctx.font = '20px Helvetica'
ctx.fillStyle = '#fff'
ctx.fillText('FPS: ' + fpsDraw.toFixed(2), 0, 20)
@ -747,43 +988,24 @@ window.onload = function () {
ctx.fillText('Health: ' + Game.health, 645, 45)
ctx.fillText('Money: ' + Game.money, 645, 65)
// Game Over text
if (Game.state === -1) {
ctx.font = '80px Helvetica'
ctx.fillStyle = '#f00'
ctx.fillText('Game Over', 100, canvas.height / 2 - 80 / 2)
}
// Draw mouse cursor
if (mX < Maps.width && mY < Maps.height) {
ctx.fillStyle = 'rgba(255, 0, 0, 0.24)'
ctx.fillRect(mX * mt, mY * mt, mt, mt)
}
// Render a selection information box
// 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)
}
// Draw all components
for (let i in Components) {
let btn = Components[i]
btn.draw()
let cmp = Components[i]
if (!(cmp) instanceof Component) continue
cmp.draw()
}
}
@ -795,6 +1017,7 @@ window.onload = function () {
update()
render()
// Update FPS
let cfps = 1000 / ((now = new Date) - lastTime)
if (now != lastTime) {
fps += (cfps - fps) / fpsRes
@ -812,19 +1035,25 @@ window.onload = function () {
updateGameState(1)
})
// Tower sell button
Components.sell = new ButtonComponent('Sell Tower', '#fff', '#f11', 490, 590, 140, 40, () => {
if (Game.towerSel) {
sellTower(Game.towerSel.x, Game.towerSel.y)
}
})
// Tower information box
Components.info = new InfoDialog()
// Add buy buttons to every tower
let index = 0
for (let i in Towers) {
Components[i] = new TowerButton(i, 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()
}