Files
cocos/assets/scripts/level/UnityDrawSort.ts
2026-07-06 09:43:46 +08:00

183 lines
6.1 KiB
TypeScript
Raw 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.
/**
* Unity 2D 绘制排序(对齐主站 GraphicsSettings + 预制体 SortingOrder + TilemapRenderer Individual
*
* - GraphicsSettings: TransparencySortMode=CustomAxis, axis=(0, 1, -0.26)
* - TilemapRenderer: SortingOrder=2, Mode=Individual, SortOrder=TopRight → 每格 -(cellX+cellY)
* - nProp=1, Tilemap/Vehicle=2, Prop=3, Player=4
* - 仅贴图 WallBlock 可遮挡实体;可行走砖永在实体之下
* - 砖↔砖:格子深度(南 y 小 → 前,同 y 东 x 大 → 前)→ JumpBlock 抬高 Y → Individual order
*/
import { Vec3 } from 'cc';
import { CommonDefine } from '../core/Define';
import { cellToWorldCenter } from '../core/GridCoords';
/** Unity GraphicsSettings.asset m_TransparencySortAxis */
export const UNITY_TRANSPARENCY_AXIS = { x: 0, y: 1, z: -0.26 } as const;
/** Unity SpriteRenderer / TilemapRenderer m_SortingOrder */
export const UNITY_SORTING_ORDER = {
nProp: 1,
tilemap: 2,
vehicle: 2,
prop: 3,
player: 4,
} as const;
const ORDER_SCALE = 10_000;
const DEPTH_SCALE = 100;
/** JumpBlock 行走面比 Baseblock 抬高的最小 Y 差px超过则优先比 depthY */
const TILE_ELEVATION_Y_THRESHOLD = 12;
export function isWallBlockTileName(tileName: string): boolean {
return tileName === CommonDefine.BlockWall;
}
export function isWalkableTileName(tileName: string): boolean {
return tileName === CommonDefine.BlockBase || tileName === CommonDefine.BlockJump;
}
/** Unity TilemapRenderer Individual + TopRightbaseOrder - (cellX+cellY) */
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 * ORDER_SCALE + depth * DEPTH_SCALE + 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;
}
function wallFaceSamples(wallX: number, wallY: number): { x: number; y: number }[] {
return [{ x: wallX, y: wallY - 1 }, { x: wallX - 1, y: wallY }];
}
function compareWallActorIso(
actor: { x: number; y: number },
wallX: number,
wallY: number,
): number {
let actorAhead = 0;
for (const face of wallFaceSamples(wallX, wallY)) {
const iso = compareIsoDrawOrder(actor.x, actor.y, face.x, face.y);
if (iso < 0) return iso;
if (iso > actorAhead) actorAhead = iso;
}
return actorAhead;
}
/** 返回值 > 0 实体更靠前;< 0 墙应挡住 */
function compareUnityEntityToWall(
entity: UnitySortableEntity,
tile: UnitySortableTile,
): number {
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 1;
}
const tileOrder = unityTileIndividualSortingOrder(tile.cellX, tile.cellY);
if (tileOrder > entity.sortingOrder) return -1;
const wallIso = compareWallActorIso(cell, tile.cellX, tile.cellY);
if (wallIso < 0) {
if (tile.cellX === cell.x && tile.cellY === cell.y - 1) return -1;
if (tile.cellY < cell.y) return 1;
return -1;
}
const tileAhead = (tile.cellX + tile.cellY) - (cell.x + cell.y);
if (wallIso > 0 && tileAhead > 0) return -1;
if (wallIso > 0) return wallIso;
const yCmp = Math.round(entity.depthY * DEPTH_SCALE) - Math.round(tile.depthY * DEPTH_SCALE);
if (yCmp !== 0) return yCmp;
if (tileAhead !== 0) return -tileAhead;
return compareIsoDrawOrder(tile.cellX, tile.cellY, cell.x, cell.y);
}
/** 砖块格子深度南侧cellY 小更靠前同行西侧cellX 小)更靠前 */
export function compareTileCellDepth(ax: number, ay: number, bx: number, by: number): number {
if (ay !== by) return by - ay;
return bx - ax;
}
/**
* 砖↔砖格子深度优先下方砖挡上方砖JumpBlock 抬高再比 depthY。
*/
export function compareUnityTileToTile(a: UnitySortableTile, b: UnitySortableTile): number {
const cellCmp = compareTileCellDepth(a.cellX, a.cellY, b.cellX, b.cellY);
if (cellCmp !== 0) return cellCmp;
const yCmp = Math.round(a.depthY * DEPTH_SCALE) - Math.round(b.depthY * DEPTH_SCALE);
if (Math.abs(yCmp) >= TILE_ELEVATION_Y_THRESHOLD) return yCmp;
return unityTileIndividualSortingOrder(a.cellX, a.cellY)
- unityTileIndividualSortingOrder(b.cellX, b.cellY);
}
/**
* 实体 vs 砖块
* 非 WallBlock 永在实体之下;仅墙砖可遮挡。
*/
export function compareUnityEntityToTile(
entity: UnitySortableEntity,
tile: UnitySortableTile,
): number {
if (!isWallBlockTileName(tile.tileName)) {
return 1;
}
return compareUnityEntityToWall(entity, tile);
}
export function compareUnityEntityToEntity(a: UnitySortableEntity, b: UnitySortableEntity): number {
const keyA = unityCompositeSortKey(a.sortingOrder, a.depthY);
const keyB = unityCompositeSortKey(b.sortingOrder, b.depthY);
if (keyA !== keyB) return keyA - keyB;
const iso = (a.cellX + a.cellY) - (b.cellX + b.cellY);
if (iso !== 0) return iso;
if (a.isPlayer && !b.isPlayer) return 1;
if (b.isPlayer && !a.isPlayer) return -1;
return 0;
}