From 555be9d60298695915e00b839beb7193ac8bec67 Mon Sep 17 00:00:00 2001 From: schmelczerandras Date: Mon, 12 Oct 2020 21:46:23 +0200 Subject: [PATCH] Add projectile damage --- backend/Dockerfile | 11 +++++ backend/src/main.ts | 2 +- backend/src/map/create-dungeon.ts | 2 +- backend/src/objects/character-physical.ts | 47 +++++++++++++------ backend/src/objects/circle-physical.ts | 8 +++- backend/src/objects/projectile-physical.ts | 25 +++++----- .../physics/containers/dynamic-physical.ts | 3 ++ .../src/physics/containers/static-physical.ts | 1 + backend/src/physics/move-circle.ts | 12 ++++- backend/src/players/player.ts | 12 +++++ .../commands/generators/touch-listener.ts | 13 +++-- frontend/src/scripts/game.ts | 4 +- .../src/scripts/objects/projectile-view.ts | 9 ++-- shared/src/settings.ts | 2 + 14 files changed, 111 insertions(+), 40 deletions(-) create mode 100644 backend/Dockerfile diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..3ea763a --- /dev/null +++ b/backend/Dockerfile @@ -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" ] diff --git a/backend/src/main.ts b/backend/src/main.ts index a8c860a..9e45d9e 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -95,7 +95,7 @@ const handlePhysics = () => { } objects.stepObjects(delta); - players.forEach((p) => p.sendObjects()); + players.forEach((p) => p.step(delta)); const physicsDelta = deltaTimeCalculator.getDeltaTimeInMilliseconds(); deltas.push(physicsDelta); diff --git a/backend/src/map/create-dungeon.ts b/backend/src/map/create-dungeon.ts index 1c6b952..46222b4 100644 --- a/backend/src/map/create-dungeon.ts +++ b/backend/src/map/create-dungeon.ts @@ -71,7 +71,7 @@ export const createDungeon = (objectContainer: PhysicalContainer) => { Random.getRandomInRange(0.5, 1), ), ), - Random.getRandomInRange(0.75, 1.5), + Random.getRandomInRange(0.25, 1), ), ); } diff --git a/backend/src/objects/character-physical.ts b/backend/src/objects/character-physical.ts index d1023db..484fafe 100644 --- a/backend/src/objects/character-physical.ts +++ b/backend/src/objects/character-physical.ts @@ -7,24 +7,26 @@ import { serializesTo, clamp, last, + GameObject, } from 'shared'; import { DynamicPhysical } from '../physics/conatiners/dynamic-physical'; -import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-bounding-box'; import { CirclePhysical } from './circle-physical'; - import { PhysicalContainer } from '../physics/containers/physical-container'; import { Spring } from './spring'; +import { BoundingBoxBase } from '../physics/bounding-boxes/bounding-box-base'; +import { ProjectilePhysical } from './projectile-physical'; @serializesTo(CharacterBase) export class CharacterPhysical extends CharacterBase implements DynamicPhysical { public readonly canCollide = true; public readonly canMove = true; - + private isDestroyed = false; private jumpEnergyLeft = settings.defaultJumpEnergy; public head: CirclePhysical; public leftFoot: CirclePhysical; public rightFoot: CirclePhysical; + public bound: CirclePhysical; private movementActions: Array = []; 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 headRadius = 50; + private static readonly feetRadius = 20; private static readonly leftFootOffset = 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); this.head = new CirclePhysical( vec2.clone(CharacterPhysical.headOffset), - 50, + CharacterPhysical.headRadius, this, container, ); this.leftFoot = new CirclePhysical( vec2.clone(CharacterPhysical.leftFootOffset), - 20, + CharacterPhysical.feetRadius, this, container, ); this.rightFoot = new CirclePhysical( vec2.clone(CharacterPhysical.rightFootOffset), - 20, + CharacterPhysical.feetRadius, this, container, ); container.addObject(this.head); container.addObject(this.leftFoot); container.addObject(this.rightFoot); + + this.bound = new CirclePhysical( + vec2.create(), + (CharacterPhysical.headRadius + CharacterPhysical.feetRadius * 2) * 2, + this, + container, + ); } - private _boundingBox?: ImmutableBoundingBox; - - public get boundingBox(): ImmutableBoundingBox { - if (!this._boundingBox) { - this._boundingBox = (this.head as CirclePhysical).boundingBox; + public onCollision(other: GameObject) { + if (other instanceof ProjectilePhysical) { + other.destroy(); + this.destroy(); } + } - return this._boundingBox; + public get boundingBox(): BoundingBoxBase { + this.bound.center = this.head.center; + return this.bound.boundingBox; } public get gameObject(): this { @@ -209,8 +222,12 @@ export class CharacterPhysical extends CharacterBase implements DynamicPhysical } public destroy() { - this.container.removeObject(this.head); - this.container.removeObject(this.leftFoot); - this.container.removeObject(this.rightFoot); + if (!this.isDestroyed) { + this.isDestroyed = true; + this.container.removeObject(this); + this.container.removeObject(this.head); + this.container.removeObject(this.leftFoot); + this.container.removeObject(this.rightFoot); + } } } diff --git a/backend/src/objects/circle-physical.ts b/backend/src/objects/circle-physical.ts index d858630..48a019e 100644 --- a/backend/src/objects/circle-physical.ts +++ b/backend/src/objects/circle-physical.ts @@ -6,7 +6,7 @@ import { BoundingBox } from '../physics/bounding-boxes/bounding-box'; import { BoundingBoxBase } from '../physics/bounding-boxes/bounding-box-base'; import { moveCircle } from '../physics/move-circle'; import { PhysicalContainer } from '../physics/containers/physical-container'; -import { DynamicPhysical } from '../physics/conatiners/dynamic-physical'; +import { DynamicPhysical } from '../physics/containers/dynamic-physical'; @serializesTo(Circle) export class CirclePhysical implements Circle, DynamicPhysical { @@ -25,7 +25,7 @@ export class CirclePhysical implements Circle, DynamicPhysical { constructor( private _center: vec2, private _radius: number, - public owner: GameObject, + public owner: DynamicPhysical, private readonly container: PhysicalContainer, private restitution = 0, ) { @@ -46,6 +46,10 @@ export class CirclePhysical implements Circle, DynamicPhysical { this.recalculateBoundingBox(); } + public onCollision(other: GameObject) { + this.owner.onCollision(other); + } + public get gameObject(): GameObject { return this.owner; } diff --git a/backend/src/objects/projectile-physical.ts b/backend/src/objects/projectile-physical.ts index a2f68a2..7ff2824 100644 --- a/backend/src/objects/projectile-physical.ts +++ b/backend/src/objects/projectile-physical.ts @@ -1,12 +1,5 @@ import { vec2 } from 'gl-matrix'; -import { - id, - settings, - serializesTo, - ProjectileBase, - GameObject, - rotate90Deg, -} from 'shared'; +import { id, settings, serializesTo, ProjectileBase, GameObject } from 'shared'; import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-bounding-box'; import { CirclePhysical } from './circle-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 { public readonly canCollide = true; public readonly canMove = true; + private isDestroyed = false; + private bounceCount = 0; public object: CirclePhysical; @@ -47,9 +42,17 @@ export class ProjectilePhysical extends ProjectileBase implements DynamicPhysica return this.object.distance(target); } - public onCollision(normal: vec2, other: GameObject) { - const tangent = rotate90Deg(normal); - vec2.scale(this.velocity, tangent, vec2.dot(tangent, this.velocity)); + public destroy() { + if (!this.isDestroyed) { + this.isDestroyed = true; + this.container.removeObject(this); + } + } + + public onCollision(other: GameObject) { + if (this.bounceCount++ === settings.projectileMaxBounceCount) { + this.destroy(); + } } public step(deltaTimeInMiliseconds: number) { diff --git a/backend/src/physics/containers/dynamic-physical.ts b/backend/src/physics/containers/dynamic-physical.ts index 326b56e..fd0e066 100644 --- a/backend/src/physics/containers/dynamic-physical.ts +++ b/backend/src/physics/containers/dynamic-physical.ts @@ -1,5 +1,8 @@ +import { GameObject } from 'shared'; import { Physical } from '../physical'; export interface DynamicPhysical extends Physical { + readonly canMove: true; step(deltaTimeInMilliseconds: number): void; + onCollision(other: GameObject): void; } diff --git a/backend/src/physics/containers/static-physical.ts b/backend/src/physics/containers/static-physical.ts index 2ad7303..47e8de8 100644 --- a/backend/src/physics/containers/static-physical.ts +++ b/backend/src/physics/containers/static-physical.ts @@ -2,5 +2,6 @@ import { Physical } from '../physical'; import { ImmutableBoundingBox } from '../bounding-boxes/immutable-bounding-box'; export interface StaticPhysical extends Physical { + readonly canMove: false; readonly boundingBox: ImmutableBoundingBox; } diff --git a/backend/src/physics/move-circle.ts b/backend/src/physics/move-circle.ts index 4ff6e8b..d7ef36a 100644 --- a/backend/src/physics/move-circle.ts +++ b/backend/src/physics/move-circle.ts @@ -1,6 +1,7 @@ import { vec2 } from 'gl-matrix'; import { Circle, rotate90Deg } from 'shared'; import { CirclePhysical } from '../objects/circle-physical'; +import { DynamicPhysical } from './containers/dynamic-physical'; import { Physical } from './physical'; 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 = getSdfAtPoint(vec2.add(vec2.create(), nextCircle.center, vec2.fromValues(1, 0))) - sdfAtCenter; @@ -44,9 +54,7 @@ export const moveCircle = ( getSdfAtPoint(vec2.add(vec2.create(), nextCircle.center, vec2.fromValues(0, 1))) - sdfAtCenter; const normal = vec2.fromValues(dx, dy); - vec2.normalize(normal, normal); - const rotatedNormal = rotate90Deg(normal); return { realDelta: delta, diff --git a/backend/src/players/player.ts b/backend/src/players/player.ts index 6a86504..0023969 100644 --- a/backend/src/players/player.ts +++ b/backend/src/players/player.ts @@ -28,6 +28,8 @@ export class Player extends CommandReceiver { private aspectRatio: number = 16 / 9; private isActive = true; + private timeSinceLastProjectile = 0; + private objectsPreviouslyInViewArea: Array = []; private objectsInViewArea: Array = []; @@ -51,6 +53,10 @@ export class Player extends CommandReceiver { [MoveActionCommand.type]: (c: MoveActionCommand) => this.character.handleMovementAction(c), [SecondaryActionCommand.type]: (c: SecondaryActionCommand) => { + if (this.timeSinceLastProjectile < settings.projectileCreationInterval) { + return; + } + const start = vec2.clone(this.character.center); const direction = vec2.subtract(vec2.create(), c.position, start); vec2.normalize(direction, direction); @@ -63,6 +69,8 @@ export class Player extends CommandReceiver { vec2.add(velocity, velocity, this.character.velocity); const projectile = new ProjectilePhysical(start, 20, velocity, this.objects); this.objects.addObject(projectile); + + this.timeSinceLastProjectile = 0; }, }; @@ -92,6 +100,10 @@ export class Player extends CommandReceiver { this.sendObjects(); } + public step(deltaTimeInMilliseconds: number) { + this.sendObjects(); + this.timeSinceLastProjectile += deltaTimeInMilliseconds; + } public sendObjects() { const viewArea = calculateViewArea(this.character.center, this.aspectRatio, 1.5); const bb = new BoundingBox(); diff --git a/frontend/src/scripts/commands/generators/touch-listener.ts b/frontend/src/scripts/commands/generators/touch-listener.ts index 371cf6b..0fa4797 100644 --- a/frontend/src/scripts/commands/generators/touch-listener.ts +++ b/frontend/src/scripts/commands/generators/touch-listener.ts @@ -33,7 +33,7 @@ export class TouchListener extends CommandGenerator { target.addEventListener('touchmove', (event: TouchEvent) => { event.preventDefault(); - const position = this.positionFromEvent(event); + const position = this.positionFromEvent(event, true); const movement = vec2.subtract(vec2.create(), position, this.previousPosition); 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 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 * (invert ? -1 : 1), + touch.clientY * (invert ? -1 : 1), + ), + ), vec2.create(), ); diff --git a/frontend/src/scripts/game.ts b/frontend/src/scripts/game.ts index 3f183ab..218393a 100644 --- a/frontend/src/scripts/game.ts +++ b/frontend/src/scripts/game.ts @@ -103,7 +103,7 @@ export class Game { { shadowTraceCount: 16, paletteSize: 10, - enableStopwatch: true, + //enableStopwatch: true, }, ); @@ -112,7 +112,7 @@ export class Game { colorPalette: [ rgb(1, 1, 1), rgb(0.4, 0.4, 0.4), - rgb(0.3, 1, 1), + rgb(1, 1, 1), ...settings.playerColors, ], enableHighDpiRendering: false, diff --git a/frontend/src/scripts/objects/projectile-view.ts b/frontend/src/scripts/objects/projectile-view.ts index bcb6ef6..bb1b2d1 100644 --- a/frontend/src/scripts/objects/projectile-view.ts +++ b/frontend/src/scripts/objects/projectile-view.ts @@ -1,19 +1,22 @@ import { vec2 } from 'gl-matrix'; -import { Circle, Renderer } from 'sdf-2d'; -import { Id, ProjectileBase } from 'shared'; +import { Circle, CircleLight, Renderer } from 'sdf-2d'; +import { Id, ProjectileBase, rgb } from 'shared'; import { ViewObject } from './view-object'; export class ProjectileView extends ProjectileBase implements ViewObject { private circle: Circle; + private light: CircleLight; constructor(id: Id, center: vec2, radius: number) { 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 draw(renderer: Renderer): void { renderer.addDrawable(this.circle); + renderer.addDrawable(this.light); } } diff --git a/shared/src/settings.ts b/shared/src/settings.ts index 4777e84..5c15dc6 100644 --- a/shared/src/settings.ts +++ b/shared/src/settings.ts @@ -9,7 +9,9 @@ export const settings = { maxVelocityY: 3650, polygonEdgeCount: 8, projectileSpeed: 2000, + projectileMaxBounceCount: 1, projectileStartOffset: 150, + projectileCreationInterval: 500, playerColorIndexOffset: 3, worldLeftEdge: -5000, worldRightEdge: 5000,