feat: 支持自动寻路算法
This commit is contained in:
@@ -52,7 +52,7 @@
|
||||
},
|
||||
"autoReleaseAssets": false,
|
||||
"_globals": {
|
||||
"__id__": 27
|
||||
"__id__": 32
|
||||
},
|
||||
"_id": "58132e64-0171-4c7f-89be-a2984ca7de6b"
|
||||
},
|
||||
@@ -69,28 +69,31 @@
|
||||
"__id__": 3
|
||||
},
|
||||
{
|
||||
"__id__": 9
|
||||
"__id__": 10
|
||||
},
|
||||
{
|
||||
"__id__": 18
|
||||
"__id__": 20
|
||||
},
|
||||
{
|
||||
"__id__": 6
|
||||
},
|
||||
{
|
||||
"__id__": 21
|
||||
"__id__": 23
|
||||
},
|
||||
{
|
||||
"__id__": 26
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 24
|
||||
"__id__": 29
|
||||
},
|
||||
{
|
||||
"__id__": 25
|
||||
"__id__": 30
|
||||
},
|
||||
{
|
||||
"__id__": 26
|
||||
"__id__": 31
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -238,6 +241,8 @@
|
||||
"z": 10
|
||||
},
|
||||
"smoothness": 0.1,
|
||||
"mapWidth": 1080,
|
||||
"mapHeight": 2560,
|
||||
"_id": "f3ED2JS1JKurfMG53fWzmg"
|
||||
},
|
||||
{
|
||||
@@ -256,6 +261,9 @@
|
||||
},
|
||||
{
|
||||
"__id__": 8
|
||||
},
|
||||
{
|
||||
"__id__": 9
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -349,6 +357,34 @@
|
||||
"loop": true,
|
||||
"_id": "bfAE5dYjNG5JfiAUJyxBbf"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.RigidBody2D",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 6
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
"enabledContactListener": false,
|
||||
"bullet": false,
|
||||
"awakeOnLoad": true,
|
||||
"_group": 4,
|
||||
"_type": 2,
|
||||
"_allowSleep": true,
|
||||
"_gravityScale": 0,
|
||||
"_linearDamping": 0,
|
||||
"_angularDamping": 0,
|
||||
"_linearVelocity": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"_angularVelocity": 0,
|
||||
"_fixedRotation": true,
|
||||
"_id": "f26+sa035BLpRjrovODcjh"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "TiledMap",
|
||||
@@ -359,19 +395,22 @@
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 10
|
||||
"__id__": 11
|
||||
},
|
||||
{
|
||||
"__id__": 13
|
||||
"__id__": 14
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 16
|
||||
"__id__": 17
|
||||
},
|
||||
{
|
||||
"__id__": 17
|
||||
"__id__": 18
|
||||
},
|
||||
{
|
||||
"__id__": 19
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -410,16 +449,16 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 9
|
||||
"__id__": 10
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 11
|
||||
"__id__": 12
|
||||
},
|
||||
{
|
||||
"__id__": 12
|
||||
"__id__": 13
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -458,7 +497,7 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 10
|
||||
"__id__": 11
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
@@ -480,7 +519,7 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 10
|
||||
"__id__": 11
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
@@ -519,16 +558,16 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 9
|
||||
"__id__": 10
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 14
|
||||
"__id__": 15
|
||||
},
|
||||
{
|
||||
"__id__": 15
|
||||
"__id__": 16
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -567,7 +606,7 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 13
|
||||
"__id__": 14
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
@@ -589,7 +628,7 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 13
|
||||
"__id__": 14
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
@@ -601,7 +640,7 @@
|
||||
"r": 255,
|
||||
"g": 255,
|
||||
"b": 255,
|
||||
"a": 255
|
||||
"a": 0
|
||||
},
|
||||
"_id": "30V2VyJEZCsKfZxqWiSZMJ"
|
||||
},
|
||||
@@ -611,7 +650,7 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 9
|
||||
"__id__": 10
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
@@ -633,7 +672,7 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 9
|
||||
"__id__": 10
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
@@ -645,6 +684,23 @@
|
||||
"cleanupImageCache": true,
|
||||
"_id": "af4s2K0x1MH5srzPTEFKZE"
|
||||
},
|
||||
{
|
||||
"__type__": "9f9f8e8d-3b4c-5d6e-9f0e-2b3c4d5e6f7g",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 10
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
"tiledMap": {
|
||||
"__id__": 18
|
||||
},
|
||||
"walkableLayerName": "WalkableLayer",
|
||||
"tileSize": 32,
|
||||
"_id": "b7cmI9hyJG85zE5AHvozc+"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "Bg",
|
||||
@@ -657,10 +713,10 @@
|
||||
"_active": false,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 19
|
||||
"__id__": 21
|
||||
},
|
||||
{
|
||||
"__id__": 20
|
||||
"__id__": 22
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -699,7 +755,7 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 18
|
||||
"__id__": 20
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
@@ -721,7 +777,7 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 18
|
||||
"__id__": 20
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
@@ -766,10 +822,10 @@
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 22
|
||||
"__id__": 24
|
||||
},
|
||||
{
|
||||
"__id__": 23
|
||||
"__id__": 25
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -808,7 +864,7 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 21
|
||||
"__id__": 23
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
@@ -830,7 +886,7 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 21
|
||||
"__id__": 23
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
@@ -840,9 +896,102 @@
|
||||
"camera": {
|
||||
"__id__": 4
|
||||
},
|
||||
"moveSpeed": 5,
|
||||
"pathfinder": {
|
||||
"__id__": 19
|
||||
},
|
||||
"moveSpeed": 300,
|
||||
"mapWidth": 1080,
|
||||
"mapHeight": 2560,
|
||||
"_id": "c1AuAU3IlKnLOzgk9vsBr4"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "Manager",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 2
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 27
|
||||
},
|
||||
{
|
||||
"__id__": 28
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 33554432,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": "faEV0Z9e5HPJZltMJt2Zn9"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 26
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 100,
|
||||
"height": 100
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": "f6ZiYZJ6JClotvMwMwGhqR"
|
||||
},
|
||||
{
|
||||
"__type__": "8a6e8808jhN2YJHxuzifqTC",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 26
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
"tiledMap": {
|
||||
"__id__": 18
|
||||
},
|
||||
"playerNode": {
|
||||
"__id__": 6
|
||||
},
|
||||
"_id": "35G3LX9BRHiJifNhcAwfoE"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
@@ -914,29 +1063,29 @@
|
||||
{
|
||||
"__type__": "cc.SceneGlobals",
|
||||
"ambient": {
|
||||
"__id__": 28
|
||||
},
|
||||
"shadows": {
|
||||
"__id__": 29
|
||||
},
|
||||
"_skybox": {
|
||||
"__id__": 30
|
||||
},
|
||||
"fog": {
|
||||
"__id__": 31
|
||||
},
|
||||
"octree": {
|
||||
"__id__": 32
|
||||
},
|
||||
"skin": {
|
||||
"__id__": 33
|
||||
},
|
||||
"lightProbeInfo": {
|
||||
"shadows": {
|
||||
"__id__": 34
|
||||
},
|
||||
"postSettings": {
|
||||
"_skybox": {
|
||||
"__id__": 35
|
||||
},
|
||||
"fog": {
|
||||
"__id__": 36
|
||||
},
|
||||
"octree": {
|
||||
"__id__": 37
|
||||
},
|
||||
"skin": {
|
||||
"__id__": 38
|
||||
},
|
||||
"lightProbeInfo": {
|
||||
"__id__": 39
|
||||
},
|
||||
"postSettings": {
|
||||
"__id__": 40
|
||||
},
|
||||
"bakedWithStationaryMainLight": false,
|
||||
"bakedWithHighpLightmap": false
|
||||
},
|
||||
|
||||
225
assets/scripts/AStarPathfinding.ts
Normal file
225
assets/scripts/AStarPathfinding.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
9
assets/scripts/AStarPathfinding.ts.meta
Normal file
9
assets/scripts/AStarPathfinding.ts.meta
Normal 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
63
assets/scripts/Manager.ts
Normal 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) {
|
||||
|
||||
}
|
||||
}
|
||||
9
assets/scripts/Manager.ts.meta
Normal file
9
assets/scripts/Manager.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "8a6e8f34-f238-4dd9-8247-c6ece27ea4c2",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
152
assets/scripts/PathfindingTest.ts
Normal file
152
assets/scripts/PathfindingTest.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
9
assets/scripts/PathfindingTest.ts.meta
Normal file
9
assets/scripts/PathfindingTest.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "a1a1a2a3-4b5c-6d7e-8f9a-3c4d5e6f7g8h",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -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();
|
||||
console.log(`移动到路径点${this.currentPathIndex}: (${targetPos.x.toFixed(2)}, ${targetPos.y.toFixed(2)})`);
|
||||
|
||||
// 使用缓动移动到目标位置
|
||||
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;
|
||||
console.log('到达目标位置');
|
||||
} else {
|
||||
// 正常移动
|
||||
const newPosition = new Vec3();
|
||||
Vec3.scaleAndAdd(newPosition, currentPos, direction, moveDistance);
|
||||
this.currentPath = [];
|
||||
this.currentPathIndex = 0;
|
||||
}
|
||||
|
||||
// 确保新位置在地图边界内
|
||||
const clampedNewPosition = this.clampPlayerPosition(newPosition);
|
||||
this.player.position = clampedNewPosition;
|
||||
}
|
||||
update(deltaTime: number) {
|
||||
// 更新逻辑现在主要由缓动系统处理
|
||||
// 这里可以添加其他需要每帧更新的逻辑
|
||||
}
|
||||
}
|
||||
250
assets/scripts/TiledMapPathfinder.ts
Normal file
250
assets/scripts/TiledMapPathfinder.ts
Normal 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()
|
||||
};
|
||||
}
|
||||
}
|
||||
9
assets/scripts/TiledMapPathfinder.ts.meta
Normal file
9
assets/scripts/TiledMapPathfinder.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "9f9f8e8d-3b4c-5d6e-9f0e-2b3c4d5e6f7g",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
Reference in New Issue
Block a user