update spirit
This commit is contained in:
@@ -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 ------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user