同步主站 Cocos 当前工程:关卡资源、运行脚本与打包工具。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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++) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 ViewController:Orthographic 缩放与拖拽 */
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user