Add server screen
This commit is contained in:
parent
89fafeafd3
commit
e2129bbb26
20 changed files with 672 additions and 174 deletions
|
|
@ -1,20 +1,24 @@
|
||||||
{
|
{
|
||||||
"name": "decla.red-server",
|
"name": "declared-server",
|
||||||
"private": true,
|
|
||||||
"description": "Game server for decla.red",
|
"description": "Game server for decla.red",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "András Schmelczer <andras@schmelczer.dev> (https://schmelczer.dev/)",
|
"author": "András Schmelczer <andras@schmelczer.dev> (https://schmelczer.dev/)",
|
||||||
"main": "index.js",
|
"version": "0.0.1",
|
||||||
|
"main": "dist/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npx webpack --mode production",
|
"build": "npx webpack --mode production",
|
||||||
"initialize": "npm install",
|
"initialize": "npm install",
|
||||||
"start": "concurrently --kill-others \"npx webpack --mode development -w\" \"nodemon --legacy-watch dist/main.js\"",
|
"start": "concurrently --kill-others \"npx webpack --mode development -w\" \"cd dist && nodemon --legacy-watch main.js\"",
|
||||||
"try-build": "npm run build && node dist/main.js"
|
"try-build": "npm run build && cd dist && node main.js && cd -"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"declared-server": "./dist/main.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/config": "0.0.36",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"http": "0.0.1-security",
|
"minimist": "^1.2.5",
|
||||||
"socket.io": "^2.3.0",
|
"socket.io": "^2.3.0",
|
||||||
"uws": "^10.148.1",
|
"uws": "^10.148.1",
|
||||||
"webpack-node-externals": "^2.5.2"
|
"webpack-node-externals": "^2.5.2"
|
||||||
|
|
|
||||||
8
backend/src/default-options.ts
Normal file
8
backend/src/default-options.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { Options } from './options';
|
||||||
|
|
||||||
|
export const defaultOptions: Options = {
|
||||||
|
port: 3000,
|
||||||
|
name: 'Localhost',
|
||||||
|
playerLimit: 16,
|
||||||
|
seed: 51,
|
||||||
|
};
|
||||||
88
backend/src/game-server.ts
Normal file
88
backend/src/game-server.ts
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
import { PhysicalContainer } from './physics/containers/physical-container';
|
||||||
|
import { Player } from './players/player';
|
||||||
|
import ioserver from 'socket.io';
|
||||||
|
import { TransportEvents, deserialize, settings, ServerInformation } from 'shared';
|
||||||
|
import { createWorld } from './map/create-world';
|
||||||
|
import { DeltaTimeCalculator } from './helper/delta-time-calculator';
|
||||||
|
import { Options } from './options';
|
||||||
|
|
||||||
|
export class GameServer {
|
||||||
|
private objects = new PhysicalContainer();
|
||||||
|
private players: Array<Player> = [];
|
||||||
|
private deltaTimes: Array<number> = [];
|
||||||
|
private deltaTimeCalculator = new DeltaTimeCalculator();
|
||||||
|
|
||||||
|
private serverName: string;
|
||||||
|
private playerLimit: number;
|
||||||
|
constructor(io: ioserver.Server, options: Options) {
|
||||||
|
this.serverName = options.name;
|
||||||
|
this.playerLimit = options.playerLimit;
|
||||||
|
|
||||||
|
createWorld(this.objects);
|
||||||
|
this.objects.initialize();
|
||||||
|
|
||||||
|
io.on('connection', (socket: SocketIO.Socket) => {
|
||||||
|
socket.on(TransportEvents.PlayerJoining, () => {
|
||||||
|
const player = new Player(this.objects, socket);
|
||||||
|
this.players.push(player);
|
||||||
|
socket.on(TransportEvents.PlayerToServer, (json: string) => {
|
||||||
|
const command = deserialize(json);
|
||||||
|
player.sendCommand(command);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('disconnect', () => {
|
||||||
|
player.destroy();
|
||||||
|
this.players = this.players.filter((p) => p !== player);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('join', (room_name: string) => {
|
||||||
|
socket.join(room_name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public start() {
|
||||||
|
this.handlePhysics();
|
||||||
|
}
|
||||||
|
|
||||||
|
private handlePhysics() {
|
||||||
|
const delta = this.deltaTimeCalculator.getNextDeltaTimeInMilliseconds();
|
||||||
|
this.deltaTimes.push(delta);
|
||||||
|
const framesBetweenDeltaTimeCalculation = 1000;
|
||||||
|
|
||||||
|
if (this.deltaTimes.length > framesBetweenDeltaTimeCalculation) {
|
||||||
|
this.deltaTimes.sort((a, b) => a - b);
|
||||||
|
console.log(
|
||||||
|
`Median physics time: ${this.deltaTimes[
|
||||||
|
framesBetweenDeltaTimeCalculation
|
||||||
|
].toFixed(2)} ms`,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`Memory used: ${(process.memoryUsage().rss / 1024 / 1024).toFixed(2)} MB`,
|
||||||
|
);
|
||||||
|
this.deltaTimes = [];
|
||||||
|
console.log(this.players.map((p) => p.latency));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.objects.stepObjects(delta / 1000);
|
||||||
|
this.players.forEach((p) => p.step(delta / 1000));
|
||||||
|
|
||||||
|
const physicsDelta = this.deltaTimeCalculator.getDeltaTimeInMilliseconds();
|
||||||
|
this.deltaTimes.push(physicsDelta);
|
||||||
|
const sleepTime = settings.targetPhysicsDeltaTimeInMilliseconds - physicsDelta;
|
||||||
|
if (sleepTime >= settings.minPhysicsSleepTime) {
|
||||||
|
setTimeout(this.handlePhysics.bind(this), sleepTime);
|
||||||
|
} else {
|
||||||
|
setImmediate(this.handlePhysics.bind(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get serverInfo(): ServerInformation {
|
||||||
|
return {
|
||||||
|
serverName: this.serverName,
|
||||||
|
playerCount: this.players.length,
|
||||||
|
playerLimit: this.playerLimit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.3.1/socket.io.js"
|
|
||||||
integrity="sha512-AcZyhRP/tbAEsXCCGlziPun5iFvcSUpEz2jKkx0blkYKbxU81F+iq8FURwPn1sYFeksJ+sDDrI5XujsqSobWdQ=="
|
|
||||||
crossorigin="anonymous"></script>
|
|
||||||
<title>Server info</title>
|
|
||||||
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body></body>
|
|
||||||
<script>
|
|
||||||
const socket = io({
|
|
||||||
transports: ['websocket'],
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('reconnect_attempt', () => {
|
|
||||||
socket.io.opts.transports = ['polling', 'websocket'];
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.emit('join', 'insights');
|
|
||||||
|
|
||||||
socket.on('insights', (message) => {
|
|
||||||
document.body.innerText += message;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,36 +1,32 @@
|
||||||
import ioserver, { Socket } from 'socket.io';
|
import ioserver from 'socket.io';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { Server } from 'http';
|
import { Server } from 'http';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import {
|
import { applyArrayPlugins, Random, serverInformationEndpoint } from 'shared';
|
||||||
applyArrayPlugins,
|
import minimist from 'minimist';
|
||||||
Random,
|
|
||||||
TransportEvents,
|
|
||||||
deserialize,
|
|
||||||
settings,
|
|
||||||
} from 'shared';
|
|
||||||
import './index.html';
|
|
||||||
import { Player } from './players/player';
|
|
||||||
import { PhysicalContainer } from './physics/containers/physical-container';
|
|
||||||
import { createDungeon } from './map/create-dungeon';
|
|
||||||
import { glMatrix } from 'gl-matrix';
|
import { glMatrix } from 'gl-matrix';
|
||||||
import { DeltaTimeCalculator } from './helper/delta-time-calculator';
|
|
||||||
|
import { GameServer } from './game-server';
|
||||||
|
|
||||||
|
import { defaultOptions } from './default-options';
|
||||||
|
|
||||||
glMatrix.setMatrixArrayType(Array);
|
glMatrix.setMatrixArrayType(Array);
|
||||||
|
|
||||||
applyArrayPlugins();
|
applyArrayPlugins();
|
||||||
|
|
||||||
Random.seed = 44;
|
const optionOverrides = minimist(process.argv.slice(2));
|
||||||
|
const options = {
|
||||||
|
...defaultOptions,
|
||||||
|
...optionOverrides,
|
||||||
|
};
|
||||||
|
|
||||||
const objects = new PhysicalContainer();
|
Random.seed = options.seed;
|
||||||
createDungeon(objects);
|
|
||||||
|
|
||||||
objects.initialize();
|
|
||||||
|
|
||||||
let players: Array<Player> = [];
|
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const deltaTimeCalculator = new DeltaTimeCalculator();
|
const server = new Server(app);
|
||||||
|
const io = ioserver(server);
|
||||||
|
|
||||||
|
const gameServer = new GameServer(io, options);
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
cors({
|
cors({
|
||||||
|
|
@ -41,70 +37,12 @@ app.use(
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const port = 3000;
|
app.get(serverInformationEndpoint, (req, res) => {
|
||||||
const server = new Server(app);
|
res.json(gameServer.serverInfo);
|
||||||
const io = ioserver(server);
|
|
||||||
|
|
||||||
/*
|
|
||||||
const log = (text: string) => {
|
|
||||||
io.to('insights').emit('insights', text + '\n');
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.get('/', function (req, res) {
|
|
||||||
res.sendFile('dist/index.html', { root: '.' });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
io.on('connection', (socket: SocketIO.Socket) => {
|
server.listen(options.port, () => {
|
||||||
socket.on(TransportEvents.PlayerJoining, () => {
|
console.log(`server started at http://localhost:${options.port}`);
|
||||||
const player = new Player(objects, socket);
|
|
||||||
players.push(player);
|
|
||||||
socket.on(TransportEvents.PlayerToServer, (json: string) => {
|
|
||||||
const command = deserialize(json);
|
|
||||||
player.sendCommand(command);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('disconnect', () => {
|
|
||||||
player.destroy();
|
|
||||||
players = players.filter((p) => p !== player);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('join', (room_name: string) => {
|
|
||||||
socket.join(room_name);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
server.listen(port, () => {
|
gameServer.start();
|
||||||
console.log(`server started at http://localhost:${port}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
let deltas: Array<number> = [];
|
|
||||||
|
|
||||||
const handlePhysics = () => {
|
|
||||||
const delta = deltaTimeCalculator.getNextDeltaTimeInMilliseconds();
|
|
||||||
deltas.push(delta);
|
|
||||||
if (deltas.length > 1000) {
|
|
||||||
deltas.sort((a, b) => a - b);
|
|
||||||
console.log(`Median physics time: ${deltas[50].toFixed(2)} ms`);
|
|
||||||
console.log(
|
|
||||||
`Memory used: ${(process.memoryUsage().rss / 1024 / 1024).toFixed(2)} MB`,
|
|
||||||
);
|
|
||||||
deltas = [];
|
|
||||||
console.log(players.map((p) => p.latency));
|
|
||||||
}
|
|
||||||
|
|
||||||
objects.stepObjects(delta / 1000);
|
|
||||||
players.forEach((p) => p.step(delta / 1000));
|
|
||||||
|
|
||||||
const physicsDelta = deltaTimeCalculator.getDeltaTimeInMilliseconds();
|
|
||||||
deltas.push(physicsDelta);
|
|
||||||
const sleepTime = settings.targetPhysicsDeltaTimeInMilliseconds - physicsDelta;
|
|
||||||
if (sleepTime >= settings.minPhysicsSleepTime) {
|
|
||||||
setTimeout(handlePhysics, sleepTime);
|
|
||||||
} else {
|
|
||||||
setImmediate(handlePhysics);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handlePhysics();
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { vec2, vec3 } from 'gl-matrix';
|
import { vec2, vec3 } from 'gl-matrix';
|
||||||
import { Random, settings } from 'shared';
|
import { Random, settings, PlanetBase } from 'shared';
|
||||||
import { LampPhysical } from '../objects/lamp-physical';
|
import { LampPhysical } from '../objects/lamp-physical';
|
||||||
import { PlanetPhysical } from '../objects/planet-physical';
|
import { PlanetPhysical } from '../objects/planet-physical';
|
||||||
import { PhysicalContainer } from '../physics/containers/physical-container';
|
import { PhysicalContainer } from '../physics/containers/physical-container';
|
||||||
import { evaluateSdf } from '../physics/functions/evaluate-sdf';
|
import { evaluateSdf } from '../physics/functions/evaluate-sdf';
|
||||||
import { Physical } from '../physics/physicals/physical';
|
import { Physical } from '../physics/physicals/physical';
|
||||||
|
|
||||||
export const createDungeon = (objectContainer: PhysicalContainer) => {
|
export const createWorld = (objectContainer: PhysicalContainer) => {
|
||||||
const objects: Array<Physical> = [];
|
const objects: Array<Physical> = [];
|
||||||
const lights: Array<Physical> = [];
|
const lights: Array<Physical> = [];
|
||||||
|
|
||||||
|
|
@ -33,12 +33,14 @@ export const createDungeon = (objectContainer: PhysicalContainer) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
objects.push(
|
objects.push(
|
||||||
createBlob(
|
new PlanetPhysical(
|
||||||
|
PlanetBase.createPlanetVertices(
|
||||||
position,
|
position,
|
||||||
Random.getRandomInRange(300, 800),
|
Random.getRandomInRange(300, 800),
|
||||||
Random.getRandomInRange(300, 800),
|
Random.getRandomInRange(300, 800),
|
||||||
Random.getRandomInRange(20, 40),
|
Random.getRandomInRange(20, 40),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,26 +74,3 @@ export const createDungeon = (objectContainer: PhysicalContainer) => {
|
||||||
|
|
||||||
[...objects, ...lights].forEach((o) => objectContainer.addObject(o));
|
[...objects, ...lights].forEach((o) => objectContainer.addObject(o));
|
||||||
};
|
};
|
||||||
|
|
||||||
const createBlob = (
|
|
||||||
center: vec2,
|
|
||||||
width: number,
|
|
||||||
height: number,
|
|
||||||
randomness: number,
|
|
||||||
): PlanetPhysical => {
|
|
||||||
const vertices = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < settings.polygonEdgeCount; i++) {
|
|
||||||
vertices.push(
|
|
||||||
vec2.fromValues(
|
|
||||||
center.x +
|
|
||||||
(width / 2) * Math.cos((i / settings.polygonEdgeCount) * -Math.PI * 2) +
|
|
||||||
Random.getRandomInRange(-randomness, randomness),
|
|
||||||
center.y +
|
|
||||||
(height / 2) * Math.sin((i / settings.polygonEdgeCount) * -Math.PI * 2) +
|
|
||||||
Random.getRandomInRange(-randomness, randomness),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return new PlanetPhysical(vertices);
|
|
||||||
};
|
|
||||||
6
backend/src/options.ts
Normal file
6
backend/src/options.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
export interface Options {
|
||||||
|
port: number;
|
||||||
|
name: string;
|
||||||
|
playerLimit: number;
|
||||||
|
seed: number;
|
||||||
|
}
|
||||||
|
|
@ -15,10 +15,33 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<!--h1>Decla.red</h1>
|
<article id="landing-ui">
|
||||||
<section id="servers"></section-->
|
<h1>decla.<span class="red">red</span></h1>
|
||||||
|
<form id="join-game-form">
|
||||||
|
<fieldset class="content">
|
||||||
|
<legend>Join a game</legend>
|
||||||
|
|
||||||
|
<div class="name-group">
|
||||||
|
<input
|
||||||
|
required
|
||||||
|
minlength="1"
|
||||||
|
maxlength="20"
|
||||||
|
placeholder=" "
|
||||||
|
id="playerName"
|
||||||
|
name="playerName"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
<label for="playerName">Choose a name</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="server-container"></div>
|
||||||
|
<button id="join-game" type="submit">Join</button>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</article>
|
||||||
|
|
||||||
<noscript>Javascript is required for this website.</noscript>
|
<noscript>Javascript is required for this website.</noscript>
|
||||||
<div id="overlay"></div>
|
<div id="overlay"></div>
|
||||||
<canvas id="main"></canvas>
|
<canvas></canvas>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
import { glMatrix } from 'gl-matrix';
|
import { glMatrix } from 'gl-matrix';
|
||||||
import { CharacterBase, LampBase, overrideDeserialization, PlanetBase } from 'shared';
|
import { CharacterBase, LampBase, overrideDeserialization, PlanetBase } from 'shared';
|
||||||
import { ProjectileBase } from 'shared/src/objects/types/projectile-base';
|
import { ProjectileBase } from 'shared/src/objects/types/projectile-base';
|
||||||
import { Game } from './scripts/game';
|
|
||||||
import { CharacterView } from './scripts/objects/character-view';
|
import { CharacterView } from './scripts/objects/character-view';
|
||||||
import { LampView } from './scripts/objects/lamp-view';
|
import { LampView } from './scripts/objects/lamp-view';
|
||||||
import { ProjectileView } from './scripts/objects/projectile-view';
|
import { ProjectileView } from './scripts/objects/projectile-view';
|
||||||
import { PlanetView } from './scripts/objects/planet-view';
|
import { PlanetView } from './scripts/objects/planet-view';
|
||||||
import './styles/main.scss';
|
import './styles/main.scss';
|
||||||
|
import { LandingPageBackground } from './scripts/landing-page-background';
|
||||||
|
import { JoinFormHandler } from './scripts/join-form-handler';
|
||||||
|
import { Game } from './scripts/game';
|
||||||
|
|
||||||
glMatrix.setMatrixArrayType(Array);
|
glMatrix.setMatrixArrayType(Array);
|
||||||
|
|
||||||
|
|
@ -15,9 +18,35 @@ overrideDeserialization(PlanetBase, PlanetView);
|
||||||
overrideDeserialization(LampBase, LampView);
|
overrideDeserialization(LampBase, LampView);
|
||||||
overrideDeserialization(ProjectileBase, ProjectileView);
|
overrideDeserialization(ProjectileBase, ProjectileView);
|
||||||
|
|
||||||
|
const addSupportForTabNavigation = () =>
|
||||||
|
(document.onkeydown = (e) => {
|
||||||
|
if (e.key === ' ') {
|
||||||
|
(document.activeElement as HTMLElement)?.click();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/*const removeUnnecessaryOutlines = () =>
|
||||||
|
(document.onclick = (e) => {
|
||||||
|
(e.target as HTMLElement)?.blur();
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
addSupportForTabNavigation();
|
||||||
|
//removeUnnecessaryOutlines();
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
try {
|
try {
|
||||||
await new Game().start();
|
const landingUI = document.querySelector('#landing-ui') as HTMLElement;
|
||||||
|
const background = new LandingPageBackground();
|
||||||
|
const joinHandler = new JoinFormHandler(
|
||||||
|
document.querySelector('#join-game-form') as HTMLFormElement,
|
||||||
|
document.querySelector('#server-container') as HTMLElement,
|
||||||
|
);
|
||||||
|
const playerDecision = await joinHandler.getPlayerDecision();
|
||||||
|
landingUI.style.display = 'none';
|
||||||
|
console.log(playerDecision);
|
||||||
|
background.destroy();
|
||||||
|
await new Game(playerDecision).start();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
alert(e);
|
alert(e);
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ import { MouseListener } from './commands/generators/mouse-listener';
|
||||||
import { TouchListener } from './commands/generators/touch-listener';
|
import { TouchListener } from './commands/generators/touch-listener';
|
||||||
import { CommandReceiverSocket } from './commands/receivers/command-receiver-socket';
|
import { CommandReceiverSocket } from './commands/receivers/command-receiver-socket';
|
||||||
|
|
||||||
import { Configuration } from './config/configuration';
|
|
||||||
import { DeltaTimeCalculator } from './helper/delta-time-calculator';
|
import { DeltaTimeCalculator } from './helper/delta-time-calculator';
|
||||||
|
import { PlayerDecision } from './join-form-handler';
|
||||||
import { GameObjectContainer } from './objects/game-object-container';
|
import { GameObjectContainer } from './objects/game-object-container';
|
||||||
import { BlobShape } from './shapes/blob-shape';
|
import { BlobShape } from './shapes/blob-shape';
|
||||||
import { Circle } from './shapes/circle';
|
import { Circle } from './shapes/circle';
|
||||||
|
|
@ -32,18 +32,23 @@ import { Polygon } from './shapes/polygon';
|
||||||
|
|
||||||
export class Game {
|
export class Game {
|
||||||
public readonly gameObjects = new GameObjectContainer(this);
|
public readonly gameObjects = new GameObjectContainer(this);
|
||||||
private readonly canvas: HTMLCanvasElement = document.querySelector(
|
private readonly canvas = document.querySelector('canvas') as HTMLCanvasElement;
|
||||||
'canvas#main',
|
|
||||||
) as HTMLCanvasElement;
|
|
||||||
private renderer!: Renderer;
|
private renderer!: Renderer;
|
||||||
private socket!: SocketIOClient.Socket;
|
private socket!: SocketIOClient.Socket;
|
||||||
|
private promises: Promise<[void, void]>;
|
||||||
private deltaTimeCalculator = new DeltaTimeCalculator();
|
private deltaTimeCalculator = new DeltaTimeCalculator();
|
||||||
private overlay: HTMLElement = document.querySelector('#overlay') as HTMLDivElement;
|
private overlay: HTMLElement = document.querySelector('#overlay') as HTMLDivElement;
|
||||||
|
|
||||||
private async setupCommunication(): Promise<void> {
|
constructor(playerDecision: PlayerDecision) {
|
||||||
await Configuration.initialize();
|
console.log(playerDecision.server);
|
||||||
|
this.promises = Promise.all([
|
||||||
|
this.setupCommunication(playerDecision.server),
|
||||||
|
this.setupRenderer(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
this.socket = io(Configuration.servers[1], {
|
private async setupCommunication(serverUrl: string): Promise<void> {
|
||||||
|
this.socket = io(serverUrl, {
|
||||||
reconnectionDelayMax: 10000,
|
reconnectionDelayMax: 10000,
|
||||||
transports: ['websocket'],
|
transports: ['websocket'],
|
||||||
});
|
});
|
||||||
|
|
@ -81,7 +86,7 @@ export class Game {
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
...Polygon.descriptor,
|
...Polygon.descriptor,
|
||||||
shaderCombinationSteps: [0, 2, 6, 16],
|
shaderCombinationSteps: [0, 1, 2, 3],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...BlobShape.descriptor,
|
...BlobShape.descriptor,
|
||||||
|
|
@ -93,11 +98,11 @@ export class Game {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...CircleLight.descriptor,
|
...CircleLight.descriptor,
|
||||||
shaderCombinationSteps: [0, 1, 2, 4, 8],
|
shaderCombinationSteps: [0, 1, 2, 4, 8, 16],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...Flashlight.descriptor,
|
...Flashlight.descriptor,
|
||||||
shaderCombinationSteps: [0, 1],
|
shaderCombinationSteps: [0],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
|
|
@ -131,7 +136,7 @@ export class Game {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async start(): Promise<void> {
|
public async start(): Promise<void> {
|
||||||
await Promise.all([this.setupCommunication(), this.setupRenderer()]);
|
await this.promises;
|
||||||
requestAnimationFrame(this.gameLoop.bind(this));
|
requestAnimationFrame(this.gameLoop.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
72
frontend/src/scripts/join-form-handler.ts
Normal file
72
frontend/src/scripts/join-form-handler.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { ServerInformation, serverInformationEndpoint } from 'shared';
|
||||||
|
|
||||||
|
import { Configuration } from './config/configuration';
|
||||||
|
|
||||||
|
export type PlayerDecision = {
|
||||||
|
playerName: string;
|
||||||
|
server: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class JoinFormHandler {
|
||||||
|
private waitingForDecision: Promise<PlayerDecision>;
|
||||||
|
private resolvePlayerDecision!: (d: PlayerDecision) => void;
|
||||||
|
|
||||||
|
constructor(form: HTMLFormElement, private readonly container: HTMLElement) {
|
||||||
|
this.waitingForDecision = new Promise((r) => (this.resolvePlayerDecision = r));
|
||||||
|
|
||||||
|
new FormData(form);
|
||||||
|
|
||||||
|
document.addEventListener('keyup', (e) => {
|
||||||
|
if (e.key === 'enter') {
|
||||||
|
form.submit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
form.onsubmit = (e) => {
|
||||||
|
const result: PlayerDecision = (Array.from(
|
||||||
|
(new FormData(form) as any).entries(),
|
||||||
|
) as Array<[string, any]>).reduce((result, [name, value]) => {
|
||||||
|
(result as any)[name] = value;
|
||||||
|
return result;
|
||||||
|
}, {}) as any;
|
||||||
|
|
||||||
|
this.resolvePlayerDecision(result);
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.loadServers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadServers() {
|
||||||
|
await Configuration.initialize();
|
||||||
|
const serverList = Configuration.servers;
|
||||||
|
|
||||||
|
serverList.map(async (url) => {
|
||||||
|
const response = await fetch(url + serverInformationEndpoint);
|
||||||
|
if (response.ok) {
|
||||||
|
const content: ServerInformation = await response.json();
|
||||||
|
this.displayNewServerInfo(content, url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getPlayerDecision(): Promise<PlayerDecision> {
|
||||||
|
return this.waitingForDecision;
|
||||||
|
}
|
||||||
|
|
||||||
|
private isFirstServer = true;
|
||||||
|
private displayNewServerInfo(content: ServerInformation, url: string) {
|
||||||
|
this.container.innerHTML += `
|
||||||
|
<div>
|
||||||
|
<input required ${
|
||||||
|
this.isFirstServer ? 'checked' : ''
|
||||||
|
} type="radio" id="${url}" name="server" value="${url}" />
|
||||||
|
<label for="${url}">${content.serverName} - ${content.playerCount}/${
|
||||||
|
content.playerLimit
|
||||||
|
} players</label>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
this.isFirstServer = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
165
frontend/src/scripts/landing-page-background.ts
Normal file
165
frontend/src/scripts/landing-page-background.ts
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
import { vec2 } from 'gl-matrix';
|
||||||
|
import {
|
||||||
|
CircleLight,
|
||||||
|
compile,
|
||||||
|
FilteringOptions,
|
||||||
|
hsl,
|
||||||
|
NoisyPolygonFactory,
|
||||||
|
Renderer,
|
||||||
|
renderNoise,
|
||||||
|
WrapOptions,
|
||||||
|
} from 'sdf-2d';
|
||||||
|
import { settings, rgb, PlanetBase, Random } from 'shared';
|
||||||
|
|
||||||
|
const landingPageVertexCount = 9;
|
||||||
|
const LangindPagePolygon = NoisyPolygonFactory(
|
||||||
|
landingPageVertexCount,
|
||||||
|
rgb(0.5, 0.4, 0.7),
|
||||||
|
);
|
||||||
|
|
||||||
|
export class LandingPageBackground {
|
||||||
|
private readonly canvas = document.querySelector('canvas') as HTMLCanvasElement;
|
||||||
|
private renderer!: Renderer;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async start(): Promise<void> {
|
||||||
|
const noiseTexture = await renderNoise([256, 256], 1.2, 2);
|
||||||
|
|
||||||
|
this.renderer = await compile(
|
||||||
|
this.canvas,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
...LangindPagePolygon.descriptor,
|
||||||
|
shaderCombinationSteps: [0, 1, 2],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...CircleLight.descriptor,
|
||||||
|
shaderCombinationSteps: [0, 2],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
|
shadowTraceCount: 16,
|
||||||
|
paletteSize: 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
this.renderer.setRuntimeSettings({
|
||||||
|
ambientLight: rgb(0, 0, 0),
|
||||||
|
lightCutoffDistance: settings.lightCutoffDistance,
|
||||||
|
textures: {
|
||||||
|
noiseTexture: {
|
||||||
|
source: noiseTexture,
|
||||||
|
overrides: {
|
||||||
|
maxFilter: FilteringOptions.LINEAR,
|
||||||
|
wrapS: WrapOptions.MIRRORED_REPEAT,
|
||||||
|
wrapT: WrapOptions.MIRRORED_REPEAT,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
requestAnimationFrame(this.gameLoop.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy() {
|
||||||
|
this.renderer.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private gameLoop(time: DOMHighResTimeStamp) {
|
||||||
|
Random.seed = 42;
|
||||||
|
|
||||||
|
this.renderer.setViewArea(
|
||||||
|
vec2.fromValues(0, this.renderer.canvasSize.y),
|
||||||
|
this.renderer.canvasSize,
|
||||||
|
);
|
||||||
|
|
||||||
|
const topPlanetPosition = vec2.fromValues(
|
||||||
|
0.7 * this.renderer.canvasSize.x,
|
||||||
|
0.7 * this.renderer.canvasSize.y,
|
||||||
|
);
|
||||||
|
|
||||||
|
const topPlanet = new LangindPagePolygon(
|
||||||
|
PlanetBase.createPlanetVertices(
|
||||||
|
topPlanetPosition,
|
||||||
|
Random.getRandomInRange(150, 400),
|
||||||
|
Random.getRandomInRange(150, 400),
|
||||||
|
Random.getRandomInRange(10, 20),
|
||||||
|
landingPageVertexCount,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
(topPlanet as any).randomOffset = 0.5 + time / 3500;
|
||||||
|
|
||||||
|
const bottomPlanetPosition = vec2.fromValues(
|
||||||
|
0.3 * this.renderer.canvasSize.x,
|
||||||
|
0.3 * this.renderer.canvasSize.y,
|
||||||
|
);
|
||||||
|
|
||||||
|
const bottomPlanet = new LangindPagePolygon(
|
||||||
|
PlanetBase.createPlanetVertices(
|
||||||
|
bottomPlanetPosition,
|
||||||
|
Random.getRandomInRange(150, 800),
|
||||||
|
Random.getRandomInRange(150, 400),
|
||||||
|
Random.getRandomInRange(10, 40),
|
||||||
|
landingPageVertexCount,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
(bottomPlanet as any).randomOffset = time / 2500;
|
||||||
|
|
||||||
|
const planetDistance = vec2.subtract(
|
||||||
|
vec2.create(),
|
||||||
|
topPlanetPosition,
|
||||||
|
bottomPlanetPosition,
|
||||||
|
);
|
||||||
|
const planetDistanceLength = vec2.length(planetDistance);
|
||||||
|
const planetDirection = vec2.normalize(planetDistance, planetDistance);
|
||||||
|
const planetAngle = Math.atan2(planetDirection.y, planetDirection.x);
|
||||||
|
|
||||||
|
this.renderer.addDrawable(topPlanet);
|
||||||
|
this.renderer.addDrawable(bottomPlanet);
|
||||||
|
this.renderer.addDrawable(
|
||||||
|
new CircleLight(
|
||||||
|
this.calculateLightPosition(
|
||||||
|
planetAngle,
|
||||||
|
planetDistanceLength * 1.2,
|
||||||
|
-time / 3000,
|
||||||
|
),
|
||||||
|
hsl(25, 75, 60),
|
||||||
|
0.75,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.renderer.addDrawable(
|
||||||
|
new CircleLight(
|
||||||
|
this.calculateLightPosition(
|
||||||
|
planetAngle,
|
||||||
|
planetDistanceLength * 1.2,
|
||||||
|
time / 2000 + Math.PI,
|
||||||
|
),
|
||||||
|
hsl(249, 79, 70),
|
||||||
|
0.25,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.renderer.renderDrawables();
|
||||||
|
|
||||||
|
requestAnimationFrame(this.gameLoop.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculateLightPosition(angle: number, length: number, t: number): vec2 {
|
||||||
|
const lightPosition = vec2.fromValues(
|
||||||
|
length * Math.sin(t),
|
||||||
|
length * Math.sin(t) * Math.cos(t),
|
||||||
|
);
|
||||||
|
|
||||||
|
const canvasCenter = vec2.scale(vec2.create(), this.renderer.canvasSize, 0.5);
|
||||||
|
|
||||||
|
vec2.add(lightPosition, lightPosition, canvasCenter);
|
||||||
|
vec2.rotate(lightPosition, lightPosition, canvasCenter, angle);
|
||||||
|
return lightPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
124
frontend/src/styles/form.scss
Normal file
124
frontend/src/styles/form.scss
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
@import './vars.scss';
|
||||||
|
|
||||||
|
form {
|
||||||
|
backdrop-filter: blur(24px);
|
||||||
|
background-color: rgba(0, 0, 0, 0.15);
|
||||||
|
padding: $small-padding / 2 $small-padding $small-padding $small-padding;
|
||||||
|
|
||||||
|
input,
|
||||||
|
label {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
border: $border;
|
||||||
|
padding: 0 $medium-padding $medium-padding $medium-padding;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
|
||||||
|
div {
|
||||||
|
// disable margin collapse
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
font-family: 'Comfortaa', sans-serif;
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-group {
|
||||||
|
position: relative;
|
||||||
|
padding: 15px 0 0;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin: 10px 0 $small-padding 0;
|
||||||
|
|
||||||
|
input {
|
||||||
|
font-family: inherit;
|
||||||
|
border: none;
|
||||||
|
border-bottom: $border;
|
||||||
|
color: white;
|
||||||
|
padding: 10px 0 0px 0;
|
||||||
|
background: transparent;
|
||||||
|
margin-bottom: $border-width-focused - $border-width;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:placeholder-shown ~ label {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
cursor: text;
|
||||||
|
top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
display: block;
|
||||||
|
transition: $animation-time;
|
||||||
|
color: $gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus,
|
||||||
|
input:not(:placeholder-shown) {
|
||||||
|
outline: none;
|
||||||
|
margin-bottom: 0;
|
||||||
|
border-width: $border-width-focused;
|
||||||
|
border-color: $accent;
|
||||||
|
|
||||||
|
~ label {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
display: block;
|
||||||
|
transition: $animation-time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='radio'] {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
|
||||||
|
+ label {
|
||||||
|
display: block;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
padding: $small-padding;
|
||||||
|
border: $border;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: $border-width-focused - $border-width;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus + label,
|
||||||
|
&:checked + label {
|
||||||
|
border-color: $accent;
|
||||||
|
border-width: $border-width-focused;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
font-size: 2rem;
|
||||||
|
font-family: 'Comfortaa', sans-serif;
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
padding-top: $medium-padding;
|
||||||
|
transition: transform $animation-time;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
color: $accent;
|
||||||
|
transform: translateY(-8px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
border-radius: $border-radius;
|
||||||
|
}
|
||||||
|
|
@ -1,27 +1,64 @@
|
||||||
$breakpoint: 800px;
|
@import './vars.scss';
|
||||||
|
@import './form.scss';
|
||||||
|
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Comfortaa:wght@300&family=Open+Sans&display=swap');
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
color: white;
|
color: white;
|
||||||
|
font-family: 'Open Sans', sans-serif;
|
||||||
|
|
||||||
|
&::selection {
|
||||||
|
color: white;
|
||||||
|
background-color: $accent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
background-color: linear-gradient(45deg, #103783, #9bafd9);
|
font-size: 0.85rem;
|
||||||
|
|
||||||
@media (max-width: $breakpoint) {
|
@media (max-width: $breakpoint) {
|
||||||
font-size: 0.7rem;
|
font-size: 0.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
&,
|
||||||
|
* {
|
||||||
|
font-family: 'Comfortaa', sans-serif;
|
||||||
|
}
|
||||||
|
font-size: 6rem;
|
||||||
|
text-align: center;
|
||||||
|
padding-bottom: $medium-padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red {
|
||||||
|
color: $red;
|
||||||
|
&::selection {
|
||||||
|
color: $accent;
|
||||||
|
background-color: white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body,
|
body,
|
||||||
canvas#main {
|
canvas {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
background: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
#landing-ui {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
#overlay {
|
#overlay {
|
||||||
margin: 0.5rem 1.25rem;
|
margin: 0.5rem 1.25rem;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
||||||
12
frontend/src/styles/vars.scss
Normal file
12
frontend/src/styles/vars.scss
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
$gray: #ccc;
|
||||||
|
$red: #b33951;
|
||||||
|
$accent: #b33951;
|
||||||
|
$border-width: 1.5px;
|
||||||
|
$border-width-focused: 3px;
|
||||||
|
$border: $border-width solid white;
|
||||||
|
$small-padding: 12px;
|
||||||
|
$medium-padding: 24px;
|
||||||
|
$large-padding: 64px;
|
||||||
|
$border-radius: 15px;
|
||||||
|
$animation-time: 200ms;
|
||||||
|
$breakpoint: 700px;
|
||||||
3
shared/src/communication/player-information.ts
Normal file
3
shared/src/communication/player-information.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export interface PlayerInformation {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
7
shared/src/communication/server-information.ts
Normal file
7
shared/src/communication/server-information.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
export interface ServerInformation {
|
||||||
|
playerLimit: number;
|
||||||
|
playerCount: number;
|
||||||
|
serverName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const serverInformationEndpoint = '/stats';
|
||||||
|
|
@ -25,6 +25,8 @@ export * from './helper/rectangle';
|
||||||
export * from './helper/mix';
|
export * from './helper/mix';
|
||||||
export * from './helper/random';
|
export * from './helper/random';
|
||||||
export * from './helper/id';
|
export * from './helper/id';
|
||||||
|
export * from './communication/server-information';
|
||||||
|
export * from './communication/player-information';
|
||||||
export * from './helper/rotate-90-deg';
|
export * from './helper/rotate-90-deg';
|
||||||
export * from './helper/rotate-minus-90-deg';
|
export * from './helper/rotate-minus-90-deg';
|
||||||
export * from './objects/game-object';
|
export * from './objects/game-object';
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import { vec2 } from 'gl-matrix';
|
import { vec2 } from 'gl-matrix';
|
||||||
|
import { Random } from '../../helper/random';
|
||||||
|
import { settings } from '../../settings';
|
||||||
import { Id } from '../../transport/identity';
|
import { Id } from '../../transport/identity';
|
||||||
import { serializable } from '../../transport/serialization/serializable';
|
import { serializable } from '../../transport/serialization/serializable';
|
||||||
import { GameObject } from '../game-object';
|
import { GameObject } from '../game-object';
|
||||||
|
|
@ -9,6 +11,30 @@ export class PlanetBase extends GameObject {
|
||||||
super(id);
|
super(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static createPlanetVertices(
|
||||||
|
center: vec2,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
randomness: number,
|
||||||
|
vertexCount = settings.polygonEdgeCount,
|
||||||
|
): Array<vec2> {
|
||||||
|
const vertices = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < vertexCount; i++) {
|
||||||
|
vertices.push(
|
||||||
|
vec2.fromValues(
|
||||||
|
center.x +
|
||||||
|
(width / 2) * Math.cos((i / vertexCount) * -Math.PI * 2) +
|
||||||
|
Random.getRandomInRange(-randomness, randomness),
|
||||||
|
center.y +
|
||||||
|
(height / 2) * Math.sin((i / vertexCount) * -Math.PI * 2) +
|
||||||
|
Random.getRandomInRange(-randomness, randomness),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return vertices;
|
||||||
|
}
|
||||||
|
|
||||||
public toArray(): Array<any> {
|
public toArray(): Array<any> {
|
||||||
return [this.id, this.vertices];
|
return [this.id, this.vertices];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,5 +13,6 @@
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"lib": ["dom", "es2017"]
|
"lib": ["dom", "es2017"]
|
||||||
}
|
},
|
||||||
|
"include": ["src/**/*.ts"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue