Laya中的A星寻路

寻路概述

在这里插入图片描述
如图,为了简便通常将整个寻路区域划分为方格。格子又称节点,也可以根据实际操作用其他形状或者直接用点位代替。绿色为起点A,红色为终点B,蓝色为不可行的障碍物节点。

寻路关键的是两个列表和一个路径排序公式,开放列表openList,封闭列表closeList,F = G + H。openList存放着待检查的节点,closeList存放着检查过的节点,参考值 = 起点到当前点的代价值 + 当前点到终点的预估值

通过遍历开放列表,计算出从 A 到 B的最短路线需要走哪些节点就找到了路径。

流程如下:

  1. 将起点加入到openList中,开始搜索。
  2. 重复如下搜索步骤:
    a. 遍历openList,查找F值最小的节点。
    b. 将F值最小的点做为当前要处理的节点,加入到closeList中。
    c. 对当前节点的相邻节点做如下操作:
    I. 忽略掉不可同行和在closeList中的节点
    II. 如果节点不在openList中,则加入到openList中,并且把当前节点设置为它的父节点,计算出该节点的 F ,G , H 值。否则,检查经由该节点到达终点是否更近。如果更近的话,将当前节点替换为该节点并重新计算它的F,G,H的值。
  3. 当把终点加入到openList中时,路径已经找到。或者没有节点可以加入到openList中时,则路径无法找到。

路径排序

路径排序的关键是这个公式:
F = G + H
G : 从起点A移动到当前节点的移动代价值。
H: 从当前节点移动到终点B的预估代价值。H值的计算有很多方法。最简单的有“曼哈顿方法”:忽略障碍物和对角线移动,直接计算当前节点横向和纵向移动到终点的距离**(为了方便计算,取10为节点格子的边长。14为对角线长度)**。这是剩余距离的预估带价值,而不是实际值,所以又称为试探法。还有计算对角线和总长度等等方法。
在这里插入图片描述
通过曼哈顿方法计算出的值是这样的。每个节点的左下角是G值, 右下角是H值,左上角是F值

开始搜索

  1. 首先将起点A放到openList,开始搜索。
    在这里插入图片描述
    如图,检查与A点相邻的节点。将可以行走的节点加到openList中,并设置为A点的子节点,这样就有了最初的9个节点。
    然后将A点从openList中移除,加入到closeList中。封闭列表中的点不在需要关注的。
    然后开始计算剩下8个节点的F,G,H值。
    在这里插入图片描述起点右边的节点(白框标注的点)的 F 值最小,因此我们选择这个节点作为当前要处理的节点。
继续搜索
  1. 在这里插入图片描述

将当前节点从openList移到closeList中,然后我们检查与它相邻的节点。忽略掉不可行节点和closeList中节点。剩下 4 个相邻的节点均在 openList 中,那就使用 G 值来判定经由这些节点到终点的距离是不是更近。先计算上面的节点,它现在的 G 值为 14 。如果我们经由当前节点, G 值将会变为 20 (其中 10 为到达当前方格的 G 值,此外还要加上从当前方格纵向移动到上面方格的 G 值 10)。显然 20 比 14 大,因此这不是最优的路径。所以将右下角的节点替换为当前的节点。
2. 这次在检查相邻节点的时候,忽略掉墙下一格的节点。这样会防止从墙角直接穿过的情况。*( 注意:穿越墙角的规则是可选的,依赖于你的节点是怎么放置的 )*当前方格下面的 2 个节点还没有加入 openList ,所以把它们加入,同时把当前节点设为他们的父节点。
3. 在剩下的3个节点中,有2个已经在 closeLlist 中 ( 一个是起点,一个是当前方格上面的方格 ) ,忽略它们。最后一个方格,也就是当前方格左边的方格,我们检查经由当前方格到达那里是否具有更小的 G 值。没有。因此我们从 openList 中选择下一个待处理的方格。
4. 不断重复这个过程,直到把终点也加入到了 openList 中。在这里插入图片描述

