Add projectile damage

This commit is contained in:
schmelczerandras 2020-10-12 21:46:23 +02:00
parent f9f6825776
commit 555be9d602
14 changed files with 111 additions and 40 deletions

11
backend/Dockerfile Normal file
View file

@ -0,0 +1,11 @@
FROM node:14.13.0-alpine3.10
COPY package.json .
RUN npm install --production
COPY dist/main.js main.js
EXPOSE 3000
CMD [ "node", "main.js" ]

View file

@ -95,7 +95,7 @@ const handlePhysics = () => {
} }
objects.stepObjects(delta); objects.stepObjects(delta);
players.forEach((p) => p.sendObjects()); players.forEach((p) => p.step(delta));
const physicsDelta = deltaTimeCalculator.getDeltaTimeInMilliseconds(); const physicsDelta = deltaTimeCalculator.getDeltaTimeInMilliseconds();
deltas.push(physicsDelta); deltas.push(physicsDelta);

View file

@ -71,7 +71,7 @@ export const createDungeon = (objectContainer: PhysicalContainer) => {
Random.getRandomInRange(0.5, 1), Random.getRandomInRange(0.5, 1),
), ),
), ),
Random.getRandomInRange(0.75, 1.5), Random.getRandomInRange(0.25, 1),
), ),
); );
} }

View file

@ -7,24 +7,26 @@ import {
serializesTo, serializesTo,
clamp, clamp,
last, last,
GameObject,
} from 'shared'; } from 'shared';
import { DynamicPhysical } from '../physics/conatiners/dynamic-physical'; import { DynamicPhysical } from '../physics/conatiners/dynamic-physical';
import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-bounding-box';
import { CirclePhysical } from './circle-physical'; import { CirclePhysical } from './circle-physical';
import { PhysicalContainer } from '../physics/containers/physical-container'; import { PhysicalContainer } from '../physics/containers/physical-container';
import { Spring } from './spring'; import { Spring } from './spring';
import { BoundingBoxBase } from '../physics/bounding-boxes/bounding-box-base';
import { ProjectilePhysical } from './projectile-physical';
@serializesTo(CharacterBase) @serializesTo(CharacterBase)
export class CharacterPhysical extends CharacterBase implements DynamicPhysical { export class CharacterPhysical extends CharacterBase implements DynamicPhysical {
public readonly canCollide = true; public readonly canCollide = true;
public readonly canMove = true; public readonly canMove = true;
private isDestroyed = false;
private jumpEnergyLeft = settings.defaultJumpEnergy; private jumpEnergyLeft = settings.defaultJumpEnergy;
public head: CirclePhysical; public head: CirclePhysical;
public leftFoot: CirclePhysical; public leftFoot: CirclePhysical;
public rightFoot: CirclePhysical; public rightFoot: CirclePhysical;
public bound: CirclePhysical;
private movementActions: Array<MoveActionCommand> = []; private movementActions: Array<MoveActionCommand> = [];
private lastMovementAction: MoveActionCommand = new MoveActionCommand(vec2.create()); private lastMovementAction: MoveActionCommand = new MoveActionCommand(vec2.create());
@ -34,6 +36,8 @@ export class CharacterPhysical extends CharacterBase implements DynamicPhysical
} }
private static readonly headOffset = vec2.fromValues(0, 40); private static readonly headOffset = vec2.fromValues(0, 40);
private static readonly headRadius = 50;
private static readonly feetRadius = 20;
private static readonly leftFootOffset = vec2.fromValues(-20, -35); private static readonly leftFootOffset = vec2.fromValues(-20, -35);
private static readonly rightFootOffset = vec2.fromValues(20, -35); private static readonly rightFootOffset = vec2.fromValues(20, -35);
@ -44,35 +48,44 @@ export class CharacterPhysical extends CharacterBase implements DynamicPhysical
super(id(), colorIndex); super(id(), colorIndex);
this.head = new CirclePhysical( this.head = new CirclePhysical(
vec2.clone(CharacterPhysical.headOffset), vec2.clone(CharacterPhysical.headOffset),
50, CharacterPhysical.headRadius,
this, this,
container, container,
); );
this.leftFoot = new CirclePhysical( this.leftFoot = new CirclePhysical(
vec2.clone(CharacterPhysical.leftFootOffset), vec2.clone(CharacterPhysical.leftFootOffset),
20, CharacterPhysical.feetRadius,
this, this,
container, container,
); );
this.rightFoot = new CirclePhysical( this.rightFoot = new CirclePhysical(
vec2.clone(CharacterPhysical.rightFootOffset), vec2.clone(CharacterPhysical.rightFootOffset),
20, CharacterPhysical.feetRadius,
this, this,
container, container,
); );
container.addObject(this.head); container.addObject(this.head);
container.addObject(this.leftFoot); container.addObject(this.leftFoot);
container.addObject(this.rightFoot); container.addObject(this.rightFoot);
this.bound = new CirclePhysical(
vec2.create(),
(CharacterPhysical.headRadius + CharacterPhysical.feetRadius * 2) * 2,
this,
container,
);
} }
private _boundingBox?: ImmutableBoundingBox; public onCollision(other: GameObject) {
if (other instanceof ProjectilePhysical) {
public get boundingBox(): ImmutableBoundingBox { other.destroy();
if (!this._boundingBox) { this.destroy();
this._boundingBox = (this.head as CirclePhysical).boundingBox; }
} }
return this._boundingBox; public get boundingBox(): BoundingBoxBase {
this.bound.center = this.head.center;
return this.bound.boundingBox;
} }
public get gameObject(): this { public get gameObject(): this {
@ -209,8 +222,12 @@ export class CharacterPhysical extends CharacterBase implements DynamicPhysical
} }
public destroy() { public destroy() {
if (!this.isDestroyed) {
this.isDestroyed = true;
this.container.removeObject(this);
this.container.removeObject(this.head); this.container.removeObject(this.head);
this.container.removeObject(this.leftFoot); this.container.removeObject(this.leftFoot);
this.container.removeObject(this.rightFoot); this.container.removeObject(this.rightFoot);
} }
} }
}

