游戏人工智能——A*寻路算法实践

简介: A*寻路算法实践一、题目背景随着多媒体设备、虚拟现实、增强现实、物联网等技术的飞跃发展,计算速度与存储容量的日益提高以及相关软件的研究取得长足进步,人工智能的应用得以进一步推广发展起来。地图寻径问题是人工智能技术的一个重要领域。在网络游戏中,寻径问题必须考虑多方面的因素,比如游戏地图中文件结构和起点与目标点之间是否可以连通以及游戏运行时运行内存资源占用、可扩展更新性、安全程度等。长久以来,游戏开发者在开发过程中为了实现这些绞尽脑汁。在搜索寻径问题中,Dijkstra算法是目前许多工程解决最短路径

 A*寻路算法实践

一、题目背景

随着多媒体设备、虚拟现实、增强现实、物联网等技术的飞跃发展,计算速度与存储容量的日益提高以及相关软件的研究取得长足进步,人工智能的应用得以进一步推广发展起来。地图寻径问题是人工智能技术的一个重要领域。在网络游戏中,寻径问题必须考虑多方面的因素,比如游戏地图中文件结构和起点与目标点之间是否可以连通以及游戏运行时运行内存资源占用、可扩展更新性、安全程度等。长久以来,游戏开发者在开发过程中为了实现这些绞尽脑汁。

在搜索寻径问题中,Dijkstra算法是目前许多工程解决最短路径问题采用的理论基础,只是不同工程对Dijkstra算法在不同方面采用了各异的改进方法。常见的搜索算法有深度优先算法(DFS )、广度优先算法(BFS )、迭代加深算法、双向广度优先搜索算法、IDA* 算法、A* 搜索等。它们分为盲目式与启发式两种。盲目式是指其没有目的性的搜索,其可以在一定的规则下全局进行搜索;而启发式则是指有目的性地朝着一定的目标进行搜索。深度优先算法意在每次先向深度中进行搜索,如果未找到结果,再换一条路线进行搜索查询,直到查询到结果。此算法较为容易理解,会占用更小的内存,但是可能会需要更长的时间。广度优先算法每次查询时,首先搜索周围,如若没有查找到结果,则扩大范围,直到查询到结果。此算法虽然速度较快但是占用内存偏多,可以应用到许多关于有速度要求的应用上。迭代加深算法在深度优先算法基础上,增加了一个深度记录,以防止在深度优先算法中无限制地搜索,等其查找到一定的深度时就进行返回。IDA*算法在迭代加深算法基础上增加了一个评估函数,此函数可以对接下来的路径进行评估,从而更快地预估出路径是否存在结果,即可以说是对迭代加深算法的一种优化。但是困难的地方在于评估函数的编写,如果评估函数优秀,那效果良好。相反,如果评估函数一般,它可能会和上一代有着相同的浪费程度。

A * 算法是游戏开发中应用最为广泛的一种路径搜索方法,可以说它是广度优先算法( BFS )的一个升级版。此算法使用了Dijkstra算法的节点信息、广度优先算法的节点扩展方式、加入队列判重、路径点估价函数等。 A * 算法的广泛应用证实了其在搜索路径中的优势与地位。其在人工智能中属于一种典型的启发式搜索算法。而相对于理论,更难的是如何将其应用在游戏工程中,因为游戏中需要考虑到更多元素,包括起点终点连通性以及障碍物、人物碰撞体等。

二、算法原理

   A * 算法是游戏开发中人工智能寻路方面最为常用的算法之一。此算法类似于对广度优先算法的优化,其原理也是在查询点周围进行寻找,其不同点

在于以下两个方面:

   第一方面为加入了估价函数:

F(n)=g(n)+h(n)

g(n)代表某点n从当前位置上距离起点的代价函数值,它是由有限个数起始点与目标点代价函数的值累加构成。h(n)代表点n从其位置上距离终点的代价函数值。即用F(n)代表n点总的路径代价。常用的经典启发函数代价计算方法 h(n)有:曼哈顿距离(Manhattan Distance)、对角线距离、欧几里得距离(Euclidean Distance)等。

第二方面为增加两个表(Open-List与Close-List)来进行路径点的筛选与扩展,更好地统计整理路径点,通过判断和比较来节省资源消耗。

三、算法实现

算法具体过程描述如下:

① 通过循环提取Open-List中的值并不断地比较寻找此时列表中代价最低的路径点,将代价最低点移除Open-List,加入 Close-List后进入 ②。持续循环此步骤直到Open-List中的路径点个数为0 。

