A* 算法

 

简单描述一下A*原理:

两个集合:

OpenList: 可到达的格子

CloseList: 已到达的格子

一个公式:

F = G + H

解释一下:F、G、H是格子的3个属性

G: 从起点走到当前格子的成本,也就是已经花费了多少步。

H: 在不考虑障碍的情况下,从当前格子走到目标格子的距离,也就是距离目标还有多远。

F: G 和 H的综合评估,也就是从起点到达当前格子,再从当前格子到达目标格子的总步数。

第1步,先把起点放入OpenList。

OpenList: Grid(1,2)

CloseList: 

第2步,找出OpenList中F值最小的方格作为当前方格。把当前格子移除OpenList,放入CloseList。代表这个格子已经到达并且检查过了。

OpenList: 

CloseList: Grid(1,2)

第3步,找出当前格子(刚刚检查过的格子)上、下、左、右所有可到达的格子,看看它们是否在OpenList或者CloseList当中。如果不在,则把它们加入OpenList当中,并计算出相应的G、H、F的值,并把当前格子作为它们的“父节点”。

OpenList: Grid(1,1)  Grid(0,2)  Grid(2,2)  Grid(1,3)

CloseList: Grid(1,2)        

Grid(1,2) 是 Grid(1,1)  Grid(0,2)  Grid(2,2)  Grid(1,3) 的父节点

找出F值最小的(F=4)进入下一轮操作,操作方式同上述三步,知道OpenList中出现终点格子为止,最终CloseList就是最终结果。

 

源码:

AStarAlgorithm.ts

export class Grid {
    x: number = 0;
    y: number = 0;
    f: number = 0; // f = g + h
    g: number = 0; // 从起点走到当前格子的成本
    h: number = 0; // 不考虑障碍物的情况下,从当前位置走到终点的成本
    parent: Grid = null;

    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }

    initGrid(parent: Grid, end: Grid) {
        this.parent = parent;
        if (this.parent !== null) {
            this.g = parent.g + 1;
        } else {
            this.g = 1;
        }
        this.h = Math.abs(this.x - end.x) + Math.abs(this.y - end.y);
        this.f = this.g + this.h;
    }
}


export class AStarAlgorithm {
    MAZE: Array<Array<number>> = [];

    private _aStarSearch(start: Grid, end: Grid): Grid {
        let openList: Array<Grid> = [];
        let closeList: Array<Grid> = [];

        openList.push(start);    // 把起点假如openList
        while (openList.length > 0) {
            let currentGrid = this._findMinGrid(openList);
            let index = openList.indexOf(currentGrid);
            openList.splice(index, 1);
            closeList.push(currentGrid);

            let neighbors: Array<Grid> = this._findNeighbors(currentGrid, openList, closeList);

            for (let key in neighbors) {
                if (openList.indexOf(neighbors[key]) === -1) {
                    neighbors[key].initGrid(currentGrid, end);
                    openList.push(neighbors[key]);
                }
            }

            for (let key in openList) {
                if (openList[key].x === end.x && openList[key].y === end.y) {
                    return openList[key];
                }
            }
        }
        return null;
    }

    private _findMinGrid(openList: Array<Grid>): Grid {
        let tempGrid = openList[0];
        for (let key in openList) {
            if (openList[key].f < tempGrid.f) {
                tempGrid = openList[key];
            }
        }
        return tempGrid;
    }

    private _findNeighbors(grid: Grid, openList: Array<Grid>, closeList: Array<Grid>): Array<Grid> {
        let gridList: Array<Grid> = [];
        if (this._isValidGrid(grid.x, grid.y - 1, openList, closeList)) {
            gridList.push(new Grid(grid.x, grid.y - 1));
        }
        if (this._isValidGrid(grid.x, grid.y + 1, openList, closeList)) {
            gridList.push(new Grid(grid.x, grid.y + 1));
        }
        if (this._isValidGrid(grid.x - 1, grid.y, openList, closeList)) {
            gridList.push(new Grid(grid.x - 1, grid.y));
        }
        if (this._isValidGrid(grid.x + 1, grid.y, openList, closeList)) {
            gridList.push(new Grid(grid.x + 1, grid.y));
        }
        return gridList;
    }

    private _isValidGrid(x: number, y: number, openList: Array<Grid>, closeList: Array<Grid>): boolean {
        // 是否超过边界
        if (x < 0 || x >= this.MAZE.length || y < 0 || y >= this.MAZE[0].length) {
            return false;
        }

        // 是否有障碍物
        if (this.MAZE[x][y] === 1) {
            return false;
        }

        // 是否已经在openList中
        if (this._containGrid(openList, x, y)) {
            return false;
        }

        // 是否已经在closeList中 
        if (this._containGrid(closeList, x, y)) {
            return false;
        }

        return true;
    }

