From 2890d28b38a8fe22da4c1b7f52f965e604789250 Mon Sep 17 00:00:00 2001 From: Evert Date: Sun, 20 Aug 2017 22:52:59 +0300 Subject: [PATCH] first commit --- index.html | 10 + index.js | 534 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 544 insertions(+) create mode 100644 index.html create mode 100644 index.js diff --git a/index.html b/index.html new file mode 100644 index 0000000..b51c8b2 --- /dev/null +++ b/index.html @@ -0,0 +1,10 @@ + + + + TDExperiment + + + + + + diff --git a/index.js b/index.js new file mode 100644 index 0000000..3cf6c70 --- /dev/null +++ b/index.js @@ -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() +}