流场寻路(Flow Field Path Finding)

2023-12-14 16:44

本文主要是介绍流场寻路(Flow Field Path Finding),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

简介

当场景中有成千上万个寻路游戏单位需要到达同一目标点时,通过常用的A*算法进行寻路不再是合适的选择,因为每个寻路游戏单位都需要依据自身所在的位置,根据算法获得一条从自身位置寻路到目标点的路径,n个游戏单位进行寻路就需要执行n次算法,这是比较大的性能开销。而流场寻路的方式便可以很好的解决这一问题,通常用于RTS(即时战略)游戏中的群体寻路。

将场景分割为x * y个网格节点,当确认目标点时,流场寻路算法会依据目标点所在的网格,依次向周围获取邻节点,计算邻节点到当前节点的代价。代价大的节点指向代价小的节点便可以形成一个方向,而这些节点形成的方向,便是流场,如下图所示。

流场形成后,寻路游戏单位只需要根据自身所在的坐标位置,获得对应的网格节点,依据节点的方向进行移动最终便可以到达目标点。

网格节点

节点类除了节点索引对应的字段x、y之外,还包括表示代价的字段cost与fCost,以及形成流场的节点方向字段。cost为基础代价,场景中可能会有多种地形,例如水泥地、沼泽地等等,不同地形有不同的代价,本文暂不考虑不同地形的情况,假设所有节点所在的地形是相同的,只考虑节点是否为可行走区域,通过字段isWalkable表示,而fCost是指节点到目标节点的最终代价。

using UnityEngine;public class FlowFieldPathFinding_GridNode
{public int x;public int y;public int cost;public int fCost;public Vector3 direction;public bool isWalkable;public FlowFieldPathFinding_GridNode(int x, int y, bool isWalkable){this.x = x;this.y = y;this.isWalkable = isWalkable;cost = isWalkable ? 10 : int.MaxValue;fCost = int.MaxValue;}
}

x * y个网格节点形成最终的网格,将所有的网格节点存储到一个字典中,通过索引i * x + j获取对应的网格节点,i表示第几行,j表示第几列,假设通过Texture资产存储地图数据,字节为255表示为可行走区域,否则为障碍区域。

public class FlowFieldPathFinding_Grid
{private readonly int x;private readonly int y;private readonly Dictionary<int, FlowFieldPathFinding_GridNode> nodesDic= new Dictionary<int, FlowFieldPathFinding_GridNode>();public FlowFieldPathFinding_Grid(int x, int y, bool[,] map){this.x = x;this.y = y;for (int i = 0; i < x; i++){for (int j = 0; j < y; j++){int index = i * x + j;nodesDic.Add(index, new FlowFieldPathFinding_GridNode(i, j, map[i, j]));}}}public FlowFieldPathFinding_Grid(int x, int y, Texture2D map){this.x = x;this.y = y;byte[] bytes = map.GetRawTextureData();if (bytes.Length != x * y)throw new ArgumentOutOfRangeException();for (int i = 0; i < x; i++){for (int j = 0; j < y; j++){int index = i * x + j;nodesDic.Add(index, new FlowFieldPathFinding_GridNode(i, j, bytes[index] == 255));}}}
}

算法思路

网格类需要提供设置目标节点的方法,设置目标节点时,将其放入一个队列中,当队列的数量大于0时,不断执行以下步骤:

  • 出列,获得当前节点;
  • 获取当前节点的邻节点;
  • 遍历邻节点,计算邻节点到当前节点的基础代价;
  • 邻节点到当前节点的基础代价加上当前节点的最终代价记为t,如果t小于邻节点的最终代价,设置邻节点的最终代价为t,并将其放入队列。