Laya中的实现

Nodes代码
export default class Nodes{
    
    

    /**列 */
    public x : number;

    /**行 */
    public y : number;

    /**
     * 代价值 F = G + H;
     */
    public f : number;

    /**
     * G 起点到当前点的代价值
     */
    public g : number;

    /**
     *  H 当前点到终点的预估代价值
     */
    public h : number;

    /**
     * 允许行走 
     * default true
     */
    public walkable : boolean = true;

    /**
     * 父节点
     */
    public parentNode: Nodes;

    /**
     * 横竖方向每个节点的代价值
     */
    public costMultiplier : number = 1;

    public constructor ($x: number, $y: number){
    
    
        this.x = $x;
        this.y = $y;
    }
}
Gird代码
import Nodes from "./Nodes";

export default class Gird{
    
    

    /**起点 */
    private _startNodes  : Nodes;

    /**终点 */
    private _endNodes    : Nodes;

    /**Node数组 */
    private _nodesArry   : Array<any>;

    /**网格列 */
    private _columnNum   : number;

    /**网格行 */
    private _rowsNum     : number;

    public constructor($column: number, $rows: number){
    
    
        this._columnNum = $column;
        this._rowsNum   = $rows;

        // 创建网格数组
        this._nodesArry = new Array();
        for (let i = 0; i < this._columnNum; i++) {
    
    
            this._nodesArry[i] = new Array();
            for (let j = 0; j < this._rowsNum; j++) {
    
    
                this._nodesArry[i][j] = new Nodes(i, j);
            }
        }
    }

    public getNodes($x: number, $y: number){
    
    
        return this._nodesArry[$x][$y];
    }

    public setStartNodes($x: number, $y: number){
    
    
        this._startNodes = this._nodesArry[$x][$y];
    }

    public setEndNodes($x: number, $y: number){
    
    
        this._endNodes = this._nodesArry[$x][$y];
    }

    public setWalkable($x: number, $y: number, $bool: boolean){
    
    
        this._nodesArry[$x][$y].walkable = $bool;
    }

    public get columnNum(): number{
    
    
        return this._columnNum;
    }

    public get rowsNum(): number{
    
    
        return this._rowsNum;
    }

    public get startNodes(): Nodes{
    
    
        return this._startNodes;
    }

    public get endNodes(): Nodes{
    
    
        return this._endNodes;
    }

}
寻路代码
import Gird from "./Gird";
import Nodes from "./Nodes";

export default class AStar{
    
    

    /**待检查列表 */
    private openList   : Array<any>;

    /**已检查列表 */
    private closedList : Array<any>

    /**节点网格 */
    private gird       : Gird;

    /**起点Nodes */
    private startNode  : Nodes;

    /**终点Nodes */
    private endNode    : Nodes;

    /**路径 */
    private pathList   : Array<any>;

    /**
     * 计算预估代价的算法
     * 
     */
    private heuristic   : Function;

    /**上下左右走的代价 */
    private straightCost : number = 1.0;

    /**斜着走的代价 */
    private diagCost    : number = 1.4;

    public constructor(){
    
    
        this.heuristic = this.diagonal;
    }

    private diagonal($node: Nodes){
    
    
        let count_column = Math.abs($node.x - this.endNode.x);
        let count_rows   = Math.abs($node.y - this.endNode.y);

        let diagon   = Math.min(count_column, count_rows);
        let staright = count_column + count_rows;
        return this.diagCost * diagon + this.straightCost * (staright - 2 * diagon);
    }

    private euclidian($node: Nodes){
    
    
        let count_column = Math.abs($node.x - this.endNode.x);
        let count_rows   = Math.abs($node.y - this.endNode.y);
        // straightCost是上下走的代价,此处应该为斜着走的代价diagCost
        return Math.sqrt(count_column * count_column + count_rows * count_rows) * this.diagCost;
    }

