first commmit
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
import { ILevelConfig, ScrollDirection } from '../data/Interfaces';
|
||||
|
||||
/**
|
||||
* Camera-scrolling model (task 7.1).
|
||||
*
|
||||
* Captures the level's camera/scrolling state without depending on `cc`. The
|
||||
* Cocos view layer maps `CameraScroller.offsetX / offsetY` into a `Camera`
|
||||
* component position every frame.
|
||||
*
|
||||
* Supported scroll modes (req 8.1-8.5, 8.8):
|
||||
* - `horizontal` — scroll never rewinds (森林/魔城).
|
||||
* - `horizontal_bi` — left/right both allowed (洞穴水路).
|
||||
* - `vertical` — scrolls upward as the player climbs (城壁).
|
||||
*
|
||||
* Values are in **landscape design pixels** (960x540 baseline).
|
||||
*/
|
||||
|
||||
export interface ICameraConfig {
|
||||
/** Scroll direction, mirrors `ILevelConfig.scrollDirection`. */
|
||||
direction: ScrollDirection;
|
||||
/** Horizontal level length (for `horizontal` and `horizontal_bi`). */
|
||||
lengthX: number;
|
||||
/** Vertical level length (for `vertical`). */
|
||||
lengthY?: number;
|
||||
/** Camera viewport (design px). */
|
||||
viewportW: number;
|
||||
viewportH: number;
|
||||
}
|
||||
|
||||
/** Four-layer parallax scroller (req 8.8). Speed ratios 1 : 2 : 4 : 4. */
|
||||
export const PARALLAX_RATIOS = [1, 2, 4, 4] as const;
|
||||
export type ParallaxLayer = 'far' | 'mid' | 'near' | 'fx';
|
||||
export const PARALLAX_LAYERS: ParallaxLayer[] = ['far', 'mid', 'near', 'fx'];
|
||||
|
||||
export class CameraScroller {
|
||||
private _offsetX = 0;
|
||||
private _offsetY = 0;
|
||||
private readonly cfg: ICameraConfig;
|
||||
|
||||
constructor(cfg: ICameraConfig) {
|
||||
this.cfg = cfg;
|
||||
}
|
||||
|
||||
public get offsetX(): number {
|
||||
return this._offsetX;
|
||||
}
|
||||
|
||||
public get offsetY(): number {
|
||||
return this._offsetY;
|
||||
}
|
||||
|
||||
/** Camera target follows the player but never rewinds on `horizontal`. */
|
||||
public followPlayer(playerX: number, playerY: number): void {
|
||||
const halfW = this.cfg.viewportW / 2;
|
||||
const halfH = this.cfg.viewportH / 2;
|
||||
if (this.cfg.direction === 'horizontal') {
|
||||
const desired = Math.max(0, playerX - halfW);
|
||||
this._offsetX = Math.min(
|
||||
Math.max(this._offsetX, desired),
|
||||
Math.max(0, this.cfg.lengthX - this.cfg.viewportW)
|
||||
);
|
||||
} else if (this.cfg.direction === 'horizontal_bi') {
|
||||
const desired = Math.max(0, playerX - halfW);
|
||||
this._offsetX = Math.min(desired, Math.max(0, this.cfg.lengthX - this.cfg.viewportW));
|
||||
} else if (this.cfg.direction === 'vertical') {
|
||||
const ly = this.cfg.lengthY ?? this.cfg.viewportH;
|
||||
const desiredY = Math.max(0, playerY - halfH);
|
||||
this._offsetY = Math.min(desiredY, Math.max(0, ly - this.cfg.viewportH));
|
||||
}
|
||||
}
|
||||
|
||||
/** Compute the world offset for a given parallax layer. */
|
||||
public offsetForLayer(layer: ParallaxLayer): { x: number; y: number } {
|
||||
const ratio = PARALLAX_RATIOS[PARALLAX_LAYERS.indexOf(layer)];
|
||||
return { x: this._offsetX / ratio, y: this._offsetY / ratio };
|
||||
}
|
||||
|
||||
/** Return the level's culling rect in world coordinates. */
|
||||
public cullRect(): { leftX: number; rightX: number; topY: number; bottomY: number } {
|
||||
return {
|
||||
leftX: this._offsetX,
|
||||
rightX: this._offsetX + this.cfg.viewportW,
|
||||
topY: this._offsetY + this.cfg.viewportH,
|
||||
bottomY: this._offsetY,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/** Build a CameraScroller from a level config. */
|
||||
export function cameraFromLevel(level: ILevelConfig, viewportW = 960, viewportH = 540): CameraScroller {
|
||||
return new CameraScroller({
|
||||
direction: level.scrollDirection,
|
||||
lengthX: level.levelLengthPx,
|
||||
lengthY: level.scrollDirection === 'vertical' ? level.levelLengthPx : undefined,
|
||||
viewportW,
|
||||
viewportH,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user