Fix physics

This commit is contained in:
schmelczerandras 2020-10-12 20:49:17 +02:00
parent 37954e2ef1
commit f9f6825776
51 changed files with 832 additions and 541 deletions

View file

@ -1,21 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width,initial-scale=1,viewport-fit=cover"
/>
<meta name="theme-color" content="#b7455e" />
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover" />
<meta name="theme-color" content="#b7455e" />
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
<title>decla.red</title>
</head>
<title>decla.red</title>
</head>
<body>
<noscript>Javascript is required for this website.</noscript>
<div id="overlay"></div>
<canvas id="main"></canvas>
</body>
</html>
<body>
<!--h1>Decla.red</h1>
<section id="servers"></section-->
<noscript>Javascript is required for this website.</noscript>
<div id="overlay"></div>
<canvas id="main"></canvas>
</body>
</html>

View file

@ -1,16 +1,19 @@
import { glMatrix } from 'gl-matrix';
import { CharacterBase, LampBase, overrideDeserialization, TunnelBase } from 'shared';
import { CharacterBase, LampBase, overrideDeserialization, StoneBase } from 'shared';
import { ProjectileBase } from 'shared/src/objects/types/projectile-base';
import { Game } from './scripts/game';
import { CharacterView } from './scripts/objects/character-view';
import { LampView } from './scripts/objects/lamp-view';
import { TunnelView } from './scripts/objects/tunnel-view';
import { ProjectileView } from './scripts/objects/projectile-view';
import { StoneView } from './scripts/objects/stone-view';
import './styles/main.scss';
glMatrix.setMatrixArrayType(Array);
overrideDeserialization(CharacterBase, CharacterView);
overrideDeserialization(TunnelBase, TunnelView);
overrideDeserialization(StoneBase, StoneView);
overrideDeserialization(LampBase, LampView);
overrideDeserialization(ProjectileBase, ProjectileView);
const main = async () => {
try {

View file

@ -5,9 +5,10 @@ import {
SecondaryActionCommand,
TernaryActionCommand,
} from 'shared';
import { Game } from '../../game';
export class MouseListener extends CommandGenerator {
constructor(target: HTMLElement) {
constructor(target: HTMLElement, private readonly game: Game) {
super();
target.addEventListener('mousemove', (event: MouseEvent) => {
@ -31,6 +32,8 @@ export class MouseListener extends CommandGenerator {
}
private positionFromEvent(event: MouseEvent): vec2 {
return vec2.fromValues(event.clientX, event.clientY);
return this.game.displayToWorldCoordinates(
vec2.fromValues(event.clientX, event.clientY),
);
}
}

View file

@ -6,11 +6,12 @@ import {
TernaryActionCommand,
MoveActionCommand,
} from 'shared';
import { Game } from '../../game';
export class TouchListener extends CommandGenerator {
private previousPosition = vec2.create();
constructor(target: HTMLElement) {
constructor(target: HTMLElement, private readonly game: Game) {
super();
target.addEventListener('touchstart', (event: TouchEvent) => {
@ -53,10 +54,12 @@ export class TouchListener extends CommandGenerator {
const touches = Array.prototype.slice.call(event.touches);
const center = touches.reduce(
(center: vec2, touch: Touch) =>
vec2.add(center, center, vec2.fromValues(-touch.clientX, touch.clientY)),
vec2.add(center, center, vec2.fromValues(-touch.clientX, -touch.clientY)),
vec2.create(),
);
return vec2.scale(center, center, 1 / event.touches.length);
return this.game.displayToWorldCoordinates(
vec2.scale(center, center, 1 / event.touches.length),
);
}
}

View file

@ -5,7 +5,6 @@ import {
compile,
FilteringOptions,
Flashlight,
InvertedTunnel,
Renderer,
renderNoise,
WrapOptions,
@ -13,25 +12,23 @@ import {
import {
broadcastCommands,
deserialize,
prettyPrint,
serialize,
settings,
StepCommand,
TransportEvents,
SetAspectRatioActionCommand,
rgb,
} from 'shared';
import { SetAspectRatioActionCommand } from 'shared/src/main';
import io from 'socket.io-client';
import { KeyboardListener } from './commands/generators/keyboard-listener';
import { MouseListener } from './commands/generators/mouse-listener';
import { TouchListener } from './commands/generators/touch-listener';
import { CommandReceiverSocket } from './commands/receivers/command-receiver-socket';
import { RenderCommand } from './commands/types/render';
import { Configuration } from './config/configuration';
import { DeltaTimeCalculator } from './helper/delta-time-calculator';
import { rgb } from './helper/rgb';
import { GameObjectContainer } from './objects/game-object-container';
import { BlobShape } from './shapes/blob-shape';
import { Polygon } from './shapes/polygon';
export class Game {
public readonly gameObjects = new GameObjectContainer(this);
@ -46,7 +43,7 @@ export class Game {
private async setupCommunication(): Promise<void> {
await Configuration.initialize();
this.socket = io(Configuration.servers[0], {
this.socket = io(Configuration.servers[1], {
reconnectionDelayMax: 10000,
transports: ['websocket'],
});
@ -69,22 +66,22 @@ export class Game {
broadcastCommands(
[
new KeyboardListener(document.body),
new MouseListener(this.canvas),
new TouchListener(this.canvas),
new MouseListener(this.canvas, this),
new TouchListener(this.canvas, this),
],
[this.gameObjects, new CommandReceiverSocket(this.socket)],
);
}
private async setupRenderer(): Promise<void> {
const noiseTexture = await renderNoise([512, 1], 50, 1 / 10);
const noiseTexture = await renderNoise([64, 1], 20, 1 / 10);
this.renderer = await compile(
this.canvas,
[
{
...InvertedTunnel.descriptor,
shaderCombinationSteps: [0, 2, 6, 16],
...Polygon.descriptor,
shaderCombinationSteps: [0, 2, 6, 16, 32],
},
{
...BlobShape.descriptor,
@ -92,7 +89,7 @@ export class Game {
},
{
...Circle.descriptor,
shaderCombinationSteps: [0, 2, 16],
shaderCombinationSteps: [0, 2, 16, 32],
},
{
...CircleLight.descriptor,
@ -111,16 +108,12 @@ export class Game {
);
this.renderer.setRuntimeSettings({
isWorldInverted: true,
ambientLight: rgb(0.35, 0.1, 0.45),
ambientLight: rgb(0.45, 0.4, 0.45),
colorPalette: [
rgb(0.4, 1, 0.6),
rgb(1, 1, 0),
rgb(0.3, 1, 1),
rgb(0.3, 1, 1),
rgb(0.3, 1, 1),
rgb(0.3, 1, 1),
rgb(1, 1, 1),
rgb(0.4, 0.4, 0.4),
rgb(0.3, 1, 1),
...settings.playerColors,
],
enableHighDpiRendering: false,
lightCutoffDistance: settings.lightCutoffDistance,
@ -155,11 +148,11 @@ export class Game {
private gameLoop(time: DOMHighResTimeStamp) {
const deltaTime = this.deltaTimeCalculator.getNextDeltaTimeInMilliseconds(time);
this.gameObjects.sendCommand(new StepCommand(deltaTime));
this.gameObjects.sendCommand(new RenderCommand(this.renderer));
this.gameObjects.stepObjects(deltaTime);
this.gameObjects.drawObjects(this.renderer);
this.renderer.renderDrawables();
this.overlay.innerText = prettyPrint(this.renderer.insights);
// this.overlay.innerText = prettyPrint(this.renderer.insights);
requestAnimationFrame(this.gameLoop.bind(this));
}
}

View file

@ -1,3 +0,0 @@
import { vec3 } from 'gl-matrix';
export const rgb = (r: number, g: number, b: number): vec3 => vec3.fromValues(r, g, b);

View file

@ -1,4 +0,0 @@
import { vec3 } from 'gl-matrix';
export const rgb255 = (r: number, g: number, b: number): vec3 =>
vec3.fromValues(r / 255, g / 255, b / 255);

View file

@ -1,29 +1,38 @@
import { vec2 } from 'gl-matrix';
import { calculateViewArea, CommandExecutors, GameObject } from 'shared';
import { RenderCommand } from '../commands/types/render';
import { Game } from '../game';
import { Renderer } from 'sdf-2d';
import { calculateViewArea, GameObject, mixRgb, settings } from 'shared';
export class Camera extends GameObject {
import { Game } from '../game';
import { ViewObject } from './view-object';
export class Camera extends GameObject implements ViewObject {
public center: vec2 = vec2.create();
private aspectRatio?: number;
protected commandExecutors: CommandExecutors = {
[RenderCommand.type]: this.draw.bind(this),
};
constructor(private game: Game) {
super(null);
}
private draw(c: RenderCommand) {
const canvasAspectRatio = c.renderer.canvasSize.x / c.renderer.canvasSize.y;
public step(deltaTimeInMilliseconds: number): void {}
public draw(renderer: Renderer) {
const canvasAspectRatio = renderer.canvasSize.x / renderer.canvasSize.y;
if (canvasAspectRatio !== this.aspectRatio) {
this.aspectRatio = canvasAspectRatio;
this.game.aspectRatioChanged(canvasAspectRatio);
}
const viewArea = calculateViewArea(this.center, canvasAspectRatio);
c.renderer.setViewArea(viewArea.topLeft, viewArea.size);
renderer.setViewArea(viewArea.topLeft, viewArea.size);
renderer.setRuntimeSettings({
backgroundColor: mixRgb(
settings.backgroundGradient[0],
settings.backgroundGradient[1],
(this.center.x - settings.worldLeftEdge) /
(Math.abs(settings.worldLeftEdge) + Math.abs(settings.worldRightEdge)),
),
});
}
}

View file

@ -1,19 +1,32 @@
import { vec2 } from 'gl-matrix';
import { CharacterBase, CommandExecutors } from 'shared';
import { RenderCommand } from '../commands/types/render';
import { Renderer } from 'sdf-2d';
import { CharacterBase, Circle, Id } from 'shared';
import { BlobShape } from '../shapes/blob-shape';
import { ViewObject } from './view-object';
export class CharacterView extends CharacterBase {
private shape = new BlobShape();
export class CharacterView extends CharacterBase implements ViewObject {
private shape: BlobShape;
protected commandExecutors: CommandExecutors = {
[RenderCommand.type]: (c: RenderCommand) => {
this.shape.setCircles([this.head, this.leftFoot, this.rightFoot]);
c.renderer.addDrawable(this.shape);
},
};
constructor(
id: Id,
colorIndex: number,
head?: Circle,
leftFoot?: Circle,
rightFoot?: Circle,
) {
super(id, colorIndex, head, leftFoot, rightFoot);
this.shape = new BlobShape(colorIndex);
}
public get position(): vec2 {
return this.head.center;
return this.head!.center;
}
public step(deltaTimeInMilliseconds: number): void {}
public draw(renderer: Renderer): void {
this.shape.setCircles([this.head!, this.leftFoot!, this.rightFoot!]);
renderer.addDrawable(this.shape);
}
}

View file

@ -1,23 +1,22 @@
import { Renderer } from 'sdf-2d';
import {
Command,
CommandExecutors,
CommandReceiver,
CreateObjectsCommand,
CreatePlayerCommand,
DeleteObjectsCommand,
GameObject,
Id,
StepCommand,
UpdateObjectsCommand,
} from 'shared';
import { Game } from '../game';
import { Camera } from './camera';
import { CharacterView } from './character-view';
import { ViewObject } from './view-object';
export class GameObjectContainer extends CommandReceiver {
protected objects: Map<Id, GameObject> = new Map();
public player: CharacterView;
public camera: Camera;
protected objects: Map<Id, ViewObject> = new Map();
public player!: CharacterView;
public camera!: Camera;
protected commandExecutors: CommandExecutors = {
[CreatePlayerCommand.type]: (c: CreatePlayerCommand) => {
@ -27,14 +26,8 @@ export class GameObjectContainer extends CommandReceiver {
this.addObject(this.camera);
},
[StepCommand.type]: (_: StepCommand) => {
if (this.player) {
this.camera.center = this.player.position;
}
},
[CreateObjectsCommand.type]: (c: CreateObjectsCommand) =>
c.objects.forEach((o) => this.addObject(o)),
c.objects.forEach((o) => this.addObject(o as ViewObject)),
[DeleteObjectsCommand.type]: (c: DeleteObjectsCommand) =>
c.ids.forEach((id: Id) => this.objects.delete(id)),
@ -42,7 +35,7 @@ export class GameObjectContainer extends CommandReceiver {
[UpdateObjectsCommand.type]: (c: UpdateObjectsCommand) => {
c.objects.forEach((o) => {
this.objects.delete(o.id);
this.addObject(o);
this.addObject(o as ViewObject);
if (o.id === this.player.id) {
this.player = o as CharacterView;
}
@ -54,15 +47,19 @@ export class GameObjectContainer extends CommandReceiver {
super();
}
protected defaultCommandExecutor(c: Command) {
this.objects.forEach((o) => o.sendCommand(c));
public stepObjects(delta: number) {
if (this.player) {
this.camera.center = this.player.position;
}
this.objects.forEach((o) => o.step(delta));
}
public sendCommandToSingleObject(id: Id, e: Command) {
this.objects.get(id)!.sendCommand(e);
public drawObjects(renderer: Renderer) {
this.objects.forEach((o) => o.draw(renderer));
}
private addObject(object: GameObject) {
private addObject(object: ViewObject) {
this.objects.set(object.id, object);
}
}

View file

@ -1,17 +1,24 @@
import { vec2, vec3 } from 'gl-matrix';
import { CircleLight } from 'sdf-2d';
import { CircleLight, Renderer } from 'sdf-2d';
import { CommandExecutors, Id, LampBase } from 'shared';
import { RenderCommand } from '../commands/types/render';
import { ViewObject } from './view-object';
export class LampView extends LampBase {
export class LampView extends LampBase implements ViewObject {
private light: CircleLight;
protected commandExecutors: CommandExecutors = {
[RenderCommand.type]: (c: RenderCommand) => c.renderer.addDrawable(this.light),
} as any;
};
constructor(id: Id, center: vec2, color: vec3, lightness: number) {
super(id, center, color, lightness);
this.light = new CircleLight(center, color, lightness);
}
public step(deltaTimeInMilliseconds: number): void {}
public draw(renderer: Renderer): void {
renderer.addDrawable(this.light);
}
}

View file

@ -0,0 +1,19 @@
import { vec2 } from 'gl-matrix';
import { Circle, Renderer } from 'sdf-2d';
import { Id, ProjectileBase } from 'shared';
import { ViewObject } from './view-object';
export class ProjectileView extends ProjectileBase implements ViewObject {
private circle: Circle;
constructor(id: Id, center: vec2, radius: number) {
super(id, center, radius);
this.circle = new Circle(center, radius);
}
public step(deltaTimeInMilliseconds: number): void {}
public draw(renderer: Renderer): void {
renderer.addDrawable(this.circle);
}
}

View file

@ -0,0 +1,25 @@
import { vec2 } from 'gl-matrix';
import { Drawable, Renderer } from 'sdf-2d';
import { CommandExecutors, Id, StoneBase } from 'shared';
import { RenderCommand } from '../commands/types/render';
import { Polygon } from '../shapes/polygon';
import { ViewObject } from './view-object';
export class StoneView extends StoneBase implements ViewObject {
private shape: Drawable;
protected commandExecutors: CommandExecutors = {
[RenderCommand.type]: (c: RenderCommand) => c.renderer.addDrawable(this.shape),
};
constructor(id: Id, vertices: Array<vec2>) {
super(id, vertices);
this.shape = new Polygon(vertices);
}
public step(deltaTimeInMilliseconds: number): void {}
public draw(renderer: Renderer): void {
renderer.addDrawable(this.shape);
}
}

View file

@ -1,17 +0,0 @@
import { vec2 } from 'gl-matrix';
import { InvertedTunnel } from 'sdf-2d';
import { CommandExecutors, Id, TunnelBase } from 'shared';
import { RenderCommand } from '../commands/types/render';
export class TunnelView extends TunnelBase {
private shape: InvertedTunnel;
protected commandExecutors: CommandExecutors = {
[RenderCommand.type]: (c: RenderCommand) => c.renderer.addDrawable(this.shape),
} as any;
constructor(id: Id, from: vec2, to: vec2, fromRadius: number, toRadius: number) {
super(id, from, to, fromRadius, toRadius);
this.shape = new InvertedTunnel(from, to, fromRadius, toRadius);
}
}

View file

@ -0,0 +1,7 @@
import { Renderer } from 'sdf-2d';
import { GameObject } from 'shared';
export interface ViewObject extends GameObject {
step(deltaTimeInMilliseconds: number): void;
draw(renderer: Renderer): void;
}

View file

@ -11,6 +11,7 @@ export class BlobShape extends Drawable {
uniform vec2 rightFootCenters[BLOB_COUNT];
uniform float headRadii[BLOB_COUNT];
uniform float footRadii[BLOB_COUNT];
uniform float blobColors[BLOB_COUNT];
float blobSmoothMin(float a, float b)
{
@ -25,7 +26,6 @@ export class BlobShape extends Drawable {
float blobMinDistance(vec2 target, out float colorIndex) {
float minDistance = 1000.0;
colorIndex = 3.0;
for (int i = 0; i < BLOB_COUNT; i++) {
float headDistance = circleDistance(headCenters[i], headRadii[i], target);
@ -61,6 +61,10 @@ export class BlobShape extends Drawable {
);
minDistance = min(minDistance, res);
if (res < 0.0) {
colorIndex = blobColors[i];
}
}
return minDistance;
@ -74,17 +78,18 @@ export class BlobShape extends Drawable {
rightFootCenter: 'rightFootCenters',
leftFootCenter: 'leftFootCenters',
headCenter: 'headCenters',
color: 'blobColors',
},
uniformCountMacroName: 'BLOB_COUNT',
shaderCombinationSteps: [0, 1, 10],
empty: new BlobShape(),
shaderCombinationSteps: [],
empty: new BlobShape(0),
};
protected head: Circle;
protected leftFoot: Circle;
protected rightFoot: Circle;
protected head!: Circle;
protected leftFoot!: Circle;
protected rightFoot!: Circle;
public constructor() {
public constructor(private readonly color: number) {
super();
const circle = new Circle(vec2.create(), 200);
@ -120,6 +125,7 @@ export class BlobShape extends Drawable {
),
headRadius: this.head.radius * transform1d,
footRadius: this.leftFoot.radius * transform1d,
color: this.color,
};
}
}

View file

@ -0,0 +1,4 @@
import { PolygonFactory } from 'sdf-2d';
import { settings } from 'shared';
export const Polygon = PolygonFactory(settings.polygonEdgeCount);