Complete Cocos Creator port with level bundles, themes, and tooling.
Adds level prefabs, theme assets, audio, extensions, and deployment scripts for the Unity WebGL migration. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
206
assets/scripts/gameplay/LineGridRenderer.ts
Normal file
206
assets/scripts/gameplay/LineGridRenderer.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
import {
|
||||
_decorator, Color, Component, Graphics, Layers, Node, UITransform, Vec3, view,
|
||||
} from 'cc';
|
||||
import { CELL_PIXEL, CAMERA_ORTHO_HALF, DESIGN_HEIGHT } from '../core/GridConstants';
|
||||
import { cellToWorldCenter, getHalfCellSize } from '../core/GridCoords';
|
||||
import { EventManager, EventType } from '../core/EventManager';
|
||||
import { GameManager } from '../manager/GameManager';
|
||||
import { GridSnapHelper } from '../level/GridSnapHelper';
|
||||
|
||||
const { ccclass } = _decorator;
|
||||
|
||||
const UI_LAYER = Layers.Enum.UI_2D;
|
||||
|
||||
/** 对齐 Unity LineGridRenderer:导航按钮切换的满屏等距辅助网格 */
|
||||
@ccclass('LineGridRenderer')
|
||||
export class LineGridRenderer extends Component {
|
||||
static instance: LineGridRenderer | null = null;
|
||||
|
||||
/** 以中心向外的格子半径(菱形个数 ≈ (2r+1)²) */
|
||||
radius = 24;
|
||||
cellSize = CELL_PIXEL;
|
||||
|
||||
private graphics: Graphics | null = null;
|
||||
private visible = false;
|
||||
|
||||
static ensure(parent: Node): LineGridRenderer {
|
||||
LineGridRenderer.purgeDuplicates(parent);
|
||||
|
||||
let comp = LineGridRenderer.instance?.isValid ? LineGridRenderer.instance : null;
|
||||
if (!comp) {
|
||||
const found = LineGridRenderer.findUnder(parent);
|
||||
comp = found?.getComponent(LineGridRenderer) ?? null;
|
||||
}
|
||||
if (!comp) {
|
||||
const node = new Node('LineGrid');
|
||||
node.parent = parent;
|
||||
comp = node.addComponent(LineGridRenderer);
|
||||
} else if (comp.node.parent !== parent) {
|
||||
comp.node.parent = parent;
|
||||
}
|
||||
|
||||
comp.setupRuntime();
|
||||
comp.sendToBack();
|
||||
return comp;
|
||||
}
|
||||
|
||||
/** 移除误挂在关卡内的重复 LineGrid(会导致叠线无法关闭) */
|
||||
private static purgeDuplicates(entrance: Node) {
|
||||
const keep = LineGridRenderer.instance?.isValid
|
||||
? LineGridRenderer.instance.node
|
||||
: LineGridRenderer.findUnder(entrance);
|
||||
const walk = (node: Node) => {
|
||||
for (let i = node.children.length - 1; i >= 0; i--) {
|
||||
const ch = node.children[i];
|
||||
if (ch.name === 'LineGrid' && ch !== keep) {
|
||||
ch.destroy();
|
||||
continue;
|
||||
}
|
||||
walk(ch);
|
||||
}
|
||||
};
|
||||
walk(entrance);
|
||||
}
|
||||
|
||||
private static findUnder(root: Node): Node | null {
|
||||
if (root.name === 'LineGrid') return root;
|
||||
for (const ch of root.children) {
|
||||
const hit = LineGridRenderer.findUnder(ch);
|
||||
if (hit) return hit;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
onLoad() {
|
||||
if (LineGridRenderer.instance && LineGridRenderer.instance !== this) {
|
||||
this.node.destroy();
|
||||
return;
|
||||
}
|
||||
LineGridRenderer.instance = this;
|
||||
this.setupRuntime();
|
||||
EventManager.register(EventType.LevelInit, this.onLevelInit);
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
EventManager.remove(EventType.LevelInit, this.onLevelInit);
|
||||
if (LineGridRenderer.instance === this) LineGridRenderer.instance = null;
|
||||
}
|
||||
|
||||
isGridVisible(): boolean {
|
||||
return this.visible;
|
||||
}
|
||||
|
||||
private setupRuntime() {
|
||||
this.node.layer = UI_LAYER;
|
||||
if (!this.node.getComponent(UITransform)) {
|
||||
this.node.addComponent(UITransform).setContentSize(1, 1);
|
||||
}
|
||||
const g = this.getComponent(Graphics) ?? this.addComponent(Graphics);
|
||||
this.graphics = g;
|
||||
g.lineWidth = 1.4;
|
||||
g.strokeColor = new Color(0, 0, 0, 255);
|
||||
if (!this.visible) {
|
||||
g.clear();
|
||||
this.node.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
private onLevelInit = () => {
|
||||
this.purgeEditorGrid();
|
||||
this.syncLevelOffset();
|
||||
this.updateGridRadius();
|
||||
if (this.visible) {
|
||||
this.rebuild();
|
||||
this.node.active = true;
|
||||
this.sendToBack();
|
||||
} else {
|
||||
this.graphics?.clear();
|
||||
this.node.active = false;
|
||||
}
|
||||
};
|
||||
|
||||
toggleGridVisibility() {
|
||||
this.setupRuntime();
|
||||
this.visible = !this.visible;
|
||||
if (this.visible) {
|
||||
this.purgeEditorGrid();
|
||||
this.syncLevelOffset();
|
||||
this.updateGridRadius();
|
||||
this.rebuild();
|
||||
this.node.active = true;
|
||||
this.sendToBack();
|
||||
} else {
|
||||
this.graphics?.clear();
|
||||
this.node.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 移除编辑器 GridSnapHelper 遗留的蓝色/灰色参考格 */
|
||||
private purgeEditorGrid() {
|
||||
const entrance = this.node.parent;
|
||||
if (entrance?.isValid) {
|
||||
GridSnapHelper.purgeRuntimeGrids(entrance);
|
||||
}
|
||||
const scene = this.node.scene;
|
||||
if (scene?.isValid) {
|
||||
GridSnapHelper.purgeRuntimeGrids(scene);
|
||||
}
|
||||
}
|
||||
|
||||
/** 保持在 MainLevelEntrance 最底层,不遮挡关卡内砖块/角色 */
|
||||
private sendToBack() {
|
||||
const parent = this.node.parent;
|
||||
if (parent?.isValid) {
|
||||
this.node.setSiblingIndex(0);
|
||||
}
|
||||
}
|
||||
|
||||
/** 与关卡根节点对齐,网格覆盖当前可见砖块区域 */
|
||||
private syncLevelOffset() {
|
||||
const parent = this.node.parent;
|
||||
if (!parent) return;
|
||||
const levelRoot = parent.children.find(
|
||||
(c) => c?.isValid && c !== this.node && (c.name ?? '').startsWith('Level'),
|
||||
);
|
||||
if (levelRoot?.isValid) {
|
||||
const p = levelRoot.position;
|
||||
this.node.setPosition(p.x, p.y, 0);
|
||||
} else {
|
||||
this.node.setPosition(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private updateGridRadius() {
|
||||
const b = GameManager.instance?.getCurLevel()?.boundary;
|
||||
const vs = view.getVisibleSize();
|
||||
const halfH = CAMERA_ORTHO_HALF;
|
||||
const halfW = halfH * (vs.width / Math.max(vs.height, DESIGN_HEIGHT));
|
||||
const cover = Math.ceil(Math.max(halfW, halfH) / (CELL_PIXEL * 0.35)) + 6;
|
||||
|
||||
if (b) {
|
||||
this.radius = Math.max(cover, b.x + 8, b.y + 8);
|
||||
} else {
|
||||
this.radius = cover;
|
||||
}
|
||||
}
|
||||
|
||||
private rebuild() {
|
||||
const g = this.graphics;
|
||||
if (!g) return;
|
||||
g.clear();
|
||||
|
||||
const { halfW, halfH } = getHalfCellSize();
|
||||
const r = this.radius;
|
||||
for (let x = -r; x <= r; x++) {
|
||||
for (let y = -r; y <= r; y++) {
|
||||
const c = cellToWorldCenter(new Vec3(x, y, 0));
|
||||
g.moveTo(c.x, c.y + halfH);
|
||||
g.lineTo(c.x + halfW, c.y);
|
||||
g.lineTo(c.x, c.y - halfH);
|
||||
g.lineTo(c.x - halfW, c.y);
|
||||
g.close();
|
||||
}
|
||||
}
|
||||
g.stroke();
|
||||
}
|
||||
}
|
||||
9
assets/scripts/gameplay/LineGridRenderer.ts.meta
Normal file
9
assets/scripts/gameplay/LineGridRenderer.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "53b54d98-b129-4a11-8891-528a4a62a302",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
56
assets/scripts/gameplay/MoveRules.ts
Normal file
56
assets/scripts/gameplay/MoveRules.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 移动 / 骑乘规则(moveCondition 查表与 Unity Movement.MoveNextCheck 一致)
|
||||
*
|
||||
* 骑乘联动、上下车见 PlayerController / VehicleController(非查表部分)
|
||||
*/
|
||||
import { GridType, MoverRole, getMoveCondition, lookupMove } from '../core/Define';
|
||||
|
||||
export type MoveCheckResult = -1 | 0 | 1;
|
||||
|
||||
/** Unity PlayerController.OnMoving:targetGridType === None 时载具跟随 */
|
||||
export function vehicleFollowsLandingTile(liveGrid: GridType): boolean {
|
||||
return liveGrid === GridType.None;
|
||||
}
|
||||
|
||||
/** 落到瓦片砖面时下车(None 保持骑乘;Ride 动态格由上车逻辑处理) */
|
||||
export function shouldDismountOnTile(tileGrid: GridType): boolean {
|
||||
return tileGrid === GridType.Across
|
||||
|| tileGrid === GridType.Jump
|
||||
|| tileGrid === GridType.Block
|
||||
|| tileGrid === GridType.Boundary;
|
||||
}
|
||||
|
||||
/** 落格后尝试保持骑乘:脚下为 None,或动态 Ride 且同格有载具 */
|
||||
export function canStayMountedAfterLanding(liveGrid: GridType): boolean {
|
||||
return liveGrid === GridType.None || liveGrid === GridType.Ride;
|
||||
}
|
||||
|
||||
/** 移动落点世界坐标应使用的 mover 角色(对齐 Unity 共享 transform / 载具甲板) */
|
||||
export function moverRoleForLandingCell(
|
||||
role: MoverRole,
|
||||
liveGrid: GridType,
|
||||
mounted: boolean,
|
||||
): MoverRole {
|
||||
if (mounted && vehicleFollowsLandingTile(liveGrid)) return 'vehicle';
|
||||
if (!mounted && liveGrid === GridType.Ride) return 'vehicle';
|
||||
return role;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查表判定一步移动(对齐 Unity Movement.MoveNextCheck)
|
||||
* - 1:可移动
|
||||
* - 0:不动(仅多人模式表外组合;单人表外为 -1)
|
||||
* - -1:失败
|
||||
*/
|
||||
export function checkMoveStep(
|
||||
role: MoverRole,
|
||||
curGrid: GridType,
|
||||
nextGrid: GridType,
|
||||
isJump: boolean,
|
||||
mult = false,
|
||||
): MoveCheckResult {
|
||||
const table = getMoveCondition(mult);
|
||||
const v = lookupMove(table, role, curGrid, nextGrid, isJump);
|
||||
if (v === undefined) return mult ? 0 : -1;
|
||||
return v as MoveCheckResult;
|
||||
}
|
||||
9
assets/scripts/gameplay/MoveRules.ts.meta
Normal file
9
assets/scripts/gameplay/MoveRules.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "261eb8b8-97f9-4df2-9de5-85bc83b9cf52",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -1,55 +1,223 @@
|
||||
import { _decorator, Component, Vec3 } from 'cc';
|
||||
import {
|
||||
Direction, GameState, GridType, MoveState, MoverRole,
|
||||
addDirection, getMoveCondition, lookupMove,
|
||||
Direction, GameState, GridType, MoveState, MoverRole, addDirection,
|
||||
} from '../core/Define';
|
||||
import { GameManager } from '../manager/GameManager';
|
||||
import { scaledMoveSpeed, UNITY_VEHICLE_MOVE_SPEED } from '../core/GridConstants';
|
||||
import { cellToWorldCenter } from '../core/GridCoords';
|
||||
import {
|
||||
entityWorldPositionForRole,
|
||||
moverLogicalGridPositionForRole,
|
||||
roleUsesPlayerStandOffset,
|
||||
worldToMoverCellForRole,
|
||||
} from '../level/EntitySpawnPlacement';
|
||||
import { checkMoveStep, MoveCheckResult } from './MoveRules';
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
/**
|
||||
* 离散格子移动基类(对齐 Unity Movement)
|
||||
* - 查表判定能否进入下一格
|
||||
* - FixedUpdate 式插值到 targetPosition
|
||||
* - 骑乘联动由 Player/Vehicle 子类覆写
|
||||
*/
|
||||
@ccclass('Movement')
|
||||
export class Movement extends Component {
|
||||
private static speedMultiplier = 1;
|
||||
|
||||
static setSpeedMultiplier(m: number) {
|
||||
Movement.speedMultiplier = m > 0 ? m : 1;
|
||||
if (typeof globalThis !== 'undefined') {
|
||||
(globalThis as { __tfrhGameSpeed?: number }).__tfrhGameSpeed = Movement.speedMultiplier;
|
||||
}
|
||||
}
|
||||
|
||||
static getSpeedMultiplier(): number {
|
||||
return Movement.speedMultiplier;
|
||||
}
|
||||
|
||||
@property
|
||||
moveSpeed = 4;
|
||||
moveSpeed = scaledMoveSpeed(UNITY_VEHICLE_MOVE_SPEED);
|
||||
|
||||
direction: Direction = Direction.North;
|
||||
moveState: MoveState = MoveState.Idle;
|
||||
moverRole: MoverRole = 'player';
|
||||
|
||||
protected targetPosition = new Vec3();
|
||||
/** 目标格类型(含 Ride 动态覆盖),对齐 Unity Movement.targetGridType */
|
||||
protected targetGridType: GridType = GridType.None;
|
||||
protected lastPosition = new Vec3();
|
||||
protected step = 0;
|
||||
|
||||
private moveWait = false;
|
||||
protected moveWait = false;
|
||||
protected committedCell: Vec3 | null = null;
|
||||
/** 关卡 spawn 逻辑格(优先于从世界坐标反推,避免站立 Y 微调导致格偏移) */
|
||||
protected spawnCell: Vec3 | null = null;
|
||||
protected landingCell: Vec3 | null = null;
|
||||
private moveStepFrom = new Vec3();
|
||||
private queue: Promise<void> = Promise.resolve();
|
||||
static callEach = false;
|
||||
|
||||
protected getMoverLocalPosition(): Vec3 {
|
||||
return this.node.position.clone();
|
||||
}
|
||||
|
||||
setSpawnCell(cell: Vec3) {
|
||||
this.spawnCell = cell.clone();
|
||||
if (!this.committedCell) this.committedCell = new Vec3();
|
||||
this.committedCell.set(cell);
|
||||
}
|
||||
|
||||
getSpawnCell(): Vec3 | null {
|
||||
return this.spawnCell ? this.spawnCell.clone() : null;
|
||||
}
|
||||
|
||||
protected syncCommittedCellFromPosition() {
|
||||
const gm = GameManager.instance;
|
||||
if (!gm) return;
|
||||
if (this.spawnCell) {
|
||||
if (!this.committedCell) this.committedCell = new Vec3();
|
||||
this.committedCell.set(this.spawnCell);
|
||||
this.spawnCell = null;
|
||||
return;
|
||||
}
|
||||
const config = gm.getCurLevel();
|
||||
const theme = config?.theme ?? gm.uiStyle;
|
||||
if (!this.committedCell) this.committedCell = new Vec3();
|
||||
const cell = worldToMoverCellForRole(
|
||||
this.getMoverLocalPosition(),
|
||||
config ?? undefined,
|
||||
theme,
|
||||
this.moverRole,
|
||||
);
|
||||
this.committedCell.set(cell);
|
||||
}
|
||||
|
||||
public shareCommittedCell(cell: Vec3) {
|
||||
if (!this.committedCell) this.committedCell = new Vec3();
|
||||
this.committedCell.set(cell);
|
||||
}
|
||||
|
||||
getCommittedCell(): Vec3 | null {
|
||||
return this.committedCell ? this.committedCell.clone() : null;
|
||||
}
|
||||
|
||||
getLandingCell(): Vec3 | null {
|
||||
return this.landingCell ? this.landingCell.clone() : null;
|
||||
}
|
||||
|
||||
isMoving(): boolean {
|
||||
return this.moveState === MoveState.Moving;
|
||||
}
|
||||
|
||||
/** 当前移动步进度 0(起步)→ 1(落点) */
|
||||
getMoveStepProgress(): number {
|
||||
if (this.moveState !== MoveState.Moving) return 1;
|
||||
const total = Vec3.distance(this.moveStepFrom, this.targetPosition);
|
||||
if (total < 1e-4) return 1;
|
||||
const remain = Vec3.distance(this.node.position, this.targetPosition);
|
||||
return Math.max(0, Math.min(1, 1 - remain / total));
|
||||
}
|
||||
|
||||
/** 逻辑格采样(含动态 Ride);骑乘时由 PlayerController 覆写 */
|
||||
protected getGridSamplePosition(): Vec3 {
|
||||
if (this.committedCell) {
|
||||
return cellToWorldCenter(this.committedCell);
|
||||
}
|
||||
const gm = GameManager.instance;
|
||||
const local = this.getMoverLocalPosition();
|
||||
if (!gm) return local;
|
||||
const config = gm.getCurLevel();
|
||||
const theme = config?.theme ?? gm.uiStyle;
|
||||
if (this.moverRole === 'player' || this.moverRole === 'vehicle') {
|
||||
return moverLogicalGridPositionForRole(
|
||||
local,
|
||||
config ?? undefined,
|
||||
theme,
|
||||
this.moverRole,
|
||||
);
|
||||
}
|
||||
return local;
|
||||
}
|
||||
|
||||
/** 当前格(含 Ride 动态格) */
|
||||
get curGrid(): GridType {
|
||||
return GameManager.instance!.calculateGridType(this.node.position);
|
||||
const gm = GameManager.instance!;
|
||||
if (this.committedCell) return gm.calculateGridTypeAtCell(this.committedCell);
|
||||
return gm.calculateGridType(this.getGridSamplePosition());
|
||||
}
|
||||
|
||||
get nextGrid(): GridType {
|
||||
return GameManager.instance!.calculateNextGridType(this.node.position, this.direction);
|
||||
const gm = GameManager.instance!;
|
||||
if (this.committedCell) return gm.calculateNextGridTypeAtCell(this.committedCell, this.direction);
|
||||
return gm.calculateNextGridType(this.getGridSamplePosition(), this.direction);
|
||||
}
|
||||
|
||||
get lastGrid(): GridType {
|
||||
return GameManager.instance!.calculateLastGridType(this.node.position, this.direction);
|
||||
const gm = GameManager.instance!;
|
||||
if (this.committedCell) return gm.calculateLastGridTypeAtCell(this.committedCell, this.direction);
|
||||
return gm.calculateLastGridType(this.getGridSamplePosition(), this.direction);
|
||||
}
|
||||
|
||||
get isFront(): boolean {
|
||||
return this.direction === Direction.South || this.direction === Direction.East;
|
||||
}
|
||||
|
||||
start() {
|
||||
this.setDirection(this.direction);
|
||||
this.syncCommittedCellFromPosition();
|
||||
}
|
||||
|
||||
protected commitLandingCell() {
|
||||
if (this.landingCell) {
|
||||
if (!this.committedCell) this.committedCell = new Vec3();
|
||||
this.committedCell.set(this.landingCell);
|
||||
this.landingCell = null;
|
||||
}
|
||||
this.snapMoverToCellStand();
|
||||
}
|
||||
|
||||
protected shouldApplyPlayerStandOffset(): boolean {
|
||||
return roleUsesPlayerStandOffset(this.moverRole);
|
||||
}
|
||||
|
||||
protected snapMoverToCellStand() {
|
||||
const gm = GameManager.instance;
|
||||
if (!gm || !this.committedCell) return;
|
||||
const config = gm.getCurLevel();
|
||||
const theme = config?.theme ?? gm.uiStyle;
|
||||
const pos = entityWorldPositionForRole(
|
||||
this.committedCell,
|
||||
config ?? undefined,
|
||||
theme,
|
||||
this.moverRole,
|
||||
);
|
||||
this.node.setPosition(pos);
|
||||
this.targetPosition.set(pos);
|
||||
}
|
||||
|
||||
/** 主题 entityDisplay 变更后重算站立 Y(缩放刷新不会自动更新节点坐标) */
|
||||
reapplyCellStandPosition() {
|
||||
this.snapMoverToCellStand();
|
||||
}
|
||||
|
||||
resetMoveRuntime() {
|
||||
this.moveState = MoveState.Idle;
|
||||
this.moveWait = false;
|
||||
this.step = 0;
|
||||
this.queue = Promise.resolve();
|
||||
}
|
||||
|
||||
update(dt: number) {
|
||||
if (this.moveState !== MoveState.Moving) return;
|
||||
const pos = this.node.position;
|
||||
const next = new Vec3();
|
||||
Vec3.moveTowards(next, pos, this.targetPosition, this.moveSpeed * dt);
|
||||
const speedMul = GameManager.instance?.getGameSpeed() ?? Movement.speedMultiplier;
|
||||
Vec3.moveTowards(next, pos, this.targetPosition, this.moveSpeed * dt * speedMul);
|
||||
this.node.setPosition(next);
|
||||
this.onMoving();
|
||||
if (Vec3.distance(next, this.targetPosition) < 0.01) {
|
||||
this.syncRideAfterMoveStep();
|
||||
if (Vec3.distance(next, this.targetPosition) < 0.005) {
|
||||
this.node.setPosition(this.targetPosition);
|
||||
this.moveState = MoveState.Idle;
|
||||
this.moveWait = false;
|
||||
@@ -57,47 +225,70 @@ export class Movement extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
protected syncRideAfterMoveStep() {}
|
||||
|
||||
setDirection(dir: Direction) {
|
||||
this.direction = dir;
|
||||
}
|
||||
|
||||
protected onMoving() {}
|
||||
protected onMoveToTarget() {}
|
||||
protected onMoveToTarget() {
|
||||
this.commitLandingCell();
|
||||
}
|
||||
/** 转向结束(Unity RotateCoroutine 只改朝向,不重新 snap 落点) */
|
||||
protected onRotateComplete() {}
|
||||
protected onMoveNextSet(_isJump: boolean) {}
|
||||
protected onMoveFail(_isJump: boolean) {}
|
||||
protected playMoveAnim() {}
|
||||
|
||||
/** 本步移动起点(子类可去掉骑乘视觉抬高等) */
|
||||
protected resolveMoveStepOrigin(_targetCell: Vec3, _landingGrid: GridType): Vec3 {
|
||||
return this.node.position.clone();
|
||||
}
|
||||
|
||||
/** 本步移动落点世界坐标 */
|
||||
protected resolveTargetWorldPosition(targetCell: Vec3, landingGrid: GridType): Vec3 {
|
||||
const gm = GameManager.instance!;
|
||||
const config = gm.getCurLevel();
|
||||
const theme = config?.theme ?? gm.uiStyle;
|
||||
return entityWorldPositionForRole(
|
||||
targetCell,
|
||||
config ?? undefined,
|
||||
theme,
|
||||
this.moverRole,
|
||||
);
|
||||
}
|
||||
|
||||
protected extraMoveStepCheck(_isJump: boolean, _toFront: boolean): MoveCheckResult | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
private moveNextCheck(isJump: boolean, toFront: boolean): number {
|
||||
const gm = GameManager.instance!;
|
||||
const mult = gm.isMultMode();
|
||||
const dir = toFront ? this.direction : addDirection(this.direction, 2);
|
||||
const targetTmp = gm.nextGridPosition(this.node.position, dir);
|
||||
const targetType = gm.calculateGridType(targetTmp);
|
||||
const table = getMoveCondition(gm.multMode);
|
||||
const nextG = toFront ? this.nextGrid : this.lastGrid;
|
||||
const v = lookupMove(table, this.moverRole, this.curGrid, nextG, isJump);
|
||||
if (v !== undefined) {
|
||||
if (v === 1) {
|
||||
if (gm.multMode) {
|
||||
const nextCell = gm.worldToCell(targetTmp);
|
||||
for (const n of ['PlayerA1', 'PlayerA2', 'PlayerA3', 'PlayerB1', 'PlayerB2', 'PlayerB3']) {
|
||||
const p = gm.findNodeByName(n);
|
||||
if (p) {
|
||||
const c = gm.worldToCell(p.worldPosition);
|
||||
if (c.x === nextCell.x && c.y === nextCell.y) return 0;
|
||||
}
|
||||
}
|
||||
if (nextG === GridType.Ride) {
|
||||
const ride = gm.getGameObject(targetTmp);
|
||||
const expect = this.node.name.replace('Player', 'Vehicle');
|
||||
if (ride && ride.name !== expect) return 0;
|
||||
}
|
||||
}
|
||||
this.targetPosition.set(targetTmp);
|
||||
this.targetGridType = targetType;
|
||||
}
|
||||
return v;
|
||||
const blocked = this.extraMoveStepCheck(isJump, toFront);
|
||||
if (blocked !== null) return blocked;
|
||||
const r = checkMoveStep(this.moverRole, this.curGrid, nextG, isJump, mult);
|
||||
if (r !== 1) return r;
|
||||
|
||||
const targetTmp = gm.nextGridPosition(this.getGridSamplePosition(), dir);
|
||||
const targetCell = gm.worldToCell(targetTmp);
|
||||
if (mult && nextG === GridType.Ride) {
|
||||
const rideNode = gm.getGameObjectAtCell(targetCell);
|
||||
const expectedVehicle = this.node.name.replace('Player', 'Vehicle');
|
||||
if (rideNode && rideNode.name !== expectedVehicle) return 0;
|
||||
}
|
||||
return gm.multMode ? 0 : -1;
|
||||
if (!this.landingCell) this.landingCell = new Vec3();
|
||||
this.landingCell.set(targetCell);
|
||||
|
||||
const landingGrid = gm.calculateGridTypeAtCell(targetCell);
|
||||
const targetPos = this.resolveTargetWorldPosition(targetCell, landingGrid);
|
||||
this.targetGridType = landingGrid;
|
||||
this.moveStepFrom.set(this.resolveMoveStepOrigin(targetCell, landingGrid));
|
||||
this.targetPosition.set(targetPos);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private enqueue(fn: () => Promise<void>) {
|
||||
@@ -128,13 +319,18 @@ export class Movement extends Component {
|
||||
}
|
||||
if (r === 1) {
|
||||
this.moveWait = true;
|
||||
this.lastPosition.set(this.node.position);
|
||||
this.lastPosition.set(this.getGridSamplePosition());
|
||||
this.moveState = MoveState.Moving;
|
||||
this.onMoveNextSet(isJump);
|
||||
await this.waitUntil(() => !this.moveWait);
|
||||
} else {
|
||||
this.playMoveAnim();
|
||||
this.moveState = MoveState.Moving;
|
||||
// 本步不可走但步数已消耗:须回传 processData / processVehicleData
|
||||
if (this.step === 0) {
|
||||
this.moveState = MoveState.Idle;
|
||||
this.onMoveToTarget();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,14 +340,20 @@ export class Movement extends Component {
|
||||
this.moveWait = true;
|
||||
this.setDirection(addDirection(this.direction, n));
|
||||
this.moveWait = false;
|
||||
this.onRotateComplete();
|
||||
// 对齐 Unity RotateCoroutine → OnMoveToTarget → ExternalCall(Scratch/Python 等待 processData)
|
||||
this.onMoveToTarget();
|
||||
}
|
||||
|
||||
protected jsCallCheck(n: number): boolean {
|
||||
const gm = GameManager.instance!;
|
||||
const name = this.node.name;
|
||||
if (name === 'Player' || name === 'Vehicle') return gm.jsCallCheck(n);
|
||||
if (gm.multMode) return gm.jsCallCheckMultMode(n, name);
|
||||
if (
|
||||
name === 'Player' || name === 'Vehicle'
|
||||
|| /^Player[AB]\d$/.test(name) || /^Vehicle[AB]\d$/.test(name)
|
||||
) {
|
||||
return gm.jsCallCheck(n);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user