UE 补完计划(四) 导航系统-2
前言:场景体素化
UE4的导航使用的是RecastDetour组件,这是一个开源组件,主要支持3D场景的导航网格导出和寻路,或者有一个更流行的名字叫做NavMesh。不管是Unity还是UE都使用了这一套组件。不过UE4对其算法做了不小的修改。
Github上有更为详细的源码、Demo和说明:https://github.com/recastnavigation/recastnavigation
本文使用的UE4源码版本为4.24.3,2020年2月25日的版本。
Recast采用了体素化的方式,来生成导航网格。大致分为三个步骤:
将场景体素化。形成一个多层的体素模型。
将不同层的体素模型划分为可重叠的2D区域。不同层的2D区域不同
沿着边界区域剥离出导航凸多边形。
本文将介绍第一部分,将场景体素化,以及后续的可行走层的过滤。
体素概念介绍
体素的概念和像素类似,将三维空间分成一个个的小格子,如下图所示:
然后是一个概念span:代表某一方向上连续的格子。
体素化的目的,就是为了将整个场景转换为一个个格子内的体素,并标记每个span的可行走状态。以方便后续做区域划分 ...
UE 补完计划(四) 导航系统-1
前言:基于Tile的导航系统
UE4的导航使用的是RecastDetour组件,这是一个开源组件,主要支持3D场景的导航网格导出和寻路,或者有一个更流行的名字叫做NavMesh。不管是Unity还是UE都使用了这一套组件。
Github上有更为详细的源码、Demo和说明:https://github.com/recastnavigation/recastnavigation
本文使用的UE4源码版本为4.26.1,2021年2月2日的版本。
这一篇是第一篇,会阐述UE4是如何划分Tile,并基于Tile构建导航数据的。在后续的文章中,会详细介绍每个Tile如何构建导航数据。
Tile 的概念
Tile的概念,就是一个正方形的格子。每个Tile中有自己独立的导航网格数据。在我看来,Tile的作用有三点:
更加友好地支持多线程构建导航网格数据。
通过标记每个Tile是否需要重新生成(Dirty属性),就能在runtime动态更新单独一个Tile的导航网格
使用Static方式的导航网格时,可以以Tile为粒度来加载导航网格
在UE4中,所有的导航网格数据都是基于Tile的。 ...
与延迟抗争,射击游戏如何做到更好的体验
战斗中,我们始终是在用将来位置的自己去打过去位置的敌人
认识游戏中的延迟
网游都是采用典型的C-S通信模式,客户端必须持续地与服务器通信才能正常运作,网络通信的round-trip time明显就是我们不想要的延迟。网络传输的丢包、重传等,都可以纳入网络延迟的范畴。
但游戏中的延迟远不止网络延迟这么简单,计算机系统(包括手机)的运作、游戏的实现机制都会引入延迟。点击鼠标产生开火指令,这个电信号的传输、被系统捕获都需要时间,客户端逻辑线程通常要到下一帧才会开始处理这次输入,逻辑处理中抛事件到渲染线程,渲染线程最快也要下一帧才能去渲染结果,渲染结果要显示到屏幕上又受限于屏幕的刷新率。可见,即使是没有网络的纯客户端游戏,延迟都不可避免地存在。很多游戏在实现中会用到Buffering技术,这还会进一步加剧延迟。
衡量游戏品质的一项重要指标就是玩家常说的“手感”,而手感的关键组成部分就是反馈的及时性。对于射击游戏、竞速游戏,玩家对手感有着极高的期待,职业玩家甚至能感知到10ms级别的延迟差距,所以游戏开发者都会在这方面下很大的功夫。
降低网络延迟
网络游戏中,网络延迟是延迟的主体部分,降低网 ...
UE4智能指针与STL智能指针对比分析
概要
UE4的智能指针有很多种,大体分为两类,一类是专门为UObject设计的,比如TStrongObjectPtr,TWeakObjectPtr,一类是通用的智能指针,比如:TSharedPtr常用的共享智能指针,TSharedRef为UE原创非空共享引用,TWeakPtr弱指针,TSharedFromThis能共享自己的智能指针。文章主要从UE的C++设计与结构层面上分析,主要以UE为主,STL为辅,UE中关键细节设计在第二章节讨论。
先看UE4的智能指针一些特性,当然这不是全部:
结构设计良好,模块化,耦合度低(会分析)
防止内存泄漏:就是自动销毁回收资源
实现了弱引用:在资源销毁时,弱指针会进一步进行检查,能更加安全
线程安全与否可以通过模板参数配置,非安全版速度更快,默认是非安全版(会分析)
可消除循环引用,通过弱指针来实现
开销小,支持const,很好的类型转换、占用空间小。(会分析)
支持非空智能引用,就是UE原创的TSharedRef(会分析)
支持自身智能指针,就是TSharedFromThis(会分析)
和UE内部方法与命名一致
没有异常处理,没有支持分配器
对比 ...
UE 补完计划(三) Unreal的可靠传输RUDP原理剖析
在Unreal Engine引擎中实现了可靠的数据传输,包括序列号、确认机制、历史记录、窗口指导、流量控制等维度确保了可靠性, 这些机制使得RUDP适用于房间类游戏业务场景,以最精简代码实现引擎可靠性网络传输需求,减少网络延迟影响。
背景
Unreal Engine(本文示例UE5.2)选择使用RUDP(Reliable UDP)而非TCP或纯UDP,主要是因为RUDP结合了TCP和UDP各自的优点,同时避免了它们的缺点。
TCP提供了可靠的数据传输,但其复杂的流量控制和拥塞控制机制可能导致延迟(latency)和抖动(jitter),这在实时性要求较高的游戏中是不可接受的。
UDP虽然能提供低延迟的数据传输,但它不保证数据的可靠性和顺序性,这可能导致游戏数据丢失或错乱。
RUDP结合了TCP的可靠性和UDP的低延迟,是一种适合实时网络游戏的协议。它在UDP的基础上增加了数据包确认和重传机制,以提高数据的可靠性,同时保持了UDP的低延迟特性。
RUDP在Unreal Engine中实现了可靠的数据传输,包括序列号、确认机制、历史记录、窗口指导、流量控制等维度确保了可靠性, 这些机制 ...
UE 补完计划(二) GC-4
前言
在前三节中,我们主要分析了 “标记-清扫” 中的标记阶段内容,并且我们知道了在 UE 中,标记阶段是分两步进行的,第一步会遍历现有对象,对一些诸如根节点的情况进行简单标记,而第二阶段则会执行更加细致的可达性分析,根据类中引用与被引用的关系来再次遍历对象,以进行完整的可达性标记。
UE 补完计划(二) GC-1
UE 补完计划(二) GC-2
UE 补完计划(二) GC-3
在正确标记了所有需要 GC 的对象后,接下来就该清理这些垃圾了,本节就来分析这部分的内容。
CollectGarbageInternal()
回到 CollectGarbageInternal() 函数,剩下的这部分内容就是与清扫相关的了。
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455FGCArrayPool& ArrayPool = FGCArrayPool::Get();TArray<FGCArrayStruct*> AllArra ...
UE 补完计划(二) GC-3
参考:
https://zhuanlan.zhihu.com/p/459287914#Collectreferences
前言
标记过程共分为两个阶段:标记对象与可达性分析。对应到具体的函数就是 MarkObjectsAsUnreachable() 与 PerformReachabilityAnalysisOnObjectsInternal(),上一节我们分析了前者,本节就来分析第二个函数。
PerformReachabilityAnalysis() 的执行逻辑
在分析该函数之前需要再理一理逻辑。
不知道大家还记不记得,CollectGarbageInternal() 最先调用的就是 PerformReachabilityAnalysis() 来执行 “标记-清扫” 中的标记阶段。我们先回顾一下这部分的逻辑:
12345678910111213141516171819202122232425262728293031323334353637383940/*** Performs reachability analysis.** @param KeepFlags Object ...
UE 补完计划(二) GC-2
前言
上节说到, MarkObjectsAsUnreachable() 与 PerformReachabilityAnalysisOnObjectsInternal() 是执行标记阶段两个步骤分别对应的函数,本节我们就来分析它们。
MarkObjectsAsUnreachable
由于这个函数比较长(200行左右),这里就不放出完整的函数内容而使用分段分析的方法,感兴趣的读者可以自行在 UObject/GarbageCollection.cpp 中查看该函数。
首先查看函数注释与签名,可以看出,该函数的作用为:将将已有的对象列表中的对象,标记为可达/不可达。并且根据 <bool bParallel, bool bWithClusters> 两个模板参数可以实例化出不同的函数以适用于各种情况。
123456/** * Marks all objects that don't have KeepFlags and EInternalObjectFlags::GarbageCollectionKeepFlags as unreachable* This function ...
UE 补完计划(二) GC-1
参考:
https://zhuanlan.zhihu.com/p/404946454
https://zhuanlan.zhihu.com/p/67055774
https://zhuanlan.zhihu.com/p/401956734
前言
由于 C++ 本身没有 GC 机制(日常鞭尸),因此 UE 便在 UObject 的基础上自己造了一套轮子,这里直接贴上 南京周润发 的回答:
UE4为我们搭建了一套UObject对象系统,并且加入了垃圾回收机制,使我们用C++进行游戏开发时更加方便,而且游戏本身也可以极大程度的避免了内存泄漏问题。
UE4采用了标记-清扫垃圾回收方式,是一种经典的垃圾回收方式。一次垃圾回收分为两个阶段。第一阶段从一个根集合出发,遍历所有可达对象,遍历完成后就能标记出可达对象和不可达对象了,这个阶段会在一帧内完成。第二阶段会渐进式的清理这些不可达对象,因为不可达的对象将永远不能被访问到,所以可以分帧清理它们,避免一下子清理很多UObject,比如map卸载时,发生明显的卡顿。
GC发生在游戏线程上,对UObject进行清理,支持多线程GC。
关于 GC ...
UE 补完计划(一) 反射-2
前言
UE 补完计划(一) 反射-1
上文我们分析了 UHT 针对一个类的生成代码,基本了解了 UE 中反射的实现原理,这节我们就来分析一下这些反射信息是如何注册到系统中的。
反射信息注册
ue中反射信息是分段注册的,由static结构体的构造为第一段,执行在main函数之前,第二段在ue启动时完成真正的注册。
反射的信息几乎都在对应类的gen.cpp文件中,这也是由UHT生成的。
在gen.cpp文件中ue会生成一个每个类唯一的FClassRegistrationInfo变量,这个变量与GetPrivateStaticClass函数定义在gen.cpp文件中的IMPLEMENT_CLASS_NO_AUTO_REGISTRATION宏中,这个变量就包含了类的信息。
12// gen.cppIMPLEMENT_CLASS_NO_AUTO_REGISTRATION(UMyObject);
查看这个宏的定义:
12345678910111213141516171819202122232425262728// Implement the GetPrivateStaticClass and th ...
UE 补完计划(一) 反射-1
参考:
https://zhuanlan.zhihu.com/p/662720305
https://zhuanlan.zhihu.com/insideue4
https://blog.csdn.net/qq_29523119/article/details/119420238
https://cloud.tencent.com/developer/article/1606872
https://www.jianshu.com/p/c335c3f6cc04
UE5引擎源码小记 —反射信息注册过程
前言
对于 UE 的使用与深入研究而言,反射始终是一个绕不开的话题,在 之前的文章 中也简单介绍过反射的概念以及 UE 中反射的实现,但当时毕竟刚入坑 UE,很多知识点都只是一知半解而已,本节将以一个更深入的视角来分析 UE 中反射的实现。
反射
首先需要说明的还是反射的概念,反射(Reflection)指的是在程序运行时检查、获取和操作其自身的信息,包括数据类型、方法、属性等。反射允许程序在运行时获取类的结构信息,而不需要在编译时就静态地知道这些信息。
在具有反射支持的编程语言中,开 ...
UE 补完计划 · 零
Index
这里将会存放本系列的博客
反射
UE 补完计划(一) 反射-1
UE 补完计划(一) 反射-2
GC
UE 补完计划(二) GC-1
UE 补完计划(二) GC-2
UE 补完计划(二) GC-3
UE 补完计划(二) GC-4
URDP
UE 入坑系列(七):UE 中的 UDP
UE 补完计划(三) Unreal的可靠传输RUDP原理剖析
导航系统
UE 补完计划(四) 导航系统-1
UE 补完计划(四) 导航系统-2
UE 补完计划(四) 导航系统-3
UE 补完计划(四) 导航系统-4
UE 补完计划(四) 导航系统-5
STL
UE 补完计划(五) UE中的STL-1
UE 补完计划(五) UE中的STL-2
C++ 补完计划(四):C++ 中的一些关键字
参考:
https://csguide.cn/cpp/
https://zhuanlan.zhihu.com/p/137662774
const
在 C/C++ 中,const 是一个关键字,用于表示常量。const 可以用于修饰变量、函数、指针等,主要作用有以下几种:
修饰变量
当 const 修饰变量时,该变量将被视为只读变量,即不能被修改。
对于确定不会被修改的变量,应该加上 const,这样可以保证变量的值不会被无意中修改,也可以使编译器在代码优化时更加智能。
这里的变量只读,其实只是编译器层面的保证,实际上可以通过指针在运行时去间接修改这个变量的值。虽然可以这样操作,但这违反了 const的语义,可能会导致程序崩溃或者产生未定义行为(undefined behavior),大家学习了解即可,实际编程中切莫如此操作。因为编译器可能会做一些优化,也就是在用到 const 变量的地方,编译器可能生成的代码直接就替换为常量的值,而不是访问一遍常量的指令。
所以极大可能你虽然修改了值,但是却不起作用!
下面这个例子,展示了使用 const_cast 修改 const 变量的值却 ...
图形学补完计划(五):Forward Rendering & Deferred Rendering
参考:
https://zhuanlan.zhihu.com/p/386420933
https://zhuanlan.zhihu.com/p/111314574
前向渲染
前向渲染是现在最基础,也是最多引擎使用的标准。前向渲染的流程是给定一个几何体,引擎对其进行从顶点到像素着色器的一系列计算,然后输出到最终的图像缓冲区。场景中有多个几何体时,引擎就是对其挨个进行渲染,完成一个再继续下一个。
前向渲染的问题
前向渲染有一个问题就是无效渲染太多,比如场景中有四个物体,互相之间存在叠压关系,按照前向渲染的流程,先前渲染了一个物体之后,它的一部分被后一个渲染的物体挡住了,那么被挡住的这部分就是无效的计算,毕竟我们在屏幕上是看不到这部分的。
另一个问题在于难以支持过多的光源,对于每个需要逐像素计算的光源,渲染一个几何体的时候需要逐个做一次光照计算。如果有一个场景,其中有10个几何体需要进行渲染,有四个光源对整个场景产生影响,那么渲染整个场景需要进行40次光照计算。而且其中还有很多的计算被挡住了。
前向渲染的优点
如果需要在场景中使用多个着色模型,甚至是每个几何体都使用不同的着色 ...
UE 入坑系列(七):UE 中的 UDP
参考:
https://zhuanlan.zhihu.com/p/580460488
https://blog.csdn.net/u012999985/article/details/117236770
https://zhuanlan.zhihu.com/p/430799766
https://blog.csdn.net/csdnsevenn/article/details/106865876
https://zhuanlan.zhihu.com/p/606905438
预备知识
UDP & TCP
TCP (Transmission Control Protocol)和 UDP(User Datagram Protocol) 协议属于传输层协议。
其中TCP提供IP环境下的数据可靠传输,它提供的服务包括数据流传送、可靠性、有效流控、全双工操作和多路复用。通过面向连接、端到端和可靠的数据包发送。通俗说,它是事先为所发送的数据开辟出连接好的通道,然后再进行数据发送;而UDP则不为IP提供可靠性、 流控或差错恢复功能。
一般来说,TCP对应的是可靠性要求高的应用,而UDP ...