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,157 @@
import { Size, SpriteFrame } from 'cc';
import { CELL_PIXEL } from '../core/GridConstants';
import { getThemeEntityDisplayCellBoxes } from '../theme/ThemeDatabase';
/** 内置默认themes-database 未加载时使用) */
export const DEFAULT_ENTITY_CELL_BOX = {
player: { w: 0.68, h: 0.9 },
vehicle: { w: 0.96, h: 0.88 },
prop: { w: 0.52, h: 0.69 },
propGround: { w: 0.52, h: 0.69 },
} as const;
/** 装饰物相对可拾取物的缩放比 */
const PROP_DECOR_RATIO = 0.45 / 0.69;
const cellW = (ratio: number) => CELL_PIXEL * ratio;
const cellH = (ratio: number) => CELL_PIXEL * ratio;
export function getEntityCellBox(theme?: string) {
if (theme) return getThemeEntityDisplayCellBoxes(theme);
return { ...DEFAULT_ENTITY_CELL_BOX };
}
export const DEFAULT_PLAYER_ANCHOR_Y = 0.35;
/** Unity silu ship pivot y = 0.45 */
export const DEFAULT_VEHICLE_ANCHOR_Y = 0.45;
export function getEntityDisplaySizes(theme?: string) {
const box = getEntityCellBox(theme);
return {
player: {
width: cellW(box.player.w),
height: cellH(box.player.h),
},
vehicle: {
width: cellW(box.vehicle.w),
height: cellH(box.vehicle.h),
},
prop: {
width: cellW(box.prop.w),
height: cellH(box.prop.h),
},
propGround: {
width: cellW(box.propGround.w),
height: cellH(box.propGround.h),
},
prop_decor: {
width: cellW(box.prop.w) * PROP_DECOR_RATIO,
height: cellH(box.prop.h) * PROP_DECOR_RATIO,
},
};
}
/** @deprecated 使用 getEntityDisplaySizes(theme) */
export const ENTITY_CELL_BOX = DEFAULT_ENTITY_CELL_BOX;
/** @deprecated 使用 getEntityDisplaySizes(theme) */
export const SANXING_ENTITY_DISPLAY = {
get player() { return getEntityDisplaySizes().player; },
get vehicle() { return getEntityDisplaySizes().vehicle; },
get prop() { return getEntityDisplaySizes().prop; },
get prop_decor() { return getEntityDisplaySizes().prop_decor; },
};
export type EntityDisplayKind = 'player' | 'vehicle' | 'prop' | 'propGround' | 'prop_decor';
type SpawnKindLike = 'player' | 'vehicle' | 'prop' | 'prop_decor';
export function entityPlaceholderSize(kind: SpawnKindLike, theme?: string): { width: number; height: number } {
const sizes = getEntityDisplaySizes(theme);
if (kind === 'prop_decor') return sizes.prop_decor;
if (kind === 'prop') return sizes.prop;
if (kind === 'vehicle') return sizes.vehicle;
return sizes.player;
}
export function spriteOriginalSize(sf: SpriteFrame): { width: number; height: number } {
const rect = sf.rect;
if (rect.width > 0 && rect.height > 0) {
return { width: rect.width, height: rect.height };
}
let w = sf.originalSize?.width ?? 0;
let h = sf.originalSize?.height ?? 0;
if (w <= 0 || h <= 0) {
w = sf.texture?.width ?? 1;
h = sf.texture?.height ?? 1;
}
return { width: Math.max(1, w), height: Math.max(1, h) };
}
/** 等比缩放完整落入主题包围盒contain */
export function fitEntityDisplaySize(
kind: EntityDisplayKind,
sf: SpriteFrame,
scaleMul = 1,
theme?: string,
): { width: number; height: number; anchorX: number; anchorY: number } {
const s = computeEntityUniformScale(kind, sf, scaleMul, theme);
return sizeFromUniformScale(sf, s, kind, theme);
}
/** 单帧 contain 缩放系数(序列动画各帧共用,避免画布尺寸不同导致比例跳变) */
export function computeEntityUniformScale(
kind: EntityDisplayKind,
sf: SpriteFrame,
scaleMul = 1,
theme?: string,
): number {
const mul = Math.max(0.1, Math.min(4, scaleMul));
const { width: ow, height: oh } = spriteOriginalSize(sf);
const ref = getEntityDisplaySizes(theme)[kind];
return Math.min(ref.width * mul / ow, ref.height * mul / oh);
}
export function sizeFromUniformScale(
sf: SpriteFrame,
uniformScale: number,
kind: EntityDisplayKind = 'player',
theme?: string,
): { width: number; height: number; anchorX: number; anchorY: number } {
const { width: ow, height: oh } = spriteOriginalSize(sf);
let anchorY = 0;
if (kind === 'player') {
anchorY = DEFAULT_PLAYER_ANCHOR_Y;
} else if (kind === 'vehicle') {
anchorY = DEFAULT_VEHICLE_ANCHOR_Y;
} else if (kind !== 'prop' && kind !== 'propGround' && kind !== 'prop_decor') {
anchorY = DEFAULT_PLAYER_ANCHOR_Y;
}
return {
width: ow * uniformScale,
height: oh * uniformScale,
anchorX: 0.5,
anchorY,
};
}
/**
* 序列帧脚点对齐:各帧保留原始比例,仅调 anchorY。
* 脚离画布底边像素一致时anchorY = baseAnchorY * refOh / oh
*/
export function playerSeqFrameAnchorY(
sf: SpriteFrame,
refSf: SpriteFrame,
baseAnchorY = DEFAULT_PLAYER_ANCHOR_Y,
): number {
const oh = Math.max(1, spriteOriginalSize(sf).height);
const refOh = Math.max(1, spriteOriginalSize(refSf).height);
if (oh === refOh) return baseAnchorY;
return baseAnchorY * refOh / oh;
}
/** 动态 SpriteFrame 补全 originalSizeImageAsset 直载时 Cocos 默认为 0 */
export function ensureSpriteFrameSize(sf: SpriteFrame, width: number, height: number) {
if (sf.originalSize.width > 0 && sf.originalSize.height > 0) return;
sf.originalSize = new Size(Math.max(1, width), Math.max(1, height));
}