② 判断此路径点是否为寻路终点,若是则计算路径,直接进入④。否则,获得此点周围的非障碍点,进入③ 。

③ 判断周围非障碍点是否已经存在于Open-List中,如果存在,重新计算并更新路径点代价函数值。如若不存在,计算路径点代价函数值后并将路径点储存在Open-List中。后将 ① 中的提取点与此周围非障碍点设为父子关系,方便最终路径点的提取。

④ 得到最终点,依次根据节点的父子关系寻找父节点并存入数列中,直至寻找到初始路径点。数列中所有点的集合,即为寻路路径。

四、程序流程

//Grid.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class Grid : MonoBehaviour {
    public GameObject NodeWall;
    public GameObject Node;
    // 节点半径
    public float NodeRadius = 0.5f;
    // 过滤墙体所在的层
    public LayerMask WhatLayer;
    // 玩家
    public Transform player;
    // 目标
    public Transform destPos;
    /// <summary>
    /// 寻路节点
    /// </summary>
    public class NodeItem {
        // 是否是障碍物
        public bool isWall;
        // 位置
        public Vector3 pos;
        // 格子坐标
        public int x, y;
        // 与起点的长度
        public int gCost;
        // 与目标点的长度
        public int hCost;
        // 总的路径长度
        public int fCost {
            get {return gCost + hCost; }
        }
        // 父节点
        public NodeItem parent;
        public NodeItem(bool isWall, Vector3 pos, int x, int y) {
            this.isWall = isWall;
            this.pos = pos;
            this.x = x;
            this.y = y;
        }
    }
    private NodeItem[,] grid;
    private int w, h;
    private GameObject WallRange, PathRange;
    private List<GameObject> pathObj = new List<GameObject> ();
    void Awake() {
        // 初始化格子
        w = Mathf.RoundToInt(transform.localScale.x * 2);
        h = Mathf.RoundToInt(transform.localScale.y * 2);
        grid = new NodeItem[w, h];
        WallRange = new GameObject ("WallRange");
        PathRange = new GameObject ("PathRange");
        // 将墙的信息写入格子中
        for (int x = 0; x < w; x++) {
            for (int y = 0; y < h; y++) {
                Vector3 pos = new Vector3 (x*0.5f, y*0.5f, -0.25f);
                // 通过节点中心发射圆形射线,检测当前位置是否可以行走
                bool isWall = Physics.CheckSphere (pos, NodeRadius, WhatLayer);
                // 构建一个节点
                grid[x, y] = new NodeItem (isWall, pos, x, y);
                // 如果是墙体,则画出不可行走的区域
                if (isWall) {
                    GameObject obj = GameObject.Instantiate (NodeWall, pos, Quaternion.identity) as GameObject;
                    obj.transform.SetParent (WallRange.transform);
                }
            }
        }
    }
    // 根据坐标获得一个节点
    public NodeItem getItem(Vector3 position) {
        int x = Mathf.RoundToInt (position.x) * 2;
        int y = Mathf.RoundToInt (position.y) * 2;
        x = Mathf.Clamp (x, 0, w - 1);
        y = Mathf.Clamp (y, 0, h - 1);
        return grid [x, y];
    }
    // 取得周围的节点
    public List<NodeItem> getNeibourhood(NodeItem node) {
        List<NodeItem> list = new List<NodeItem> ();
        for (int i = -1; i <= 1; i++) {
            for (int j = -1; j <= 1; j++) {
                // 如果是自己,则跳过
                if (i == 0 && j == 0)
                    continue;
                int x = node.x + i;
                int y = node.y + j;
                // 判断是否越界,如果没有,加到列表中
                if (x < w && x >= 0 && y < h && y >= 0)
                    list.Add (grid [x, y]);
            }
        }
        return list;
    }
    // 更新路径
    public void updatePath(List<NodeItem> lines) {
        int curListSize = pathObj.Count;
        for (int i = 0, max = lines.Count; i < max; i++) {
            if (i < curListSize) {
                pathObj [i].transform.position = lines [i].pos;
                pathObj [i].SetActive (true);
            } else {
                GameObject obj = GameObject.Instantiate (Node, lines [i].pos, Quaternion.identity) as GameObject;
                obj.transform.SetParent (PathRange.transform);
                pathObj.Add (obj);
            }
        }
        for (int i = lines.Count; i < curListSize; i++) {
            pathObj [i].SetActive (false);
        }
    }
}

image.gif

