update spirit

This commit is contained in:
jakciehan
2026-06-07 22:10:03 +08:00
parent 427a33c55b
commit 9c57deff6d
82 changed files with 5465 additions and 149 deletions
+27 -2
View File
@@ -44,6 +44,8 @@ export interface IPlayerMotionOptions {
platforms: IPlatform[];
/** Starting color state. */
initialColorState?: PlayerColorState;
/** Level horizontal extent (px). Used to clamp player X so they cannot leave the level. */
levelLengthPx: number;
}
export const DEFAULT_GRAVITY = 2500; // px/s² — tuned so a 250px jump peaks in ~0.45s
@@ -63,12 +65,14 @@ export class PlayerMotionModel {
private _aabb: IAxisAlignedBox;
private _platforms: IPlatform[];
private readonly gravity: number;
private readonly _levelLengthPx: number;
constructor(options: IPlayerMotionOptions) {
this._aabb = { ...options.aabb };
this._platforms = options.platforms.slice();
this._colorState = options.initialColorState ?? PlayerColorState.Red;
this.gravity = options.gravity ?? DEFAULT_GRAVITY;
this._levelLengthPx = options.levelLengthPx;
}
// -- accessors ----------------------------------------------------------
@@ -130,6 +134,9 @@ export class PlayerMotionModel {
* ground; mid-air `_vx` is preserved (起跳定型).
*/
public update(dt: number): void {
// Remember feet position before integration (for sweep test).
const prevFeetY = this._aabb.y - this._aabb.h / 2;
if (this._grounded) {
this._vx = this._horizontalInput * MOVE_SPEED[this._colorState];
this._vy = 0;
@@ -143,16 +150,34 @@ export class PlayerMotionModel {
x: this._aabb.x + this._vx * dt,
y: this._aabb.y + this._vy * dt,
};
// Resolve against platforms (basic AABB vs. top-surface only).
const curFeetY = this._aabb.y - this._aabb.h / 2;
// Resolve against platforms using sweep test.
// If the feet crossed a platform surface this frame (prev above → cur below),
// snap the player onto that platform — prevents tunneling at high fall speeds.
this._grounded = false;
for (const p of this._platforms) {
if (this.isRestingOn(p)) {
const withinHorizontal = this._aabb.x >= p.leftX && this._aabb.x <= p.rightX;
if (!withinHorizontal) continue;
if (this._vy > 0) continue; // moving upward — cannot land
// Sweep test: feet were above topY last frame, now at or below topY.
const crossedSurface = prevFeetY >= p.topY - 0.5 && curFeetY <= p.topY + 0.5;
// Also catch the small-window case (slow fall, feet near surface).
const nearSurface = curFeetY <= p.topY + 0.5 && curFeetY >= p.topY - 6;
if (crossedSurface || nearSurface) {
this._grounded = true;
this._aabb = { ...this._aabb, y: p.topY + this._aabb.h / 2 };
if (this._vy < 0) this._vy = 0;
break;
}
}
// Clamp AABB X within level boundaries so the player cannot leave the level.
const halfW = this._aabb.w / 2;
this._aabb.x = Math.max(halfW, Math.min(this._aabb.x, this._levelLengthPx - halfW));
}
// -- helpers ------------------------------------------------------------