    private _containGrid(grids: Array<Grid>, x: number, y: number): boolean {
        for (let key in grids) {
            if (grids[key].x === x && grids[key].y === y) {
                return true;
            }
        }
        return false;
    }

    public testMain(maze: Array<Array<number>>, sp: cc.Vec2, ep: cc.Vec2): Array<Array<number>> {
        this.MAZE = maze;
        let startGrid: Grid = new Grid(sp.x, sp.y);
        let endGrid: Grid = new Grid(ep.x, ep.y);
        let resultGrid: Grid = this._aStarSearch(startGrid, endGrid);

        let path: Array<Grid> = [];
        while (resultGrid) {
            path.push(new Grid(resultGrid.x, resultGrid.y));
            resultGrid = resultGrid.parent;
        }

        for (let i = 0; i < this.MAZE.length; i++) {
            let str = "";
            for (let j = 0; j < this.MAZE[0].length; j++) {
                if (this._containGrid(path, i, j)) {
                    // str += "*, ";
                    this.MAZE[i][j] = 4;
                } else {
                    // str += "" + this.MAZE[i][j] + ", ";
                }
            }
            console.log(str);
        }
        return this.MAZE;
    }
}

AStar.ts

import { AStarAlgorithm } from "./AStarAlgorithm";

const { ccclass, property } = cc._decorator;

@ccclass
export default class AStar extends cc.Component {

    @property(cc.Node)
    nodeMap: cc.Node = null;

    @property(cc.Prefab)
    prfTile: cc.Prefab = null;

    @property
    rowNum: number = 10;

    @property
    columnNum: number = 10;

    @property
    startRow: number = 1;

    @property
    startColumn: number = 1;

    @property(cc.Color)
    colorNormal: cc.Color = cc.Color.WHITE;

    @property(cc.Color)
    colorStart: cc.Color = cc.Color.GREEN;

    @property(cc.Color)
    colorEnd: cc.Color = cc.Color.RED;

    @property(cc.Color)
    colorWall: cc.Color = cc.Color.BLUE;

    // 保存地图的二维数组
    mapArr: Array<Array<number>> = [];

    clickCount: number = 0;      // 记录开始/结束位置
    startPos: cc.Vec2 = null;    // 开始行-列
    endPos: cc.Vec2 = null;      // 结束行-列

    MAZE = [
        [0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 1, 0, 0, 0],
        [0, 0, 0, 1, 0, 0, 0],
        [0, 0, 0, 1, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0],
    ];

    onLoad() {
        this.node.on('clickCell', this.cellClick.bind(this), this);
    }

    start() {
        this.createMap();
    }

    private cellClick(data: any) {
        let tag = data.detail.tag;
        let type = data.detail.type;

        let row = Math.floor(tag / this.columnNum);
        let col = tag % this.columnNum;

        if (type === 0) {
            ++this.clickCount;
            if (this.clickCount % 2 === 1) {
                this.refreshMap();
                // 设置开始位置
                let tile = this.nodeMap.getChildByName("" + tag);
                if (tile) {
                    let tileCtrl = tile.getComponent("Tile");
                    this.mapArr[row][col] = 2;
                    tileCtrl.setType(2);
                    this.startPos = cc.v2(row, col);
                }
            } else {
                // 设置结束位置
                let tile = this.nodeMap.getChildByName("" + tag);
                if (tile) {
                    let tileCtrl = tile.getComponent("Tile");
                    this.mapArr[row][col] = 3;
                    tileCtrl.setType(3);
                    this.endPos = cc.v2(row, col);
                    this.findWay();
                }
            }
        } else {
            console.log('4不4洒!!!');
        }
    }

    // 初始化地图
    public createMap() {
        this.nodeMap.removeAllChildren(true);

        let mapSize = this.nodeMap.getContentSize();
        let tileWidth = mapSize.width / this.columnNum;
        let tileHeight = mapSize.height / this.rowNum;

        let startX = -mapSize.width / 2 + tileWidth / 2;
        let startY = mapSize.height / 2 - tileHeight / 2;

        this.mapArr = [];
        let wallArr = this.createWall();
        for (let i = 0; i < this.rowNum; i++) {
            let arr: Array<number> = [];
            for (let j = 0; j < this.columnNum; j++) {
                let tile = cc.instantiate(this.prfTile);
                tile.setContentSize(cc.size(tileWidth, tileHeight));
                let pos = cc.v2(startX + j * tileWidth, startY - i * tileHeight);
                tile.setPosition(pos);

                let tileCtrl = tile.getComponent("Tile");
                let tag = i * this.columnNum + j;
                let type = 0;
                if (wallArr.indexOf(tag) != -1) {
                    type = 1;
                }
                tileCtrl.init(tag, type);

                arr.push(type);
                this.nodeMap.addChild(tile);
            }
            this.mapArr.push(arr);
        }
    }

