同步主站 Cocos 当前工程:关卡资源、运行脚本与打包工具。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-12 14:40:34 +08:00
parent ae75fce80b
commit fac515a669
5609 changed files with 27744634 additions and 5653 deletions

View File

@@ -233,20 +233,27 @@ export class PlayerController extends Movement {
private onLevelInit = () => {
this.sendFinally = false;
this.coins = 0;
this.animator?.flushCycleWaiters();
this.resetMoveRuntime();
this.unbindVehicle();
this.refreshVisual(PlayerAction.Idle);
this.syncCommittedCellFromPosition();
this.snapMoverToCellStand();
this.resetToSpawn();
this.checkIfCurIsRide();
tryLinkPlayerVehicle(this);
};
private onInputEnd = () => {
const gm = GameManager.instance!;
this.externalCallResult(gm.allPropsCollected());
void this.settleInputEnd();
};
/** 等当前这一步落地并拾取后再判胜负,避免最后一格刚起步就失败 */
private async settleInputEnd() {
await this.waitUntilIdle();
const gm = GameManager.instance;
if (!gm || gm.gameState !== GameState.Run) return;
this.externalCallResult(gm.allPropsCollected());
}
protected override snapMoverToCellStand() {
const gm = GameManager.instance;
if (!gm || !this.committedCell) return;
@@ -357,14 +364,19 @@ export class PlayerController extends Movement {
if (isJump && this.targetGridType === GridType.Jump && this.vehicle) {
this.unbindVehicle();
}
this.animator?.setAction(isJump ? PlayerAction.Jump : PlayerAction.Move);
const next = isJump ? PlayerAction.Jump : PlayerAction.Move;
const keepCycle = this.animator?.getAction() === next;
this.animator?.setAction(next);
if (keepCycle) this.animator?.beginCycle();
if (isJump && this.targetGridType === GridType.Jump) {
this.targetPosition.y += scaledJumpArcOffset();
}
}
protected override playMoveAnim() {
const keepCycle = this.animator?.getAction() === PlayerAction.Move;
this.animator?.setAction(PlayerAction.Move);
if (keepCycle) this.animator?.beginCycle();
}
protected override syncRideAfterMoveStep() {
@@ -381,9 +393,7 @@ export class PlayerController extends Movement {
if (!this.sfxSource) return;
const action = this.animator?.getAction() ?? PlayerAction.Idle;
if (action === PlayerAction.Move) {
const key = vehicleFollowsLandingTile(this.targetGridType) && this.vehicle
? 'vehicleMove' : 'move';
void GameAudio.playSfxOnSource(this.sfxSource, key);
void GameAudio.playSfxOnSource(this.sfxSource, 'move');
} else if (action === PlayerAction.Jump) {
void GameAudio.playSfxOnSource(this.sfxSource, 'jump');
}
@@ -514,11 +524,15 @@ export class PlayerController extends Movement {
if (!gm) return;
if (gm.gameState === GameState.ResultWin) {
this.animator?.setAction(PlayerAction.Win);
} else if (gm.gameState === GameState.ResultFail) {
this.animator?.setAction(PlayerAction.Fail);
} else {
this.animator?.setAction(PlayerAction.Idle);
return;
}
if (gm.gameState === GameState.ResultFail) {
this.animator?.setAction(PlayerAction.Fail);
return;
}
// CallMove(n) 尚未走完:保持当前 Move/Jump避免每格切 Idle 重播第 0 帧
if (this.step > 0) return;
this.animator?.setAction(PlayerAction.Idle);
}
callPlayerInfo() {
@@ -561,12 +575,23 @@ export class PlayerController extends Movement {
externalCall() {
const gm = GameManager.instance!;
const list: ExternalDataList = { direction: this.direction, externalDatas: [] };
const sample = this.getGridSamplePosition();
const self = gm.worldToCell(sample);
this.emitProcessData(gm.worldToCell(sample), this.curGrid, sample);
}
/** 按指定逻辑格回传(提前 processData 时用落点,而不是当前脚底) */
private externalCallAtCell(cell: Vec3) {
const gm = GameManager.instance!;
const sample = cellToWorldCenter(cell);
this.emitProcessData(cell, gm.calculateGridTypeAtCell(cell), sample);
}
private emitProcessData(selfCell: Vec3, selfGrid: GridType, sample: Vec3) {
const gm = GameManager.instance!;
const list: ExternalDataList = { direction: this.direction, externalDatas: [] };
list.externalDatas.push({
position: { x: self.x, y: self.y, z: 0 },
gridType: this.curGrid,
position: { x: selfCell.x, y: selfCell.y, z: 0 },
gridType: selfGrid,
direction: 'self',
});
for (let d = Direction.North; d <= Direction.West; d++) {

View File

@@ -58,6 +58,12 @@ export class PropController extends Component {
this.onCollected(player);
}
/** 同关重开:金币只隐藏不销毁,立刻放回格子,避免异步补刷拖过编辑器 200ms */
restoreForRestart() {
this.collected = false;
if (this.node?.isValid) this.node.active = true;
}
private onCollected(player: PlayerController) {
if (this.collected) return;
this.collected = true;
@@ -66,8 +72,6 @@ export class PropController extends Component {
player.addCoins();
const propCell = this.getLogicCell();
gm.removePropAtCell(propCell);
this.node.removeFromParent();
this.node.destroy();
if (gm.allPropsCollected()) {
player.externalCallResult(true);
}

View File

@@ -28,7 +28,10 @@ export class VehicleController extends Movement {
/** 不调用 super.start(),避免 component.start 晚于 LevelInit 绑定后再次用 spawn 方向刷贴图 */
start() {
this.syncCommittedCellFromPosition();
if (!this.getCommittedCell()) {
this.syncCommittedCellFromPosition();
this.markDrawOrderDirty();
}
if (this.player) {
this.syncFacingFromRider();
}

View File

@@ -1,11 +1,24 @@
import {
_decorator, Camera, Component, EventMouse, EventTouch, Input, input, Vec2, view,
_decorator, Camera, Component, EventMouse, EventTouch, Input, input, sys, Vec2, view,
} from 'cc';
import { CAMERA_ORTHO_HALF, DESIGN_WIDTH } from '../core/GridConstants';
import { getEmbeddedOrthoHalf } from '../core/EmbeddedView';
const { ccclass } = _decorator;
/** 与 UIMain 相同165×165 按钮 + 右侧 20px 边距,按 2560 设计宽缩到 960 */
const HUD_BTN = Math.round(165 * DESIGN_WIDTH / 2560);
const HUD_PAD_RIGHT = Math.round(20 * DESIGN_WIDTH / 2560);
const HUD_RIGHT_SLACK = 8;
function markMapViewDirty(): void {
(globalThis as { __tfrhPreserveMapView?: boolean }).__tfrhPreserveMapView = true;
}
function clearMapViewDirty(): void {
(globalThis as { __tfrhPreserveMapView?: boolean }).__tfrhPreserveMapView = false;
}
/** 对齐 Unity ViewControllerOrthographic 缩放与拖拽 */
@ccclass('ViewController')
export class ViewController extends Component {
@@ -19,6 +32,7 @@ export class ViewController extends Component {
private camera: Camera | null = null;
private dragOrigin = new Vec2();
private readonly uiLoc = new Vec2();
private dragging = false;
onLoad() {
@@ -55,12 +69,14 @@ export class ViewController extends Component {
const cam = this.camera;
if (!cam || cam.orthoHeight <= this.minOrtho) return;
cam.orthoHeight = Math.max(this.minOrtho, cam.orthoHeight - this.zoomSpeed);
markMapViewDirty();
}
zoomOut() {
const cam = this.camera;
if (!cam || cam.orthoHeight >= this.maxOrtho) return;
cam.orthoHeight = Math.min(this.maxOrtho, cam.orthoHeight + this.zoomSpeed);
markMapViewDirty();
}
resetZoom() {
@@ -68,30 +84,40 @@ export class ViewController extends Component {
if (!cam) return;
cam.orthoHeight = getEmbeddedOrthoHalf();
cam.node.setPosition(0, 0, cam.node.position.z);
clearMapViewDirty();
}
private usingMouseInput(): boolean {
return !sys.isMobile && sys.hasFeature(sys.Feature.EVENT_MOUSE);
}
private onTouchStart(e: EventTouch) {
if (this.isPointerOnUI(e.getLocation())) return;
// 桌面端鼠标会同时模拟 touch只走 MOUSE_* 以免双倍位移、并过滤非左键
if (this.usingMouseInput()) return;
e.getUILocation(this.uiLoc);
if (this.isPointerOnUI(this.uiLoc)) return;
this.dragging = true;
e.getLocation(this.dragOrigin);
this.dragOrigin.set(this.uiLoc);
}
private onTouchEnd() {
if (this.usingMouseInput()) return;
this.dragging = false;
}
private onTouchMove(e: EventTouch) {
if (!this.dragging) return;
const cur = new Vec2();
e.getLocation(cur);
this.applyDrag(cur);
e.getLocation(this.dragOrigin);
if (this.usingMouseInput() || !this.dragging) return;
e.getUILocation(this.uiLoc);
this.applyDrag(this.uiLoc);
this.dragOrigin.set(this.uiLoc);
}
private onMouseDown(e: EventMouse) {
if (this.isPointerOnUI(new Vec2(e.getLocationX(), e.getLocationY()))) return;
if (e.getButton() !== EventMouse.BUTTON_LEFT) return;
e.getUILocation(this.uiLoc);
if (this.isPointerOnUI(this.uiLoc)) return;
this.dragging = true;
this.dragOrigin.set(e.getLocationX(), e.getLocationY());
this.dragOrigin.set(this.uiLoc);
}
private onMouseUp() {
@@ -100,37 +126,38 @@ export class ViewController extends Component {
private onMouseMove(e: EventMouse) {
if (!this.dragging) return;
this.applyDrag(new Vec2(e.getLocationX(), e.getLocationY()));
this.dragOrigin.set(e.getLocationX(), e.getLocationY());
e.getUILocation(this.uiLoc);
this.applyDrag(this.uiLoc);
this.dragOrigin.set(this.uiLoc);
}
private applyDrag(cur: Vec2) {
if (!this.camera) return;
const delta = cur.subtract(this.dragOrigin);
const dx = cur.x - this.dragOrigin.x;
const dy = cur.y - this.dragOrigin.y;
const ortho = this.camera.orthoHeight;
const { width, height } = view.getVisibleSize();
if (width <= 0 || height <= 0) return;
const worldPerPixelX = (2 * ortho * (width / height)) / width;
const worldPerPixelY = (2 * ortho) / height;
const pos = this.camera.node.position;
let nx = pos.x - delta.x * worldPerPixelX;
let ny = pos.y - delta.y * worldPerPixelY;
let nx = pos.x - dx * worldPerPixelX;
let ny = pos.y - dy * worldPerPixelY;
const limit = ortho - 20;
if (Math.abs(nx) > limit) nx = pos.x;
if (Math.abs(ny) > limit) ny = pos.y;
this.camera.node.setPosition(nx, ny, pos.z);
markMapViewDirty();
}
/** 右侧 UIMain 区域不拖拽镜头(与 UIMain 边距一致) */
/** 仅拦截右侧 HUD 按钮列;坐标为 UI 空间(原点左下,与 visibleSize 一致) */
private isPointerOnUI(loc: Vec2): boolean {
const vis = view.getVisibleSize();
const margin = Math.max(96, (DESIGN_WIDTH * 0.5) * 0.14);
const right = vis.width > DESIGN_WIDTH
? DESIGN_WIDTH * 0.5
: vis.width * 0.5;
return loc.x >= right - margin;
const right = vis.width > 0 ? vis.width : DESIGN_WIDTH;
return loc.x >= right - HUD_BTN - HUD_PAD_RIGHT - HUD_RIGHT_SLACK;
}
}