first commit
This commit is contained in:
commit
2890d28b38
10
index.html
Normal file
10
index.html
Normal file
@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>TDExperiment</title>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canvas" height="640" width="860"></canvas>
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
534
index.js
Normal file
534
index.js
Normal file
@ -0,0 +1,534 @@
|
||||
window.onload = function () {
|
||||
let canvas = document.getElementById('canvas')
|
||||
let ctx = canvas.getContext('2d')
|
||||
|
||||
let fps = 0
|
||||
let fpsDraw = 0
|
||||
let fpsCount = 0
|
||||
|
||||
// mouse
|
||||
let mX = 0
|
||||
let mY = 0
|
||||
let mXr = 0
|
||||
let mYr = 0
|
||||
|
||||
let Game = {
|
||||
state: 2,
|
||||
enemies: [],
|
||||
towers: [],
|
||||
particles: [],
|
||||
map: null,
|
||||
health: 100,
|
||||
money: 100,
|
||||
enemySpawn: 0,
|
||||
pace: 1,
|
||||
wave: 0,
|
||||
waveTimer: 0,
|
||||
tower: 'simple'
|
||||
}
|
||||
|
||||
let Enemies = {
|
||||
basic: {
|
||||
speed: 10,
|
||||
node: 1,
|
||||
dmg: 50,
|
||||
health: 50,
|
||||
reward: 10
|
||||
},
|
||||
speedy: {
|
||||
speed: 20,
|
||||
node: 1,
|
||||
dmg: 60,
|
||||
health: 60,
|
||||
reward: 15
|
||||
},
|
||||
tough: {
|
||||
speed: 5,
|
||||
node: 1,
|
||||
dmg: 100,
|
||||
health: 100,
|
||||
reward: 20
|
||||
}
|
||||
}
|
||||
|
||||
let Towers = {
|
||||
simple: {
|
||||
range: 6,
|
||||
damage: 15,
|
||||
rate: 20,
|
||||
name: 'Simple',
|
||||
speed: 30,
|
||||
cost: 50,
|
||||
icon: null
|
||||
}
|
||||
}
|
||||
|
||||
let Maps = {
|
||||
width: 20,
|
||||
height: 20,
|
||||
tile: 32,
|
||||
first: {
|
||||
tiles: [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
|
||||
0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
|
||||
0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
|
||||
0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
|
||||
0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
|
||||
0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
|
||||
0, 3, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
],
|
||||
pathgen: [
|
||||
{x: 1, y: 2, end: false},
|
||||
{x: 14, y: 2, end: false},
|
||||
{x: 14, y: 6, end: false},
|
||||
{x: 8, y: 6, end: false},
|
||||
{x: 8, y: 10, end: false},
|
||||
{x: 17, y: 10, end: false},
|
||||
{x: 17, y: 17, end: false},
|
||||
{x: 5, y: 17, end: false},
|
||||
{x: 5, y: 12, end: false},
|
||||
{x: 1, y: 12, end: false},
|
||||
{x: 1, y: 17, end: true},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
let Buttons = {}
|
||||
|
||||
let ButtonComponent = function (text, textColor, color, x, y, w, h, fn) {
|
||||
this.x = x
|
||||
this.y = y
|
||||
this.w = w
|
||||
this.h = h
|
||||
this.disabled = false
|
||||
this.hovered = false
|
||||
this.draw = () => {
|
||||
ctx.fillStyle = color
|
||||
ctx.fillRect(this.x, this.y, this.w, this.h)
|
||||
ctx.fillStyle = textColor
|
||||
let txtMeasure = ctx.measureText(text)
|
||||
let tx = this.x + (this.w / 2 - txtMeasure.width / 2)
|
||||
let ty = this.y + (this.h / 2) * 1.2
|
||||
|
||||
ctx.fillText(text, tx, ty)
|
||||
|
||||
if (this.disabled) {
|
||||
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.fillRect(this.x, this.y, this.w, this.h)
|
||||
}
|
||||
}
|
||||
|
||||
this.fn = () => {
|
||||
if (fn != null && typeof fn === 'function') {
|
||||
fn.apply(this, [])
|
||||
}
|
||||
}
|
||||
|
||||
this.setDisabled = (disable) => {
|
||||
this.disabled = typeof disabled === 'boolean' ? disabled : !this.disabled
|
||||
}
|
||||
}
|
||||
|
||||
function clickBtn () {
|
||||
for (let i in Buttons) {
|
||||
let btn = Buttons[i]
|
||||
if (btn.disabled) return
|
||||
if (mXr > btn.x && mYr > btn.y && mXr < btn.x + btn.w && mYr < btn.y + btn.h) {
|
||||
btn.fn()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function nextWave () {
|
||||
Game.wave++
|
||||
|
||||
if (Game.wave % 5 === 0) {
|
||||
addEnemies(Game.wave / 5, Enemies.speedy)
|
||||
} else if (Game.wave % 10 === 0) {
|
||||
addEnemies(1, Enemies.tough)
|
||||
} else if (Game.wave > 5) {
|
||||
addEnemies(10 + Game.wave, Enemies.speedy)
|
||||
if (Game.wave > 10) {
|
||||
addEnemies(Game.wave - 10, Enemies.tough)
|
||||
}
|
||||
}
|
||||
|
||||
addEnemies(10 + Game.wave, Enemies.basic)
|
||||
}
|
||||
|
||||
function getTileIn (map, x, y) {
|
||||
let tile = x + y * Maps.width
|
||||
return map[tile]
|
||||
}
|
||||
|
||||
function updateGameState (gst) {
|
||||
if (Game.state !== -1 && Game.health <= 0) {
|
||||
Game.health = 0
|
||||
Game.state = -1
|
||||
}
|
||||
|
||||
if (Game.state === 2 && gst === 1) {
|
||||
nextWave()
|
||||
}
|
||||
|
||||
if (gst === 2 && Game.state === 1) {
|
||||
Game.waveTimer = 0
|
||||
}
|
||||
|
||||
if (gst != null) {
|
||||
Game.state = gst
|
||||
}
|
||||
|
||||
Buttons.wave.disabled = (Game.state !== 2)
|
||||
}
|
||||
|
||||
function updateEnemyMovements () {
|
||||
for (let i in Game.enemies) {
|
||||
let enemy = Game.enemies[i]
|
||||
let enemyTrackTarget = Game.map.pathgen[enemy.node]
|
||||
if (enemyTrackTarget) {
|
||||
let tx = enemyTrackTarget.x
|
||||
let ty = enemyTrackTarget.y
|
||||
let vexLen = Math.sqrt(Math.pow(tx - enemy.x, 2) + Math.pow(ty - enemy.y, 2))
|
||||
let velX = (tx - enemy.x) / Math.abs(vexLen)
|
||||
let velY = (ty - enemy.y) / Math.abs(vexLen)
|
||||
enemy.velocity = {x: velX, y: velY, dist: Math.abs(vexLen)}
|
||||
}
|
||||
|
||||
if (enemy.velocity.dist > 0.1) {
|
||||
enemy.x += (enemy.velocity.x * 0.01) * enemy.speed * Game.pace
|
||||
enemy.y += (enemy.velocity.y * 0.01) * enemy.speed * Game.pace
|
||||
} else {
|
||||
if (Game.map.pathgen[enemy.node + 1]) {
|
||||
enemy.node += 1
|
||||
} else if (enemyTrackTarget.end === true) {
|
||||
Game.enemies.splice(i, 1)
|
||||
Game.health -= Math.floor(enemy.dmg / 2)
|
||||
if (Game.health < 0) {
|
||||
Game.health = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Game.state === 1 && Game.enemies.length === 0 && Game.enemySpawn === 0) {
|
||||
updateGameState(2)
|
||||
}
|
||||
}
|
||||
|
||||
function towerFire (tower) {
|
||||
let enemiesProxima = []
|
||||
let target = null
|
||||
|
||||
for (let i in Game.enemies) {
|
||||
let enemy = Game.enemies[i]
|
||||
let proxi = Math.abs(Math.sqrt(Math.pow(enemy.x - tower.x, 2) + Math.pow(enemy.y - tower.y, 2)))
|
||||
if (proxi > tower.range) continue
|
||||
enemiesProxima.push(Object.assign({dist: proxi}, enemy))
|
||||
}
|
||||
|
||||
if (!enemiesProxima.length) return
|
||||
|
||||
enemiesProxima.sort((a, b) => {
|
||||
return a.dist - b.dist
|
||||
})
|
||||
|
||||
if (tower.setting === 1) {
|
||||
target = enemiesProxima[0]
|
||||
} else {
|
||||
target = enemiesProxima[enemiesProxima.length - 1]
|
||||
}
|
||||
|
||||
Game.particles.push({
|
||||
x: tower.x,
|
||||
y: tower.y,
|
||||
velX: (target.x - tower.x) / target.dist * 1.24,
|
||||
velY: (target.y - tower.y) / target.dist * 1.24,
|
||||
dmg: tower.damage,
|
||||
speed: tower.speed,
|
||||
life: 100
|
||||
})
|
||||
}
|
||||
|
||||
function tickTowers () {
|
||||
for (let i in Game.towers) {
|
||||
let tower = Game.towers[i]
|
||||
|
||||
// Tick towers
|
||||
tower.tick++
|
||||
tower.tick %= tower.rate
|
||||
|
||||
// fire
|
||||
if (tower.tick === 0) {
|
||||
towerFire(tower)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function tickParticles () {
|
||||
for (let i in Game.particles) {
|
||||
let parti = Game.particles[i]
|
||||
parti.x += parti.velX * 0.01 * parti.speed
|
||||
parti.y += parti.velY * 0.01 * parti.speed
|
||||
|
||||
parti.life--
|
||||
if (parti.life <= 0) {
|
||||
Game.particles.splice(i, 1)
|
||||
continue
|
||||
}
|
||||
|
||||
for (let j in Game.enemies) {
|
||||
let enemy = Game.enemies[j]
|
||||
|
||||
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 (enemy.dmg <= 0) {
|
||||
Game.enemies.splice(j, 1)
|
||||
Game.money += 10
|
||||
}
|
||||
|
||||
// remove particle
|
||||
Game.particles.splice(i, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addEnemies (cnt, type) {
|
||||
Game.enemySpawn += cnt
|
||||
let ect = setInterval(() => {
|
||||
if (Game.enemySpawn === 0) return clearInterval(ect)
|
||||
Game.enemySpawn--
|
||||
|
||||
Game.enemies.push(Object.assign({
|
||||
x: Game.map.pathgen[0].x, y: Game.map.pathgen[0].y
|
||||
}, type))
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
function towerAt (x, y) {
|
||||
for (let i in Game.towers) {
|
||||
let tower = Game.towers[i]
|
||||
if (tower.x === x && tower.y === y) return tower
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function placeTower (tower, x, y) {
|
||||
if (tower.cost > Game.money) return // no money
|
||||
|
||||
let tileAt = getTileIn(Game.map.tiles, x, y)
|
||||
if (tileAt !== 0) return
|
||||
|
||||
if (towerAt(x, y)) return
|
||||
|
||||
Game.money -= tower.cost
|
||||
Game.towers.push(Object.assign({
|
||||
x: x,
|
||||
y: y,
|
||||
tick: tower.rate,
|
||||
setting: 1
|
||||
}, tower))
|
||||
}
|
||||
|
||||
function update (dt) {
|
||||
fpsCount++
|
||||
fpsCount %= 20
|
||||
if (fpsCount === 0) {
|
||||
fpsDraw = fps
|
||||
}
|
||||
|
||||
tickTowers()
|
||||
updateEnemyMovements()
|
||||
tickParticles()
|
||||
|
||||
for (let i in Buttons) {
|
||||
let btn = Buttons[i]
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
updateGameState()
|
||||
if (Game.state === 1) {
|
||||
Game.waveTimer++
|
||||
}
|
||||
}
|
||||
|
||||
function render () {
|
||||
let mt = Maps.tile
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
for (let i in Game.map.tiles) {
|
||||
let tile = Game.map.tiles[i]
|
||||
let index = parseInt(i)
|
||||
let y = Math.floor(index / Maps.width)
|
||||
let x = Math.floor(index % Maps.height)
|
||||
|
||||
if (tile === 1) {
|
||||
ctx.fillStyle = '#fdd'
|
||||
} else if (tile === 2) {
|
||||
ctx.fillStyle = '#aaf'
|
||||
} else if (tile === 3) {
|
||||
ctx.fillStyle = '#f3a'
|
||||
} else {
|
||||
ctx.fillStyle = '#0fa'
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
*/
|
||||
for (let i in Game.towers) {
|
||||
let tower = Game.towers[i]
|
||||
ctx.fillStyle = '#333'
|
||||
ctx.fillRect(tower.x * mt + 2, tower.y * mt + 2, 28, 28)
|
||||
}
|
||||
|
||||
for (let i in Game.enemies) {
|
||||
let enemy = Game.enemies[i]
|
||||
let rx = (enemy.x * mt) + mt / 8
|
||||
let ry = (enemy.y * mt) + mt / 8
|
||||
ctx.fillStyle = '#f00'
|
||||
ctx.fillRect(rx, ry, 16, 16)
|
||||
|
||||
// health bars
|
||||
let hx = rx - 6
|
||||
let hy = ry - 12
|
||||
ctx.fillStyle = '#f00'
|
||||
ctx.fillRect(hx, hy, 16 + 12, 5)
|
||||
|
||||
ctx.fillStyle = '#0f0'
|
||||
ctx.fillRect(hx, hy, (16 + 12) * enemy.dmg / enemy.health, 5)
|
||||
}
|
||||
|
||||
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
|
||||
if (Game.state === 2 && Game.tower && mX < Maps.width && mY < Maps.height) {
|
||||
let towerData = Towers[Game.tower]
|
||||
ctx.strokeStyle = '#ddd'
|
||||
ctx.fillStyle = 'rgba(200, 200, 200, 0.25)'
|
||||
ctx.beginPath()
|
||||
ctx.arc(mX * mt + mt / 2, mY * mt + mt / 2, towerData.range * mt, 0, 2 * Math.PI)
|
||||
ctx.stroke()
|
||||
ctx.fill()
|
||||
ctx.closePath()
|
||||
}
|
||||
|
||||
ctx.fillStyle = '#996633'
|
||||
ctx.fillRect(640, 0, 240, 640)
|
||||
|
||||
ctx.font = '20px Helvetica'
|
||||
ctx.fillStyle = '#fff'
|
||||
ctx.fillText('FPS: ' + fpsDraw.toFixed(2), 0, 20)
|
||||
ctx.fillText('Wave: ' + Game.wave, 645, 25)
|
||||
ctx.fillText('Health: ' + Game.health, 645, 45)
|
||||
ctx.fillText('Money: ' + Game.money, 645, 65)
|
||||
|
||||
for (let i in Buttons) {
|
||||
let btn = Buttons[i]
|
||||
btn.draw()
|
||||
}
|
||||
|
||||
if (Game.state === -1) {
|
||||
ctx.font = '80px Helvetica'
|
||||
ctx.fillStyle = '#f00'
|
||||
ctx.fillText('Game Over', 100, canvas.height / 2 - 80 / 2)
|
||||
}
|
||||
|
||||
if (mX < Maps.width && mY < Maps.height) {
|
||||
ctx.fillStyle = 'rgba(255, 0, 0, 0.24)'
|
||||
ctx.fillRect(mX * mt, mY * mt, mt, mt)
|
||||
}
|
||||
}
|
||||
|
||||
let lastTime = Date.now()
|
||||
function gameLoop () {
|
||||
requestAnimationFrame(gameLoop)
|
||||
|
||||
let delta = (Date.now() - lastTime) / 1000
|
||||
|
||||
fps = 1 / (delta === 0 ? 1 : delta)
|
||||
|
||||
update(delta)
|
||||
render()
|
||||
|
||||
lastTime = Date.now()
|
||||
}
|
||||
|
||||
function initialize () {
|
||||
Game.map = Maps.first
|
||||
|
||||
Buttons.wave = new ButtonComponent('Next Wave', '#fff', '#11f', 650, 570, 200, 60, () => {
|
||||
updateGameState(1)
|
||||
})
|
||||
|
||||
gameLoop()
|
||||
}
|
||||
|
||||
canvas.addEventListener('click', (e) => {
|
||||
if (Game.state === 2 && mX < Maps.width && mY < Maps.height && Game.tower) {
|
||||
placeTower(Towers[Game.tower], mX, mY)
|
||||
}
|
||||
|
||||
clickBtn()
|
||||
})
|
||||
|
||||
canvas.addEventListener('mousemove', (e) => {
|
||||
if (e.changedTouches) {
|
||||
let touch = e.changedTouches[0]
|
||||
if (touch) {
|
||||
e.pageX = touch.pageX
|
||||
e.pageY = touch.pageY
|
||||
}
|
||||
}
|
||||
|
||||
if (e.pageX || e.pageY) {
|
||||
mXr = e.pageX
|
||||
mYr = e.pageY
|
||||
} else {
|
||||
mXr = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft
|
||||
mYr = e.clientY + document.body.scrollTop + document.documentElement.scrollTop
|
||||
}
|
||||
|
||||
mXr -= canvas.offsetLeft
|
||||
mYr -= canvas.offsetTop
|
||||
|
||||
mX = Math.floor(mXr / Maps.tile)
|
||||
mY = Math.floor(mYr / Maps.tile)
|
||||
})
|
||||
|
||||
initialize()
|
||||
}
|
Loading…
Reference in New Issue
Block a user