From 68f9f6d19e8b10fe0af037ad1a17d25bca8ef994 Mon Sep 17 00:00:00 2001 From: NriotHrreion Date: Sun, 3 Nov 2024 08:38:14 +0800 Subject: [PATCH] feat: Add wall mode and wall test --- src/simulator/hitbox.ts | 3 +++ src/simulator/hitboxes/convexHitbox.ts | 22 +++++++++++++++------- src/simulator/hitboxes/groundHitbox.ts | 4 ++++ src/simulator/hitboxes/roundHitbox.ts | 11 +++++++++++ src/simulator/object.ts | 12 ++++++++++++ src/simulator/objects/ground.ts | 5 ++--- src/simulator/render/render.ts | 20 ++++++++++++++++++++ src/ui/panel/panel.tsx | 2 ++ 8 files changed, 69 insertions(+), 10 deletions(-) diff --git a/src/simulator/hitbox.ts b/src/simulator/hitbox.ts index 467eb82..ac00e60 100644 --- a/src/simulator/hitbox.ts +++ b/src/simulator/hitbox.ts @@ -1,3 +1,4 @@ +import type { Canvas } from "@/ui/canvas/canvas"; import type { CanvasObject } from "./object"; import type { Point } from "./render/render"; @@ -18,6 +19,7 @@ export interface IHitbox extends IDisposable { * @param obj A target object to test */ test(obj: CanvasObject): void + testWall(canvas: Canvas): "x" | "y" | null /** * Set a new anchor for the hitbox * @@ -47,6 +49,7 @@ export abstract class Hitbox extends Disposable implements IHitbox { } public abstract test(obj: CanvasObject): void; + public abstract testWall(canvas: Canvas): "x" | "y" | null; public setAnchor(anchor: Point) { this.anchor = anchor; diff --git a/src/simulator/hitboxes/convexHitbox.ts b/src/simulator/hitboxes/convexHitbox.ts index be55dab..8f50bbc 100644 --- a/src/simulator/hitboxes/convexHitbox.ts +++ b/src/simulator/hitboxes/convexHitbox.ts @@ -1,5 +1,6 @@ import type { Point } from "@/simulator/render/render"; import type { CanvasObject } from "@/simulator/object"; +import type { Canvas } from "@/ui/canvas/canvas"; import { Hitbox, type IHitbox } from "@/simulator/hitbox"; import { type Vector, VectorCollection } from "@/simulator/vector"; @@ -7,18 +8,15 @@ import { type Vector, VectorCollection } from "@/simulator/vector"; import { RoundHitbox } from "./roundHitbox"; interface IConvexHitbox extends IHitbox { - boundaries: VectorCollection + boundaries: Vector[] } export class ConvexHitbox extends Hitbox implements IConvexHitbox { - public boundaries: VectorCollection; - - public constructor(boundaryVectors: Vector[], anchor: Point) { + + public constructor(public boundaries: Vector[], anchor: Point) { super(anchor); - this.boundaries = new VectorCollection(boundaryVectors); - - if(this.boundaries.getSum().length !== 0) { + if(new VectorCollection(boundaries).getSum().length !== 0) { throw new Error("The sum of the boundary vectors must be 0."); } } @@ -37,4 +35,14 @@ export class ConvexHitbox extends Hitbox implements IConvexHitbox { return false; } } + + public testWall(canvas: Canvas) { + if(this.anchor.y <= 0) { + return "y"; + } else if(this.anchor.x <= 0 || this.anchor.x + this.boundaries[0].x >= canvas.width) { + return "x"; + } + + return null; + } } diff --git a/src/simulator/hitboxes/groundHitbox.ts b/src/simulator/hitboxes/groundHitbox.ts index f0e0ec0..21fe91d 100644 --- a/src/simulator/hitboxes/groundHitbox.ts +++ b/src/simulator/hitboxes/groundHitbox.ts @@ -37,4 +37,8 @@ export class GroundHitbox extends Hitbox implements IGroundHitbox { } } } + + public testWall(): null { + return null; + } } diff --git a/src/simulator/hitboxes/roundHitbox.ts b/src/simulator/hitboxes/roundHitbox.ts index 858b4c3..0e04d8c 100644 --- a/src/simulator/hitboxes/roundHitbox.ts +++ b/src/simulator/hitboxes/roundHitbox.ts @@ -1,5 +1,6 @@ import type { Point } from "@/simulator/render/render"; import type { CanvasObject } from "@/simulator/object"; +import type { Canvas } from "@/ui/canvas/canvas"; import { Hitbox, type IHitbox } from "@/simulator/hitbox"; import { getPointDistance } from "@/common/utils/utils"; @@ -38,4 +39,14 @@ export class RoundHitbox extends Hitbox implements IRoundHitbox { } } } + + public testWall(canvas: Canvas) { + if(this.anchor.y <= 0) { + return "y"; + } else if(this.anchor.x <= 0 || this.anchor.x + 2 * this.radius >= canvas.width) { + return "x"; + } + + return null; + } } diff --git a/src/simulator/object.ts b/src/simulator/object.ts index d3d1911..c7c5044 100644 --- a/src/simulator/object.ts +++ b/src/simulator/object.ts @@ -58,6 +58,7 @@ interface ICanvasObject extends Renderable { * Clear all forces from the object */ clearForces(): void + reverseVelocity(direction: "x" | "y", damping?: number): void /** * Update the anchor point of the hitbox, * so that it matches the current position of the object. @@ -224,6 +225,17 @@ export class CanvasObject extends Disposable implemen this._forces.clear(); } + public reverseVelocity(direction: "x" | "y", damping: number = 1) { + const n = new Vector(0, 1); + + const vy = this.velocity.getComponent(n); + const vx = Vector.sub(this.velocity, vy); + + direction === "y" + ? this.velocity = Vector.add(vx, Vector.multiplyScalar(Vector.reverse(vy), damping)) + : this.velocity = Vector.add(vy, Vector.multiplyScalar(Vector.reverse(vx), damping)); + } + public updateHitboxAnchor(): void { const bound = this.obj.getBounds(); this.hitbox.setAnchor({ diff --git a/src/simulator/objects/ground.ts b/src/simulator/objects/ground.ts index 90907c4..8ed3bd6 100644 --- a/src/simulator/objects/ground.ts +++ b/src/simulator/objects/ground.ts @@ -15,7 +15,7 @@ export class Ground extends CanvasObject { public static readonly id = "ground"; public static readonly GROUND_HEIGHT = 50; - public static readonly DAMPING = 1; + public static readonly DAMPING = .9; public static readonly STABLE_VELOCITY = 5; public readonly normalVector: Vector = new Vector(0, 1); @@ -41,10 +41,9 @@ export class Ground extends CanvasObject { obj.updateHitboxAnchor(); const vy = obj.velocity.getComponent(this.normalVector); - const vx = Vector.sub(obj.velocity, vy); if(vy.length > Ground.STABLE_VELOCITY) { - obj.velocity = Vector.add(vx, Vector.multiplyScalar(Vector.reverse(vy), Ground.DAMPING)); + obj.reverseVelocity("y", Ground.DAMPING); } else { obj.velocity.y = 0; obj.applyForce("ground.support", Force.reverse(Force.gravity(obj.mass))); diff --git a/src/simulator/render/render.ts b/src/simulator/render/render.ts index 3bad77b..ecea4d9 100644 --- a/src/simulator/render/render.ts +++ b/src/simulator/render/render.ts @@ -80,6 +80,12 @@ interface IRender extends Renderable, IDisposable { * @param enabled Is the mouse mode enabled */ setMouseMode(enabled: boolean): void + /** + * Toggle the wall mode (on / off) + * + * @param enabled Is the wall mode enabled + */ + setWallMode(enabled: boolean): void onRender: Event } @@ -96,6 +102,7 @@ export class Render extends Disposable implements IRender { public isPaused: boolean = false; public isMouseMode: boolean = false; + public isWallMode: boolean = true; public constructor(public canvas: Canvas) { super(); @@ -157,6 +164,14 @@ export class Render extends Disposable implements IRender { obj.hitbox.test(_obj); } } + + // Test wall + if(!this.isWallMode) return; + + const hitDirection = obj.hitbox.testWall(this.canvas); + if(hitDirection) { + obj.reverseVelocity(hitDirection); + } } } @@ -223,6 +238,10 @@ export class Render extends Disposable implements IRender { this.isMouseMode = enabled; } + public setWallMode(enabled: boolean) { + this.isWallMode = enabled; + } + public update(delta: number) { // Pre-rendering stage if(!this._prerenderObjects.isEmpty()) { @@ -244,6 +263,7 @@ export class Render extends Disposable implements IRender { infoList.push(`Objects: ${this._objects.length}`); if(this.isMouseMode) infoList.push("MouseMode"); + if(this.isWallMode) infoList.push("WallMode"); for(let i = 0; i < infoList.length; i++) { this.container.addChild(this.createText(infoList[i], 10, 10 + i * 20)); diff --git a/src/ui/panel/panel.tsx b/src/ui/panel/panel.tsx index 81dc78f..687b2f7 100644 --- a/src/ui/panel/panel.tsx +++ b/src/ui/panel/panel.tsx @@ -5,6 +5,7 @@ import type { ObjectNameMap } from "@/simulator/object"; import { type IconNode, Box, + BrickWall, Info, MousePointer2, Pause, @@ -73,6 +74,7 @@ export class Panel extends Component implements IP toolbarLeftGroup.addButton({ icon: Settings, tooltip: "设置" }, () => modalProvider.open("settings")); toolbarLeftGroup.addButton({ icon: Box, tooltip: "管理" }, () => modalProvider.open("manager")); toolbarLeftGroup.addSwitcher({ icon: MousePointer2, tooltip: "鼠标模式" }, ({ isActive }) => this._renderer.setMouseMode(isActive)); + toolbarLeftGroup.addSwitcher({ icon: BrickWall, tooltip: "边界墙", defaultValue: true }, ({ isActive }) => this._renderer.setWallMode(isActive)); this._refreshButton = toolbarLeftGroup.addButton({ icon: RotateCw, tooltip: "刷新" }); this._pauseSwitcher = toolbarLeftGroup.addSwitcher({ icon: Pause, tooltip: "暂停" }, ({ isActive }) => { isActive ? this._pauseRenderer() : this._unpauseRenderer();