Commit 0d39b6c7 by Alexander Bazo

Add destroyable world object

parent 91d05c72
{ {
"name": "stargazer", "name": "stargazer",
"version": "0.1.0", "version": "0.1.1",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
......
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
...@@ -6,7 +6,6 @@ var Config = { ...@@ -6,7 +6,6 @@ var Config = {
ENEMEY_SPAWN_DELAY: 1000, ENEMEY_SPAWN_DELAY: 1000,
FPS_BUFFER_LENGTH: 50, FPS_BUFFER_LENGTH: 50,
BACKGROUND_COLOR: "#333", BACKGROUND_COLOR: "#333",
DEFAULT_ENEMEY_COLOR: "#dd3939",
DEFAULT_GAZE_POINT_RADIUS: 15, DEFAULT_GAZE_POINT_RADIUS: 15,
DEFAULT_GAZE_POINT_COLOR: "#4cd494", DEFAULT_GAZE_POINT_COLOR: "#4cd494",
DEBUG_POSITION_OFFSET: 10, DEBUG_POSITION_OFFSET: 10,
......
...@@ -2,52 +2,74 @@ import Logger from "../utils/Logger.js"; ...@@ -2,52 +2,74 @@ import Logger from "../utils/Logger.js";
import Config from "./GameConfig.js"; import Config from "./GameConfig.js";
import Time from "../utils/Time.js"; import Time from "../utils/Time.js";
import GameRenderer from "./GameRenderer.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, var gameLoop,
gameArea, gameArea,
lastUpdate, lastUpdate,
lastEnemySpawn, lastEnemySpawn,
options, options,
world,
enemies, enemies,
gazePoints; gazePoints;
function onTick() { function onTick() {
let now = Date.now(), let now = Date.now(),
delta = now - lastUpdate, delta = now - lastUpdate;
adjustment = (Time.ONE_SECOND_IN_MS / options.frameRate) / delta;
updateEnemies(now, adjustment);
updateGazePoints(now); updateGazePoints(now);
GameRenderer.render(getCurrentState(now)); updateEnemies(now, delta);
GameRenderer.render(getCurrentState(delta));
lastUpdate = Date.now(); lastUpdate = Date.now();
} }
function getCurrentState(now) { function getCurrentState(delta) {
return { return {
delta: now - lastUpdate, delta: delta,
frameRate: options.frameRate, frameRate: options.frameRate,
enemies: enemies, objects: [...enemies, world],
gazePoints: gazePoints, gazePoints: gazePoints,
debug: options.debug, 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]; let currentGazePosition = gazePoints[gazePoints.length - 1];
if ((now - lastEnemySpawn) >= Config.ENEMEY_SPAWN_DELAY) { if ((now - lastEnemySpawn) >= Config.ENEMEY_SPAWN_DELAY) {
if (enemies.length < Config.MAX_NUMBER_OF_ENEMIES) { if (enemies.length < Config.MAX_NUMBER_OF_ENEMIES) {
Logger.log("Add new enemy", "Game"); 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; lastEnemySpawn = now;
} }
} }
for (let i = enemies.length - 1; i >= 0; i--) { for (let i = enemies.length - 1; i >= 0; i--) {
// Update enemy's position // Update enemy's position
enemies[i].update(gameArea.center, adjustment); enemies[i].update(delta);
// Check if enemy has hit target // Check if enemy has hit target
if (enemies[i].distanceTo(gameArea.center) < Config.TARGET_RADIUS) { if (world.isHitBy(enemies[i])) {
onEnemyHitsTarget(enemies[i].damage); 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); 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 // Skip other checks if no gaze data is available
if (currentGazePosition === undefined) { if (currentGazePosition === undefined) {
...@@ -70,33 +92,13 @@ function updateEnemies(now, adjustment) { ...@@ -70,33 +92,13 @@ function updateEnemies(now, adjustment) {
} }
} }
function onEnemyHitsTarget(damage) { function onWorldDestroyed() {
Logger.log(`Enemy hit target for ${damage} dmg`, "Game"); Logger.log(`World destroyed [Current health ${world.health}]`, "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);
}
} }
class GameManager { class GameManager {
constructor() { init(gameOptions) {
enemies = [];
gazePoints = [];
lastEnemySpawn = 0;
}
setOptions(gameOptions) {
options = gameOptions; options = gameOptions;
gameArea = { gameArea = {
width: options.width, width: options.width,
...@@ -106,9 +108,14 @@ class GameManager { ...@@ -106,9 +108,14 @@ class GameManager {
y: options.height / 2, y: options.height / 2,
}, },
}; };
enemies = [];
gazePoints = [];
world = new World(gameArea.center.x, gameArea.center.y);
lastEnemySpawn = 0;
} }
start() { start() {
GameRenderer.init(options.canvas, options.width, options.height, options.version); GameRenderer.init(options.canvas, options.width, options.height, options.version);
lastUpdate = Date.now(); lastUpdate = Date.now();
gameLoop = setInterval(onTick, (Time.ONE_SECOND_IN_MS / options.frameRate)); gameLoop = setInterval(onTick, (Time.ONE_SECOND_IN_MS / options.frameRate));
......
...@@ -10,7 +10,7 @@ var canvas, ...@@ -10,7 +10,7 @@ var canvas,
function draw(state) { function draw(state) {
updateFPS(state.delta); updateFPS(state.delta);
context.clearRect(0, 0, canvas.width, canvas.height); context.clearRect(0, 0, canvas.width, canvas.height);
drawEnemies(state.enemies); drawObjects(state.objects);
drawGazePoints(state.gazePoints); drawGazePoints(state.gazePoints);
if (state.debug === true) { if (state.debug === true) {
drawDebugInfo(); drawDebugInfo();
...@@ -28,26 +28,12 @@ function updateFPS(delta) { ...@@ -28,26 +28,12 @@ function updateFPS(delta) {
} }
} }
function drawEnemies(enemies) { function drawObjects(objects) {
context.fillStyle = Config.DEFAULT_ENEMEY_COLOR; for (let i = 0; i < objects.length; i++) {
for (let i = 0; i < enemies.length; i++) { objects[i].draw(context);
drawSingleEnemey(enemies[i]);
} }
} }
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) { function drawGazePoints(gazePoints) {
context.fillStyle = Config.DEFAULT_GAZE_POINT_COLOR; context.fillStyle = Config.DEFAULT_GAZE_POINT_COLOR;
for (let i = 0; i < gazePoints.length; i++) { for (let i = 0; i < gazePoints.length; i++) {
...@@ -56,9 +42,10 @@ function drawGazePoints(gazePoints) { ...@@ -56,9 +42,10 @@ function drawGazePoints(gazePoints) {
} }
function drawSingleGazePoint(gazePoint) { 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.save();
context.globalAlpha = gazePoint.inversedAge; context.globalAlpha = inversedAge;
context.beginPath(); context.beginPath();
context.ellipse(gazePoint.targetX, gazePoint.targetY, radius, radius, Math.PI / context.ellipse(gazePoint.targetX, gazePoint.targetY, radius, radius, Math.PI /
4, 0, 2 * Math.PI); 4, 0, 2 * Math.PI);
......
...@@ -6,7 +6,7 @@ var canvas, provider; ...@@ -6,7 +6,7 @@ var canvas, provider;
function init(options) { function init(options) {
Logger.log("Starting StarGazer game", "Game"); Logger.log("Starting StarGazer game", "Game");
canvas = options.canvas; canvas = options.canvas;
GameManager.setOptions({ GameManager.init({
canvas: canvas, canvas: canvas,
frameRate: options.fps, frameRate: options.fps,
version: options.version, version: options.version,
......
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
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
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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment