refactor: reorganize package structure and decouple framework packages (#338)
* refactor: reorganize package structure and decouple framework packages ## Package Structure Reorganization - Reorganized 55 packages into categorized subdirectories: - packages/framework/ - Generic framework (Laya/Cocos compatible) - packages/engine/ - ESEngine core modules - packages/rendering/ - Rendering modules (WASM dependent) - packages/physics/ - Physics modules - packages/streaming/ - World streaming - packages/network-ext/ - Network extensions - packages/editor/ - Editor framework and plugins - packages/rust/ - Rust WASM engine - packages/tools/ - Build tools and SDK ## Framework Package Decoupling - Decoupled behavior-tree and blueprint packages from ESEngine dependencies - Created abstracted interfaces (IBTAssetManager, IBehaviorTreeAssetContent) - ESEngine-specific code moved to esengine/ subpath exports - Framework packages now usable with Cocos/Laya without ESEngine ## CI Configuration - Updated CI to only type-check and lint framework packages - Added type-check:framework and lint:framework scripts ## Breaking Changes - Package import paths changed due to directory reorganization - ESEngine integrations now use subpath imports (e.g., '@esengine/behavior-tree/esengine') * fix: update es-engine file path after directory reorganization * docs: update README to focus on framework over engine * ci: only build framework packages, remove Rust/WASM dependencies * fix: remove esengine subpath from behavior-tree and blueprint builds ESEngine integration code will only be available in full engine builds. Framework packages are now purely engine-agnostic. * fix: move network-protocols to framework, build both in CI * fix: update workflow paths from packages/core to packages/framework/core * fix: exclude esengine folder from type-check in behavior-tree and blueprint * fix: update network tsconfig references to new paths * fix: add test:ci:framework to only test framework packages in CI * fix: only build core and math npm packages in CI * fix: exclude test files from CodeQL and fix string escaping security issue
This commit is contained in:
364
packages/framework/pathfinding/src/grid/GridMap.ts
Normal file
364
packages/framework/pathfinding/src/grid/GridMap.ts
Normal file
@@ -0,0 +1,364 @@
|
||||
/**
|
||||
* @zh 网格地图实现
|
||||
* @en Grid Map Implementation
|
||||
*/
|
||||
|
||||
import type {
|
||||
IPathfindingMap,
|
||||
IPathNode,
|
||||
IPoint,
|
||||
HeuristicFunction
|
||||
} from '../core/IPathfinding';
|
||||
import { createPoint, octileDistance } from '../core/IPathfinding';
|
||||
|
||||
// =============================================================================
|
||||
// 网格节点 | Grid Node
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* @zh 网格节点
|
||||
* @en Grid node
|
||||
*/
|
||||
export class GridNode implements IPathNode {
|
||||
readonly id: string;
|
||||
readonly position: IPoint;
|
||||
readonly x: number;
|
||||
readonly y: number;
|
||||
cost: number;
|
||||
walkable: boolean;
|
||||
|
||||
constructor(x: number, y: number, walkable: boolean = true, cost: number = 1) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.id = `${x},${y}`;
|
||||
this.position = createPoint(x, y);
|
||||
this.walkable = walkable;
|
||||
this.cost = cost;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 移动方向 | Movement Directions
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* @zh 4方向偏移 (上下左右)
|
||||
* @en 4-directional offsets (up, down, left, right)
|
||||
*/
|
||||
export const DIRECTIONS_4 = [
|
||||
{ dx: 0, dy: -1 }, // Up
|
||||
{ dx: 1, dy: 0 }, // Right
|
||||
{ dx: 0, dy: 1 }, // Down
|
||||
{ dx: -1, dy: 0 } // Left
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* @zh 8方向偏移 (含对角线)
|
||||
* @en 8-directional offsets (including diagonals)
|
||||
*/
|
||||
export const DIRECTIONS_8 = [
|
||||
{ dx: 0, dy: -1 }, // Up
|
||||
{ dx: 1, dy: -1 }, // Up-Right
|
||||
{ dx: 1, dy: 0 }, // Right
|
||||
{ dx: 1, dy: 1 }, // Down-Right
|
||||
{ dx: 0, dy: 1 }, // Down
|
||||
{ dx: -1, dy: 1 }, // Down-Left
|
||||
{ dx: -1, dy: 0 }, // Left
|
||||
{ dx: -1, dy: -1 } // Up-Left
|
||||
] as const;
|
||||
|
||||
// =============================================================================
|
||||
// 网格地图配置 | Grid Map Options
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* @zh 网格地图配置
|
||||
* @en Grid map options
|
||||
*/
|
||||
export interface IGridMapOptions {
|
||||
/** @zh 是否允许对角移动 @en Allow diagonal movement */
|
||||
allowDiagonal?: boolean;
|
||||
/** @zh 对角移动代价 @en Diagonal movement cost */
|
||||
diagonalCost?: number;
|
||||
/** @zh 是否避免穿角 @en Avoid corner cutting */
|
||||
avoidCorners?: boolean;
|
||||
/** @zh 启发式函数 @en Heuristic function */
|
||||
heuristic?: HeuristicFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 默认网格地图配置
|
||||
* @en Default grid map options
|
||||
*/
|
||||
export const DEFAULT_GRID_OPTIONS: Required<IGridMapOptions> = {
|
||||
allowDiagonal: true,
|
||||
diagonalCost: Math.SQRT2,
|
||||
avoidCorners: true,
|
||||
heuristic: octileDistance
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// 网格地图 | Grid Map
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* @zh 网格地图
|
||||
* @en Grid Map
|
||||
*
|
||||
* @zh 基于二维数组的网格地图实现,支持4方向和8方向移动
|
||||
* @en Grid map implementation based on 2D array, supports 4 and 8 directional movement
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Create a 10x10 grid
|
||||
* const grid = new GridMap(10, 10);
|
||||
*
|
||||
* // Set some cells as obstacles
|
||||
* grid.setWalkable(5, 5, false);
|
||||
* grid.setWalkable(5, 6, false);
|
||||
*
|
||||
* // Use with pathfinder
|
||||
* const pathfinder = new AStarPathfinder(grid);
|
||||
* const result = pathfinder.findPath(0, 0, 9, 9);
|
||||
* ```
|
||||
*/
|
||||
export class GridMap implements IPathfindingMap {
|
||||
readonly width: number;
|
||||
readonly height: number;
|
||||
private readonly nodes: GridNode[][];
|
||||
private readonly options: Required<IGridMapOptions>;
|
||||
|
||||
constructor(width: number, height: number, options?: IGridMapOptions) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.options = { ...DEFAULT_GRID_OPTIONS, ...options };
|
||||
this.nodes = this.createNodes();
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 创建网格节点
|
||||
* @en Create grid nodes
|
||||
*/
|
||||
private createNodes(): GridNode[][] {
|
||||
const nodes: GridNode[][] = [];
|
||||
|
||||
for (let y = 0; y < this.height; y++) {
|
||||
nodes[y] = [];
|
||||
for (let x = 0; x < this.width; x++) {
|
||||
nodes[y][x] = new GridNode(x, y, true, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取指定位置的节点
|
||||
* @en Get node at position
|
||||
*/
|
||||
getNodeAt(x: number, y: number): GridNode | null {
|
||||
if (!this.isInBounds(x, y)) {
|
||||
return null;
|
||||
}
|
||||
return this.nodes[y][x];
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 检查坐标是否在边界内
|
||||
* @en Check if coordinates are within bounds
|
||||
*/
|
||||
isInBounds(x: number, y: number): boolean {
|
||||
return x >= 0 && x < this.width && y >= 0 && y < this.height;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 检查位置是否可通行
|
||||
* @en Check if position is walkable
|
||||
*/
|
||||
isWalkable(x: number, y: number): boolean {
|
||||
const node = this.getNodeAt(x, y);
|
||||
return node !== null && node.walkable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 设置位置是否可通行
|
||||
* @en Set position walkability
|
||||
*/
|
||||
setWalkable(x: number, y: number, walkable: boolean): void {
|
||||
const node = this.getNodeAt(x, y);
|
||||
if (node) {
|
||||
node.walkable = walkable;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 设置位置的移动代价
|
||||
* @en Set movement cost at position
|
||||
*/
|
||||
setCost(x: number, y: number, cost: number): void {
|
||||
const node = this.getNodeAt(x, y);
|
||||
if (node) {
|
||||
node.cost = cost;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取节点的邻居
|
||||
* @en Get neighbors of a node
|
||||
*/
|
||||
getNeighbors(node: IPathNode): GridNode[] {
|
||||
const neighbors: GridNode[] = [];
|
||||
const { x, y } = node.position;
|
||||
const directions = this.options.allowDiagonal ? DIRECTIONS_8 : DIRECTIONS_4;
|
||||
|
||||
for (let i = 0; i < directions.length; i++) {
|
||||
const dir = directions[i];
|
||||
const nx = x + dir.dx;
|
||||
const ny = y + dir.dy;
|
||||
|
||||
if (!this.isInBounds(nx, ny)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const neighbor = this.nodes[ny][nx];
|
||||
|
||||
if (!neighbor.walkable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check corner cutting for diagonal movement
|
||||
if (this.options.avoidCorners && dir.dx !== 0 && dir.dy !== 0) {
|
||||
const horizontal = this.getNodeAt(x + dir.dx, y);
|
||||
const vertical = this.getNodeAt(x, y + dir.dy);
|
||||
|
||||
if (!horizontal?.walkable || !vertical?.walkable) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
neighbors.push(neighbor);
|
||||
}
|
||||
|
||||
return neighbors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 计算启发式距离
|
||||
* @en Calculate heuristic distance
|
||||
*/
|
||||
heuristic(a: IPoint, b: IPoint): number {
|
||||
return this.options.heuristic(a, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 计算移动代价
|
||||
* @en Calculate movement cost
|
||||
*/
|
||||
getMovementCost(from: IPathNode, to: IPathNode): number {
|
||||
const dx = Math.abs(from.position.x - to.position.x);
|
||||
const dy = Math.abs(from.position.y - to.position.y);
|
||||
|
||||
// Diagonal movement
|
||||
if (dx !== 0 && dy !== 0) {
|
||||
return to.cost * this.options.diagonalCost;
|
||||
}
|
||||
|
||||
// Cardinal movement
|
||||
return to.cost;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 从二维数组加载地图
|
||||
* @en Load map from 2D array
|
||||
*
|
||||
* @param data - @zh 0=可通行,非0=不可通行 @en 0=walkable, non-0=blocked
|
||||
*/
|
||||
loadFromArray(data: number[][]): void {
|
||||
for (let y = 0; y < Math.min(data.length, this.height); y++) {
|
||||
for (let x = 0; x < Math.min(data[y].length, this.width); x++) {
|
||||
this.nodes[y][x].walkable = data[y][x] === 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 从字符串加载地图
|
||||
* @en Load map from string
|
||||
*
|
||||
* @param str - @zh 地图字符串,'.'=可通行,'#'=障碍 @en Map string, '.'=walkable, '#'=blocked
|
||||
*/
|
||||
loadFromString(str: string): void {
|
||||
const lines = str.trim().split('\n');
|
||||
|
||||
for (let y = 0; y < Math.min(lines.length, this.height); y++) {
|
||||
const line = lines[y];
|
||||
for (let x = 0; x < Math.min(line.length, this.width); x++) {
|
||||
this.nodes[y][x].walkable = line[x] !== '#';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 导出为字符串
|
||||
* @en Export to string
|
||||
*/
|
||||
toString(): string {
|
||||
let result = '';
|
||||
|
||||
for (let y = 0; y < this.height; y++) {
|
||||
for (let x = 0; x < this.width; x++) {
|
||||
result += this.nodes[y][x].walkable ? '.' : '#';
|
||||
}
|
||||
result += '\n';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 重置所有节点为可通行
|
||||
* @en Reset all nodes to walkable
|
||||
*/
|
||||
reset(): void {
|
||||
for (let y = 0; y < this.height; y++) {
|
||||
for (let x = 0; x < this.width; x++) {
|
||||
this.nodes[y][x].walkable = true;
|
||||
this.nodes[y][x].cost = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 设置矩形区域的通行性
|
||||
* @en Set walkability for a rectangle region
|
||||
*/
|
||||
setRectWalkable(
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
walkable: boolean
|
||||
): void {
|
||||
for (let dy = 0; dy < height; dy++) {
|
||||
for (let dx = 0; dx < width; dx++) {
|
||||
this.setWalkable(x + dx, y + dy, walkable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 工厂函数 | Factory Function
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* @zh 创建网格地图
|
||||
* @en Create grid map
|
||||
*/
|
||||
export function createGridMap(
|
||||
width: number,
|
||||
height: number,
|
||||
options?: IGridMapOptions
|
||||
): GridMap {
|
||||
return new GridMap(width, height, options);
|
||||
}
|
||||
14
packages/framework/pathfinding/src/grid/index.ts
Normal file
14
packages/framework/pathfinding/src/grid/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @zh 网格地图模块
|
||||
* @en Grid Map Module
|
||||
*/
|
||||
|
||||
export {
|
||||
GridNode,
|
||||
GridMap,
|
||||
createGridMap,
|
||||
DIRECTIONS_4,
|
||||
DIRECTIONS_8,
|
||||
type IGridMapOptions,
|
||||
DEFAULT_GRID_OPTIONS
|
||||
} from './GridMap';
|
||||
Reference in New Issue
Block a user