    // 曼哈顿算法
    private manhattan($node: Nodes){
    
    
        let count_column = Math.abs($node.x - this.endNode.x);
        let count_rows   = Math.abs($node.y - this.endNode.y);
        // straightCost是上下走的代价,此处应该为斜着走的代价diagCost
        return (count_column + count_rows) * this.straightCost;
    }

    // 未检查
    private isOpen($node: Nodes){
    
    
        for (let index = 0; index < this.openList.length; index++) {
    
    
            if(this.openList[index] == $node){
    
    
                return true;
            }
        }
        return false;
    }

    // 已检查
    private isClosed($node: Nodes){
    
    
        for (let index = 0; index < this.closedList.length; index++) {
    
    
            if (this.closedList[index] == $node){
    
    
                return true;
            }
        }
        return false;
    }

    public search(): boolean{
    
    
        let nodes: Nodes = this.startNode;
        while (nodes != this.endNode) {
    
    
            let startX = Math.max(0, nodes.x - 1);
            let endX   = Math.min(this.gird.columnNum - 1, nodes.x + 1);
            let startY = Math.max(0, nodes.y - 1);
            let endY   = Math.min(this.gird.rowsNum - 1, nodes.y + 1);

            for (let i = startX; i <= endX; i++) {
    
    
                for (let j = startY; j <= endY; j++) {
    
    
                    // 不让斜着走
                    // if(i != nodes.x && j != nodes.y) continue;

                    let testNodes : Nodes = this.gird.getNodes(i, j);
                    if (testNodes == nodes || 
                        !testNodes.walkable ||
                        !this.gird.getNodes(nodes.x, testNodes.y).walkable || 
                        !this.gird.getNodes(testNodes.x, nodes.y).walkable){
    
    
                        continue;
                    }
                    
                    let cost : number = this.straightCost;
                    if(!((nodes.x == testNodes.x) || (nodes.y == testNodes.y))){
    
    
                        cost = this.diagCost;
                    }
                    
                    let value_g = nodes.g + cost * testNodes.costMultiplier;
                    let value_h = this.heuristic(testNodes);
                    let value_f = value_g + value_h;
                    if (this.isOpen(testNodes) || this.isClosed(testNodes)){
    
    
                        if (testNodes.f > value_f){
    
    
                            testNodes.f = value_f;
                            testNodes.g = value_g;
                            testNodes.h = value_h;
                            testNodes.parentNode = nodes;
                        }
                    }else{
    
    
                        testNodes.f = value_f;
                        testNodes.g = value_g;
                        testNodes.h = value_h;
                        testNodes.parentNode = nodes;
                        this.openList.push(testNodes);
                    }
                }
            }

            this.closedList.push(nodes);
            if (this.openList.length <= 0){
    
    
                console.error("AStar can`t find path");
                return false;
            }

            for (let m = 0; m < this.openList.length; m++) {
    
    
                for (let n = m + 1; n < this.openList.length; n++) {
    
    
                    if (this.openList[m].f > this.openList[n].f){
    
    
                        let temp         = this.openList[m];
                        this.openList[m] = this.openList[n];
                        this.openList[n] = temp;
                    }
                }
            }

            nodes = this.openList.shift() as Nodes;
        }

        this.buildPath();
        return true;
    }

    private buildPath(): void{
    
    
        this.pathList     = new Array();
        let nodes : Nodes = this.endNode;
        this.pathList.push(nodes);
        while(nodes != this.startNode){
    
    
            nodes = nodes.parentNode;
            this.pathList.unshift(nodes);
        }
    }

    public findPath($gird: Gird): boolean{
    
    
        this.gird       = $gird;
        this.openList   = new Array();
        this.closedList = new Array();

        this.startNode  = this.gird.startNodes;
        this.endNode    = this.gird.endNodes;

        this.startNode.g = 0;
        this.startNode.h = this.heuristic(this.startNode);
        this.startNode.f = this.startNode.g + this.startNode.h;

        return this.search();
    }

    get path(){
    
    
        return this.pathList;
    }
}
界面代码

