Files
cocos/tools/verify-unity-draw-sort.mjs
2026-07-06 09:43:46 +08:00

168 lines
5.8 KiB
JavaScript
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.
#!/usr/bin/env node
/**
* 自测 Unity 绘制排序(与 UnityDrawSort.ts 对齐)
* 运行: node tools/verify-unity-draw-sort.mjs
*/
const UNITY_SORTING_ORDER = { nProp: 1, tilemap: 2, vehicle: 2, prop: 3, player: 4 };
const ORDER_SCALE = 10_000;
const DEPTH_SCALE = 100;
const TILE_ELEVATION_Y_THRESHOLD = 12;
const HALF_W = 50;
const HALF_H = 25;
function cellToWorldCenter(x, y) {
const wx = (x - y) * HALF_W;
const wy = (x + y) * HALF_H + HALF_H;
return { x: wx, y: wy };
}
function unityTileIndividualSortingOrder(cellX, cellY) {
return UNITY_SORTING_ORDER.tilemap - cellX - cellY;
}
function compareTileCellDepth(ax, ay, bx, by) {
if (ay !== by) return by - ay;
return bx - ax;
}
function compareUnityTileToTile(a, b) {
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);
}
function tileInFrontOfTile(front, back, depthFront = 0, depthBack = 0) {
const a = { cellX: front.x, cellY: front.y, depthY: depthFront };
const b = { cellX: back.x, cellY: back.y, depthY: depthBack };
return compareUnityTileToTile(a, b) > 0;
}
function compareIsoDrawOrder(ax, ay, bx, by) {
const ka = ax + ay;
const kb = bx + by;
if (ka !== kb) return kb - ka;
return ax - bx;
}
function wallFaceSamples(wallX, wallY) {
return [{ x: wallX, y: wallY - 1 }, { x: wallX - 1, y: wallY }];
}
function compareWallActorIso(actor, wallX, wallY) {
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;
}
function compareUnityEntityToWall(entity, tile) {
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);
}
function compareUnityEntityToTile(entity, tile) {
if (tile.tileName !== 'WallBlock') return 1;
return compareUnityEntityToWall(entity, tile);
}
function entityInFrontOfTile(entity, tile) {
return compareUnityEntityToTile(entity, tile) > 0;
}
function walkableUnderPlayer(playerCell, tileCell) {
return entityInFrontOfTile(
{ sortingOrder: 4, cellX: playerCell.x, cellY: playerCell.y, centerY: 0, depthY: 0 },
{ cellX: tileCell.x, cellY: tileCell.y, tileName: 'Baseblock', centerY: 0, depthY: 0 },
);
}
function playerBottomY(cellX, cellY) {
return cellToWorldCenter(cellX, cellY).y - 0.35 * 90;
}
function wallFrontY(cellX, cellY) {
const c = cellToWorldCenter(cellX, cellY);
return Math.max(c.y, c.y + (1 - 0.67) * 115);
}
const cases = [
{
name: '砖↔砖:斜向路径 (0,-2)→(0,-1)→(0,0)→(1,0) 下方逐格遮挡',
pass: tileInFrontOfTile({ x: 0, y: -2 }, { x: 0, y: -1 })
&& tileInFrontOfTile({ x: 0, y: -1 }, { x: 0, y: 0 })
&& tileInFrontOfTile({ x: 0, y: 0 }, { x: 1, y: 0 }),
},
{
name: '砖↔砖:对角路径 (0,-3)→(1,-2)→(2,-1)→(3,0) 下方逐格遮挡',
pass: tileInFrontOfTile({ x: 0, y: -3 }, { x: 1, y: -2 })
&& tileInFrontOfTile({ x: 1, y: -2 }, { x: 2, y: -1 })
&& tileInFrontOfTile({ x: 2, y: -1 }, { x: 3, y: 0 }),
},
{
name: '砖↔砖:南侧 (0,-1) 挡住北侧 (0,0)',
pass: tileInFrontOfTile({ x: 0, y: -1 }, { x: 0, y: 0 }),
},
{
name: '砖↔砖:东南 (2,-2) 挡住西北 (0,-1)',
pass: tileInFrontOfTile({ x: 2, y: -2 }, { x: 0, y: -1 }),
},
{
name: '91614 出生 (0,-1) 不被北侧墙 (0,0) 挡住',
pass: entityInFrontOfTile(
{ sortingOrder: 4, cellX: 0, cellY: -1, centerY: 0, depthY: 0 },
{ cellX: 0, cellY: 0, tileName: 'WallBlock', centerY: 0, depthY: 0 },
),
},
{
name: '跨格路径砖:南侧 (0,-2) 不挡北侧角色 (0,-1)',
pass: entityInFrontOfTile(
{ sortingOrder: 4, cellX: 0, cellY: -1, centerY: 0, depthY: 0 },
{ cellX: 0, cellY: -2, tileName: 'Baseblock', centerY: 0, depthY: 100 },
),
},
{
name: '91614 真实Y出生 (0,-1) 在 (0,0) 墙前',
pass: entityInFrontOfTile(
{ sortingOrder: 4, cellX: 0, cellY: -1, centerY: 0, depthY: playerBottomY(0, -1) },
{ cellX: 0, cellY: 0, tileName: 'WallBlock', centerY: 0, depthY: wallFrontY(0, 0) },
),
},
];
let failed = 0;
for (const c of cases) {
const ok = !!c.pass;
console.log(`${ok ? '✓' : '✗'} ${c.name}`);
if (!ok) failed++;
}
if (failed > 0) {
console.error(`\n${failed} case(s) failed`);
process.exit(1);
}
console.log(`\nAll ${cases.length} cases passed.`);