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:
2026-06-16 15:30:58 +08:00
parent cba5105908
commit d393302388
6248 changed files with 17322729 additions and 11036 deletions

View File

@@ -0,0 +1,185 @@
import { Vec3 } from 'cc';
import { CELL_PIXEL } from '../core/GridConstants';
import { CommonDefine, MoverRole } from '../core/Define';
import { cellToWorldCenter, worldCenterToCell } from '../core/GridCoords';
import {
getThemePropPlacementOffsets,
getThemeMoverEmptyCellYOffset,
getThemeMoverJumpCellYOffset,
getThemePlayerStandYOffset,
DEFAULT_PROP_BLOCK_Y_OFFSET,
DEFAULT_PROP_GROUND_Y_OFFSET,
} from '../theme/ThemeDatabase';
import { getTilePivot } from '../visual/TilePivots';
import { getTileDrawSize } from '../visual/TileSizes';
import { LevelConfig, SpawnConfig } from './LevelTypes';
/** 与 Unity Prop / nProp 一致:砖块上偏高,空地偏低 */
export type PropPlacement = 'block' | 'ground';
const HALF_H = CELL_PIXEL * 0.25;
/** Unity checkPoint 偏移PPU≈100Prop ≈ +14pxnProp ≈ -11px */
export const PROP_BLOCK_Y_OFFSET = DEFAULT_PROP_BLOCK_Y_OFFSET;
export const PROP_GROUND_Y_OFFSET = DEFAULT_PROP_GROUND_Y_OFFSET;
export function cellHasTile(cellX: number, cellY: number, config: LevelConfig | undefined): boolean {
if (!config) return false;
const key = `${cellX},${cellY}`;
const hasGround = !!config.ground?.[key];
const hasBorder = config.border?.[key] !== undefined && config.border?.[key] !== false;
return hasGround || hasBorder;
}
/** 砖块 pivot 对齐格心后,行走面相对格心的抬高量(与 Baseblock 差值用于 JumpBlock */
function tileWalkSurfaceAboveCenter(tileName: string, theme?: string): number {
const draw = getTileDrawSize(tileName, undefined, undefined, theme);
const pivot = getTilePivot(tileName, theme);
return draw.height * (1 - pivot.y);
}
/** JumpBlock 比 Baseblock 更高的行走面补偿px */
export function resolveMoverJumpCellYOffset(
cellX: number,
cellY: number,
config: LevelConfig | undefined,
theme?: string,
): number {
const key = `${cellX},${cellY}`;
const tile = config?.ground?.[key];
if (tile !== CommonDefine.BlockJump) return 0;
const auto = tileWalkSurfaceAboveCenter(CommonDefine.BlockJump, theme)
- tileWalkSurfaceAboveCenter(CommonDefine.BlockBase, theme);
return auto + getThemeMoverJumpCellYOffset(theme);
}
/** 角色/载具JumpBlock 抬高;有普通砖为 0空地/载具格补齐 Baseblock 高度 */
export function resolveMoverCellStandYOffset(
cellX: number,
cellY: number,
config: LevelConfig | undefined,
theme?: string,
): number {
if (!cellHasTile(cellX, cellY, config)) {
return getThemeMoverEmptyCellYOffset(theme);
}
return resolveMoverJumpCellYOffset(cellX, cellY, config, theme);
}
export function entityMoverWorldPosition(
cell: Vec3,
config: LevelConfig | undefined,
theme?: string,
): Vec3 {
const pos = cellToWorldCenter(cell);
pos.y += resolveMoverCellStandYOffset(cell.x, cell.y, config, theme);
return pos;
}
/**
* 从带站立/空地 Y 微调的世界坐标反推逻辑格(与 entityMoverWorldPosition 互逆)。
* 视觉 Y 会干扰等距 worldToCell移动采样须先还原到格心平面。
*/
export function worldToMoverCell(
world: Vec3,
config: LevelConfig | undefined,
theme?: string,
forPlayer = false,
): Vec3 {
let cell = worldCenterToCell(world);
for (let i = 0; i < 4; i++) {
const off = resolveMoverCellStandYOffset(cell.x, cell.y, config, theme);
const stand = forPlayer ? getThemePlayerStandYOffset(theme) : 0;
const adjustedY = world.y - stand - off;
const next = worldCenterToCell(new Vec3(world.x, adjustedY, world.z));
if (next.x === cell.x && next.y === cell.y) break;
cell = next;
}
return cell;
}
/**
* 移动/格子判定用的逻辑采样点(格心,不含主题站立 Y
* @param levelLocal 关卡根节点本地坐标(与砖块 alignTileNode 同一空间)
*/
export function moverLogicalGridPosition(
levelLocal: Vec3,
config: LevelConfig | undefined,
theme?: string,
forPlayer = false,
): Vec3 {
return cellToWorldCenter(worldToMoverCell(levelLocal, config, theme, forPlayer));
}
/** 角色生成/移动位置(含主题站立 Y 微调) */
export function entityPlayerWorldPosition(
cell: Vec3,
config: LevelConfig | undefined,
theme?: string,
): Vec3 {
const pos = entityMoverWorldPosition(cell, config, theme);
pos.y += getThemePlayerStandYOffset(theme);
return pos;
}
/** 角色/载具是否按玩家站立 Y 反推逻辑格(与 worldToMoverCell / entityPlayerWorldPosition 一致) */
export function roleUsesPlayerStandOffset(role: MoverRole): boolean {
return role === 'player';
}
export function worldToMoverCellForRole(
world: Vec3,
config: LevelConfig | undefined,
theme: string | undefined,
role: MoverRole,
): Vec3 {
return worldToMoverCell(world, config, theme, roleUsesPlayerStandOffset(role));
}
export function moverLogicalGridPositionForRole(
levelLocal: Vec3,
config: LevelConfig | undefined,
theme: string | undefined,
role: MoverRole,
): Vec3 {
return moverLogicalGridPosition(
levelLocal,
config,
theme,
roleUsesPlayerStandOffset(role),
);
}
/** 逻辑格 → 世界站立点player 含主题站立 Yvehicle 为 mover 甲板) */
export function entityWorldPositionForRole(
cell: Vec3,
config: LevelConfig | undefined,
theme: string | undefined,
role: MoverRole,
): Vec3 {
return roleUsesPlayerStandOffset(role)
? entityPlayerWorldPosition(cell, config, theme)
: entityMoverWorldPosition(cell, config, theme);
}
export function propWorldYOffset(placement: PropPlacement, theme?: string): number {
const offsets = getThemePropPlacementOffsets(theme);
return placement === 'ground' ? offsets.ground : offsets.block;
}
/** 无 ground/border 视为空地nProp有砖块视为 Prop */
export function resolvePropPlacement(spawn: SpawnConfig, config: LevelConfig): PropPlacement {
const key = `${spawn.x},${spawn.y}`;
const hasGround = !!config.ground?.[key];
const hasBorder = config.border?.[key] !== undefined && config.border?.[key] !== false;
if (spawn.propPlacement === 'ground' || spawn.propPlacement === 'block') {
if (!hasGround && !hasBorder) return 'ground';
return spawn.propPlacement;
}
if (!hasGround && !hasBorder) return 'ground';
return 'block';
}
export function isGroundPropSpawn(spawn: SpawnConfig, config: LevelConfig): boolean {
return spawn.kind === 'prop' && resolvePropPlacement(spawn, config) === 'ground';
}