//FindPath.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class FindPath : MonoBehaviour {
    private Grid grid;
    // Use this for initialization
    void Start () {
        grid = GetComponent<Grid> ();
    }
    // Update is called once per frame
    void Update () {
        FindingPath (grid.player.position, grid.destPos.position);
    }
    // A*寻路
    void FindingPath(Vector3 s, Vector3 e) {
        Grid.NodeItem startNode = grid.getItem (s);
        Grid.NodeItem endNode = grid.getItem (e);
        List<Grid.NodeItem> openSet = new List<Grid.NodeItem> ();
        HashSet<Grid.NodeItem> closeSet = new HashSet<Grid.NodeItem> ();
        openSet.Add (startNode);
        while (openSet.Count > 0) {
            Grid.NodeItem curNode = openSet [0];
            for (int i = 0, max = openSet.Count; i < max; i++) {
                if (openSet [i].fCost <= curNode.fCost &&
                    openSet [i].hCost < curNode.hCost) {
                    curNode = openSet [i];
                }
            }
            openSet.Remove (curNode);
            closeSet.Add (curNode);
            // 找到的目标节点
            if (curNode == endNode) {
                generatePath (startNode, endNode);
                return;
            }
            // 判断周围节点,选择一个最优的节点
            foreach (var item in grid.getNeibourhood(curNode)) {
                // 如果是墙或者已经在关闭列表中
                if (item.isWall || closeSet.Contains (item))
                    continue;
                // 计算当前相领节点现开始节点距离
                int newCost = curNode.gCost + getDistanceNodes (curNode, item);
                // 如果距离更小,或者原来不在开始列表中
                if (newCost < item.gCost || !openSet.Contains (item)) {
                    // 更新与开始节点的距离
                    item.gCost = newCost;
                    // 更新与终点的距离
                    item.hCost = getDistanceNodes (item, endNode);
                    // 更新父节点为当前选定的节点
                    item.parent = curNode;
                    // 如果节点是新加入的,将它加入打开列表中
                    if (!openSet.Contains (item)) {
                        openSet.Add (item);
                    }
                }
            }
        }
        generatePath (startNode, null);
    }
    // 生成路径
    void generatePath(Grid.NodeItem startNode, Grid.NodeItem endNode) {
        List<Grid.NodeItem> path = new List<Grid.NodeItem>();
        if (endNode != null) {
            Grid.NodeItem temp = endNode;
            while (temp != startNode) {
                path.Add (temp);
                temp = temp.parent;
            }
            // 反转路径
            path.Reverse ();
        }
        // 更新路径
        grid.updatePath(path);
    }
    // 获取两个节点之间的距离
    int getDistanceNodes(Grid.NodeItem a, Grid.NodeItem b) {
        int cntX = Mathf.Abs (a.x - b.x);
        int cntY = Mathf.Abs (a.y - b.y);
        // 判断到底是那个轴相差的距离更远
        if (cntX > cntY) {
            return 14 * cntY + 10 * (cntX - cntY);
        } else {
            return 14 * cntX + 10 * (cntY - cntX);
        }
    }
}

image.gif

五、程序运行结果

image.gif编辑

image.gif编辑

红色小球作为起始点,而蓝色小球作为目标点,键盘的wasd控制蓝色小球的移动,键盘方向键可以控制红色小球的移动;A*寻路算法可以实现在迷宫中用最短路径找寻目标点,上图中蓝色的路径就是算法所呈现出的最短路径。本次实验使用A*算法实现了一款类似迷宫的小游戏,可以已最快的路径实现起始点与目标点的相遇。

注释:

本次实验是使用unity游戏引擎进行实践。是对网上开源代码与模型进行修改与学习。

