目录
第一部分:常见问题
1.请简述GC(垃圾回收)产生的原因,并描述如何避免?
答:GC回收堆上的内存,
避免方式:
● 减少new产生对象的次数
● 使用公用的对象(静态成员)
● stringbuilder
2.渲染管线,每个阶段包含哪些流程
渲染管线是built-in Render Pipeline(内置渲染管线)
物体剔除阶段
• 视锥体剔除:判断物体是否在摄像机的视锥体范围内。在视锥体外的物体不会被渲染,这样可以减少不必要的渲染计算。
• 遮挡剔除:确定一个物体是否被其他物体遮挡。如果被遮挡,且系统开启了遮挡剔除功能,这个物体就不会进入渲染流程,这也有助于提高渲染效率。
光照计算阶段
• 环境光计算:考虑场景中的环境光对物体的影响,环境光通常会给物体一个基础的亮度。
•
直接光照计算:像平行光(比如太阳光)、点光、聚光灯等光源对物体的光照效果计算。这可能涉及到光照模型,如兰伯特(Lambert)光照模型用于漫反射计算,以及基于镜面反射的光照模型等,来确定物体表面不同位置的光照强度和颜色。
材质处理阶段
• 材质属性读取:从材质资源中读取相关属性,如漫反射颜色、高光反射颜色、金属度、粗糙度等。这些属性会影响物体表面的外观。
•
材质与光照结合:根据材质属性和前面计算的光照,确定物体表面最终呈现的颜色。例如,金属材质和非金属材质在光照下的表现会有很大差异,通过材质和光照的结合计算可以真实地模拟这种差异。
3.Interface接口与抽象类之间的不同
- 接口只有方法等的声明,没有实现,用于定义行为规范,一个类可实现多个接口;
- 抽象类可以有抽象方法和有具体实现的非抽象方法,一个类只能继承一个抽象类。
4.Net与Mono的关系?
答:mono是.net的一个开源跨平台工具,就类似java虚拟机,java本身不是跨平台语言,但运行在虚拟机上就能够实现了跨平台。.net只能在windows下运行,mono可以实现跨平台编译运行,可以运行于Linux,Unix,Mac OS等。
5.前后端协议?
pb,json
6.向量的点乘、叉乘以及归一化的意义?
答: 1)点乘描述了两个向量的相似程度,结果越大两向量越相似,还可表示投影
2)叉乘得到的向量垂直于原来的两个向量
3)标准化向量:用在只关系方向,不关心大小的时候
7.协同程序
答:
实现原理 : yield return指定一个等待条件,每帧查看条件是否满足,如果满足就继续执行
使用场景 :1. 加载资源 2. 动画控制 3. 逻辑控制 4. 特效与粒子系统控制
8.设计原则 具体 设计模式用在什么地方为什么这么用
概述序列化: 答:序列化简单理解成把对象转换为容易传输的格式的过程。比如,可以序列化一个对象,然后使用HTTP通过Internet在客户端和服务器端之间传输该对象
9.C#中的排序方式有哪些?
答:选择排序,冒泡排序,快速排序,插入排序,希尔排序,归并排序
10.客户端与服务器交互方式有几种?
答: socket通常也称作"套接字",实现服务器和客户端之间的物理连接,并进行数据传输,主要有UDP和TCP两个协议。Socket处于网络协议的传输层。 http协议传输的主要有http协议 和基于http协议的Soap协议(web service),常见的方式是 http 的post 和get 请求,web 服务。
打包工具
是公司自己写的,但是在打包之前还要进行一系列的操作比如svn update,检查资源格式,等等
jenkins是工具的工具,jenkins按照我们的要求把这一系列操作搞完然后出包,可能还涉及到后续的分发之类的。
公司的框架,如何与服务器交互的
公司自己的框架,不是网上的开源的(qframework)
在与服务器交互方面,是借助TCP/IP通信协议来实现的。通过建立相应的TCP/IP连接,客户端可以向服务器发送请求,比如获取游戏数据、提交玩家操作信息等,服务器接收请求后进行相应处理并返回结果给客户端,以此完成两者间的数据交互流程。
公司采用的是MVC框架,其特点在于将UI(用户界面展示)、Data(数据相关部分)以及逻辑处理这三方面进行分离,这样的结构有助于提高代码的可维护性与扩展性,各模块分工明确,便于开发人员专注于各自负责的部分进行优化和迭代。
14.Unity和Android与iOS如何交互?概述序列化:
15.mask rectmask 区别
16.图集
第二部分:热更新
流程
- 连接服务器
- 请求版本信息
- 对比本地资源版本 确定需要更新的资源
- 下载需要更新的资源 配合断点续传 多线程下载,并使用hash校验等确认文件完整性
- 下载后缓存到本地
使用哪种热更新⽅案:
使用xLua的热更新方案。借助xLua可以使lua代码与c#相互调用。开发者在代码中使用c#编写,对于需要更新的地方添加标签,再从服务器上下载需要替换的lua代码直接替换
1.1那整个资源的一个管理方式是什么样子的啊?
按照这些就是资源的类型去划分目录,然后比如:
对于模型资源,按照类型划分目录可以方便在需要特定模型(角色模型、场景道具模型)时快速定位。
对于UI图片,根据不同模块细分,有助于团队成员清晰地找到与某个功能模块相关的UI元素,比如登录界面、游戏内商店界面等对应的UI资源。
音频资源:这样管理也能够区分背景音乐、音效等不同类型。
脚本:按功能模块或者系统分类放在相应目录下,可以提高代码的可维护性。
材质资源:的分类同样能让美术人员或者相关开发者高效地找到所需的材质用于模型渲染等操作。
1.2在Lua 里面,如果说我们要去重新加载Lua,如何实现?特别就是如果说有了热更新系统过后,你的代码如何进行热加载?
嗯,就是它需要去reload,就是它有一个大表,就是已经加载过的标准,它会用存在这个表,要把需要把已加载的这个这一块这个字段给置为nil,然后再重新的去加载这里做。
在Lua中重新加载模块,可通过清除package.loaded中模块记录来实现。热更新有两种常见思路:一是函数替换法,检测到更新后,用新函数代码替换旧函数;二是模块替换法,先检测模块是否更新,更新后清除原有记录重新加载,同时要注意依赖关系和状态保存,在实际开发中结合热更新框架(如XLua)能更方便地操作。
1.3那么对于一些就是加载顺序有要求的一些文件的工作如何处理?
可以在热重载之前,分析下文件的依赖关系,那种属于根节点的就优先重载,避免出错。可以自己写工具通过require语句来分析,(git也有这种工具,思路都是语法分析)。
第三部分:资源管理
一、Unity的资源管理
- Editor状态下,使用AssetDatabase加载卸载
- 运行时,Resources和AsstBundle或者AddressAble
二、unity工程中如何使用AssetBundle对资源进行管理的?
1.资源本身设置
● 设置资源格式,如纹理压缩格式(安卓ETC或者ETC2, IOS ASTC),read/write开启关闭,miamap,模型导入设置,以优化资源大小
● 合理设置目录,对资源进行分类,
● 图集与大图设置,图片大小设置
2.资源打包(BuildPipline )
● 遍历所有已设置AB名字的分组
● 查找分组中的资源和依赖项目,并创建一个资源清单
● 对上一步的资源和依赖资源进行序列化,保存到一个二进制文件
● 对资源清单和序列化后的资源打包成一个二进制文件,生成一个包含所有资源的AssetBundle文件
● 根据需要压缩和加密
3.资源加载
● 加载API,加载资源包到内存
● 通过资源包对象加载资源
4.资源卸载
● 引用计数
● 缓存 复用
三、如何避免重复打包依赖资源?
- 使用工具分析重复的资源
- 合理的资源管理,如通用的资源单独打一个AB,
- 使用AB变体,针对不同平台创建不同的AB,使用相同的依赖资源
- 优化打包流程,使用增量打包
四、如何进行增量打包?
- 通过修改时间,hash值或者Git SVN版本控制系统,确定版本之间的资源变化,
- 根据资源变化,针对修改过的资源进行打包
第四部分:关于Lua
一、Lua与C#的交互
原理: Lua解释器是一个使用Lua标准库实现的独立的解释器,他是一个很小的应用(总共不超过500行代码)。
解释器负责程序和使用者的接口即从使用者那里获取文件或者字符串并传给Lua标准库,Lua标准库实现最终的代码运行,所以Lua可以作为程序库用来扩展应用的功能,也就是Lua可以作为扩展性语言的原因所在。
Lua调用C#
C#调用Lua
二、Lua如何实现面向对象
原理:表的特性,table里保存的可以是任何类型,包括方法和table
● 实现封装:local
● 实现继承:在子类表中设置元表为父类,字类__index设为自身,然后定义构造方法new以及静态方法 成员方法属性等。主要归功于table的查找机制
● 实现多态:子类重写父类方法,利用table查找机制,先查找子类的属性和方法,得以实现子类重写父类方法
这里解释一下lua的元表,元方法:
在Lua table中我们可以访问对应的key来得到value值,但是却无法对两个table进行操作。因此Lua
提供了元表(Metatable),允许我们改变table的行为,每个行为关联了对应的元方法。通俗来说,元表就像是一个“操作指南”,里面包含了一系列操作的解决方案,例如__index方法就是定义了这个表在索引失败的情况下该怎么办,__add方法就是告诉table在相加的时候应该怎么做。这里面的__index,__add就是元方法,
Lua的查找机制,先查找son.proty1,如果有,返回,如果没有,查看son是否有metatable
也就是father,如果有,检查father的__index,也就是father的操作指南有没有,如果设置了自己,再查找father.proty1
第五部分:关于性能优化
一、CPU方面
1. 繁琐脚本计算
● 优化逻辑,
● 少new,缓存对象,字符串
2. 物理模拟
● 碰撞分区
● 算法
3. 对象查找
● 缓存对象
● 路径查找
二、GPU方面
1. 过多顶点数据和面数
- 减面算法
- LOD
- 分辨率
2. DC 或者SetPass Call 或者over draw过多
- 静态 动态合批
- 材质使用
- UI的合批操作 避免图文混排 减少不必要的canvas
- 注意不要太多半透明
3. 复杂shader
- 去除不必要的细节计算
- 减少光照 使用光照贴图
- 探针
三、内存方面
- 高分辨率纹理和大尺寸模型
- 合理的压缩格式,减少内存占用
- 动态加载
- 模型 图片的资源优化
- 内存泄漏
- 代码review
- 及时释放与销毁
- 缓存 对象池等
四、UI性能优化
1、减少UI层级
复杂的UI层级会增加渲染的计算量。尽量减少不必要的嵌套,比如避免在一个UI界面中过度使用多层级的Panel。如果一个UI元素在视觉上可以通过简单的布局实现,就不要使用过多的父容器来包裹它。
2、合理使用UI组件
- 对于一些复杂的自定义UI组件,要谨慎使用。如果一个简单的原生组件能够满足功能需求,就优先选择原生组件。因为自定义组件可能会带来额外的性能开销,比如额外的脚本计算和渲染成本。
- 控制UI组件的数量,例如在一个场景中,如果有大量的按钮或者文本框等组件,并且其中部分组件在特定情况下不需要显示,可考虑在不需要的时候将其隐藏或者销毁,而不是一直保留在场景中占用资源。
3、优化UI图片资源
- 图片的分辨率和格式对UI性能影响很大。使用合适的分辨率,避免使用过高分辨率的图片,因为高分辨率图片会消耗更多的内存和渲染时间。
- 对于UI中的图标等小图片,尽量使用精灵图(SpriteSheet)来减少图片的加载次数。并且,选择合适的图片格式,如对于具有透明度的简单图标,PNG格式比较合适;对于没有透明度的背景图片等,JPEG格式可能更节省资源。
4、批处理(Batching)
- 利用Unity的批处理机制,确保尽可能多的UI元素能够被合并成一个批次进行渲染。这要求UI元素使用相同的材质和纹理,所以在设计UI时,可以考虑将这些元素进行合理分组,以便更好地利用批处理来提高渲染效率。
5、动态加载与缓存
- 对于一些不经常出现的UI界面,可以采用动态加载的方式,在需要的时候才加载到内存中,避免一次性加载过多UI资源。
- 对于频繁使用的UI元素,建立缓存机制,这样当再次需要这些元素时,可以直接从缓存中获取,减少重新创建和初始化的时间。
五、如何减少DrawCall
- 静态合批
- 动态合批 对于少于900个顶点属性的
- GPUInstance shader是否支持
- 尽量使用相同材质 可能合批
- 遮挡剔除 减少不必要的渲染
- 使用对象池 间接减少因为新建对象导致的DC
- UI 避免图文混排 遮挡 canvas
五、实际案例
关于刮刮乐的优化
- 实现方案由单张图变为两张图,减少mask的大小,减小需要修改的像素点数量
- 插值点 算法优化
- 数据存储代替直接像素修改 逻辑优化
- Computer Shader使用,充分利用GPU的并行计算
关于首充界面的优化 - 主图集+通用Common图集+第三图集,优化掉第三图集
- 剔除动态加载却在预制上的引用图
- 图集的整理,尽量减少图集的引用个数(第一步),以及图集的大小
- 成效:380ms的加载时间减少到190ms 标准就是低于200ms
四、设计模式 结合工作实例
- 单例模式 mgr类
- 监听者模式 消息中心 消息中心
- 状态机模式 状态切换
- 工厂模式 根据参数实例化不同对象,玩家与敌人
- 装饰器模式 buff
七、TCP 三握四挥
- 第一次:我想建立连接
- 第二次:ok来吧 我准备好了
- 试试到底好没好 确认一下
八、基于三色标记的GC
进阶
七、描述你们的项目架构
八、跨平台的兼容问题
九、后处理效果
- 抗锯齿 详细见其他笔记
- Bloom
- 模糊 高斯模糊 正态分布的高斯核 对像素的颜色值进行加权平均
- 描边 Sobel算子,卷积核,计算每个像素梯度,判断是否处理边缘
第六部分:UI框架
一、基于UGUI做的。如果说我们要去做一些界面的动态效果,一般会怎么样一些实现?特别是假如我们所有的 UI 可能都需要有动态的一些展示效果。
A:用Dotween去实现的
Q: Dotween 的话,实现来说它可能更多的在于一些位压缩放之类的。然后我们可能会有一些UI 的一些变形汇总,是吧?甚至还会有一些 UI 是需要做到 3D 的一些产出效果?是怎么做的?(这个地方不懂)
1. UI变形效果实现
•
利用UGUI组件的属性变化:在UGUI中,像Image和Text这样的组件有一些可动画化的属性,如RectTransform的位置、大小、旋转等。Dotween可以方便地对这些属性进行动画操作,实现位移动画、缩放动画等。对于UI变形效果,可以结合RectTransform的锚点(Anchor)来实现更复杂的变形。例如,通过改变锚点位置和组件的大小,可以让UI元素产生拉伸、挤压等变形效果。•
使用脚本控制顶点:对于更高级的变形效果,可以通过脚本访问UI元素(如MeshFilter)的顶点数据。在Unity中,通过获取UI图形的顶点数组,然后在每一帧中修改顶点的位置来实现自定义的变形效果。不过这种方法相对复杂,需要对图形渲染有一定的了解。可以在Dotween动画的每一帧回调函数中添加代码来控制顶点变形,实现如扭曲、波浪等效果。
2. UI 3D产出效果实现
•
利用3D模型作为UI元素:可以将3D模型放置在UGUI的Canvas下,通过调整模型的位置、大小和旋转,使其与其他UI元素融合。为了让3D模型在UI中显示自然,可以调整Canvas的渲染模式,例如使用“世界空间”(World
Space)渲染模式。在这种模式下,Canvas就像一个3D对象,可以将3D模型放置在合适的位置,通过Dotween对模型的Transform属性进行动画操作,实现3D效果的UI展示。•
使用Shader特效:创建或使用带有3D效果的Shader来应用于UI元素。例如,通过编写一个能够模拟3D光照效果的Shader,然后将其应用到UGUI的Image组件上。Dotween可以用于动画Shader的参数,如光照的强度、方向等,让UI元素产生3D光影变化的效果。还可以使用Shader来实现3D透视效果,如让UI元素看起来有远近之分,增强3D立体感。
无论是UI还是3d,对于dotween来说都是3维的,只不过UI的故意减少一个维度的计算,核心都是插值处理
二、你再去做功能开发的时候,你会对于界面的一些展示有自己的一些要求吗?
1、要避免界面过度复杂,若界面内容繁多,最好进行拆分,对于那些并非一开始就要展示的内容,采用动态显示的方式,而非一次性全部加载,这样能有效减轻初始加载的压力,提升加载速度与性能。
2、是注重动静分离,针对UI元素图片的颜色、大小等会动态改变的属性,合理规划,尽量降低每个画布重绘时的资源消耗。比如通过优化布局、合理控制元素更新时机等手段,让界面在动态变化过程中更高效地进行渲染,避免因频繁重绘导致性能下降,确保界面展示流畅且响应及时,给用户带来更好的交互体验。
第七部分:对象池
适合环节:
•
游戏中的子弹或投射物:在射击类游戏中,子弹会被频繁发射和销毁。如果不使用对象池,每次发射子弹都要实例化一个新的子弹对象,用完后销毁,这会产生大量的性能开销。而使用对象池,可以预先创建一定数量的子弹对象存储在池中,当需要发射子弹时,从池中获取一个已有的子弹对象,使用完后再放回池中,循环利用,减少实例化和销毁的次数。•
游戏中的特效粒子:例如爆炸特效、技能特效等。这些特效粒子通常会在游戏过程中频繁出现和消失。通过对象池,提前创建一批特效粒子对象,在需要显示特效时,从池中取出合适的粒子对象进行配置和播放,结束后放回池中,这样可以提高性能,尤其是在特效频繁出现的场景下。
对象池的使用方法:
•
创建对象池:首先要确定对象池的大小,即池中预先存储的对象数量。然后,实例化这些对象,并将它们存储在一个合适的数据结构中,如列表(List)。例如,创建一个子弹对象池,可以这样做:
•
获取和归还对象:当需要使用对象时,从对象池中获取(GetObject)。使用完毕后,将对象放回对象池(ReturnObject)。以子弹为例,在射击逻辑中,从子弹对象池获取子弹进行发射,当子弹超出屏幕或者击中目标后,将子弹放回对象池。
可能遇到的问题:
•
对象池大小设置不合理:如果对象池大小设置过小,在需要大量对象时,可能会频繁地实例化新对象,无法充分发挥对象池的优势。反之,如果对象池太大,会占用过多的内存资源,即使有些对象可能很少被使用。•
对象状态管理复杂:当从对象池中获取对象后,需要确保对象的状态被正确重置。例如,子弹对象可能需要重置位置、速度等属性。如果状态重置不完全或者不正确,可能会导致游戏出现异常行为,如子弹位置错乱、特效播放异常等。•
对象池与游戏逻辑的耦合问题:在复杂的游戏系统中,对象池可能会与其他游戏逻辑产生紧密的耦合。如果没有良好的设计,当游戏逻辑发生变化时,对象池的代码可能需要大量修改,增加维护成本。
十、前后端协议
- TCP/IP 稳定 可靠 面向连接 有序 不重复
- UDP 快速,会丢数据
- WebSocket 建立在TCP上 实时 双向 可靠 高效
- HTTP/HTTPS 请求-响应 可靠,非实时
十一、数据类型
- ProtoBuffer 高效 跨语言
- Json
具体问题
第六部分:关于容易混淆的概念
一、Mask RectMask2D区别
- mask原理模版测试 rect原理 裁剪alpha
- mask可嵌套 rect不行
- mask可合批 rect不行
- mask低效
- mask形状自定义 rect只能是矩形
二、帧同步与状态同步
最大的区别在于:状态同步把战斗的核心逻辑写在服务器,
而帧同步在客户端,服务器只负责转发
三、接口与抽象类
抽象类是自下而上的,从子类中发现公共部分,然后泛化成抽象类,子类继承该父类即可,
但是接口不同。实现它的子类可以不存在任何关系,共同之处。
例如猫、狗可以抽象成一个动物类抽象类,具备叫的方法。鸟、飞机可以实现飞Fly接口,具备飞的行为,这里我们总不能将鸟、飞机共用一个父类吧!
所以说抽象类所体现的是一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is-a"关系
即父类和派生类在概念本质上应该是相同的。对于接口则不然,并不要求接口的实现者和接口定义在概念本质上是一致的, 仅仅是实现了接口定义的规则而已。
四、委托与事件
委托:委托是一个类,可以将方法当作参数进行传递,保存对函数的引用。可以将委托看成执行方法的一个东西。对于方法的限制没有,有无返回值有无参数都可以。
事件:事件是委托的子集,可以使用事件的地方都可以使用委托来代替,事件对于操作者有一定的权限限制,订阅者只能订阅和取消订阅,更加安全。事件就是为广播/订阅这种模式而生。
五、DrawCall Batches
CPU向GPU发送一次渲染指令, 等于呼叫一次 DrawIndexedPrimitive (DX) or glDrawElements (OGL),等于一个 Batch。
如何计算DrawCall的限制:
NVIDIA 在GDC上曾提出 25k batch/sec的渲染量会使1GHz的CPU达到100%的使用率,因此使用公式
25K∗n(GHZ)∗Percentage/Framerate=Batch/Frame
可以推算出某些CPU可以抗多少Batch。例如红米手机CPU为1.5GHz,假设分出20%资源供渲染,希望游戏跑到30帧。那么能抗多少DrawCall?
25k * 1.5 * 0.2 / 30 =
250。因此从这方面也能看出,如果CPU不能分出更多的资源供渲染计算,能抗的DrawCall就会变少。
第七部分:算法
1. 冒泡算法
重复地走访要排序的数列,每次比较相邻的两个元素,如果顺序错误就把它们交换过来。就像气泡一样,轻(小)的元素会慢慢“浮”到数列的前面。
例如,有数列 [5, 4, 3, 2, 1]。
第一次遍历,比较相邻元素5和4,因为5 > 4,所以交换它们,得到 [4, 5, 3, 2, 1];接着比较5和3,交换得到 [4, 3, 5, 2, 1];依次类推,经过第一轮比较后,最大的元素5就“沉”到了最后,数列变为 [4, 3, 2, 1, 5]。
然后进行第二轮遍历,经过比较交换后得到 [3, 2, 1, 4, 5]。
这样持续遍历,直到整个数列排序完成。
具体步骤如下:
从数列的第一个元素开始,比较相邻的两个元素。
如果前面的元素大于后面的元素,就交换这两个元素的位置。
对整个数列进行一次这样的操作后,最大的元素就会移动到数列的末尾。
重复上述步骤,但因为每次最大的元素已经就位,所以下一次遍历的范围就减少一个元素,直到整个数列有序。
冒泡排序的时间复杂度是 O(n^2),其中n是数列元素的个数。这是因为对于n个元素,要进行n - 1轮比较,每轮比较n - i次(i是轮数)。它是一种稳定的排序算法,相等元素的相对位置不会改变。
2. TopK
3. 快速排序
通过选择一个“基准”元素,将数组分为两部分。小于基准的元素放在基准左边,大于基准的元素放在右边。然后对这两部分分别递归地进行排序,最终整个数组就有序了。
例如有数组 [4, 7, 2, 6, 4, 1],假设我们选择第一个元素4作为基准。经过一轮排序后,数组可能变成 [2, 1, 4, 6, 4, 7],此时4左边的元素都比它小,右边的元素都比它大。接着再对4左边的 [2, 1]和右边的 [6, 4, 7]分别进行同样的操作,直到整个数组有序。
具体步骤如下:
选择一个基准元素,通常是数组的第一个或最后一个元素。
设置两个指针,一个从数组头部开始(除基准元素外),一个从数组尾部开始。头部指针向后移动寻找大于基准的元素,尾部指针向前移动寻找小于基准的元素,当找到这样的元素后,交换它们的位置。
持续这个过程,直到两个指针相遇,这时候把基准元素和相遇位置的元素交换。
对基准元素划分后的左右两个子数组,重复上述步骤,直到子数组的长度为1或0,此时数组就完成排序。
快速排序的时间复杂度在平均情况下是 O(nlogn),在最坏情况下(例如数组已经有序)是 O(n^2),不过通过一些优化手段可以尽量避免最坏情况。它是一种不稳定的排序算法,即相等元素的相对位置可能会改变。
4. A* 寻路算法
A* 算法是一种在图形数据结构中用于路径搜索的算法,常用于游戏开发中的寻路功能。
它结合了Dijkstra算法(一种能找到起始点到图中所有节点最短路径的算法)和贪心最佳优先搜索(总是朝着目标节点的方向进行搜索)的优点。
基本原理是这样的:A*算法通过一个评估函数f(n) = g(n) + h(n)来确定每个节点的优先级。其中,
g(n)是从起始节点到节点n的实际代价,
h(n)是从节点n到目标节点的预估代价。
例如,在一个网格地图游戏中,假设角色要从A点走到B点。g(n)可以是走过的格子数量(每一格子有一个固定的移动代价),h(n)可以通过简单的曼哈顿距离(两点在南北方向和东西方向上距离之和)或者欧几里得距离(两点间直线距离)来估算。
算法的主要步骤如下:
- 把起始节点放入一个开放列表(记录待考察节点的列表),这个列表里的节点会按照优先级排序。
- 重复以下步骤直到开放列表为空或者找到目标节点:
• 从开放列表中取出优先级最高的节点。
• 检查这个节点的相邻节点,如果相邻节点是目标节点,就找到了路径;如果不是,计算相邻节点的优先级,并把它们放入开放列表中(如果它们不在开放列表或者关闭列表中的话)。同时把当前节点放入关闭列表(记录已考察节点的列表)。
- 如果开放列表为空还没找到目标节点,就表示不存在路径。
当找到目标节点后,可以通过记录每个节点的父节点来构建出从起始点到目标点的最短路径。