在上篇文章中我们介绍了Lazy Theta*。本篇中我会演示一下我实现的Lazy Theta*。

先上代码

//在一个点被expand时是否调用DebugOnExpanded事件,用于debug查看expand的范围。
#define _PATHDEBUGEVENT_
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using C5;
namespace CSPathFinding {
public interface IPFVertex<T> where T:IPFVertex<T> , IEquatable<T>{
/// <summary>
/// 检查视线
/// </summary>
/// <param name="other">另一点</param>
/// <returns>能否看到另一点</returns>
bool CheckLOS(T other);
/// <summary>
/// 该点到另一点的消耗
/// </summary>
/// <param name="other">另一点</param>
/// <returns></returns>
float Cost(T other);
/// <summary>
/// 获取该点的邻居
/// </summary>
/// <returns>邻居列表</returns>
IEnumerable<T> Neighbours();
} public static class BasicPathFind<T> where T : IPFVertex<T>, IEquatable<T> {
public class PathFinder {
public int maxIterations = 200;
public float heuristicWeight = 1.5f; struct _HPacker :IComparable<_HPacker>{
public _HPacker(T val,float gph) {
value = val;
this.GPH = gph;
}
public T value;
public float GPH; public int CompareTo(_HPacker other) {
if (GPH < other.GPH) {
return -1;
}
if (GPH > other.GPH)
return 1;
return 0;
}
} //priority queue from C5.
IntervalHeap<_HPacker> open = new IntervalHeap<_HPacker>();
System.Collections.Generic.HashSet<T> close = new System.Collections.Generic.HashSet<T>();
Dictionary<T, T> parent = new Dictionary<T, T>();
Dictionary<T, float> g = new Dictionary<T, float>();
public T start { get; private set; }
public T end{ get; private set; }
int iteration = 0;
public PathFinder(T start,T end,float heuristicWeight = 1.0f,int maxIterations = 200) {
this.start = start;
this.end = end;
this.heuristicWeight = heuristicWeight;
this.maxIterations = maxIterations;
parent[start] = start;
g[start] = 0;
open.Add(new _HPacker(start, g[start] + this.heuristicWeight * start.Cost(end)));
}
public event Action<T> DebugOnExpanded;
//the code here is basicly a translation of psuedo-code from http://aigamedev.com/wp-content/blogs.dir/5/files/2013/07/fig53-full.png
private bool InternalIterate() {
if (open.Count == 0)
return true;
var s = open.FindMin().value;
open.DeleteMin();
#if _PATHDEBUGEVENT_
if (DebugOnExpanded != null)
DebugOnExpanded(s);
#endif
if (close.Contains(s))
return false;
SetVertex(s);
close.Add(s);
if (iteration++ > maxIterations || s.Equals(end)) {
return true;
}
foreach (var sp in s.Neighbours()) {
if (!close.Contains(sp)) {
if (!g.ContainsKey(sp)) {
g[sp] = Mathf.Infinity;
parent.Remove(sp);
}
UpdateVertex(s, sp);
}
}
return false;
} public IEnumerable<T> QuickFind() {
while (!InternalIterate()) ; List<T> res = new List<T>();
var temp = close
.OrderBy(
v => v.Cost(end))
.FirstOrDefault();
if (temp == null)
return null;
while (!parent[temp].Equals(temp)) {
res.Add(temp);
temp = parent[temp];
}
res.Reverse();
return res;
} void UpdateVertex(T s,T sp) {
var gold = g[sp];
ComputeCost(s, sp);
if (g[sp] < gold) {
open.Add(new _HPacker(sp,g[sp]+ heuristicWeight * sp.Cost(end)));
}
}
void ComputeCost(T s,T sp) {
if (g[parent[s]] + parent[s].Cost(sp) < g[sp]) {
parent[sp] = parent[s];
g[sp] = g[parent[s]] + parent[s].Cost(sp);
}
}
void SetVertex(T s) {
if (!parent[s].CheckLOS(s)) {
var temp = s
.Neighbours()
.Intersect(close)
.Select(sp => new { sp = sp, gpc = g[sp] + sp.Cost(s) })
.OrderBy(sppair => sppair.gpc)
.FirstOrDefault();
if (temp == null)
return;
parent[s] = temp.sp;
g[s] = temp.gpc; ;
}
}
} public static PathFinder FindPath(T start, T end) {
return new PathFinder(start, end);
}
}
}

