Files
climb/assets/scripts/TiledMapPathfinder.ts

250 lines
8.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.

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()
};
}
}