因为前面的没有开放列表 关闭列表 只是个贪心算法 现在重新写
A*寻路算法(四方向移动)完整步骤
- 初始化数据结构
开启列表:一个优先队列(按F值从小到大排列),存放待检查的节点
关闭列表:一个集合或字典,存放已检查过的节点
将起点加入开启列表,设置起点的:
G值 = 0(从起点到该点的实际代价)
H值 = 估计到终点的距离
F值 = G + H
父节点 = null - 主循环(处理开启列表)
WHILE 开启列表不为空:
取出F值最小的节点作为当前节点
将当前节点移到关闭列表
检查是否到达终点
如果是当前节点 == 终点:
回溯路径(通过父节点指针从终点回到起点)
返回找到的路径
遍历四个邻居(上下左右)
邻居节点坐标 = 当前节点坐标 + (0,1), (0,-1), (1,0), (-1,0)
对每个邻居节点检查:
是否在地图范围内?→ 如果否,跳过
是否是障碍物(墙)?→ 如果否,跳过
是否在关闭列表中?→ 如果否,跳过
为有效邻居节点计算代价值:
G_temp = 当前节点的G值 + 1(四方向,每次移动代价为1)
H值 = |邻居x - 终点x| + |邻居y - 终点y|(曼哈顿距离,四方向专用)
F_temp = G_temp + H
判断邻居是否在开启列表中:
如果不在:
创建邻居节点,设父节点 = 当前节点
设G值 = G_temp,F值 = F_temp
加入开启列表
如果已在开启列表:
比较G_temp与原来的G值
如果G_temp < 原G值(找到更优路径):
更新该节点的G值 = G_temp,F值 = F_temp
更新父节点 = 当前节点
- 循环结束
如果开启列表为空且没到达终点 → 路径不存在
返回"无路径"或空路径
go
import { _decorator, Collider2D, Component, Contact2DType, director, IPhysics2DContact, Node } from 'cc';
const { ccclass, property,executeInEditMode} = _decorator;
interface AStarNode {
x: number;
y: number;
f: number;
g: number;
h: number;
parent: AStarNode | null;
}
@ccclass('star')
@executeInEditMode
export class star extends Component {
juesex:number=null;
juesey:number=null;
mubiaox:number=null;
mubiaoy:number=null;
shangf:number=0;
shangg:number=0;
shangh:number=0;
zuof:number=0;
zuoh:number=0;
zuog:number=0;
youf:number=0;
youh:number=0;
youg:number=0;
xiaf:number=0;
xiah:number=0;
xiag:number=0;
shangx:number=0;
shangy:number=0;
zuox:number=0;
zuoy:number=0;
youx:number=0;
youy:number=0;
xiax:number=0;
xiay:number=0;
nextx:number=0;
nexty:number=0;
nextg:number=0;
private closedSet: Set<string> = new Set();
private openList: AStarNode[] = [];
onLoad(){
// 监听碰撞事件
const collider = this.getComponent(Collider2D);
if (collider) {
collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
}
director.on('gezi_ditu_xiaobing', this.huoquditu, this);
}
// 碰撞回调
onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
if(otherCollider.node.name.includes("shantou")){
this.scheduleOnce(()=>{
selfCollider.node.destroy();
},0.7);
}
}
start() {
director.emit('xiaobing_position_gezi', this.node.worldPosition);
}
update(deltaTime: number) {
}
huoquditu(ditu:[][]){
let len=ditu.length;
console.log(len+"=");
for(let y=0;y<len;y++){
for(let x=0;x<ditu[y].length;x++){
console.log(ditu[y][x]);
if(ditu[y][x]==1){
this.mubiaox=x;
this.mubiaoy=y;
//console.log("/"+this.mubiaox,this.mubiaoy)
}else if(ditu[y][x]==7){
this.juesex=x;
this.juesey=y;
//console.log("/"+this.juesex,this.juesey);
}
}
}
//this.jisuanf(this.juesex,this.juesey);
this.huoqushangxiazuoyou(this.juesex,this.juesey,0);
}
jisuannext(x:number,y:number,f:number,g:number){
if(f>0){
//g=g+1;
this.huoqushangxiazuoyou(x,y,(g+1));
}
}
huoqushangxiazuoyou(qidianx:number,qidiany:number,g:number){
this.openList = [];
this.closedSet.clear();
// 创建起点节点
const startNode: AStarNode = {
x: this.juesex,
y: this.juesey,
g: 0,
h: this.jisuanh(qidianx, qidiany),
f: 0,
parent: null
};
startNode.f = startNode.g + startNode.h;
// 添加到开放列表
this.openList.push(startNode);
while(this.openList.length>0){
//const dangqiangezi=// 1. 找到F值最小的节点
const dangqiangezi = this.openhuoquzuixiaofgezi();;//在开放列表先寻最小f的格子 由这个作为起点 开始计算四周格子 之前用的是递归能获取上一步的格子 现在是循环 得自己找
this.removeFromOpenList(dangqiangezi);//因为是准备走的格子 所以从开放列表移除 最后几个放在关闭列表
this.closedSet.add(''+dangqiangezi.x+','+dangqiangezi.y+'');
if(dangqiangezi.x==this.mubiaox&&dangqiangezi.y==this.mubiaoy){
console.log("find");
this.fujiedian(dangqiangezi);
}
// 检查四个方向的邻居
const neighbors = [
{dx: 0, dy: -1, name: '上'}, // 上
{dx: 0, dy: 1, name: '下'}, // 下
{dx: -1, dy: 0, name: '左'}, // 左
{dx: 1, dy: 0, name: '右'} // 右
];
for(let i=0;i<neighbors.length;i++){
let newx=neighbors[i].dx+dangqiangezi.x;
let newy=neighbors[i].dy+dangqiangezi.y;
// 边界检查
if (!this.isValidPosition(newx, newy)) {
continue;
}
//if(this.bianjie(newx,newy)==false){//跳过 不处理越界的
//continue;
//}
if(this.closedSet.has(''+newx+','+newy+'')){//跳过 不处理在关闭列表的
continue;
}
const newg=dangqiangezi.g+1;
const existingNode=this.shifouzaiopenlist(newx,newy);
if(existingNode==null){
// 创建起点节点
const newh=this.jisuanh(newx,newy);
const newNode: AStarNode = {//好像是将四周的格子都存进去 将走过的取出放进关闭 每次取出都是最小f
x: newx,
y: newy,
g:newg,
h: newh,
f: newg+newh,
parent: dangqiangezi
};
this.openList.push(newNode);
}
else{
// 如果找到更好的路径(g值更小),更新节点
if (newg< existingNode.g) {
//console.log(` 🔄 更新 ${dir.name}[${neighborX},${neighborY}] g=${tentativeG} < ${existingNode.g}`);
existingNode.g =newg;
existingNode.f = existingNode.h+existingNode.g;
existingNode.parent = dangqiangezi;
}
}}
}
}
// 添加边界检查方法
isValidPosition(x: number, y: number): boolean {
return x >= 0 && x < 10 && y >= 0 && y < 10;
}
jisuanh(x:number,y:number){
return Math.abs(x-this.mubiaox)+Math.abs(y-this.mubiaoy);
}
openhuoquzuixiaofgezi(): AStarNode | null{
if (this.openList.length === 0) return null;
let minIndex = 0;
let minF = this.openList[0].f;
for (let i = 1; i < this.openList.length; i++) {
if (this.openList[i].f < minF) {
minF = this.openList[i].f;
minIndex = i;
}
}
return this.openList[minIndex];
}
private removeFromOpenList(node: AStarNode) {
const index = this.openList.findIndex(n =>
n.x === node.x && n.y === node.y
);
if (index !== -1) {
this.openList.splice(index, 1);
}
}
bianjie(x:number,y:number): boolean{
return x >= 0 && x < 10 && y >= 0 && y < 10;
}
shifouzaiopenlist(x:number,y:number): AStarNode | null{
for (const node of this.openList) {
if (node.x === x && node.y === y) {
return node;
}
}
return null;
}
fujiedian(endnode:AStarNode){
const path: {x: number, y: number}[] = [];
let currentNode: AStarNode | null = endnode;
// 从终点回溯到起点
while (currentNode) {
path.unshift({x: currentNode.x, y: currentNode.y});
currentNode = currentNode.parent;
}
// 发送路径信息给gezi组件
this.sendPathToGrid(path);
}
// 发送路径到网格显示
sendPathToGrid(path: {x: number, y: number}[]) {
// 排除起点(角色当前位置)
const displayPath = path.slice(1);
// 发送每条路径点
for (let i = 0; i < displayPath.length; i++) {
const point = displayPath[i];
// 发送路径点数据,9表示路径点
const data = {
type: 9, // 路径点类型
x: point.x,
y: point.y,
index: i,
total: displayPath.length
};
director.emit('xiaobing_path_point', data);
}
}
}
格子
go
import { _decorator, Color, Component, director, Graphics, Node, Vec3 } from 'cc';
const { ccclass, property,executeInEditMode} = _decorator;
@ccclass('gezi')
@executeInEditMode
export class gezi extends Component {
@property
gridWidth: number = 10; // 水平格子数
@property
gridHeight: number = 10; // 垂直格子数
@property
cellSize: number = 100; // 每个格子的大小(像素)
@property
lineWidth: number = 2; // 线宽
@property
lineColor: Color = Color.GRAY; // 线颜色
@property
targetColor: Color = new Color(1, 27, 3); // 线颜色
@property
jueseColor: Color = new Color(1, 27, 3); // 线颜色
@property
fillColor: Color = new Color(240, 240, 240); // 填充颜色(浅灰)
@property
xiaColor: Color = new Color(240, 240, 240); // 填充颜色(浅灰)
@property
zuixiaoColor: Color = new Color(240, 240, 240); // 填充颜色(浅灰)
@property
zhangaiColor: Color = new Color(240, 240, 240); // 填充颜色(浅灰)
@property(Node)
xiaobing:Node=null;
private graphics: Graphics | null = null;
gezi=[[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,1,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
];
shangx:number=0;
shangy:number=0;
shangf:number=0;
xianshigezi=[[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,1,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
];
onLoad() {
director.on('xiaobing_sxzy_gezi', this.huasxzy, this);
director.on('xiaobing_path_point', this.showPathPoint, this);
// 1. 创建并获取 Graphics 组件
this.graphics = this.node.getComponent(Graphics);
if (!this.graphics) {
this.graphics = this.node.addComponent(Graphics);
}
// 2. 绘制网格
director.on('xiaobing_position_gezi', this.xiaobingposition, this);
// 获取角色位置并标记
const worldPos = this.xiaobing.getWorldPosition();
const gridPos = this.worldPositionToGrid(worldPos);
console.log("转换结果:", gridPos);
// 关键修正:检查边界并更新数组
if (this.isInGrid(gridPos.x, gridPos.y)) {
// 注意:你的数组是 gezi[y][x] 结构!所以要 [gridPos.y][gridPos.x]
this.gezi[gridPos.y][gridPos.x] = 7;
this.xianshigezi[gridPos.y][gridPos.x] = 7;
} else {
console.warn("角色在网格外!位置:", gridPos);
}
this.drawGrid(this.gezi);
director.emit('gezi_ditu_xiaobing', this.gezi);
}
// 添加边界检查方法
private isInGrid(x: number, y: number): boolean {
return x >= 0 && x < this.gridWidth && y >= 0 && y < this.gridHeight;
}
// 在gezi.ts中添加一个**可靠的**坐标转换方法
public worldPositionToGrid(worldPos: Vec3): { x: number, y: number } {
const gridWorldPos = this.node.getWorldPosition();
const offsetX = worldPos.x - gridWorldPos.x;
const offsetY = worldPos.y - gridWorldPos.y;
// DEBUG: 打印调试信息
//console.log("=== 坐标转换调试 ===");
//console.log("1. 网格中心:", gridWorldPos);
//console.log("2. 角色位置:", worldPos);
//console.log("3. 偏移量 offsetX=", offsetX, "offsetY=", offsetY);
// 网格半宽/半高 (注意:这里有坐标轴方向问题)
const halfWidth = (this.gridWidth * this.cellSize) / 2;
const halfHeight = (this.gridHeight * this.cellSize) / 2;
// Cocos Y轴向上为正,但数组通常是左下角为(0,0)
// 所以需要调整Y轴方向
const gridX = Math.floor((offsetX + halfWidth) / this.cellSize);
const gridY = Math.floor((offsetY + halfHeight) / this.cellSize);
// Y轴翻转(因为屏幕上方是正Y,但数组从上到下索引)
const flippedY = (this.gridHeight - 1) - gridY;
//console.log(`4. 计算:gridX=${gridX} (${offsetX}+${halfWidth})/${this.cellSize}`);
//console.log(`5. 计算:gridY=${gridY} (${offsetY}+${halfHeight})/${this.cellSize}`);
//console.log(`6. 翻转后:gridX=${gridX}, flippedY=${flippedY}`);
//console.log(`7. 结果:[${this.gezi[flippedY][gridX]}]`);
// 使用 flippedY 因为数组是从上到下存储
return { x: gridX, y: flippedY };
}
huoquditu(){
return this.gezi;
}
huasxzy(data:any){
//console.log("....");
//console.log("/"+data.shangf+"z"+data.zuof+"y"+data.youf+"x"+data.xiaf);
let xiaof=0;
//this.hua(data.shangx,data.shangy);
//this.hua(data.zuox,data.zuoy);
//this.hua(data.youx,data.youy);
//this.hua(data.xiax,data.xiay);
this.huazuixiaof(data.zuixiaofangxiangx,data.zuixiaofangxiangy,data.mubiaox,data.mubiaoy,data.juesex,data.juesey);
this.drawGrid(this.xianshigezi);
}
hua(x:number,y:number){
this.xianshigezi[y][x]=4;
}
huazuixiaof(x:number,y:number,mubiaox:number,mubiaoy:number,juesex:number,juesey:number){
//let mubiaox=0;
//let mubiaoy=0;
if(this.gezi[y][x]==11){
console.log("no");
return;
}
// 判断是否是目标点
const isTargetPoint = (x === mubiaox && y === mubiaoy);
if(isTargetPoint) {
console.log(`⭐ 这是目标点[${x},${y}],保持为1`);
// 保持目标标记不变,或者标记为其他区分色
this.xianshigezi[y][x] = 1; // 保持原目标值
} else {
// 普通路径点
console.log(`📝 标记路径点[${x},${y}]为9`);
this.xianshigezi[y][x] = 9;
}
}
// 显示路径点
showPathPoint(data: any) {
const { x, y, type, index, total } = data;
if (this.isInGrid(x, y)) {
this.xianshigezi[y][x] = type; // type为9表示路径点
// 可以添加特殊标记,比如根据index显示不同颜色
console.log(`路径点 ${index + 1}/${total}: [${x}, ${y}]`);
}
this.drawGrid(this.xianshigezi);
}
public gridToWorldPosition(gridX: number, gridY: number): Vec3 {
const gridWorldPos = this.node.getWorldPosition();
// 计算世界坐标(格子中心点)
const worldX = gridWorldPos.x + (gridX - this.gridWidth / 2 + 0.5) * this.cellSize;
const worldY = gridWorldPos.y +
(gridY - this.gridHeight / 2 + 0.5) * this.cellSize;
return new Vec3(worldX, worldY, 0);
}
xiaobingposition(position:Vec3){
console.log("xiaobing",position);
}
drawGrid(g:number[][]) {
if (!this.graphics) return;
// 清空之前的绘制
this.graphics.clear();
// 设置线条样式
this.graphics.lineWidth = this.lineWidth;
this.graphics.strokeColor = this.lineColor;
// 首先填充背景色
this.graphics.fillColor = this.fillColor;
// 计算网格的总宽高(用于居中显示)
const totalWidth = this.gridWidth * this.cellSize;
const totalHeight = this.gridHeight * this.cellSize;
// 设置绘制原点在节点中心(Cocos默认)
// 也可以从左上角开始画,看个人习惯
// 绘制每个格子的填充背景
for (let x = 0; x < this.gridWidth; x++) {
for (let y = 0; y < this.gridHeight; y++) {
let huoqugezi=g[y][x];
// 然后反转Y坐标,让第一行显示在最上面
const screenY = this.gridHeight - 1 - y;
const posX = (x - this.gridWidth/2) * this.cellSize;
//const posY = (y - this.gridWidth/2) * this.cellSize;
const posY = (screenY - this.gridHeight/2) * this.cellSize;
if(huoqugezi==1){
this.graphics.fillColor = this.targetColor;
//this.graphics.rect(posX, posY, this.cellSize, this.cellSize);
//console.log(huoqugezi+"shi");
}else if(huoqugezi==7){
this.graphics.fillColor = this.jueseColor;
//this.graphics.rect(posX, posY, this.cellSize, this.cellSize);
//console.log(huoqugezi+"shi");
}else if(huoqugezi==4){
this.graphics.fillColor = this.xiaColor;
}else if(huoqugezi==9){
this.graphics.fillColor = this.zuixiaoColor;
}
else if(huoqugezi==11){
this.graphics.fillColor = this.zhangaiColor;
}
else{
this.graphics.fillColor = this.fillColor;
}
this.graphics.rect(posX, posY, this.cellSize, this.cellSize);
//console.log(huoqugezi);
this.graphics.fill();
}
}
// 绘制垂直线
for (let x = 0; x <= this.gridWidth; x++) {
const lineX = (x - this.gridWidth/2) * this.cellSize;
const startY = (-this.gridHeight/2) * this.cellSize;
const endY = (this.gridHeight/2) * this.cellSize;
this.graphics.moveTo(lineX, startY);
this.graphics.lineTo(lineX, endY);
}
// 绘制水平线
for (let y = 0; y <= this.gridHeight; y++) {
const lineY = (y - this.gridHeight/2) * this.cellSize;
const startX = (-this.gridWidth/2) * this.cellSize;
const endX = (this.gridWidth/2) * this.cellSize;
this.graphics.moveTo(startX, lineY);
this.graphics.lineTo(endX, lineY);
}
// 描边完成绘制
this.graphics.stroke();
// 可选:绘制坐标标签(调试用)
this.drawCoordinates();
}
// 绘制坐标标签(可选,调试用)
private drawCoordinates() {
const font = '14px Arial';
for (let x = 0; x < this.gridWidth; x++) {
for (let y = 0; y < this.gridHeight; y++) {
const centerX = (x - this.gridWidth/2) * this.cellSize + this.cellSize/2;
const centerY = (y - this.gridHeight/2) * this.cellSize + this.cellSize/2;
// Graphics 不支持文字,可以用Label或另想办法
// 这里先注释,下文有改进方法
}
}
}
// 获取格子中心的世界坐标(有用!)
public getCellCenterPosition(gridX: number, gridY: number): { x: number, y: number } {
const worldX = this.node.position.x +
(gridX - this.gridWidth/2) * this.cellSize +
this.cellSize/2;
const worldY = this.node.position.y +
(gridY - this.gridHeight/2) * this.cellSize +
this.cellSize/2;
return { x: worldX, y: worldY };
}
// 世界坐标转格子坐标(有用!)
public worldToGrid(worldX: number, worldY: number): { x: number, y: number } {
const localX = worldX - this.node.position.x;
const localY = worldY - this.node.position.y;
const gridX = Math.floor((localX + this.gridWidth * this.cellSize / 2) / this.cellSize);
const gridY = Math.floor((localY + this.gridHeight * this.cellSize / 2) / this.cellSize);
return { x: gridX, y: gridY };
}
// 绘制带颜色的格子(用于标记障碍、起点、终点等)
public drawColoredCell(gridX: number, gridY: number, color: Color) {
if (!this.graphics) return;
const posX = (gridX - this.gridWidth/2) * this.cellSize;
const posY = (gridY - this.gridHeight/2) * this.cellSize;
this.graphics.fillColor = color;
this.graphics.rect(posX, posY, this.cellSize, this.cellSize);
this.graphics.fill();
}
}