diff --git a/Readme.md b/Readme.md index 9af32fd..230a87a 100644 --- a/Readme.md +++ b/Readme.md @@ -16,4 +16,4 @@ Hosted versions of the game are available here: ## Building and Testing - Run `npm install` to install dependencies and build game to `build/` -- Run `npm start` to run the game on a local test server \ No newline at end of file +- Run `npm start` to run the game on a local test server \ No newline at end of file diff --git a/package.json b/package.json index 47b69ff..20825ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stargazer", - "version": "0.1.0", + "version": "0.1.1", "description": "", "main": "index.js", "scripts": { diff --git a/resources/js/game/Enemy.js b/resources/js/game/Enemy.js deleted file mode 100644 index 1711ea1..0000000 --- a/resources/js/game/Enemy.js +++ /dev/null @@ -1,50 +0,0 @@ -const DEFAULT_SPEED = 1, - DEFAULT_HEALTH = 100, - DEFAULT_WIDTH = 20, - DEFAULT_HEIGHT = 20, - DEFAULT_DAMAGE = 10, - DEFAULT_HIT_BOX_RADIUS = 30; - -class Enemy { - - constructor(x, y, direction) { - this.x = x; - this.y = y; - this.width = DEFAULT_WIDTH; - this.height = DEFAULT_HEIGHT; - this.speed = DEFAULT_SPEED; - this.health = DEFAULT_HEALTH; - this.damage = DEFAULT_DAMAGE; - this.direction = direction; - } - - update(target, adjustment) { - this.direction = Math.atan2(target.y - this.y, target.x - this.x); - this.x = this.x + Math.cos(this.direction) * this.speed * adjustment; - this.y = this.y + Math.sin(this.direction) * this.speed * adjustment; - } - - isHitBy(pos) { - let distance = this.distanceTo(pos); - if (distance <= DEFAULT_HIT_BOX_RADIUS) { - return true; - } - return false; - } - - distanceTo(target) { - let deltaX = Math.abs(this.x - target.x), - deltaY = Math.abs(this.y - target.y); - return Math.sqrt(deltaX * deltaX + deltaY * deltaY); - } - - static createEnemy(area) { - let xPos = parseInt(Math.random() * area.width), - yPos = parseInt(Math.random() * area.height), - direction = Math.atan2(area.center.y - yPos, area.center.x - xPos); - return new Enemy(xPos, yPos, direction); - } - -} - -export default Enemy; \ No newline at end of file diff --git a/resources/js/game/GameConfig.js b/resources/js/game/GameConfig.js index b4a3116..9e5e98d 100644 --- a/resources/js/game/GameConfig.js +++ b/resources/js/game/GameConfig.js @@ -6,7 +6,6 @@ var Config = { ENEMEY_SPAWN_DELAY: 1000, FPS_BUFFER_LENGTH: 50, BACKGROUND_COLOR: "#333", - DEFAULT_ENEMEY_COLOR: "#dd3939", DEFAULT_GAZE_POINT_RADIUS: 15, DEFAULT_GAZE_POINT_COLOR: "#4cd494", DEBUG_POSITION_OFFSET: 10, diff --git a/resources/js/game/GameManager.js b/resources/js/game/GameManager.js index 7ee2184..413aef6 100644 --- a/resources/js/game/GameManager.js +++ b/resources/js/game/GameManager.js @@ -2,52 +2,74 @@ import Logger from "../utils/Logger.js"; import Config from "./GameConfig.js"; import Time from "../utils/Time.js"; import GameRenderer from "./GameRenderer.js"; -import Enemy from "./Enemy.js"; +import Enemy from "./objects/Enemy.js"; +import World from "./objects/World.js"; var gameLoop, gameArea, lastUpdate, lastEnemySpawn, options, + world, enemies, gazePoints; function onTick() { let now = Date.now(), - delta = now - lastUpdate, - adjustment = (Time.ONE_SECOND_IN_MS / options.frameRate) / delta; - updateEnemies(now, adjustment); + delta = now - lastUpdate; updateGazePoints(now); - GameRenderer.render(getCurrentState(now)); + updateEnemies(now, delta); + GameRenderer.render(getCurrentState(delta)); lastUpdate = Date.now(); } -function getCurrentState(now) { +function getCurrentState(delta) { return { - delta: now - lastUpdate, + delta: delta, frameRate: options.frameRate, - enemies: enemies, + objects: [...enemies, world], gazePoints: gazePoints, debug: options.debug, }; } -function updateEnemies(now, adjustment) { +function updateGazePoints(now) { + for (let i = gazePoints.length - 1; i >= 0; i--) { + let age = now - gazePoints[i].createdAt; + if (age > Config.MAX_GAZE_POINT_AGE) { + gazePoints.splice(i, 1); + } else { + gazePoints[i].relativeAge = age / Config.MAX_GAZE_POINT_AGE; + } + } +} + +function updateEnemies(now, delta) { let currentGazePosition = gazePoints[gazePoints.length - 1]; if ((now - lastEnemySpawn) >= Config.ENEMEY_SPAWN_DELAY) { if (enemies.length < Config.MAX_NUMBER_OF_ENEMIES) { Logger.log("Add new enemy", "Game"); - enemies.push(Enemy.createEnemy(gameArea)); + enemies.push(Enemy.createEnemy(gameArea.width, -50, gameArea.center.x, + gameArea.center.y)); lastEnemySpawn = now; } } for (let i = enemies.length - 1; i >= 0; i--) { // Update enemy's position - enemies[i].update(gameArea.center, adjustment); + enemies[i].update(delta); // Check if enemy has hit target - if (enemies[i].distanceTo(gameArea.center) < Config.TARGET_RADIUS) { - onEnemyHitsTarget(enemies[i].damage); + if (world.isHitBy(enemies[i])) { + Logger.log(`Enemy hit target for ${enemies[i].damage} dmg`, "Game"); + // Reduce world health after enemy has hit + world.health -= enemies[i].damage; + if (world.health <= 0) { + onWorldDestroyed(); + } + // Remove enemy after it has hit target enemies.splice(i, 1); + // Skip other checks if enemy has hit target + // TODO: Add exposion at last position + continue; } // Skip other checks if no gaze data is available if (currentGazePosition === undefined) { @@ -70,33 +92,13 @@ function updateEnemies(now, adjustment) { } } -function onEnemyHitsTarget(damage) { - Logger.log(`Enemy hit target for ${damage} dmg`, "Game"); -} - -function updateGazePoints(now) { - let lastIndexForRemoval = -1; - for (let i = 0; i < gazePoints.length; i++) { - gazePoints[i].age = now - gazePoints[i].createdAt; - gazePoints[i].inversedAge = 1 - (gazePoints[i].age / Config.MAX_GAZE_POINT_AGE); - if (gazePoints[i].age > Config.MAX_GAZE_POINT_AGE) { - lastIndexForRemoval = i; - } - } - if (lastIndexForRemoval !== -1) { - gazePoints.splice(0, lastIndexForRemoval + 1); - } +function onWorldDestroyed() { + Logger.log(`World destroyed [Current health ${world.health}]`, "Game"); } class GameManager { - constructor() { - enemies = []; - gazePoints = []; - lastEnemySpawn = 0; - } - - setOptions(gameOptions) { + init(gameOptions) { options = gameOptions; gameArea = { width: options.width, @@ -106,9 +108,14 @@ class GameManager { y: options.height / 2, }, }; + enemies = []; + gazePoints = []; + world = new World(gameArea.center.x, gameArea.center.y); + lastEnemySpawn = 0; } start() { + GameRenderer.init(options.canvas, options.width, options.height, options.version); lastUpdate = Date.now(); gameLoop = setInterval(onTick, (Time.ONE_SECOND_IN_MS / options.frameRate)); diff --git a/resources/js/game/GameRenderer.js b/resources/js/game/GameRenderer.js index f6ff569..7567ee5 100644 --- a/resources/js/game/GameRenderer.js +++ b/resources/js/game/GameRenderer.js @@ -10,7 +10,7 @@ var canvas, function draw(state) { updateFPS(state.delta); context.clearRect(0, 0, canvas.width, canvas.height); - drawEnemies(state.enemies); + drawObjects(state.objects); drawGazePoints(state.gazePoints); if (state.debug === true) { drawDebugInfo(); @@ -28,26 +28,12 @@ function updateFPS(delta) { } } -function drawEnemies(enemies) { - context.fillStyle = Config.DEFAULT_ENEMEY_COLOR; - for (let i = 0; i < enemies.length; i++) { - drawSingleEnemey(enemies[i]); +function drawObjects(objects) { + for (let i = 0; i < objects.length; i++) { + objects[i].draw(context); } } -function drawSingleEnemey(enemy) { - context.save(); - context.beginPath(); - context.translate(enemy.x, enemy.y + enemy.height/2); - context.rotate(enemy.direction); - context.moveTo(0,-enemy.height/2); - context.lineTo(-(enemy.width/2), enemy.height/2); - context.lineTo(enemy.width/2, enemy.height/2); - context.closePath(); - context.fill(); - context.restore(); -} - function drawGazePoints(gazePoints) { context.fillStyle = Config.DEFAULT_GAZE_POINT_COLOR; for (let i = 0; i < gazePoints.length; i++) { @@ -56,9 +42,10 @@ function drawGazePoints(gazePoints) { } function drawSingleGazePoint(gazePoint) { - let radius = gazePoint.inversedAge * Config.DEFAULT_GAZE_POINT_RADIUS; + let inversedAge = 1 - gazePoint.relativeAge, + radius = inversedAge * Config.DEFAULT_GAZE_POINT_RADIUS; context.save(); - context.globalAlpha = gazePoint.inversedAge; + context.globalAlpha = inversedAge; context.beginPath(); context.ellipse(gazePoint.targetX, gazePoint.targetY, radius, radius, Math.PI / 4, 0, 2 * Math.PI); diff --git a/resources/js/game/StarGazer.js b/resources/js/game/StarGazer.js index 4673c07..bf79a79 100644 --- a/resources/js/game/StarGazer.js +++ b/resources/js/game/StarGazer.js @@ -6,7 +6,7 @@ var canvas, provider; function init(options) { Logger.log("Starting StarGazer game", "Game"); canvas = options.canvas; - GameManager.setOptions({ + GameManager.init({ canvas: canvas, frameRate: options.fps, version: options.version, diff --git a/resources/js/game/objects/Enemy.js b/resources/js/game/objects/Enemy.js new file mode 100644 index 0000000..b3dbf2e --- /dev/null +++ b/resources/js/game/objects/Enemy.js @@ -0,0 +1,43 @@ +import GameObject from "./GameObject.js"; + +const DEFAULT_VELOCITY = 1, + DEFAULT_HEALTH = 100, + DEFAULT_WIDTH = 50, + DEFAULT_HEIGHT = 50, + DEFAULT_DAMAGE = 10, + DEFAULT_HIT_BOX_RADIUS = 30, + DEFAULT_ENEMEY_COLOR = "#dd3939"; + +class Enemy extends GameObject { + + constructor(x, y, direction) { + super(x, y, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_HIT_BOX_RADIUS, + DEFAULT_VELOCITY, direction, DEFAULT_ENEMEY_COLOR); + this.health = DEFAULT_HEALTH; + this.damage = DEFAULT_DAMAGE; + } + + draw(context) { + context.save(); + context.fillStyle = this.color; + context.beginPath(); + context.translate(this.x, this.y); + context.rotate(this.direction * Math.PI / 180); + context.moveTo(0, -this.height / 2); + context.lineTo(-(this.width / 2), this.height / 2); + context.lineTo(this.width / 2, this.height / 2); + context.closePath(); + context.fill(); + context.restore(); + } + + static createEnemy(rangeX, rangeY, targetX, targetY) { + let xPos = parseInt(Math.random() * rangeX), + yPos = parseInt(Math.random() * rangeY), + direction = Math.atan2(targetY - yPos, targetX - xPos) * 180 / Math.PI; + return new Enemy(xPos, yPos, direction); + } + +} + +export default Enemy; \ No newline at end of file diff --git a/resources/js/game/objects/GameObject.js b/resources/js/game/objects/GameObject.js new file mode 100644 index 0000000..e68e38d --- /dev/null +++ b/resources/js/game/objects/GameObject.js @@ -0,0 +1,40 @@ +class GameObject { + + constructor(x, y, width, height, hitBoxRadius, velocity, direction, color) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.hitBoxRadius = hitBoxRadius; + this.velocity = velocity; + this.direction = direction; + this.color = color; + } + + update(delta) { + if(this.velocity === 0) { + return; + } + this.x += this.velocity * Math.cos(this.direction * Math.PI / 180); + this.y += this.velocity * Math.sin(this.direction * Math.PI / 180); + } + + isHitBy(object) { + let distance = this.distanceTo(object); + if (distance <= this.hitBoxRadius) { + return true; + } + return false; + } + + distanceTo(target) { + let deltaX = Math.abs(this.x - target.x), + deltaY = Math.abs(this.y - target.y); + return Math.sqrt(deltaX * deltaX + deltaY * deltaY); + } + + draw(context) {} + +} + +export default GameObject; \ No newline at end of file diff --git a/resources/js/game/objects/World.js b/resources/js/game/objects/World.js new file mode 100644 index 0000000..919a254 --- /dev/null +++ b/resources/js/game/objects/World.js @@ -0,0 +1,37 @@ +import GameObject from "./GameObject.js"; + +const DEFAULT_HEALTH = 100, + DEFAULT_WIDTH = 200, + DEFAULT_HEIGHT = 200, + DEFAULT_HIT_BOX_RADIUS = 100, + DEFAULT_WORLD_COLOR = "#0d396f", + DEFAULT_WORLD_BORDER_COLOR = "#dafffe", + DEFAULT_WORLD_BORDER_WIDTH = "10"; + +class World extends GameObject { + + constructor(x, y) { + super(x, y, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_HIT_BOX_RADIUS, + 0, 0, DEFAULT_WORLD_COLOR); + this.health = DEFAULT_HEALTH; + this.borderColor = DEFAULT_WORLD_BORDER_COLOR; + this.borderWidth = DEFAULT_WORLD_BORDER_WIDTH; + } + + draw(context) { + context.save(); + context.fillStyle = this.color; + context.strokeStyle = this.borderColor; + context.lineWidth = this.borderWidth; + context.beginPath(); + context.ellipse(this.x, this.y, this.width / 2, this.height / 2, Math.PI / + 4, 0, 2 * Math.PI); + context.closePath(); + context.fill(); + context.stroke(); + context.restore(); + } + +} + +export default World; \ No newline at end of file