View file

@ -6,7 +6,7 @@ import { BoundingBox } from '../physics/bounding-boxes/bounding-box';
import { BoundingBoxBase } from '../physics/bounding-boxes/bounding-box-base'; import { BoundingBoxBase } from '../physics/bounding-boxes/bounding-box-base';
import { moveCircle } from '../physics/move-circle'; import { moveCircle } from '../physics/move-circle';
import { PhysicalContainer } from '../physics/containers/physical-container'; import { PhysicalContainer } from '../physics/containers/physical-container';
import { DynamicPhysical } from '../physics/conatiners/dynamic-physical'; import { DynamicPhysical } from '../physics/containers/dynamic-physical';
@serializesTo(Circle) @serializesTo(Circle)
export class CirclePhysical implements Circle, DynamicPhysical { export class CirclePhysical implements Circle, DynamicPhysical {
@ -25,7 +25,7 @@ export class CirclePhysical implements Circle, DynamicPhysical {
constructor( constructor(
private _center: vec2, private _center: vec2,
private _radius: number, private _radius: number,
public owner: GameObject, public owner: DynamicPhysical,
private readonly container: PhysicalContainer, private readonly container: PhysicalContainer,
private restitution = 0, private restitution = 0,
) { ) {
@ -46,6 +46,10 @@ export class CirclePhysical implements Circle, DynamicPhysical {
this.recalculateBoundingBox(); this.recalculateBoundingBox();
} }
public onCollision(other: GameObject) {
this.owner.onCollision(other);
}
public get gameObject(): GameObject { public get gameObject(): GameObject {
return this.owner; return this.owner;
} }

View file

@ -1,12 +1,5 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { import { id, settings, serializesTo, ProjectileBase, GameObject } from 'shared';
id,
settings,
serializesTo,
ProjectileBase,
GameObject,
rotate90Deg,
} from 'shared';
import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-bounding-box'; import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-bounding-box';
import { CirclePhysical } from './circle-physical'; import { CirclePhysical } from './circle-physical';
import { DynamicPhysical } from '../physics/conatiners/dynamic-physical'; import { DynamicPhysical } from '../physics/conatiners/dynamic-physical';
@ -16,6 +9,8 @@ import { PhysicalContainer } from '../physics/containers/physical-container';
export class ProjectilePhysical extends ProjectileBase implements DynamicPhysical { export class ProjectilePhysical extends ProjectileBase implements DynamicPhysical {
public readonly canCollide = true; public readonly canCollide = true;
public readonly canMove = true; public readonly canMove = true;
private isDestroyed = false;
private bounceCount = 0;
public object: CirclePhysical; public object: CirclePhysical;
@ -47,9 +42,17 @@ export class ProjectilePhysical extends ProjectileBase implements DynamicPhysica
return this.object.distance(target); return this.object.distance(target);
} }
public onCollision(normal: vec2, other: GameObject) { public destroy() {
const tangent = rotate90Deg(normal); if (!this.isDestroyed) {
vec2.scale(this.velocity, tangent, vec2.dot(tangent, this.velocity)); this.isDestroyed = true;
this.container.removeObject(this);
}
}
public onCollision(other: GameObject) {
if (this.bounceCount++ === settings.projectileMaxBounceCount) {
this.destroy();
}
} }
public step(deltaTimeInMiliseconds: number) { public step(deltaTimeInMiliseconds: number) {

View file

@ -1,5 +1,8 @@
import { GameObject } from 'shared';
import { Physical } from '../physical'; import { Physical } from '../physical';
export interface DynamicPhysical extends Physical { export interface DynamicPhysical extends Physical {
readonly canMove: true;
step(deltaTimeInMilliseconds: number): void; step(deltaTimeInMilliseconds: number): void;
onCollision(other: GameObject): void;
} }

View file

@ -2,5 +2,6 @@ import { Physical } from '../physical';
import { ImmutableBoundingBox } from '../bounding-boxes/immutable-bounding-box'; import { ImmutableBoundingBox } from '../bounding-boxes/immutable-bounding-box';
export interface StaticPhysical extends Physical { export interface StaticPhysical extends Physical {
readonly canMove: false;
readonly boundingBox: ImmutableBoundingBox; readonly boundingBox: ImmutableBoundingBox;
} }

View file

@ -1,6 +1,7 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { Circle, rotate90Deg } from 'shared'; import { Circle, rotate90Deg } from 'shared';
import { CirclePhysical } from '../objects/circle-physical'; import { CirclePhysical } from '../objects/circle-physical';
import { DynamicPhysical } from './containers/dynamic-physical';
import { Physical } from './physical'; import { Physical } from './physical';
export const moveCircle = ( export const moveCircle = (
@ -37,6 +38,15 @@ export const moveCircle = (
}; };
} }
const intersecting = possibleIntersectors
.filter((i) => i.canCollide && i.canMove)
.find((i) => i.distance(nextCircle.center) < circle.radius);
(intersecting?.gameObject as DynamicPhysical)?.onCollision(circle.gameObject);
((circle.gameObject as unknown) as DynamicPhysical).onCollision(
intersecting?.gameObject,
);
const dx = const dx =
getSdfAtPoint(vec2.add(vec2.create(), nextCircle.center, vec2.fromValues(1, 0))) - getSdfAtPoint(vec2.add(vec2.create(), nextCircle.center, vec2.fromValues(1, 0))) -
sdfAtCenter; sdfAtCenter;
@ -44,9 +54,7 @@ export const moveCircle = (
getSdfAtPoint(vec2.add(vec2.create(), nextCircle.center, vec2.fromValues(0, 1))) - getSdfAtPoint(vec2.add(vec2.create(), nextCircle.center, vec2.fromValues(0, 1))) -
sdfAtCenter; sdfAtCenter;
const normal = vec2.fromValues(dx, dy); const normal = vec2.fromValues(dx, dy);
vec2.normalize(normal, normal); vec2.normalize(normal, normal);
const rotatedNormal = rotate90Deg(normal); const rotatedNormal = rotate90Deg(normal);
return { return {
realDelta: delta, realDelta: delta,

View file

@ -28,6 +28,8 @@ export class Player extends CommandReceiver {
private aspectRatio: number = 16 / 9; private aspectRatio: number = 16 / 9;
private isActive = true; private isActive = true;
private timeSinceLastProjectile = 0;
private objectsPreviouslyInViewArea: Array<Physical> = []; private objectsPreviouslyInViewArea: Array<Physical> = [];
private objectsInViewArea: Array<Physical> = []; private objectsInViewArea: Array<Physical> = [];
@ -51,6 +53,10 @@ export class Player extends CommandReceiver {
[MoveActionCommand.type]: (c: MoveActionCommand) => [MoveActionCommand.type]: (c: MoveActionCommand) =>
this.character.handleMovementAction(c), this.character.handleMovementAction(c),
[SecondaryActionCommand.type]: (c: SecondaryActionCommand) => { [SecondaryActionCommand.type]: (c: SecondaryActionCommand) => {
if (this.timeSinceLastProjectile < settings.projectileCreationInterval) {
return;
}
const start = vec2.clone(this.character.center); const start = vec2.clone(this.character.center);
const direction = vec2.subtract(vec2.create(), c.position, start); const direction = vec2.subtract(vec2.create(), c.position, start);
vec2.normalize(direction, direction); vec2.normalize(direction, direction);
@ -63,6 +69,8 @@ export class Player extends CommandReceiver {
vec2.add(velocity, velocity, this.character.velocity); vec2.add(velocity, velocity, this.character.velocity);
const projectile = new ProjectilePhysical(start, 20, velocity, this.objects); const projectile = new ProjectilePhysical(start, 20, velocity, this.objects);
this.objects.addObject(projectile); this.objects.addObject(projectile);
this.timeSinceLastProjectile = 0;
}, },
}; };
@ -92,6 +100,10 @@ export class Player extends CommandReceiver {
this.sendObjects(); this.sendObjects();
} }
public step(deltaTimeInMilliseconds: number) {
this.sendObjects();
this.timeSinceLastProjectile += deltaTimeInMilliseconds;
}
public sendObjects() { public sendObjects() {
const viewArea = calculateViewArea(this.character.center, this.aspectRatio, 1.5); const viewArea = calculateViewArea(this.character.center, this.aspectRatio, 1.5);
const bb = new BoundingBox(); const bb = new BoundingBox();

View file

@ -33,7 +33,7 @@ export class TouchListener extends CommandGenerator {
target.addEventListener('touchmove', (event: TouchEvent) => { target.addEventListener('touchmove', (event: TouchEvent) => {
event.preventDefault(); event.preventDefault();
const position = this.positionFromEvent(event); const position = this.positionFromEvent(event, true);
const movement = vec2.subtract(vec2.create(), position, this.previousPosition); const movement = vec2.subtract(vec2.create(), position, this.previousPosition);
if (vec2.squaredLength(movement) > 0) { if (vec2.squaredLength(movement) > 0) {
@ -50,11 +50,18 @@ export class TouchListener extends CommandGenerator {
}); });
} }
private positionFromEvent(event: TouchEvent): vec2 { private positionFromEvent(event: TouchEvent, invert = false): vec2 {
const touches = Array.prototype.slice.call(event.touches); const touches = Array.prototype.slice.call(event.touches);
const center = touches.reduce( const center = touches.reduce(
(center: vec2, touch: Touch) => (center: vec2, touch: Touch) =>
vec2.add(center, center, vec2.fromValues(-touch.clientX, -touch.clientY)), vec2.add(
center,
center,
vec2.fromValues(
touch.clientX * (invert ? -1 : 1),
touch.clientY * (invert ? -1 : 1),
),
),
vec2.create(), vec2.create(),
); );

View file

@ -103,7 +103,7 @@ export class Game {
{ {
shadowTraceCount: 16, shadowTraceCount: 16,
paletteSize: 10, paletteSize: 10,
enableStopwatch: true, //enableStopwatch: true,
}, },
); );
@ -112,7 +112,7 @@ export class Game {
colorPalette: [ colorPalette: [
rgb(1, 1, 1), rgb(1, 1, 1),
rgb(0.4, 0.4, 0.4), rgb(0.4, 0.4, 0.4),
rgb(0.3, 1, 1), rgb(1, 1, 1),
...settings.playerColors, ...settings.playerColors,
], ],
enableHighDpiRendering: false, enableHighDpiRendering: false,

View file

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

View file

@ -9,7 +9,9 @@ export const settings = {
maxVelocityY: 3650, maxVelocityY: 3650,
polygonEdgeCount: 8, polygonEdgeCount: 8,
projectileSpeed: 2000, projectileSpeed: 2000,
projectileMaxBounceCount: 1,
projectileStartOffset: 150, projectileStartOffset: 150,
projectileCreationInterval: 500,
playerColorIndexOffset: 3, playerColorIndexOffset: 3,
worldLeftEdge: -5000, worldLeftEdge: -5000,
worldRightEdge: 5000, worldRightEdge: 5000,