Files
cocos/assets/scripts/level/EntitySpawnPlacement.ts
刘宇飞 d393302388 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>
2026-06-16 15:30:58 +08:00

186 lines
6.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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';
}