相关文章
|
6月前
|
机器学习/深度学习 人工智能 算法
当AI提示词遇见精密算法:TimeGuessr如何用数学魔法打造文化游戏新体验
TimeGuessr融合AI与历史文化,首创时间与空间双维度评分体系,结合分段惩罚、Haversine距离计算与加权算法,辅以连击、速度与完美奖励机制,实现公平且富挑战性的游戏体验。
|
6月前
|
算法 机器人 定位技术
【机器人路径规划】基于流场寻路算法(Flow Field Pathfinding)的机器人路径规划(Python代码实现)
【机器人路径规划】基于流场寻路算法(Flow Field Pathfinding)的机器人路径规划(Python代码实现)
357 4
机器学习/深度学习 算法 自动驾驶
1151 0
|
6月前
|
算法 API 数据安全/隐私保护
深度解析京东图片搜索API:从图像识别到商品匹配的算法实践
京东图片搜索API基于图像识别技术,支持通过上传图片或图片URL搜索相似商品,提供智能匹配、结果筛选、分页查询等功能。适用于比价、竞品分析、推荐系统等场景。支持Python等开发语言,提供详细请求示例与文档。
|
9月前
|
监控 算法 安全
公司电脑监控软件关键技术探析:C# 环形缓冲区算法的理论与实践
环形缓冲区(Ring Buffer)是企业信息安全管理中电脑监控系统设计的核心数据结构,适用于高并发、高速率与短时有效的多源异构数据处理场景。其通过固定大小的连续内存空间实现闭环存储,具备内存优化、操作高效、数据时效管理和并发支持等优势。文章以C#语言为例,展示了线程安全的环形缓冲区实现,并结合URL访问记录监控应用场景,分析了其在流量削峰、关键数据保护和高性能处理中的适配性。该结构在日志捕获和事件缓冲中表现出色,对提升监控系统效能具有重要价值。
257 1
|
10月前
|
监控 算法 数据处理
基于 C++ 的 KD 树算法在监控局域网屏幕中的理论剖析与工程实践研究
本文探讨了KD树在局域网屏幕监控中的应用,通过C++实现其构建与查询功能,显著提升多维数据处理效率。KD树作为一种二叉空间划分结构,适用于屏幕图像特征匹配、异常画面检测及数据压缩传输优化等场景。相比传统方法,基于KD树的方案检索效率提升2-3个数量级,但高维数据退化和动态更新等问题仍需进一步研究。未来可通过融合其他数据结构、引入深度学习及开发增量式更新算法等方式优化性能。
247 17
|
10月前
|
存储 算法 安全
如何控制上网行为——基于 C# 实现布隆过滤器算法的上网行为管控策略研究与实践解析
在数字化办公生态系统中,企业对员工网络行为的精细化管理已成为保障网络安全、提升组织效能的核心命题。如何在有效防范恶意网站访问、数据泄露风险的同时,避免过度管控对正常业务运作的负面影响,构成了企业网络安全领域的重要研究方向。在此背景下,数据结构与算法作为底层技术支撑,其重要性愈发凸显。本文将以布隆过滤器算法为研究对象,基于 C# 编程语言开展理论分析与工程实践,系统探讨该算法在企业上网行为管理中的应用范式。
274 8
|
10月前
|
存储 监控 算法
基于 C# 时间轮算法的控制局域网上网时间与实践应用
在数字化办公与教育环境中,局域网作为内部网络通信的核心基础设施,其精细化管理水平直接影响网络资源的合理配置与使用效能。对局域网用户上网时间的有效管控,已成为企业、教育机构等组织的重要管理需求。这一需求不仅旨在提升员工工作效率、规范学生网络使用行为,更是优化网络带宽资源分配的关键举措。时间轮算法作为一种经典的定时任务管理机制,在局域网用户上网时间管控场景中展现出显著的技术优势。本文将系统阐述时间轮算法的核心原理,并基于 C# 编程语言提供具体实现方案,以期深入剖析该算法在局域网管理中的应用逻辑与实践价值。
246 5
|
人工智能 算法 计算机视觉
【01】opencv项目实践第一步opencv是什么-opencv项目实践-opencv完整入门以及项目实践介绍-opencv以土壤和水滴分离的项目实践-人工智能AI项目优雅草卓伊凡
【01】opencv项目实践第一步opencv是什么-opencv项目实践-opencv完整入门以及项目实践介绍-opencv以土壤和水滴分离的项目实践-人工智能AI项目优雅草卓伊凡
483 63
【01】opencv项目实践第一步opencv是什么-opencv项目实践-opencv完整入门以及项目实践介绍-opencv以土壤和水滴分离的项目实践-人工智能AI项目优雅草卓伊凡
|
机器学习/深度学习 人工智能 算法
基于Python深度学习的眼疾识别系统实现~人工智能+卷积网络算法
眼疾识别系统,本系统使用Python作为主要开发语言,基于TensorFlow搭建卷积神经网络算法,并收集了4种常见的眼疾图像数据集(白内障、糖尿病性视网膜病变、青光眼和正常眼睛) 再使用通过搭建的算法模型对数据集进行训练得到一个识别精度较高的模型,然后保存为为本地h5格式文件。最后使用Django框架搭建了一个Web网页平台可视化操作界面,实现用户上传一张眼疾图片识别其名称。
652 5
基于Python深度学习的眼疾识别系统实现~人工智能+卷积网络算法