feat: 支持自动寻路算法

This commit is contained in:
2025-09-21 21:31:54 +08:00
parent 35cfabb66b
commit d6aa74cb9d
10 changed files with 1003 additions and 90 deletions

View File

@@ -0,0 +1,225 @@
import { _decorator, Component, Vec2 } from 'cc';
const { ccclass, property } = _decorator;
// A*寻路节点
export class PathNode {
x: number;
y: number;
gCost: number = 0; // 从起点到当前点的实际代价
hCost: number = 0; // 从当前点到终点的启发式代价
fCost: number = 0; // 总代价 f = g + h
parent: PathNode | null = null;
walkable: boolean = true;
constructor(x: number, y: number, walkable: boolean = true) {
this.x = x;
this.y = y;
this.walkable = walkable;
}
calculateFCost() {
this.fCost = this.gCost + this.hCost;
}
}
@ccclass('AStarPathfinding')
export class AStarPathfinding extends Component {
private grid: PathNode[][] = [];
private gridWidth: number = 0;
private gridHeight: number = 0;
/**
* 初始化寻路网格
* @param width 网格宽度
* @param height 网格高度
* @param walkableData 可行走数据0表示不可行走1表示可行走
*/
initializeGrid(width: number, height: number, walkableData: number[][]) {
this.gridWidth = width;
this.gridHeight = height;
this.grid = [];
for (let x = 0; x < width; x++) {
this.grid[x] = [];
for (let y = 0; y < height; y++) {
const walkable = walkableData[y] && walkableData[y][x] === 1;
this.grid[x][y] = new PathNode(x, y, walkable);
}
}
}
/**
* 使用A*算法寻找路径
* @param startX 起点X坐标
* @param startY 起点Y坐标
* @param targetX 终点X坐标
* @param targetY 终点Y坐标
* @returns 路径点数组,如果找不到路径返回空数组
*/
findPath(startX: number, startY: number, targetX: number, targetY: number): Vec2[] {
// 验证起点和终点是否有效
if (!this.isValidPosition(startX, startY) || !this.isValidPosition(targetX, targetY)) {
console.warn('起点或终点坐标无效');
return [];
}
if (!this.grid[startX][startY].walkable || !this.grid[targetX][targetY].walkable) {
console.warn('起点或终点不可行走');
return [];
}
if (startX === targetX && startY === targetY) {
return [new Vec2(startX, startY)];
}
// 重置所有节点
this.resetNodes();
const startNode = this.grid[startX][startY];
const targetNode = this.grid[targetX][targetY];
const openSet: PathNode[] = [];
const closedSet: PathNode[] = [];
openSet.push(startNode);
while (openSet.length > 0) {
// 找到f值最小的节点
let currentNode = openSet[0];
for (let i = 1; i < openSet.length; i++) {
if (openSet[i].fCost < currentNode.fCost ||
(openSet[i].fCost === currentNode.fCost && openSet[i].hCost < currentNode.hCost)) {
currentNode = openSet[i];
}
}
// 从开放列表移除当前节点,添加到关闭列表
openSet.splice(openSet.indexOf(currentNode), 1);
closedSet.push(currentNode);
// 如果到达目标节点,重建路径
if (currentNode === targetNode) {
return this.retracePath(startNode, targetNode);
}
// 检查相邻节点
const neighbors = this.getNeighbors(currentNode);
for (const neighbor of neighbors) {
if (!neighbor.walkable || closedSet.indexOf(neighbor) !== -1) {
continue;
}
const newGCost = currentNode.gCost + this.getDistance(currentNode, neighbor);
if (newGCost < neighbor.gCost || openSet.indexOf(neighbor) === -1) {
neighbor.gCost = newGCost;
neighbor.hCost = this.getDistance(neighbor, targetNode);
neighbor.calculateFCost();
neighbor.parent = currentNode;
if (openSet.indexOf(neighbor) === -1) {
openSet.push(neighbor);
}
}
}
}
// 没有找到路径
return [];
}
/**
* 获取节点的相邻节点4方向
*/
private getNeighbors(node: PathNode): PathNode[] {
const neighbors: PathNode[] = [];
// 四个方向:上、下、左、右
const directions = [
{ x: 0, y: 1 }, // 上
{ x: 0, y: -1 }, // 下
{ x: -1, y: 0 }, // 左
{ x: 1, y: 0 } // 右
];
for (const dir of directions) {
const checkX = node.x + dir.x;
const checkY = node.y + dir.y;
if (this.isValidPosition(checkX, checkY)) {
neighbors.push(this.grid[checkX][checkY]);
}
}
return neighbors;
}
/**
* 获取两个节点之间的距离(曼哈顿距离)
*/
private getDistance(nodeA: PathNode, nodeB: PathNode): number {
const distX = Math.abs(nodeA.x - nodeB.x);
const distY = Math.abs(nodeA.y - nodeB.y);
return distX + distY;
}
/**
* 重建路径
*/
private retracePath(startNode: PathNode, endNode: PathNode): Vec2[] {
const path: Vec2[] = [];
let currentNode = endNode;
while (currentNode !== startNode) {
path.push(new Vec2(currentNode.x, currentNode.y));
currentNode = currentNode.parent!;
}
path.push(new Vec2(startNode.x, startNode.y));
path.reverse();
return path;
}
/**
* 重置所有节点的寻路参数
*/
private resetNodes() {
for (let x = 0; x < this.gridWidth; x++) {
for (let y = 0; y < this.gridHeight; y++) {
const node = this.grid[x][y];
node.gCost = 0;
node.hCost = 0;
node.fCost = 0;
node.parent = null;
}
}
}
/**
* 检查位置是否有效
*/
private isValidPosition(x: number, y: number): boolean {
return x >= 0 && x < this.gridWidth && y >= 0 && y < this.gridHeight;
}
/**
* 设置节点的可行走状态
*/
setWalkable(x: number, y: number, walkable: boolean) {
if (this.isValidPosition(x, y)) {
this.grid[x][y].walkable = walkable;
}
}
/**
* 获取节点是否可行走
*/
isWalkable(x: number, y: number): boolean {
if (!this.isValidPosition(x, y)) {
return false;
}
return this.grid[x][y].walkable;
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "8e8e7d9a-2a3b-4c5d-8f9e-1a2b3c4d5e6f",
"files": [],
"subMetas": {},
"userData": {}
}

63
assets/scripts/Manager.ts Normal file
View File

@@ -0,0 +1,63 @@
import { _decorator, Component, Node, TiledMap } from 'cc';
import { TiledMapPathfinder } from './TiledMapPathfinder';
import { PlayerController } from './PlayerController';
const { ccclass, property } = _decorator;
@ccclass('Manager')
export class Manager extends Component {
@property(TiledMap)
tiledMap: TiledMap | null = null;
@property(Node)
playerNode: Node | null = null;
private pathfinder: TiledMapPathfinder | null = null;
private playerController: PlayerController | null = null;
start() {
this.initializeGame();
}
private initializeGame() {
// 初始化寻路系统
if (this.tiledMap) {
// 为TiledMap添加寻路组件
this.pathfinder = this.tiledMap.node.getComponent(TiledMapPathfinder);
if (!this.pathfinder) {
this.pathfinder = this.tiledMap.node.addComponent(TiledMapPathfinder);
this.pathfinder.tiledMap = this.tiledMap;
this.pathfinder.walkableLayerName = 'WalkableLayer';
this.pathfinder.tileSize = 32;
}
}
// 初始化玩家控制器
if (this.playerNode) {
this.playerController = this.playerNode.getComponent(PlayerController);
if (this.playerController && this.pathfinder) {
this.playerController.pathfinder = this.pathfinder;
}
}
console.log('游戏初始化完成');
this.logMapInfo();
}
private logMapInfo() {
if (this.pathfinder) {
const mapInfo = this.pathfinder.getMapInfo();
if (mapInfo) {
console.log('地图信息:', {
mapSize: mapInfo.mapSize,
tileSize: mapInfo.tileSize,
orientation: mapInfo.orientation
});
}
}
}
update(deltaTime: number) {
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "8a6e8f34-f238-4dd9-8247-c6ece27ea4c2",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,152 @@
import { _decorator, Component, Node, TiledMap, Vec3, Label, input, Input, EventTouch } from 'cc';
import { TiledMapPathfinder } from './TiledMapPathfinder';
const { ccclass, property } = _decorator;
@ccclass('PathfindingTest')
export class PathfindingTest extends Component {
@property(TiledMap)
tiledMap: TiledMap | null = null;
@property(Node)
startPoint: Node | null = null;
@property(Node)
endPoint: Node | null = null;
@property(Label)
infoLabel: Label | null = null;
private pathfinder: TiledMapPathfinder | null = null;
private currentPath: Vec3[] = [];
onLoad() {
input.on(Input.EventType.TOUCH_START, this.onTouch, this);
}
onDestroy() {
input.off(Input.EventType.TOUCH_START, this.onTouch, this);
}
start() {
this.initializePathfinder();
this.runTests();
}
private initializePathfinder() {
if (!this.tiledMap) {
console.error('TiledMap未设置');
return;
}
this.pathfinder = this.tiledMap.node.getComponent(TiledMapPathfinder);
if (!this.pathfinder) {
this.pathfinder = this.tiledMap.node.addComponent(TiledMapPathfinder);
this.pathfinder.tiledMap = this.tiledMap;
this.pathfinder.walkableLayerName = 'WalkableLayer';
this.pathfinder.tileSize = 32;
}
}
private runTests() {
if (!this.pathfinder) {
console.error('寻路器未初始化');
return;
}
// 等待一帧让寻路器完全初始化
this.scheduleOnce(() => {
this.performPathfindingTest();
}, 0.1);
}
private performPathfindingTest() {
if (!this.pathfinder || !this.startPoint || !this.endPoint) {
console.error('测试组件未完整设置');
return;
}
const startPos = this.startPoint.position;
const endPos = this.endPoint.position;
console.log(`测试寻路: 从 (${startPos.x}, ${startPos.y}) 到 (${endPos.x}, ${endPos.y})`);
// 检查位置是否可行走
const startWalkable = this.pathfinder.isWorldPositionWalkable(startPos);
const endWalkable = this.pathfinder.isWorldPositionWalkable(endPos);
console.log(`起点可行走: ${startWalkable}, 终点可行走: ${endWalkable}`);
// 如果位置不可行走,寻找最近的可行走位置
let adjustedStartPos = startPos;
let adjustedEndPos = endPos;
if (!startWalkable) {
const closestStart = this.pathfinder.getClosestWalkablePosition(startPos);
if (closestStart) {
adjustedStartPos = closestStart;
console.log(`调整起点到: (${adjustedStartPos.x}, ${adjustedStartPos.y})`);
}
}
if (!endWalkable) {
const closestEnd = this.pathfinder.getClosestWalkablePosition(endPos);
if (closestEnd) {
adjustedEndPos = closestEnd;
console.log(`调整终点到: (${adjustedEndPos.x}, ${adjustedEndPos.y})`);
}
}
// 执行寻路
this.currentPath = this.pathfinder.findPath(adjustedStartPos, adjustedEndPos);
if (this.currentPath.length > 0) {
console.log(`找到路径,包含 ${this.currentPath.length} 个点:`);
this.currentPath.forEach((point, index) => {
console.log(` 路径点 ${index}: (${point.x.toFixed(2)}, ${point.y.toFixed(2)})`);
});
this.updateInfoLabel(`路径找到!包含 ${this.currentPath.length} 个点`);
} else {
console.log('未找到路径');
this.updateInfoLabel('未找到路径');
}
}
private onTouch(event: EventTouch) {
// 可以通过触摸来动态测试寻路
if (!this.pathfinder || !this.startPoint) return;
const touchLocation = event.getUILocation();
// 这里可以添加触摸点寻路测试的逻辑
}
private updateInfoLabel(text: string) {
if (this.infoLabel) {
this.infoLabel.string = text;
}
}
// 公共方法供外部调用测试
public testPathfinding(startWorldPos: Vec3, endWorldPos: Vec3): Vec3[] {
if (!this.pathfinder) {
console.error('寻路器未初始化');
return [];
}
return this.pathfinder.findPath(startWorldPos, endWorldPos);
}
// 获取当前找到的路径
public getCurrentPath(): Vec3[] {
return this.currentPath.slice(); // 返回副本
}
// 检查位置是否可行走
public isPositionWalkable(worldPos: Vec3): boolean {
if (!this.pathfinder) {
return false;
}
return this.pathfinder.isWorldPositionWalkable(worldPos);
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "a1a1a2a3-4b5c-6d7e-8f9a-3c4d5e6f7g8h",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,4 +1,5 @@
import { _decorator, Component, Node, Vec3, input, Input, EventTouch, Camera, view } from 'cc';
import { _decorator, Component, Node, Vec3, input, Input, EventTouch, Camera, view, tween } from 'cc';
import { TiledMapPathfinder } from './TiledMapPathfinder';
const { ccclass, property } = _decorator;
@ccclass('PlayerController')
@@ -10,8 +11,11 @@ export class PlayerController extends Component {
@property(Camera)
camera: Camera | null = null; // 主摄像机
@property({ range: [1, 20] })
moveSpeed: number = 5; // 移动速度
@property(TiledMapPathfinder)
pathfinder: TiledMapPathfinder | null = null; // 寻路组件
@property({ range: [1, 300] })
moveSpeed: number = 300; // 移动速度(像素/秒)
@property
mapWidth: number = 1080; // 地图宽度
@@ -20,7 +24,8 @@ export class PlayerController extends Component {
mapHeight: number = 2560; // 地图高度
private isMoving: boolean = false;
private targetPosition: Vec3 = new Vec3();
private currentPath: Vec3[] = [];
private currentPathIndex: number = 0;
private originalPosition: Vec3 = new Vec3();
onLoad() {
@@ -40,7 +45,7 @@ export class PlayerController extends Component {
}
private onTouchStart(event: EventTouch) {
if (!this.player || !this.camera) return;
if (!this.player || !this.camera || !this.pathfinder) return;
// 获取触摸点的UI坐标
const touchLocation = event.getUILocation();
@@ -51,7 +56,7 @@ export class PlayerController extends Component {
console.log(`触摸UI坐标: (${touchLocation.x}, ${touchLocation.y})`);
console.log(`转换后世界坐标: (${worldPos.x.toFixed(2)}, ${worldPos.y.toFixed(2)})`);
this.moveToPosition(worldPos);
this.moveToPositionWithPathfinding(worldPos);
}
private screenToWorldPoint(screenPos: { x: number, y: number }): Vec3 {
@@ -82,17 +87,41 @@ export class PlayerController extends Component {
}
private moveToPosition(worldPos: Vec3) {
if (!this.player) return;
private moveToPositionWithPathfinding(worldPos: Vec3) {
if (!this.player || !this.pathfinder) return;
// 停止当前移动
this.stopMovement();
// 限制目标位置在地图边界内
const clampedPos = this.clampPlayerPosition(worldPos);
// 设置目标位置保持Z轴不变
this.targetPosition.set(clampedPos.x, clampedPos.y, this.player.position.z);
this.isMoving = true;
// 检查目标位置是否可行走
if (!this.pathfinder.isWorldPositionWalkable(clampedPos)) {
console.log('目标位置不可行走,寻找最近的可行走位置');
const closestWalkable = this.pathfinder.getClosestWalkablePosition(clampedPos);
if (!closestWalkable) {
console.warn('找不到可行走的位置');
return;
}
clampedPos.set(closestWalkable);
}
console.log(`移动目标: (${clampedPos.x.toFixed(2)}, ${clampedPos.y.toFixed(2)})`);
// 使用寻路算法计算路径
const startPos = this.player.position;
this.currentPath = this.pathfinder.findPath(startPos, clampedPos);
if (this.currentPath.length === 0) {
console.warn('无法找到路径');
return;
}
console.log(`找到路径,包含${this.currentPath.length}个点`);
// 开始沿路径移动
this.currentPathIndex = 0;
this.isMoving = true;
this.moveToNextWaypoint();
}
// 限制玩家位置在地图边界内
@@ -109,41 +138,50 @@ export class PlayerController extends Component {
return clampedPosition;
}
update(deltaTime: number) {
if (!this.isMoving || !this.player) return;
const currentPos = this.player.position;
const distance = Vec3.distance(currentPos, this.targetPosition);
// 如果距离很小,直接到达目标位置
if (distance < 0.1) {
this.player.position = this.targetPosition.clone();
/**
* 移动到路径中的下一个路径点
*/
private moveToNextWaypoint() {
if (!this.player || this.currentPath.length === 0 || this.currentPathIndex >= this.currentPath.length) {
this.isMoving = false;
console.log('到达目标位置');
console.log('路径移动完成');
return;
}
// 计算移动方向
const direction = new Vec3();
Vec3.subtract(direction, this.targetPosition, currentPos);
direction.normalize();
const targetPos = this.currentPath[this.currentPathIndex];
const currentPos = this.player.position;
// 计算这一帧应该移动距离
const moveDistance = this.moveSpeed * deltaTime * 100; // 增加移动速度倍数
// 计算移动距离和时间
const distance = Vec3.distance(currentPos, targetPos);
const moveTime = distance / this.moveSpeed;
// 如果剩余距离小于这一帧要移动的距离,直接到达目标
if (distance <= moveDistance) {
this.player.position = this.targetPosition.clone();
this.isMoving = false;
console.log('到达目标位置');
} else {
// 正常移动
const newPosition = new Vec3();
Vec3.scaleAndAdd(newPosition, currentPos, direction, moveDistance);
console.log(`移动到路径点${this.currentPathIndex}: (${targetPos.x.toFixed(2)}, ${targetPos.y.toFixed(2)})`);
// 确保新位置在地图边界内
const clampedNewPosition = this.clampPlayerPosition(newPosition);
this.player.position = clampedNewPosition;
// 使用缓动移动到目标位置
tween(this.player)
.to(moveTime, { position: targetPos }, {
onComplete: () => {
this.currentPathIndex++;
this.moveToNextWaypoint();
}
})
.start();
}
/**
* 停止当前移动
*/
private stopMovement() {
if (this.player) {
tween(this.player).stop();
}
this.isMoving = false;
this.currentPath = [];
this.currentPathIndex = 0;
}
update(deltaTime: number) {
// 更新逻辑现在主要由缓动系统处理
// 这里可以添加其他需要每帧更新的逻辑
}
}

View File

@@ -0,0 +1,250 @@
import { _decorator, Component, Node, TiledMap, TiledLayer, Vec2, Vec3, Size } from 'cc';
import { AStarPathfinding } from './AStarPathfinding';
const { ccclass, property } = _decorator;
@ccclass('TiledMapPathfinder')
export class TiledMapPathfinder extends Component {
@property(TiledMap)
tiledMap: TiledMap | null = null;
@property({ displayName: '可行走图层名称' })
walkableLayerName: string = 'WalkableLayer';
@property({ displayName: '瓦片尺寸' })
tileSize: number = 32;
private pathfinding: AStarPathfinding | null = null;
private mapSize: Size = new Size(0, 0);
private walkableData: number[][] = [];
onLoad() {
// 获取或创建寻路组件
this.pathfinding = this.getComponent(AStarPathfinding);
if (!this.pathfinding) {
this.pathfinding = this.addComponent(AStarPathfinding);
}
}
start() {
if (this.tiledMap) {
this.initializePathfinding();
} else {
console.error('TiledMapPathfinder: TiledMap组件未设置');
}
}
/**
* 初始化寻路系统
*/
private initializePathfinding() {
if (!this.tiledMap) {
console.error('TiledMap未设置');
return;
}
// 获取地图尺寸
this.mapSize = this.tiledMap.getMapSize();
console.log(`地图尺寸: ${this.mapSize.width}x${this.mapSize.height}`);
// 获取可行走图层
const walkableLayer = this.tiledMap.getLayer(this.walkableLayerName);
if (!walkableLayer) {
console.error(`找不到图层: ${this.walkableLayerName}`);
return;
}
// 读取可行走数据
this.extractWalkableData(walkableLayer);
// 初始化A*寻路算法
if (this.pathfinding) {
this.pathfinding.initializeGrid(this.mapSize.width, this.mapSize.height, this.walkableData);
console.log('寻路系统初始化完成');
}
}
/**
* 从TiledLayer提取可行走数据
*/
private extractWalkableData(layer: TiledLayer) {
this.walkableData = [];
for (let y = 0; y < this.mapSize.height; y++) {
this.walkableData[y] = [];
for (let x = 0; x < this.mapSize.width; x++) {
// 获取指定位置的瓦片GID
const gid = layer.getTileGIDAt(x, y);
// GID > 0 表示有瓦片,表示可行走
// GID = 0 表示没有瓦片,表示不可行走
this.walkableData[y][x] = gid > 0 ? 1 : 0;
}
}
console.log('可行走数据提取完成');
this.debugPrintWalkableData();
}
/**
* 调试打印可行走数据(仅打印部分数据以避免日志过长)
*/
private debugPrintWalkableData() {
console.log('可行走数据示例前10行:');
for (let y = 0; y < Math.min(10, this.walkableData.length); y++) {
const row = this.walkableData[y].slice(0, Math.min(10, this.walkableData[y].length));
console.log(`${y}行: [${row.join(', ')}]`);
}
}
/**
* 寻找路径
* @param startWorldPos 起点世界坐标
* @param targetWorldPos 终点世界坐标
* @returns 路径的世界坐标数组
*/
findPath(startWorldPos: Vec3, targetWorldPos: Vec3): Vec3[] {
if (!this.pathfinding || !this.tiledMap) {
console.warn('寻路系统未初始化');
return [];
}
// 将世界坐标转换为瓦片坐标
const startTilePos = this.worldToTileCoordinate(startWorldPos);
const targetTilePos = this.worldToTileCoordinate(targetWorldPos);
console.log(`寻路: 起点瓦片坐标(${startTilePos.x}, ${startTilePos.y}) -> 终点瓦片坐标(${targetTilePos.x}, ${targetTilePos.y})`);
// 使用A*算法寻找路径
const tilePath = this.pathfinding.findPath(
startTilePos.x, startTilePos.y,
targetTilePos.x, targetTilePos.y
);
if (tilePath.length === 0) {
console.warn('未找到路径');
return [];
}
// 将瓦片坐标路径转换为世界坐标路径
const worldPath: Vec3[] = [];
for (const tilePos of tilePath) {
const worldPos = this.tileToWorldCoordinate(tilePos);
worldPath.push(worldPos);
}
console.log(`找到路径,包含${worldPath.length}个点`);
return worldPath;
}
/**
* 世界坐标转换为瓦片坐标
*/
private worldToTileCoordinate(worldPos: Vec3): Vec2 {
if (!this.tiledMap) {
return new Vec2(0, 0);
}
// 获取地图节点的位置偏移
const mapNode = this.tiledMap.node;
const mapPosition = mapNode.position;
// 计算相对于地图的坐标
const relativeX = worldPos.x - mapPosition.x;
const relativeY = worldPos.y - mapPosition.y;
// 获取地图尺寸信息
const mapSize = this.tiledMap.getMapSize();
const tileSize = this.tiledMap.getTileSize();
// 计算瓦片坐标
// 注意Cocos Creator的Y轴向上为正但瓦片地图的Y轴向下为正
const tileX = Math.floor((relativeX + mapSize.width * tileSize.width * 0.5) / tileSize.width);
const tileY = Math.floor((mapSize.height * tileSize.height * 0.5 - relativeY) / tileSize.height);
// 确保坐标在地图范围内
const clampedX = Math.max(0, Math.min(mapSize.width - 1, tileX));
const clampedY = Math.max(0, Math.min(mapSize.height - 1, tileY));
return new Vec2(clampedX, clampedY);
}
/**
* 瓦片坐标转换为世界坐标
*/
private tileToWorldCoordinate(tilePos: Vec2): Vec3 {
if (!this.tiledMap) {
return new Vec3(0, 0, 0);
}
// 获取地图节点的位置偏移
const mapNode = this.tiledMap.node;
const mapPosition = mapNode.position;
// 获取地图尺寸信息
const mapSize = this.tiledMap.getMapSize();
const tileSize = this.tiledMap.getTileSize();
// 计算世界坐标(瓦片中心点)
const worldX = (tilePos.x + 0.5) * tileSize.width - mapSize.width * tileSize.width * 0.5 + mapPosition.x;
const worldY = mapSize.height * tileSize.height * 0.5 - (tilePos.y + 0.5) * tileSize.height + mapPosition.y;
return new Vec3(worldX, worldY, 0);
}
/**
* 检查指定世界坐标是否可行走
*/
isWorldPositionWalkable(worldPos: Vec3): boolean {
if (!this.pathfinding) {
return false;
}
const tilePos = this.worldToTileCoordinate(worldPos);
return this.pathfinding.isWalkable(tilePos.x, tilePos.y);
}
/**
* 获取最近的可行走位置
*/
getClosestWalkablePosition(worldPos: Vec3, maxSearchRadius: number = 5): Vec3 | null {
const centerTilePos = this.worldToTileCoordinate(worldPos);
// 螺旋搜索最近的可行走位置
for (let radius = 0; radius <= maxSearchRadius; radius++) {
for (let x = -radius; x <= radius; x++) {
for (let y = -radius; y <= radius; y++) {
// 只检查当前半径边界上的点
if (Math.abs(x) !== radius && Math.abs(y) !== radius && radius > 0) {
continue;
}
const checkX = centerTilePos.x + x;
const checkY = centerTilePos.y + y;
if (this.pathfinding && this.pathfinding.isWalkable(checkX, checkY)) {
return this.tileToWorldCoordinate(new Vec2(checkX, checkY));
}
}
}
}
return null;
}
/**
* 获取地图信息
*/
getMapInfo() {
if (!this.tiledMap) {
return null;
}
return {
mapSize: this.tiledMap.getMapSize(),
tileSize: this.tiledMap.getTileSize(),
orientation: this.tiledMap.getMapOrientation()
};
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "9f9f8e8d-3b4c-5d6e-9f0e-2b3c4d5e6f7g",
"files": [],
"subMetas": {},
"userData": {}
}