/// <summary>
/// 设置目标节点
/// </summary>
/// <param name="target"></param>
public void SetTarget(FlowFieldPathFinding_GridNode target)
{foreach (var node in nodesDic.Values){node.cost = node.isWalkable ? 10 : int.MaxValue;node.fCost = int.MaxValue;}target.cost = 0;target.fCost = 0;target.direction = Vector3.zero;Queue<FlowFieldPathFinding_GridNode> queue= new Queue<FlowFieldPathFinding_GridNode>();queue.Enqueue(target);while (queue.Count > 0){FlowFieldPathFinding_GridNode currentNode = queue.Dequeue();List<FlowFieldPathFinding_GridNode> neighbourNodes = GetNeighbouringNodes(currentNode);for (int i = 0; i < neighbourNodes.Count; i++){FlowFieldPathFinding_GridNode neighbourNode = neighbourNodes[i];if (neighbourNode.cost == int.MaxValue) continue;neighbourNode.cost = CalculateCost(neighbourNode, currentNode);if (neighbourNode.cost + currentNode.fCost < neighbourNode.fCost){neighbourNode.fCost = neighbourNode.cost + currentNode.fCost;queue.Enqueue(neighbourNode);}}}
}

搜索邻节点时有两种方式,四连通与八连通,前者是指向当前节点的上、下、左、右四个方向搜索邻节点,后者是指向当前节点的上、下、左、右、左上、右上、左下、右下八个方向搜索邻节点,多数情况下会使用八连通,本文也通过八连通的方式搜索邻节点。

/// <summary>
/// 获取指定节点的邻节点
/// </summary>
/// <param name="node">要获取邻节点的节点</param>
/// <returns>邻节点列表</returns>
public List<FlowFieldPathFinding_GridNode> GetNeighbouringNodes(FlowFieldPathFinding_GridNode node)
{List<FlowFieldPathFinding_GridNode> neighbours = new List<FlowFieldPathFinding_GridNode>();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 >= 0 && x < this.x && y >= 0 && y < this.y)neighbours.Add(nodesDic[x * this.x + y]);}}return neighbours;
}

计价方式

每向正上、下、左右方向走一步代价为1,根据勾股定理,每向斜方向走一步代价为根号2,近似1.414,而为了便于计算、节省性能,将正方向移动一步的代价记为10,斜方向移动一步的代价记为14,都取int整数。

//计算两个节点之间的代价
private int CalculateCost(FlowFieldPathFinding_GridNode node1, FlowFieldPathFinding_GridNode node2)
{//取绝对值int deltaX = node1.x - node2.x;if (deltaX < 0) deltaX = -deltaX;int deltaY = node1.y - node2.y;if (deltaY < 0) deltaY = -deltaY;int delta = deltaX - deltaY;if (delta < 0) delta = -delta;//每向上、下、左、右方向移动一个节点代价增加10//每斜向移动一个节点代价增加14(勾股定理,精确来说是近似14.14~)return 14 * (deltaX > deltaY ? deltaY : deltaX) + 10 * delta;
}

流场生成

设置目标节点计算各节点的代价后生成流场,遍历节点的邻节点,如果邻节点的最终代价小于当前节点的最终代价,那么当前节点的方向便是当前节点指向邻节点的方向。若出现代价相同的情况,需要考虑哪一个邻节点更加接近目标节点。

/// <summary>
/// 生成流场
/// </summary>
public void GenerateFlowField(FlowFieldPathFinding_GridNode target)
{foreach (var node in nodesDic.Values){List<FlowFieldPathFinding_GridNode> neighbourNodes = GetNeighbouringNodes(node);int fCost = node.fCost;FlowFieldPathFinding_GridNode temp = null;for (int i = 0; i < neighbourNodes.Count; i++){FlowFieldPathFinding_GridNode neighbourNode = neighbourNodes[i];if (neighbourNode.fCost < fCost){temp = neighbourNode;fCost = neighbourNode.fCost;node.direction = new Vector3(neighbourNode.x - node.x, 0, neighbourNode.y - node.y);}else if (neighbourNode.fCost == fCost && temp != null){if (CalculateCost(neighbourNode, target) < CalculateCost(temp, target)){temp = neighbourNode;fCost = neighbourNode.fCost;node.direction = new Vector3(neighbourNode.x - node.x, 0, neighbourNode.y - node.y);}}}}
}

根据流场方向移动

这篇关于流场寻路(Flow Field Path Finding)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/493273

相关文章

解决jupyterLab打开后出现Config option `template_path`not recognized by `ExporterCollapsibleHeadings`问题

