187 lines
5.8 KiB
TypeScript
187 lines
5.8 KiB
TypeScript
/**
|
||
* 等距绘制排序(重设计)
|
||
*
|
||
* 单一全序标量,无成环、无特判互打:
|
||
* frontKey = -(cellX + cellY) * CELL // 更小 x+y → 屏幕更靠下 → 更靠前
|
||
* + typeRank // 同对角条带内:地板 < 金币 < 载具 < 角色 < 墙
|
||
*
|
||
* 移动中采样拆开:
|
||
* - 角色:朝前落点 / 朝后起点
|
||
* - 载具:朝前起点(防浮到前砖上)/ 朝后落点(防途中比落点更靠前而不被下方砖挡)
|
||
* 贴墙南/西立面格上的实体:抬到该墙之上(露脚)。
|
||
* 角色永远盖过同条带载具。
|
||
*/
|
||
|
||
import { Vec3 } from 'cc';
|
||
import { CommonDefine } from '../core/Define';
|
||
import { cellToWorldCenter } from '../core/GridCoords';
|
||
|
||
export const UNITY_TRANSPARENCY_AXIS = { x: 0, y: 1, z: -0.26 } as const;
|
||
|
||
export const UNITY_SORTING_ORDER = {
|
||
nProp: 1,
|
||
tilemap: 2,
|
||
vehicle: 2,
|
||
prop: 3,
|
||
player: 4,
|
||
} as const;
|
||
|
||
const CELL = 100;
|
||
/** 同对角条带内层级:地板 < 金币 < 载具 < 角色 < 墙立面 */
|
||
const RANK_FLOOR = 0;
|
||
const RANK_PICKABLE = 20;
|
||
const RANK_VEHICLE = 30;
|
||
const RANK_PLAYER = 35;
|
||
const RANK_WALL = 40;
|
||
|
||
export function isWallBlockTileName(tileName: string): boolean {
|
||
return tileName === CommonDefine.BlockWall;
|
||
}
|
||
|
||
export function isWalkableTileName(tileName: string): boolean {
|
||
return tileName === CommonDefine.BlockBase || tileName === CommonDefine.BlockJump;
|
||
}
|
||
|
||
export function unityTileIndividualSortingOrder(cellX: number, cellY: number): number {
|
||
return UNITY_SORTING_ORDER.tilemap - cellX - cellY;
|
||
}
|
||
|
||
export function unityTransparencyDepth(worldX: number, worldY: number, worldZ = 0): number {
|
||
return worldX * UNITY_TRANSPARENCY_AXIS.x
|
||
+ worldY * UNITY_TRANSPARENCY_AXIS.y
|
||
+ worldZ * UNITY_TRANSPARENCY_AXIS.z;
|
||
}
|
||
|
||
export function unityCompositeSortKey(
|
||
sortingOrder: number,
|
||
depthWorldY: number,
|
||
depthWorldZ = 0,
|
||
bias = 0,
|
||
): number {
|
||
const depth = unityTransparencyDepth(0, depthWorldY, depthWorldZ);
|
||
return sortingOrder * 10_000 + depth * 100 + bias;
|
||
}
|
||
|
||
export function unityCellCenterY(cellX: number, cellY: number): number {
|
||
return cellToWorldCenter(new Vec3(cellX, cellY, 0)).y;
|
||
}
|
||
|
||
export interface UnitySortableTile {
|
||
cellX: number;
|
||
cellY: number;
|
||
tileName: string;
|
||
centerY: number;
|
||
depthY: number;
|
||
}
|
||
|
||
export interface UnitySortableEntity {
|
||
sortingOrder: number;
|
||
cellX: number;
|
||
cellY: number;
|
||
centerY: number;
|
||
depthY: number;
|
||
isPlayer: boolean;
|
||
isPickable: boolean;
|
||
}
|
||
|
||
/** 返回值 > 0 表示 (ax,ay) 应排在 (bx,by) 之后(更靠前) */
|
||
export function compareIsoDrawOrder(ax: number, ay: number, bx: number, by: number): number {
|
||
const ka = ax + ay;
|
||
const kb = bx + by;
|
||
if (ka !== kb) return kb - ka;
|
||
return ax - bx;
|
||
}
|
||
|
||
export function compareTileCellDepth(ax: number, ay: number, bx: number, by: number): number {
|
||
return compareIsoDrawOrder(ax, ay, bx, by);
|
||
}
|
||
|
||
function wallFaceSamples(wallX: number, wallY: number): { x: number; y: number }[] {
|
||
return [{ x: wallX, y: wallY - 1 }, { x: wallX - 1, y: wallY }];
|
||
}
|
||
|
||
function typeRankForTile(tileName: string): number {
|
||
return isWallBlockTileName(tileName) ? RANK_WALL : RANK_FLOOR;
|
||
}
|
||
|
||
function typeRankForEntity(entity: UnitySortableEntity): number {
|
||
if (entity.isPickable) return RANK_PICKABLE;
|
||
if (entity.isPlayer) return RANK_PLAYER;
|
||
return RANK_VEHICLE;
|
||
}
|
||
|
||
/** 越大越靠前(后绘制) */
|
||
export function isoDrawFrontKey(cellX: number, cellY: number, typeRank: number): number {
|
||
return -(cellX + cellY) * CELL + typeRank;
|
||
}
|
||
|
||
export function tileDrawFrontKey(tile: UnitySortableTile): number {
|
||
return isoDrawFrontKey(tile.cellX, tile.cellY, typeRankForTile(tile.tileName));
|
||
}
|
||
|
||
export function entityDrawFrontKey(entity: UnitySortableEntity): number {
|
||
return isoDrawFrontKey(entity.cellX, entity.cellY, typeRankForEntity(entity));
|
||
}
|
||
|
||
/**
|
||
* 若实体踩在某墙南/西立面上,应露在该墙前面。
|
||
* 返回需要超过的墙 frontKey,否则 null。
|
||
*/
|
||
export function entityFaceLiftOverWall(
|
||
entity: UnitySortableEntity,
|
||
tile: UnitySortableTile,
|
||
): number | null {
|
||
if (!isWallBlockTileName(tile.tileName)) return null;
|
||
const cell = { x: entity.cellX, y: entity.cellY };
|
||
for (const face of wallFaceSamples(tile.cellX, tile.cellY)) {
|
||
if (face.x === cell.x && face.y === cell.y) {
|
||
return tileDrawFrontKey(tile);
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/** 实体相对整张关卡的 frontKey(含贴墙抬升) */
|
||
export function entityDrawFrontKeyWithWalls(
|
||
entity: UnitySortableEntity,
|
||
walls: UnitySortableTile[],
|
||
): number {
|
||
let key = entityDrawFrontKey(entity);
|
||
for (const w of walls) {
|
||
const lift = entityFaceLiftOverWall(entity, w);
|
||
if (lift != null && key <= lift) key = lift + 1;
|
||
}
|
||
return key;
|
||
}
|
||
|
||
export function compareUnityTileToTile(a: UnitySortableTile, b: UnitySortableTile): number {
|
||
const d = tileDrawFrontKey(a) - tileDrawFrontKey(b);
|
||
if (d !== 0) return d;
|
||
return a.cellX - b.cellX;
|
||
}
|
||
|
||
export function compareUnityEntityToEntity(a: UnitySortableEntity, b: UnitySortableEntity): number {
|
||
const d = entityDrawFrontKey(a) - entityDrawFrontKey(b);
|
||
if (d !== 0) return d;
|
||
if (a.isPlayer && !b.isPlayer) return 1;
|
||
if (b.isPlayer && !a.isPlayer) return -1;
|
||
return a.sortingOrder - b.sortingOrder;
|
||
}
|
||
|
||
/**
|
||
* 实体 vs 砖:统一用 frontKey。
|
||
* (保留函数供外部/测试调用;排序主路径用 build 里的 key)
|
||
*/
|
||
export function compareUnityEntityToTile(
|
||
entity: UnitySortableEntity,
|
||
tile: UnitySortableTile,
|
||
): number {
|
||
if (!isWallBlockTileName(tile.tileName)) {
|
||
// 地板永在实体下
|
||
return 1;
|
||
}
|
||
const lift = entityFaceLiftOverWall(entity, tile);
|
||
const eKey = lift != null ? lift + 1 : entityDrawFrontKey(entity);
|
||
return eKey - tileDrawFrontKey(tile);
|
||
}
|