为了泛用性考虑,我使用了一个接口代表寻路模型中的节点。寻路时传入两个继承该接口的节点即可。如果寻路的节点时是临时生成的(在获取邻点的时候直接new出来的),那么应该实现自己的IEquatable方法来保证相等。

对自己的寻路模型实现IPFVertex后,调用BasicPathFinder.FindPath(s,e)得到一个PathFinder,然后调用PathFinder.QuickFind(),即可返回一个IPFVertex的IEnuemerable,表示路径。

主体代码基本上是上一篇中伪代码的直接翻译。但是我把循环打开节点放在了单独的方法中,这样以后可以修改成不要求单帧内完成的寻路。

  1. IntervalHeap来自一个github上的集合库C5(https://github.com/sestoft/C5/),功能类似c++中的priority queue,可以加速在open里查询最优先点的过程。需要注意的是这里不能用sortedList,因为sortedList按键值存储,重复的键会抛出异常。如果不用C5的话直接换成List也可以。
  2. close用了HashSet,因为只需要做求交、插入两种操作。
  3. parent用的是Dictionary,最快的实现应该是用数组表示,但是要求节点能够返回一个唯一表示自己的正数,太麻烦了。
  4. g用的Dictionary,理由同上。

原文中的UpdateVertex中做的操作是查询open里是否存在sp,存在则先移除,这是防止一个节点被多次expand。我的实现里去掉了这一行,相应的在expand节点时检测该节点是否已经被close。这是考虑到PriorityQueue的检测元素的效率较低。同时考虑到:一个节点多个存在于PriorityQueue中的话,只有优先值最低的那个会先出来,被expand。而被expand之后,就会被加入到close表里去。对于之后的多个相同节点,我们只需检测是否在close里就可以知道它们是不是被expand过了。

需要注意的是在许多讨论A*的文章中这一步的具体实现都是略过的,主要是同时支持优先级、调整优先级、检测存在这三个特性并且时间复杂度过得去的容器根本不存在,上面我提到的就是一种workaround,我之前刷题写的A*都是用的这种workaround,可以保证正确性。

加入了一个maxIteration的设置,因为如果目标点不可达,而不停止的话,最终所有可达的点都会被expand一遍,然后cpu就炸了。不过如果有一个点可达但只是路程太绕的话,这个设置可能会导致一直撞墙,还是需要自己取舍。

在到达maxiteration或者到达终点之后,会在所有close的点里找一个和终点最接近的。这样就算目标点在墙里,也会走到离目标点最近的地方,而不是原地不动,更加合理。同时如果太远的话,也可以分段寻路。

下面是一些演示:

右上角绿色是起点,左下角绿色是终点。

白色块的是进入过open的节点。

白线是最终的路径。

HeuristicWeight = 1.0f时(超过MaxIteration了,直接返回了中间的路径。)

HeuristicWeight = 1.5f时

在Unity(C#)下实现Lazy Theta*寻路的更多相关文章

  1. A*算法改进——Any-Angle Path Planning的Theta*算法与Lazy Theta*算法

    本文是该篇文章的归纳http://aigamedev.com/open/tutorial/lazy-theta-star/#Nash:07 . 传统的A*算法中,寻找出来的路径只能是沿着给出的模型(比 ...

  2. Unity Editor 下创建Lua和Text文件

    预览 在Project视图中,扩展右键菜单,右键 – Create - Text File 创建一个Text文件,或者Lua文件. 关键点 获取当前选择的路径,以Assets路径开头 var sele ...

  3. 单例模式在多线程环境下的lazy模式为什么要加两个if(instance==null)

    刚才在看阿寻的博客”C#设计模式学习笔记-单例模式“时,发现了评论里有几个人在问单例模式在多线程环境下为什么lazy模式要加两个if进行判断,评论中的一个哥们剑过不留痕,给他们写了一个demo来告诉他 ...

  4. 【Unity】12.2 导航网格寻路简单示例

    开发环境:Win10.Unity5.3.4.C#.VS2015 创建日期:2016-05-09 一.简介 本节通过一个简单例子,演示如何利用静态对象实现导航网格,并让某个动态物体利用导航网格自动寻路, ...

  5. Unity 安卓下DLL热更新一(核心思想)

    大家都知道一谈起热更新的话首选是Ulua这个插件, 其实Unity可以使用dll热更新的,如果你实在不想用Lua来编写逻辑,0.0请下看Dll+AssetBundle如何实现热更新的.让你看完这个文章 ...

  6. Unity编辑器下重启

    我们项目AssetBundle打包走的是全自动化流程,打包之前要进行各种资源检测,如果检测顺利通过,则进入打包,否则提示错误资源名称及路径,打包中断!有时候即使资源检测通过也会打包崩溃,初步断定是Un ...

  7. 在Unity环境下使用抽象和接口

    http://gamasutra.com/blogs/VictorBarcelo/20131217/207204/Using_abstractions_and_interfaces_with_Unit ...

  8. Unity引擎下最快的Xml读取器:UnityRapidXml

    近期发现无论是系统的System.Xml还是Mono.Xml,其实都有这样或者那样的问题,所以决定自己搞一个快一点的xml parse.以前在C++里用过rapidxml,这个确实是神一般的存在,速度 ...

  9. Unity编辑器下,界面替换NGUI字体以及字号

    项目中有需要批量替换字体以及字号的需求,一般也就是多语言处理吧. 提供界面如下: 手机拍图,就这样凑合看吧.但是代码不打折. 紧急避让,我只提供修改UILabel以及UIPopupList 下的字体, ...

随机推荐

  1. guacamole实现剪切复制

    主要功能是实现把堡垒机的内容复制到浏览器端,把浏览器端的文本复制到堡垒机上. 借助一个中间的文本框,现将堡垒机内容复制到一个文本框,然后把文本框内容复制出来.或者将需要传递到堡垒机的内容先复制到文本框 ...

  2. Linux使用imagemagick的convert命令压缩图片,节省服务器空间

    1,安装imagemagick yum install ImageMagick 2,获取图片 find ./ -regex '.*\(jpg\|JPG\|png\|jpeg\)' -size +500 ...

  3. Pc移植到Mac的技术细节

    1.样式不对: 2.布局不对: 3.Mac的菜单替换PC的菜单: Mac的菜单替换PC的菜单: 1)左上角图标没有手动添加且不需要添加的情况下出现,而且点击是Help菜单内容: 2)把HelpBtn和 ...

  4. Linux-Shell脚本编程-学习-8-函数

    在这章往后的学习中,我讲尽可能详细的讲书中讲到的都记录到这里,以便以后方便查看. 什么是函数,函数就是一段代码,这段代码可以在我们需要的位置调用,那么这段代码就叫做函数. 在Shell中,定义一个函数 ...

  5. deepin linux 安装/启动jeakins报错:处理

    ERROR: No Java executable found in current PATH: /bin:/usr/bin:/sbin:/usr/sbin 安装报错: 1.如还未安装java,则安装 ...

  6. 【性能监控】虚拟内存监控命令vmstat详解

    一.Vmstat说明 vmstat是Virtual Meomory Statistics(虚拟内存统计)的缩写,可对操作系统的虚拟内存.进程.CPU活动进行监控.vmstat 工具提供了一种低开销的系 ...

  7. PAT——乙级1015/甲级1062:德才论

    这两个题是一模一样的 1015 德才论 (25 point(s)) 宋代史学家司马光在<资治通鉴>中有一段著名的“德才论”:“是故才德全尽谓之圣人,才德兼亡谓之愚人,德胜才谓之君子,才胜德 ...

  8. winform timer时间间隔小于执行时间

    如果SetTimer的时间间隔为t,其响应事件OnTimer代码执行一遍的时间为T,且T>t.这样,一次未执行完毕,下一次定时到,这时候程序会如何执行? 可能的情况:1.丢弃还未执行的代码,开始 ...

  9. (转)MongoDB numa系列问题三:overcommit_memory和zone_reclaim_mode

    内核参数overcommit_memory : 它是 内存分配策略 可选值:0.1.2.0:表示内核将检查是否有足够的可用内存供应用进程使用:如果有足够的可用内存,内存申请允许:否则,内存申请失败,并 ...

  10. 详细介绍弹性盒模型(display:flex)

    弹性盒模型,即Flexbox,是css3中的新特性,其实弹性盒模型的原身是dispaly:box:这里,我们暂时不考虑旧的,我们只看新的. 为容器指定弹性盒子,只需在父元素(也就是容器)中设置:dis ...