import Grid from "../astar/Gird";
import Node from "../astar/Nodes";
import AStar from "../astar/AStar";

export default class GameUI extends Laya.Scene {
    
    

    private _player   : Laya.Sprite;
    private _grid     : Grid;
	private _index    : number;
	private _path     : Array<any>;
	private _width_n  : number;
	private _height_n : number;

    constructor(){
    
    
        super();
    }

    onAwake(){
    
    
		this._width_n  = Math.floor(Laya.stage.width / 36);
		this._height_n = Math.floor(Laya.stage.height / 84);
		console.log("长宽比:", this._width_n, this._height_n);
		this.makePlayer();
        this.makeGrid();

		Laya.stage.on(Laya.Event.CLICK, this, this.onGridClick);
    }


    makePlayer(){
    
    
        console.log("makePlayer");
        this._player = new Laya.Sprite();
        this._player.graphics.drawCircle(0,0,5,"0xff0000");
        this._player.x = Math.random() * 36 * this._width_n;
        this._player.y = Math.random() * 84 * this._height_n;
        Laya.stage.addChild(this._player);
    }

    makeGrid(){
    
    
        this._grid = new Grid(36, 84);
        for(let index = 0; index < 200; index++)
		{
    
    
			this._grid.setWalkable(Math.floor(Math.random() * 36), 
			Math.floor(Math.random() * 84), false);
		}
		this.drawGrid();
    }

    private drawGrid():void
	{
    
    
		this.graphics.clear();
		for(let i = 0; i < this._grid.columnNum; i++)
		{
    
    
			for(let j = 0; j <this._grid.rowsNum; j++)
			{
    
    
				var node : Node =this._grid.getNodes(i, j);
				let sp:Laya.Sprite = new Laya.Sprite();
				sp.graphics.drawRect( 0, 0, this._width_n, this._height_n, this.getColor(node));
				sp.x = i * this._width_n;
				sp.y = j * this._height_n;
				this.addChild(sp);
			}
		}
		this.addChild(this._player);
	}

    private getColor(node : Node)
	{
    
    
		if(!node.walkable) return 0;
		if(node == this._grid.startNodes) return 0xcccccc;
		if(node == this._grid.endNodes) return 0xcccccc;
		return 0xffffff;
	}

	private onGridClick(event:any):void
	{
    
    
		var xpos = Math.floor(event.stageX / this._width_n);
		var ypos = Math.floor(event.stageY / this._height_n);
		console.log("位置:", xpos, ypos);
		if (xpos >= 36 || ypos >= 84) return;
		this._grid.setEndNodes(xpos, ypos);
		
		xpos = Math.floor(this._player.x / this._width_n);
		ypos = Math.floor(this._player.y / this._height_n);
		this._grid.setStartNodes(xpos, ypos);
		
		this.drawGrid();
		this.findPath();
	}

	private findPath():void
	{
    
    
		var aStar : AStar = new AStar();
		if(aStar.findPath(this._grid))
		{
    
    
			this._path = aStar.path;
			this._index = 0;
			console.log("路径:", this._path, this._index);
			Laya.timer.loop(100, this, this.onEnterFrame);
		}
	}
	
	private onEnterFrame():void
	{
    
    
		var targetX = this._path[this._index].x * this._width_n +  this._width_n / 2;
		var targetY = this._path[this._index].y * this._height_n + this._height_n / 2;
		var dx = targetX - this._player.x;
		var dy = targetY - this._player.y;
		var dist = Math.sqrt(dx * dx + dy * dy);
		// console.log("距离:", dist);
		if(dist < 1) {
    
    
			this._index++;
			if(this._index >= this._path.length)
			{
    
    
				Laya.timer.clear(this, this.onEnterFrame);
			}
		} else {
    
    
			this._player.x += dx * .5;
			this._player.y += dy * .5;
		}
	}

}

猜你喜欢

转载自blog.csdn.net/the_vnas/article/details/125994477