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 补全 originalSize(ImageAsset 直载时 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)); }