- 博客(165)
- 资源 (4)
- 收藏
- 关注
原创 A*寻路详解
搜索最基本的思路是穷举搜索,例如广度优先和深度优先搜索,但当搜索空间过大,也即可能性太多时,最好可以提供一种方式,对可能性做筛选,减少搜索空间,进而提高搜索效率。常用的说法是cost,从一个点到另外一个点的成本,通常成本就是距离的度量,在机器学习中,还会加上其他因素的度量,寻路算法中只有距离度量。通常来说,搜索是一步步进行的,每一步会将新的可能加入到待处理队列中,如果每一步都从当前可能中选择一个最优的结果,这就是贪心的。游戏中通常需要最快找到该路径(A*算法),而不是找到最短的路径(Dijkstra算法)
2025-02-11 20:42:58
669
原创 游戏角色战斗行为分析(1)——概述
角色最重要的一类行为就是战斗,战斗行为最为丰富的是动作游戏战斗行为有对应的战斗状态和战斗属性,属性是一种特殊的状态信息战斗行为通常又可以分为攻击、防御、闪避等行为,分别有各自对应的状态和属性,是战斗状态的细分和战斗属性的扩展1.有的游戏(不是动作游戏),攻击方式只有一种,机制是攻击到就击杀2.在1的基础上,引入伤害和血量机制,每次攻击有固定伤害,减少固定血量,血量为0时击杀。该机制引入了基础属性,血量;攻击属性,攻击力;战斗状态:攻击中这时的战斗机制主要是依靠移动来闪避固定的攻击。
2025-01-20 20:22:04
892
原创 Unity Burst详解
Burst是Unity的编译优化技术,优化了从C#代码编译成Native代码的过程,经过编译优化后代码有更高的运行效率。在Unity中使用Burst很简单,在方法或类前加上[BurstCompile]特性即可。在构建时编译代码的步骤,Burst编译器会识别该特性对方法或类做编译优化。其适用于高性能计算的场景,逻辑复杂的场景不适用。Burst编译的实现得益于已有的SIMD和LLVM技术。
2025-01-08 20:39:16
1371
原创 行为树详解(6)——黑板模式
黑板模式的数据管理本质还是通过键值对的方式,为处理不同的情况,我们需要对每种情况提供不同的Key。因此,在实现动作节点时,属于其他模块的直接调用其他模块的接口或在其他模块内实现,属于角色行为的在动作节点内实现。在黑板中,我们需要根据这些参数生成唯一的Key,这里自然而然的就会需要对参数做封装,用泛型,用对象池。根据行为树ID,节点ID,数据ID,方法ID可以实现不同的数据获取,程序只需实现方法ID即可。同样的,我们需要有这些参数对应的结果,考虑数据类型差异,结果有效性等,自然也需要做封装。
2025-01-06 20:18:22
1117
原创 网络时延与rtt
这个公式可以累积展开,EWMA_n和之前的EWMA_0到WEMA_n-1皆有关系,第n到第0个数据的权重依次是alpha,alpha*(1-alpha),alpha *(1-alpha)^2,......,alpha * (1-alpha)^n。由于网络的复杂性,通过每个数据包计算出来的时延都不相等,为了得到一个确定的时延值,通常的做法是取过去数次的时延平均值作为当前的时延值。例如,在rtt计算中,数据的特征是越近的数据越有效,其权重应该更大,但与较远的数据不能差距过大,函数可以是指数、抛物线等。
2024-12-31 13:05:11
898
原创 行为树详解(5)——事件驱动
这里要对控制节点做特殊处理,控制节点会控制自身的子节点运行,其状态受子节点影响,当打断存在时,子节点可能不需要再次运行,因此控制节点在控制子节点运行前需要知道当前是否被打断。为了解决该问题,就需要给动作节点引入一些事件,当特定事件发生时,在动作节点内接收该事件,直接结束动作节点的运行,返回失败。需要将打断动作节点及节点的逻辑做成单独的Conditional节点,这些节点接收消息或监听变化,以决定是否打断其子节点。如果行为树的节点很多,那么会存在要经过很多节点才会走到动作节点的情况。显然,性能上不如状态机。
2024-12-28 19:51:47
518
原创 行为树详解(4)——节点参数配置化
节点是通用的,不可能在获取模块A的数据时在Update中调用A方法,在获取模块B的数据时调用B方法,这里需要一个统一的调用方式。比较难解决的是最下层的参数数据,这在很多配置数据中是通用的,其主要问题是参数类型、数量、值等各不相同,如何做一个统一的描述?行为树是否足够灵活强大依赖于足够丰富的各类条件节点和动作节点,在实现这些节点时,不可避免的,节点本身需要有一些参数供配置。我们需要有一个结构来描述行为树的配置数据,这个结构是用于运行时初始化数据的,可以将编辑数据和运行时数据结构分隔开来。
2024-12-11 20:30:29
607
原创 行为树详解(3)——多节点实现
NPC围绕固定的点50米内巡逻,当有多个敌人进入巡逻范围内或者和NPC距离小于20米时,会选择其中距离更近的敌人追击,如果和敌人距离小米3米,释放1技能攻击,如果在追击中距离敌人大于30米,NPC速度提高一倍,距离敌人小于3米,释放2技能攻击,如果和敌人距离大于50米或者距离中心点超过100米,则回到重新开始巡逻。可以更明显的看到,行为逻辑是配置出来的,配置一般由策划负责,相对一般的数值配置来说,行为树的配置增添了逻辑性,更加考验策划。再次,对每个状态下的行为划分优先级,优先级高的行为先判断。
2024-11-27 20:33:45
943
原创 行为树详解(2)——最简单的行为树
结合具体的实际需求来:假设有这样一个NPC,绕着固定的某个点以半径20米随机巡逻,让主角进入时,开始朝着主角攻击,距离主角3米时,开始攻击主角,如果距离主角超过10米,则重新巡逻。实际上应该有更多的节点来保证逻辑的正常,我们再后面的文章中结合实际来添加更多节点。如何做状态划分是任意的,划分的原则是尽可能让状态更少,这里将NPC的四个行为分成巡逻和追击两个状态。前后对比可以发现:真正的执行动作的代码区别不大,执行时所需参数的来源和之前一样。最后,对每个状态下的行为划分优先级,优先级高的行为先判断。
2024-11-21 20:31:43
861
原创 行为树详解(1)——与状态机的区别
但是,这两者的能起到的作用有限。状态的概念在行为树中被具体化为行为,一个行为包括动画状态机中的一个或多个状态,也即执行一个行为,会多次调用状态机的状态切换接口。这里的逻辑其实就是NPC行为的逻辑,逻辑本身在于一系列条件的判断,而不在于动作节点(即NPC的动作),动作是动画状态机的。也许你听过OverrideState的概念,包括分层,并发等不同的状态机,但这些都是条件的体现,而不是状态。状态之间的关联使得状态转移过于复杂,新增的状态可能会与已经的众多状态做转移,甚至影响已有的状态之间的转移。
2024-11-11 21:09:38
915
原创 Unity Job System详解(5)——NativeHashSet/Map源码分析
两者的核心都在HashMapHelper中,数据在HashMapHelper* m_Data,内存分配同样调用Memory.Unmanaged.Allocate,释放调用Memory.Unmanaged.Free,有依赖释放时会自动创建NativeHashMapDisposeJob,逻辑与NativeList完全一致。与UnsafeList不同的是,这里数据类型不同,没用Block,直接调用的Memory.Unmanaged.Allocate。不再贴完整源码,只贴关键部分源码。
2024-11-05 20:42:42
490
原创 Unity Job System详解(3)——NativeList源码分析
查看NativeList源码需要安装Unity的Entities PackageNativeList要实现的基本功能类似C# List,如下:(一些简单的类同NativeArray的不在说明)构造函数、析构函数、取值赋值扩容、添加、移除操作解析步骤包括,基本函数,常用操作,常用属性,接口函数。
2024-11-03 21:50:45
921
原创 Unity Avatar详解
Avatar用于解决人形动画的复用问题。物理上,两个不同人形角色的骨骼结构整体一致。为其中一个角色制作好动画后,为了避免再次做同样的动画,我们自然而然想到将已经做好的动画迁移到另外一个角色上。但是我们不能直接去使用,例如将角色A的AnimationClip给角色B使用,角色B无任何反应。(角色模型和角色动画数据是可以分离的。因为无法找到骨骼节点对应的曲线数据。AnimationClip中会记录每个曲线对应哪个节点,通过相对根节点的路径作为标识。(运行时为省内存一般会哈希化)
2024-10-24 13:19:44
1776
原创 Unity AnimationClip详解(2)——动画数据的优化
首先要意识到运行时和编辑时的区别,当运行时和编辑时所需的数据相差不大时,我们用同一套数据结构即可,当两者差异较多或者数据量很大时,需要有各自的数据结构,这意味着在打包或构建时需要将编辑时数据转为运行时数据。(所以Unity中的AnimationClip Curve数据不提供给非editor情况下使用)(内存优化把握三个核心方向:一是内存中只存在当前或最近需要的资源及数据;二是需要的资源及数据在内存中仅存在一份;三是简化资源和数据的结构,运行时和编辑时的数据区分是方向1)由前文可知,动画至少有30个骨骼节点,
2024-10-21 21:43:52
1116
原创 数据压缩(5)——上下文转换编码
统计压缩编码基于单个字符,字典编码基于单词;上下文变换基于具有联系的上下文,例如:RLE编码针对重复字符:AAAABCCC可以记为[A,4]B[C,3]
2024-10-18 13:30:40
907
原创 数据压缩(4)——字典编码
变长编码,统计压缩编码都是基于单个字符的编码,字典编码基于数个连续字符(也叫基于单词),例如ABCABD中AB可以替换成一个新的字符,其可能会减少字符数量,得到的新数据的熵比原来的小,可以对新的数据再用统计编码字典编码的困难之处在于如何选择最佳的单词,其会使得新数据集的熵变得更小。出于性能上的考虑,不可能去遍历整个数据集来寻找最佳的单词,通常会去寻找最长匹配的数据,通过偏移+长度的方式记录匹配数据。因此,字典编码适合有较多重复子串的情况。
2024-10-16 21:31:43
803
原创 数据压缩(3)——统计编码
VLC编码需要数据集中符号概率分布与对应的VLC算法匹配,这限制了其应用范围。而统计编码可以根据数据集符号的概率分布构建出相适应的码字表,尽管在特定数据集上比VLC编码的压缩效果差。哈夫曼编码在分配码字时巧妙利用了二叉树路径唯一的特点,给每个字符分配带有前缀性质的码字。解码就是编码的逆过程:0.83先在[0.4,0.9),第一个字符为G,又在[0.6,0.85),第二个字符为G,最后在[0.825,0.85),第三个字符为B,合起来为GGB。
2024-10-14 20:01:00
1088
原创 Unity AnimationClip详解(1)
前文我们介绍了骨骼动画,在Unity中骨骼动画的部分静态数据存储在SkinedMeshRender中,而另一部分动态的关键帧数据就是存储在AnimationClip中的。
2024-09-09 22:10:46
2089
1
原创 数据压缩(2)——变长编码
变长编码(VLC,variable-length codes)会给出现频率高的字符更短的码字,这样编码后数据集的整体长度就降低。,编码时将码字表写入,再一次写入每个字符的编码。难点在于如何从一个01的stream中区分码字,定长编码每次读取固定长度就行,变长编码不清楚每次需要读取的长度。其核心在于需要通过一套规则,给不同字符合适的码字,以确保频率高的字符有更短的码字,并使得不同码字可以互相区分。如果ASCII表上的大部分字符都出现过了,那么定长编码的压缩方式就很差,需要采用变长编码。
2024-08-28 20:11:02
583
原创 数据压缩(1)——简介
数据压缩存在于计算机、网络的各个地方,是很底层的技术支持,例如歌曲、图像、视频、网页、文本等的保存和传输都是用过数据压缩算法的。总的来说,我们常使用数据压缩来增多硬盘存储的内容、减少网络传输的流量。数据压缩研究的是,在可接受的信息恢复程度下,可以将信息变得有多紧凑。通常有两个思路:减少数据中不同符号的数据量;用更少的位数对更常见的符号进行编码。数据压缩的算法多种多样,没有万能的算法,通用算法能保证数据集经过压缩后会变小,但与数据特征相匹配的算法能将数据集压缩得更小。
2024-08-25 17:50:06
852
原创 程序的自我修养(1)——自测
在开发过程中,每完成一个小地方,测试看看功能是否符合预期,是每个程序都会做的,这一过程也叫做自测。可以说,每个程序都知道并且会自测,然而,实际中却经常有不自测的情况,可能由于程序疏忽、偷懒、被逼等等原因,我们来分析一下。我们将不自测导致的情况分为编译不通过、进入有报错、测试有Bug三类。
2024-08-15 20:09:26
860
原创 UI框架与MVC模式详解(3)——MVC\MVP\MVVM
在MVC中,业务数据直接就是界面所需的交互数据,但在MVVM中,会有一个业务数据和界面数据的映射,例如业务数据中的UserName字段界面数据中可能仍是原样显示,但地区需要从“中国”显示为“China”在MVVM中,View中需要新增一个绑定逻辑,为PanelViewBind,其将V中的组件的数据与VM中的数据绑定,以便于双方中有变化时会自动通知对方取修改值。MVC是整个UI框架层级上的模式,PDI是在已有的UI框架模式下(不一定是MVC)的具体功能上的模式,他们的核心都是实现逻辑和数据的分离。
2024-07-31 21:32:36
1307
1
原创 UI框架与MVC模式详解(2)——数据管理
如果很多,我们要逻辑和数据分离,这里是方法层级的逻辑和数据分离,前文说的是功能层级的逻辑和数据分离。对于不同的数据单位,加载时的逻辑是相同的。加载前的信息和加载后的方法对加载时的逻辑而言都是数据,这是流程的逻辑数据和分离,比之前说的方法的逻辑数据和分离复杂点。有个数据管理的类,初始化时把所有需要的数据加载到内存中,提供不同的Get方法,让同一个界面获取不同的数据或者不同的界面获取相同的数据。数据单位要大而全,因为不同界面需要的显示数据虽然都来自同一个数据单位,但有的显示数据单位中的A,而有的不会.
2024-06-28 19:30:02
980
1
原创 UI框架与MVC模式详解(1)——逻辑与数据分离
分离的方式是显示的逻辑、获取数据的逻辑(耦合的方式没有该逻辑,其直接就有数据)、数据集合的逻辑,显示的逻辑和数据集合的逻辑不耦合,两者通过获取数据的逻辑关联起来,当新增显示时,显示的逻辑不变、获取数据的逻辑不变,数据集合需要新增数据,一个界面改变引起一处逻辑改变。交互会使得界面显示内容发生变化,交互的数据记录的是界面的状态。耦合的方式将界面显示的逻辑与显示需要的数据耦合在一起,当需要新增显示时,显示逻辑不变,但数据增加,即数据改变,导致需要修改显示逻辑,一个界面改变引起两处逻辑改变。
2024-06-07 20:48:47
1263
原创 Unity构建详解(12)——自动构建
自动构建是指整个构建流程不需要人工操作,只需要输入启动构建指令即可获取构建结果。支持命令行参数启动我们不可能每次构建时都打开Unity去手动点击构建,必须支持通过命令行启动Unity自动执行构建我们每次构建的需求不同,可能构建Debug、Release或者其他特殊需求的应用,因此Unity需要支持识别命令行输入的不同参数支持工程自动检查我们构建时需要保证不会出现错误,否则中途就构建失败了。
2024-05-10 22:34:01
1559
1
原创 Unity构建详解(11)——代码剥离
我们知道代码在程序启动时全量加载到内存中的,一个库内的全部代码有些是不需要使用的,遵循减少内存的一个目标:内存中仅存在需要的数据,如果可以将这些不需要的代码去除,就可以减少内存,这就是代码剥离。对于大型的项目而言,代码剥离可以减少几十M的常驻内存。在Unity中,被剥离的是托管代码,并不是全部代码。使用 IL2CPP 编译时,托管代码剥离还可以减少构建时间,因为需要转换为 C++ 并进行编译的代码减少。游戏逻辑的C# 脚本构建的程序集插件中的程序集。
2024-04-28 19:26:34
1742
原创 Unity构建详解(9)——Addressable的打包结果
这里说的Task不算是真正SBP管线里的了,而是Addressable中自定义的,包括之前的GenerateLocationListsTask和Post Processing AssetBundles都是在Addressable中的。一般使用SBP时都会用上Addressable。
2024-04-15 21:24:08
1164
原创 Unity构建详解(8)——SBP的Bundle生成
这个Task目的是为Addressable创建寻址信息,在Addressable中每个Bundle、Asset都会有一个用于寻址的Location,上层加载传入路径,根据路径找到Location,根据Location找到Bundle、Asset。这一步会生成实际的SerializedFile文件,文件名就是之前的InternalName,但这还不是最终的Bundle文件,所以在之前的处理中会有一个InternalnameToBundleName的映射。这里生成最终的Bundle文件。
2024-04-13 18:32:29
1296
原创 Unity构建详解(7)——AssetBundle格式解析
文件可以分为文本文件、图片文件、音频文件、视频文件等等,我们常见的这些文件都有行业内的标准格式,其意味着按照一定的规则和规范去保存读取文件,可以获取我们想要的数据。有些软件会有自己的文件格式,会按照其自己设计的规则和规范去保存读取文件。我们自己在做开发时,也需要保存和读取一些文件,但我们需要保存的数据一般会比较简单,很少去做设计格式。例如,要保存一系列角色和怪物的当前血量。为了读取时能区分是角色和怪物,可以先保存角色个数。读取时先读取角色个数n,随后读取n个血量数据,这些都是角色的,随后读取的是怪物的。个数
2024-04-11 20:42:26
2215
原创 Unity构建详解(6)——SBP的Bundle写操作生成
这里其实就是将构建Bundle需要的参数封装成命令模式,每一个File(AssetBundle)都会有一个WriteOperation。这里主要是为了处理BuildExtendedAssetData,创建对应的AssetLoadInfo。
2024-04-02 21:21:48
1227
原创 Unity构建详解(5)——SBP的Bundle组装
Bundle里包含Asset、Asset由Object组成,Object由Type组成。前文说了Type由MonoScript的方式处理,这里我们需要从Object看起。IDeterministicIdentifiers:确定性标识符,对每个对象可以生成唯一一个标识符,具有可重复性和确定性。可以理解为一种哈希方式。Object会依赖其他Object,所以Asset会依赖其他Asset,进而Bundle依赖其他Bundle。我们组建Bundle有两个目标和一套规则。
2024-03-30 18:14:46
993
原创 Unity构建详解(4)——SBP的依赖后处理
这里Task存在的主要原因是:指定哪些Asset要被打包时不是一个个指定的,可以指定贴图所在的文件夹,此时文件夹内的贴图都被认为是要打包的Aseert,但实际上可能有些贴图根本不需要被打包,这里就是为了去掉不需要使用的贴图。Hash算法接受的输入是byte[],通常我们需要将输入数据转为byte[],转化的实现看HashingMethods.GetRawBytes。这里主要是因为要根据依赖计算Hash值,所以才放在后面,如果仅仅根据指定的Bundle的名字来算Hash值,可以去掉这个Task。
2024-03-27 20:30:05
1633
原创 Unity构建详解(3)——SBP的依赖计算
增量是缓存的一种方式,每次在已有缓存的基础上进行构建,就是增量构建。一般的缓存,例如对象池等,其数据还在内存中,增量时缓存数据要保存在磁盘中,构建时再加载到内存中。
2024-03-24 17:43:00
1807
原创 Unity构建详解(2)——SBP的初始设置和脚本编译
TypeDB包含了Unity中所有的类、结构体、枚举、委托,类的名称、命名空间、父类、成员变量、方法等信息,它是Unity的一个核心组件,可以用来进行反射和序列化等操作,可以通过C#代码或Unity编辑器中的各种工具来访问和修改。这里步骤是可选得,在BuildApp时做,其可以用增量缓存,会生成一系列的文件和Dll,如果有缓存,可以直接读取。也是直接调用unity提供的接口,重新打包图集,这一步也可以完全自己做,一般会根据项目的实际情况,自己做图集打包。
2024-03-19 20:31:35
1033
原创 Unity构建详解(1)——SBP介绍
Unity的资源工作流程分为导入、创建、构建、分发、加载。我们说的是其中的构建步骤。构建是指将项目工程中的资源文件和代码整合程可执行文件的过程,构建的结果是生成可执行文件,在win平台上是exe,在Android平台上是apk,在ios平台上是ipa。游戏比互联网的app多了很多资产,资产的整合是构建过程中非常耗时且重要的一步,这一步通常会被单独拿出来说,叫打包,在Unity中叫打Bundle。因此,我们说游戏构建通常分为两个大步骤,一是打包,二是构建。
2024-03-18 20:04:02
3286
原创 C#流Stream与IO详解(5)——读取文件的详细流程
这里说的是阻塞式读写文件,只说主要的流程,不包括每个流程中为了处理不同情况的更细节处理。
2023-11-21 13:53:08
876
原创 Unity Job System详解(2)——如何将业务逻辑Job化
Job化只是加速计算,并不改变原有的业务计算逻辑。并不是一上来就使用Job,而是要先优化下原有的业务逻辑。如果优化完了之后提升并不大,可以考虑再Job化。最好真的有一块的业务逻辑需要你Job化,要不然可能看不懂接下来再说什么。
2023-11-16 19:45:14
1201
原创 Unity Job System详解(1)——基础知识
当需要加速计算以优化性能时可以考虑使用Job。为了更好的理解和使用Job,需要有些并行编程的基础。必须先了解下C# Task是怎么回事。
2023-11-14 20:58:33
1578
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人