《解决jupyterLab打开后出现Configoption`template_path`notrecognizedby`ExporterCollapsibleHeadings`问题》在Ju... 目录jupyterLab打开后出现“templandroidate_path”相关问题这是 tensorflo

解读静态资源访问static-locations和static-path-pattern

《解读静态资源访问static-locations和static-path-pattern》本文主要介绍了SpringBoot中静态资源的配置和访问方式,包括静态资源的默认前缀、默认地址、目录结构、访... 目录静态资源访问static-locations和static-path-pattern静态资源配置

python中os.stat().st_size、os.path.getsize()获取文件大小

《python中os.stat().st_size、os.path.getsize()获取文件大小》本文介绍了使用os.stat()和os.path.getsize()函数获取文件大小,文中通过示例代... 目录一、os.stat().st_size二、os.path.getsize()三、函数封装一、os

GNSS CTS GNSS Start and Location Flow of Android15

目录 1. 本文概述2.CTS 测试3.Gnss Flow3.1 Gnss Start Flow3.2 Gnss Location Output Flow 1. 本文概述 本来是为了做Android 14 Gnss CTS 的相关环境的搭建和测试,然后在测试中遇到了一些问题,去寻找CTS源码(/cts/tests/tests/location/src/android/locat

Python QT实现A-star寻路算法

目录 1、界面使用方法 2、注意事项 3、补充说明 用Qt5搭建一个图形化测试寻路算法的测试环境。 1、界面使用方法 设定起点: 鼠标左键双击,设定红色的起点。左键双击设定起点,用红色标记。 设定终点: 鼠标右键双击,设定蓝色的终点。右键双击设定终点,用蓝色标记。 设置障碍点: 鼠标左键或者右键按着不放,拖动可以设置黑色的障碍点。按住左键或右键并拖动,设置一系列黑色障碍点

MonoHuman: Animatable Human Neural Field from Monocular Video 翻译

MonoHuman:来自单目视频的可动画人类神经场 摘要。利用自由视图控制来动画化虚拟化身对于诸如虚拟现实和数字娱乐之类的各种应用来说是至关重要的。已有的研究试图利用神经辐射场(NeRF)的表征能力从单目视频中重建人体。最近的工作提出将变形网络移植到NeRF中,以进一步模拟人类神经场的动力学,从而动画化逼真的人类运动。然而,这种流水线要么依赖于姿态相关的表示,要么由于帧无关的优化而缺乏运动一致性

Understanding the GitHub Flow

这里看下Github的入门介绍    --链接 GitHub Flow is a lightweight, branch-based workflow that supports teams and projects where deployments are made regularly. This guide explains how and why GitHub Flow works

Flask 创建app 时候传入的 static_folder 和 static_url_path参数理解

Flask 在创建app的时候 是用 app = Flask(__name__) 来创建的,不传入 static_folder参数的话 ,默认的静态文件的位置是在 static目录下 我们可以进入 Flask的源码里面查看 ctrl+鼠标左键进入 这是Flask的 __init__源码(后面还有一些,我就选了需要的代码)     def __init__(self,import_

(4)SVG-path中的椭圆弧A(绝对)或a(相对)

1、概念 表示经过起始点(即上一条命令的结束点),到结束点之间画一段椭圆弧 2、7个参数 rx,ry,x-axis-rotation,large-arc-flag,sweep-flag,x,y (1)和(2)rx,ry rx:椭圆的x轴半径(即水平半径) ry:椭圆的y轴半径(即垂直半径) 这两个参数好理解,就是椭圆的两条对称轴半径,相等即为圆 也可以写比例,写比例时默认用符合条件

Versioned Staged Flow-Sensitive Pointer Analysis

VSFS 1.Introduction2.Approach2.1.相关概念2.2.VSFS 3.Evaluation参考文献 1.Introduction 上一篇blog我介绍了目前flow-sensitive pointer analysis常用的SFS算法。相比IFDS-based方法,SFS显著通过稀疏分析提升了效率,但是其内部依旧有许多冗余计算,留下了很大优化空间。 以