Commit 6e825baf by Alexander Bazo

Add game objects and server side simulation

parent f660c13c
{
"parserOptions": {
"sourceType": "module"
},
"rules":
{
"block-scoped-var": "error",
"camelcase": "warn",
"comma-dangle": ["error", "always-multiline"],
"consistent-return": "error",
"curly": "error",
"default-case": "error",
"eqeqeq": "error",
"guard-for-in": "error",
"no-alert": "error",
"no-console": "warn",
"no-else-return": "error",
"no-empty-function": "error",
"no-eval": "error",
"no-eq-null": "error",
"no-extend-native": "warn",
"no-extra-bind": "error",
"no-loop-func": "error",
"no-magic-numbers": ["warn", {"ignore": [0, 1, -1], "ignoreArrayIndexes": true}],
"no-multiple-empty-lines": ["warn", {"max": 1}],
"no-multi-spaces": "warn",
"no-new": "error",
"no-new-func": "error",
"no-new-wrappers": "error",
"no-unused-expressions": "error",
"no-useless-concat": "error",
"no-undef-init": "error",
"no-underscore-dangle": "error",
"no-param-reassign": "error",
"no-self-compare": "error",
"no-void": "error",
"no-with": "error",
"one-var": "error",
"quotes": ["warn", "double"],
"semi": "error",
"strict": "error",
"vars-on-top": "error",
"yoda": "error"
},
"env":
{
"es6": true,
"browser": true
},
"extends": "eslint:recommended"
}
\ No newline at end of file
{
// Details: https://github.com/victorporof/Sublime-HTMLPrettify#using-your-own-jsbeautifyrc-options
"js": {
"allowed_file_extensions": ["js", "json", "jshintrc", "jsbeautifyrc"],
"brace_style": "collapse-preserve-inline",
"break_chained_methods": false,
"e4x": false,
"eol": "\n",
"end_with_newline": false,
"indent_char": " ",
"indent_level": 0,
"indent_size": 2,
"indent_with_tabs": false,
"jslint_happy": false,
"keep_array_indentation": false,
"keep_function_indentation": false,
"max_preserve_newlines": 0,
"preserve_newlines": true,
"space_after_anon_function": false,
"space_before_conditional": true,
"space_in_empty_paren": false,
"space_in_paren": false,
"unescape_strings": false,
"wrap_line_length": 80
}
}
\ No newline at end of file
const http = require("http"); /* eslint-env node */
const express = require("express");
const cors = require("cors"); const http = require("http"),
const colyseus = require("colyseus"); express = require("express"),
const monitor = require("@colyseus/monitor").monitor; cors = require("cors"),
const socialRoutes = require("@colyseus/social/express").default; colyseus = require("colyseus"),
const port = process.env.PORT || 2567; monitor = require("@colyseus/monitor").monitor,
const app = express(); socialRoutes = require("@colyseus/social/express").default,
const StarGazerRoom = require("./lib/rooms/StarGazerRoom"); StarGazerRoom = require("./lib/rooms/StarGazerRoom.js"),
Logger = require("./lib/utils/Logger.js"),
Config = require("./lib/config/ServerConfig.js");
var app = express(),
server = http.createServer(app),
gameServer;
Logger.enable();
app.use(cors()); app.use(cors());
app.use(express.json()); app.use(express.json());
const server = http.createServer(app); gameServer = new colyseus.Server({
const gameServer = new colyseus.Server({
server: server, server: server,
express: app express: app
}); });
gameServer.define(Config.getGameRoomName(), StarGazerRoom);
// Must be set after game server is created
app.use(Config.getSocialRoute(), socialRoutes);
app.use(Config.getMonitorRoute(), monitor(gameServer));
// Prrobably shoud be set after game server is created
app.use(Config.getGameRoute(), express.static(Config.getGamePath()));
gameServer.define("star_gazer_lab", StarGazerRoom); gameServer.listen(Config.getPort());
app.use("/", socialRoutes); Logger.log(`Listening on ws://localhost:${ Config.getPort() }`, "Server");
app.use("/colyseus", monitor(gameServer)); \ No newline at end of file
gameServer.listen(port);
console.log(`Listening on ws://localhost:${ port }`)
/* eslint-disable no-magic-numbers */
var version,
fps,
mapWidth,
mapHeight,
maxGazePointAge,
gazePointRadius,
maxNumberOfEnemies,
enemySpawnDelay,
playerDamage;
class GameConfiguration {
constructor() {}
reset() {
version = "$VERSION";
fps = 60;
mapWidth = 1920,
mapHeight = 1080;
maxGazePointAge = 500;
gazePointRadius = 15;
maxNumberOfEnemies = 10;
enemySpawnDelay = 500;
playerDamage = 100;
}
setVersion(value) {
version = value;
}
getVersion() {
return version;
}
setFPS(value) {
fps = value;
}
getFPS() {
return fps;
}
setMapWidth(value) {
mapWidth = value;
}
getMapWidth() {
return mapWidth;
}
setMapHeight(value) {
mapHeight = value;
}
getMapHeight() {
return mapHeight;
}
setMaxGazePointAge(value) {
maxGazePointAge = value;
}
getMaxGazePointAge() {
return maxGazePointAge;
}
setGazePointRadius(value) {
gazePointRadius = value;
}
getGazePointRadius() {
return gazePointRadius;
}
setGazePointColor(value) {
gazePointColor = value;
}
getGazePointColor() {
return gazePointColor;
}
setMaxNumberOfEnemies(value) {
maxNumberOfEnemies = value;
}
getMaxNumberOfEnemies() {
return maxNumberOfEnemies;
}
setEnemySpawnDelay(value) {
enemySpawnDelay = value;
}
getEnemySpawnDelay() {
return enemySpawnDelay;
}
setPlayerDamage(value) {
playerDamage = value;
}
getPlayerDamage() {
return playerDamage;
}
}
const Config = new GameConfiguration;
Config.reset();
module.exports = Config;
\ No newline at end of file
/* eslint-disable no-magic-numbers */
var port,
socialRoute,
monitorRoute,
gameRoute,
gamePath,
gameRoomName;
class ServerConfig {
constructor() {}
reset() {
port = process.env.PORT || 2567;
socialRoute = "/";
monitorRoute = "/colyseus";
gameRoute = "/game";
gamePath = "../StarGazerClient/build";
gameRoomName = "star_gazer_lab";
}
getPort() {
return port;
}
getSocialRoute() {
return socialRoute;
}
getMonitorRoute() {
return monitorRoute;
}
getGameRoute() {
return gameRoute;
}
getGamePath() {
return gamePath;
}
getGameRoomName() {
return gameRoomName;
}
}
const Config = new ServerConfig;
Config.reset();
module.exports = Config;
...@@ -6,32 +6,37 @@ const DEFAULT_VELOCITY = 1, ...@@ -6,32 +6,37 @@ const DEFAULT_VELOCITY = 1,
DEFAULT_WIDTH = 50, DEFAULT_WIDTH = 50,
DEFAULT_HEIGHT = 50, DEFAULT_HEIGHT = 50,
DEFAULT_DAMAGE = 10, DEFAULT_DAMAGE = 10,
DEFAULT_HIT_BOX_RADIUS = 30, DEFAULT_HIT_BOX_RADIUS = 30;
DEFAULT_ENEMY_COLOR = "#3f0d76",
DEFAULT_ENEMEY_TIP_COLOR = "#d2ccf3",
DEFAULT_ENEMY_HEALTH_COLOR = "#d2ccf3",
DEFAULT_ENEMY_HEALTH_WIDTH = 10,
DEFAULT_ENEMY_HEALTH_RADIUS = 50;
class Enemy extends GameObject { class Enemy extends GameObject {
constructor(x, y, width, height, hitBoxRadius, velocity, direction, color) { constructor(x, y, width, height, hitBoxRadius, velocity, direction) {
super(x, y, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_HIT_BOX_RADIUS, super(x, y, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_HIT_BOX_RADIUS,
DEFAULT_VELOCITY, direction, DEFAULT_ENEMY_COLOR); DEFAULT_VELOCITY, direction);
this.lp = 100; this.lp = DEFAULT_HEALTH;
this.damage = DEFAULT_DAMAGE;
}
setFocus() {
this.focus = true;
}
removeFocus() {
this.focus = false;
} }
static createEnemy(rangeX, rangeY, targetX, targetY) { static createEnemy(rangeX, rangeY, targetX, targetY) {
let xPos = parseInt(Math.random() * rangeX), let xPos = parseInt(Math.random() * rangeX),
yPos = parseInt(Math.random() * rangeY), yPos = parseInt(Math.random() * rangeY),
direction = Math.atan2(targetY - yPos, targetX - xPos) * 180 / Math.PI; direction = Math.atan2(targetY - yPos, targetX - xPos) * 180 / Math.PI;
return new Enemy(xPos, yPos, direction); return new Enemy(xPos, yPos, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_HIT_BOX_RADIUS, DEFAULT_VELOCITY, direction);
} }
} }
schema.defineTypes(Enemy, { schema.defineTypes(Enemy, {
lp: "number" lp: "number",
damage: "number"
}); });
......
...@@ -3,7 +3,7 @@ const Schema = schema.Schema; ...@@ -3,7 +3,7 @@ const Schema = schema.Schema;
class GameObject extends Schema { class GameObject extends Schema {
constructor(x, y, width, height, hitBoxRadius, velocity, direction, color) { constructor(x, y, width, height, hitBoxRadius, velocity, direction) {
super(); super();
this.x = x; this.x = x;
this.y = y; this.y = y;
...@@ -12,7 +12,6 @@ class GameObject extends Schema { ...@@ -12,7 +12,6 @@ class GameObject extends Schema {
this.hitBoxRadius = hitBoxRadius; this.hitBoxRadius = hitBoxRadius;
this.velocity = velocity; this.velocity = velocity;
this.direction = direction; this.direction = direction;
this.color = color;
} }
update(delta) { update(delta) {
...@@ -45,8 +44,7 @@ schema.defineTypes(GameObject, { ...@@ -45,8 +44,7 @@ schema.defineTypes(GameObject, {
width: "number", width: "number",
height: "number", height: "number",
velocity: "number", velocity: "number",
direction: "number", direction: "number"
color: "string"
}); });
module.exports = GameObject; module.exports = GameObject;
\ No newline at end of file
const schema = require("@colyseus/schema");
const Schema = schema.Schema;
class GazePoint extends Schema {
constructor(screenX, screenY) {
this.screenX = screenX;
this.screenY = screenY;
this.createdAt = Date.now();
this.id = this.createdAt;
}
}
schema.defineTypes(GazePoint, {
screenX: "number",
screenY: "number",
createdAt: "number",
player: "text",
id: "number",
});
module.exports = GazePoint;
\ No newline at end of file
const schema = require("@colyseus/schema"),
GameObject = require("./GameObject.js");
const DEFAULT_HEALTH = 100,
DEFAULT_HIT_BOX_RADIUS = 270,
DEFAULT_WIDTH = 500,
DEFAULT_HEIGHT = 500;
class Planet extends GameObject {
constructor(x, y) {
super(x, y, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_HIT_BOX_RADIUS,
0, 0);
this.lp = DEFAULT_HEALTH;
}
}
schema.defineTypes(Planet, {
lp: "number"
});
module.exports = Planet;
\ No newline at end of file
const schema = require("@colyseus/schema"), const schema = require("@colyseus/schema"),
Logger = require("../utils/Logger.js"),
GazePoint = require("./GazePoint.js"),
Enemy = require("./Enemy.js"), Enemy = require("./Enemy.js"),
Planet = require("./Planet.js"),
Schema = schema.Schema, Schema = schema.Schema,
ArraySchema = schema.ArraySchema, ArraySchema = schema.ArraySchema,
MAX_ENEMIES = 10, GameConfig = require("../config/GameConfig.js");
ENEMY_SPAWN_DELAY = 1000;
var lastEnemySpawn;
class StarGazerState extends Schema { class StarGazerState extends Schema {
constructor() { constructor() {
console.log("Initalizing game state"); Logger.log("Initalizing game state", "Game");
super(); super();
this.stateType = "idle"; this.stateType = "idle";
this.width = 1920; this.width = GameConfig.getMapWidth();
this.height = 1080; this.height = GameConfig.getMapHeight();
this.gazePoints = new ArraySchema();
this.planet = new Planet(GameConfig.getMapWidth()/2, GameConfig.getMapHeight());
this.enemies = new ArraySchema(); this.enemies = new ArraySchema();
this.lastUpdate = Date.now();
this.lastEnemySpawn = 0;
} }
update(deltaTime) { update(deltaTime) {
this.spawnEnemy(); let now = Date.now(),
delta = now - this.lastUpdate;
this.updateGazePoints(now);
this.updateEnemies(now, delta);
this.lastUpdate = Date.now();
} }
spawnEnemy() { updateGazePoints(now) {
let now = Date.now(); for (let i = this.gazePoints.length - 1; i >= 0; i--) {
if (lastEnemySpawn === undefined) { let age = now - gazePoints[i].createdAt;
this.addEnemy(); if (age > GameConfig.getMaxGazePointAge()) {
lastEnemySpawn = now; this.gazePoints.splice(i, 1);
return; } else {
this.gazePoints[i].relativeAge = age / GameConfig.getMaxGazePointAge();
}
} }
if ((now - lastEnemySpawn) > ENEMY_SPAWN_DELAY) {
this.addEnemy();
lastEnemySpawn = now;
}
} }
addEnemy() { updateEnemies(now, delta) {
if (this.enemies.length < MAX_ENEMIES) { let currentGazePosition = this.gazePoints[this.gazePoints.length - 1];
this.enemies.push(new Enemy()); if ((now - this.lastEnemySpawn) >= GameConfig.getEnemySpawnDelay()) {
if (this.enemies.length < GameConfig.getMaxNumberOfEnemies()) {
Logger.log("Add new enemy", "Game");
this.enemies.push(Enemy.createEnemy(GameConfig.getMapWidth(), 100, this.planet.x, this.planet.y));
this.lastEnemySpawn = now;
}
} }
for (let i = this.enemies.length - 1; i >= 0; i--) {
// Remove focus from enemy
this.enemies[i].removeFocus();
// Update enemy's position
this.enemies[i].update(delta);
// Check if enemy has hit target
if (this.planet.isHitBy(this.enemies[i])) {
Logger.log(`Enemy hit target for ${this.enemies[i].damage} dmg`, "Game");
// Reduce planet health after enemy has hit
this.planet.health -= this.enemies[i].damage;
if (this.planet.health <= 0) {
this.onWorldDestroyed();
}
// Remove enemy after it has hit target
this.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) {
continue;
}
// Check if enemy was hit
if (this.enemies[i].isHitBy({
x: currentGazePosition.targetX,
y: currentGazePosition.targetY,
})) {
// Set focus on currently watched enemy
this.enemies[i].setFocus();
// Update enemy's health
this.enemies[i].health -= GameConfig.getPlayerDamage() / (1000 / delta);
// Remove enemy if destroyed
if (this.enemies[i].health <= 0) {
Logger.log(`Enemy destroyed by player`, "Game");
this.enemies.splice(i, 1);
// TODO: Add exposion at last position
}
}
}
}
onWorldDestroyed() {
} }
} }
...@@ -48,6 +101,8 @@ schema.defineTypes(StarGazerState, { ...@@ -48,6 +101,8 @@ schema.defineTypes(StarGazerState, {
stateType: "string", stateType: "string",
width: "number", width: "number",
height: "number", height: "number",
planet: Planet,
gazePoints: [GazePoint],
enemies: [Enemy], enemies: [Enemy],
}); });
......
class Logger {
constructor() {
this.enabled = false;
}
enable() {
this.enabled = true;
}
disable() {
this.enabled = false;
}
log(msg, label) {
let time = new Date(),
hours = time.getUTCHours(),
minutes = time.getUTCMinutes(),
seconds = time.getUTCSeconds();
if (hours < 10) {
hours = "0" + hours;
}
if (minutes < 10) {
minutes = "0" + minutes;
}
if (seconds < 10) {
seconds = "0" + seconds;
}
if (this.enabled === false) {
return;
}
console.log(`[${label}]\t${hours}:${minutes}:${seconds} - ${msg}`);
}
}
module.exports = new Logger();
\ 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