    // 创建墙
    public createWall(wallNum: number = 0): Array<number> {
        let mapSize = this.rowNum * this.columnNum;
        let wn = (wallNum > 0 && wallNum < mapSize) ? wallNum : mapSize / 10;
        let wallArr: Array<number> = [];
        for (let i = 0; i < wn; i++) {
            let rn = Math.floor(Math.random() * mapSize);
            if (wallArr.indexOf(rn) != -1)
                continue;
            wallArr.push(rn);
        }
        return wallArr;
    }

    // 更新地图
    public onBtnNewMap(event: cc.Event, data: string) {
        if (event.type === cc.Node.EventType.TOUCH_END) {
            this.createMap();
        }
    }

    // 重置地图:即重置开始以及结束位置
    public onBtnResetMap(event: cc.Event, data: string) {
        if (event.type === cc.Node.EventType.TOUCH_END) {
            this.refreshMap();
        }
    }

    private refreshMap() {
        for (let i = 0; i < this.mapArr.length; i++) {
            for (let j = 0; j < this.mapArr[0].length; j++) {
                let index = i * this.columnNum + j;
                let tile = this.nodeMap.getChildByName('' + index);
                if (tile) {
                    let tileCtrl = tile.getComponent("Tile");
                    if (tileCtrl.type !== 0 && tileCtrl.type !== 1) {
                        tileCtrl.setType(0);
                        this.mapArr[i][j] = 0;
                    }
                }
            }
        }
    }

    public findWay() {
        let asa = new AStarAlgorithm();
        let maze = asa.testMain(this.mapArr, this.startPos, this.endPos);

        for (let i = 0; i < maze.length; i++) {
            for (let j = 0; j < maze[i].length; j++) {
                let tag = i * this.columnNum + j;
                let tile = this.nodeMap.getChildByName('' + tag);
                if (tile) {
                    let tileCtrl = tile.getComponent("Tile");
                    if (maze[i][j] === 4 && tileCtrl.type === 0) {
                        tileCtrl.setType(4);
                        this.mapArr[i][j] = 4;
                    }
                }
            }
        }
    }

}

Tile.ts


const { ccclass, property } = cc._decorator;

@ccclass
export default class Tile extends cc.Component {

    @property(cc.Sprite)
    sprTile: cc.Sprite = null;

    tag: number = 0;     // 唯一标识
    type: number = 0;    // 0:可走 1:墙 2:起始位置 3:终点位置 4:寻路路径

    onLoad() {

    }

    start() {
        this.node.on(cc.Node.EventType.TOUCH_START, this.onTouchStart, this);
        this.node.on(cc.Node.EventType.TOUCH_MOVE, this.onTouchMove, this);
        this.node.on(cc.Node.EventType.TOUCH_END, this.onTouchEnd, this);
    }

    private onTouchStart() {

    }

    private onTouchMove() {

    }

    private onTouchEnd() {
        console.log("[Tile] onTouchEnd tag = " + this.tag);
        // this.node.emit('clickCell', this.tag);

        var e = new cc.Event.EventCustom("clickCell", true);
        e.detail = { tag: this.tag, type: this.type };
        this.node.dispatchEvent(e);
    }

    public init(tag: number, type: number) {
        this.tag = tag;
        this.type = type;
        this.node.name = "" + this.tag;

        this.setTileColor();
    }

    public setType(type: number) {
        this.type = type
        this.setTileColor();
    }

    private setTileColor() {
        if (this.type === 0) {
            this.sprTile.node.color = cc.Color.WHITE;
        } else if (this.type === 1) {
            this.sprTile.node.color = cc.Color.BLACK;
        } else if (this.type === 2) {
            this.sprTile.node.color = cc.Color.GREEN;
        } else if (this.type === 3) {
            this.sprTile.node.color = cc.Color.RED;
        } else if (this.type === 4) {
            this.sprTile.node.color = cc.Color.YELLOW;
        }
    }

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值