找回密码
 立即注册
查看: 253|回复: 4

UE5 PuerTS学习与实践(三)运行时编写PCGGraph法则

[复制链接]
发表于 2023-8-15 16:08 | 显示全部楼层 |阅读模式
在学习Puerts和虚幻引擎的PCG框架过程中,发现PCG框架的大部门代码标识表记标帜为蓝图可用,而且一些函数也未做WITH_EDITOR的宏限制。
既然这些蓝图可用的函数并未限制在编纂器下使用,我们便可以在Puerts中使用这些导出的函数,在运行时创建PCGGraph,而且添加相应的PCG蓝图节点,对这些节点进行连线,从而生成一系列有效的PCG法则来安插场景。有趣的是,这些操作都是在打包后的法式里面完成的。
本文简单记录在学习Puerts及PCG框架过程中,将两者融合在一起,实现动态的内容生成和场景安插。
1、涉及到的PCG框架内容简述




PCGGraph内容

在编纂器中使用PCGGraph编写PCG生成法则时,会在里面拖放PCGNode,例如GetSplineData、SplineSampler等,这些节点内部持有一个TObjectPtr<UPCGSettingsInterface> SettingsInterface,此变量决定了PCGNode节点的名称、输入/输出引脚名称以及点击节点后的细节面板信息。
在PCGGraph上任意双击一个节点跳转到C++代码中,便可看到该类型对应的是UPCGSettings的哪个子类。在后面使用Puerts脚本去生成PCGNode设置具体的UPCGSettings时,以及模拟在细节面板调整一个节点的属性值时,都需要UPCGSettings派生类中的具体信息。
这里打算在一个封锁的Spline圈出的区域内部采样一些点位,将这些点位投影到Landscape上,并做一些随机的Rotation、Scale变换,然后在每个点生成一些简单的静态网格体。
2、选择点数据输入来源

这里我们选择在一个Actor类添加一个Spline作为采点的输入源,同时里面放置一个PCGComponent组件,不外我们不必为其指定Graph的值,留着等运行时设置为临时创建的PCGGraph。



BP类设置

3、运行时编写PCGGraph法则

使用ts脚本来编写PCGGraph,同样也遵照UE编纂器下的使用模式:

  • 创建PCGGraph资产
  • 创建所需的PCGNode
  • 设置PCGNode的细节面板值
  • 连接各个PCGNode
  • 赋值到需要使用PCGGraph的处所
后面针对个点详细展开。
3.1、创建PCGGraph

使用Puerts创建一个PCGGraph斗劲简单,直接new一个相应的对象即可。
  1. // example:运行时生成PCGGraph
  2. let RuntimeNewPCGGraph = new UE.PCGGraph();
复制代码
创建一个PCGGraph后,其内部会自动创建两个节点:InputNode和OutputNode,不外这两个节点不是必需要使用的。
PCGGraph上的连线关系与蓝图类等Blueprint分歧,其内节点间维护的是数据的流动关系,数据并不必然要从InputNode开始流动,也不是必需流动到OutputNode才能发生效果。任意可以发生点数据的节点都可以作为连线的开始节点。
此次Demo便以GetSplineData节点作为数据发生的开始节点,以StaticMeshSpawner节点作为结束节点。
3.2、创建所需的PCGNode

向PCGGraph中添加一个所需的节点,只需遵循以下“公式”即可:

  • 创建一个所需类型节点的UPCGxxxSettings子类对象
  • 设置第一步创建出来的对象的一些所需的参数,该法式非必需
  • 调用PCGGraph的AddNodeInstance函数,将第一步创建出来的对象作为参数传入
颠末以上三步,便可以创建一个所需的PCGNode。
我们在代码中模拟创建以下节点:



代码需要模拟创建的PCGGraph示意图

创建一个GetSplineData节点

这个节点的创建及添加到PCGGraph斗劲简单,因为没有对PCGGetSplineSettings中的参数做改削。创建GetLandscapeData、Projection节点的方式与创建GetSplineData类似,不再赘述。
  1. let GetSplineDataSettings = new UE.PCGGetSplineSettings();
  2. let GetSplineDataNode = RuntimeNewPCGGraph.AddNodeInstance(GetSplineDataSettings);
复制代码
创建SplineSampler节点

需要对此中的个别参数进行设置。以创建SplineSampler节点为例,在代码中设置了对样条的采样方式为:对样条内的区域采样,每个采样点的空间距离=2000cm。
  1. let SplineSamplerSettings = new UE.PCGSplineSamplerSettings();
  2. SplineSamplerSettings.SamplerParams.Dimension = UE.EPCGSplineSamplingDimension.OnInterior;
  3. SplineSamplerSettings.SamplerParams.InteriorSampleSpacing = 2000.0;
  4. let SplineSamplerNode = RuntimeNewPCGGraph.AddNodeInstance(SplineSamplerSettings);
复制代码
此中InteriorSampleSpacing参数的感化如下图所示:


TransformPoints节点的创建方式及参数设置与之类似,我在这里对点位做了绕z轴,取值范围在(-180,180)之间的随机旋转,并对Scale做了取值范围在(0.2,1.5)之间的随机缩放。
创建StaticMeshSpawner节点

在PCGGraph中配置StaticMeshSpawner节点,如下图所示:



在PCGGraph中配置StaticMeshSpawner节点

使用代码创建一个StaticMeshSpawner节点却斗劲麻烦,因为它的参数配置并不直接显式的放在UPCGStaticMeshSpawnerSettings中。
在查看PCGStaticMeshSpawner.h文件时,发现了一个FPCGStaticMeshSpawnerEntry的布局体与上图的配置形式斗劲类似,但是它的反射标识表记标帜信息中写到DeprecationMessage=”Use MeshSelectorWeighted instead.”,说明该布局体已经弃用了,但是在头文件中并没有发现它所说的MeshSelectorWeighted的变量。
对比该节点的细节面板和头文件,发现UPCGStaticMeshSpawnerSettings中的MeshSelectorType变量是一个TSubclassOf<UPCGMeshSelectorBase>类型变量,当在细节面板上下拉选择该变量的值时,有PCGMeshSelectorWeighted字样。
同时,该头文件中还有一个TObjectPtr<UPCGMeshSelectorBase> 类型的变量MeshSelectorInstance。这不就说明一个是选择子类类型,一个是持有子类对象的指针。
因此,顺着UPCGMeshSelectorBase往下查找派生类,可以看到一个UPCGMeshSelectorWeighted类,而此中有一个TArray<FPCGMeshSelectorWeightedEntry>类型的变量MeshEntries,与细节面板上显示的对照了起来。
我们可以在代码中创建一个UPCGMeshSelectorWeighted对象,依次创建所需的对象,最后设置最内层的Static Mesh软引用即可。



设置MeshSelectorInstance
  1. // 创建UPCGStaticMeshSpawnerSettings对象
  2. let StaticMeshSpawnerSettings = new UE.PCGStaticMeshSpawnerSettings();
  3. // 设置StaticMeshSpawnerSettings中的MeshSelectorInstance
  4. let PCGMeshSelectorWeighted = new UE.PCGMeshSelectorWeighted();
  5. ...
  6. StaticMeshSpawnerSettings.MeshSelectorInstance = PCGMeshSelectorWeighted;
  7. // 添加一个StaticMeshSpawnerNode
  8. let StaticMeshSpawnerNode = RuntimeNewPCGGraph.AddNodeInstance(StaticMeshSpawnerSettings);
复制代码
3.3、连接PCGNode的引脚

创建完所有的节点后,我们便可以使用UPCGNode类中的AddEdgeTo函数连接相应的引脚,该函数将UPCGNode输出引脚连接到一个指定的UPCGNode对象的输入引脚上,通过PinLabel来确定输入、输出引脚。
  1. GetSplineDataNode.AddEdgeTo(”Out”,SplineSamplerNode,”Spline”);
  2. SplineSamplerNode.AddEdgeTo(”Out”,TransformPointsNode,”In”);
  3. TransformPointsNode.AddEdgeTo(”Out”,ProjectionNode,”In”);
  4. ProjectionNode.AddEdgeTo(”Out”,StaticMeshSpawnerNode,”In”);
  5. StaticMeshSpawnerNode.AddEdgeTo(”Out”,OutputNode,”Out”);
  6. PCGLandscapeDataNode.AddEdgeTo(”Out”,ProjectionNode,”Projection Target”);
复制代码
这里要注意连接节点的名称,比如”Projection Target”,千万不要少打一个空格,这个名称不确定的话,到UPCGSettings相关代码中寻找PinLable的真实值。
3.4、使用PCGGraph

这里我们只需在运行时创建一个第2节设计的蓝图类的对象,获取到此中的PCGComponent,令该PCGComponent使用我们运行时生成的PCGGraph,然后调用一下PCGComponent->Generate(true)函数,即可执行生成内容。
  1. let PCGComp = Actor_SplinePCG.GetComponentByClass(UE.PCGComponent.StaticClass()) as UE.PCGComponent;
  2. if(PCGComp)
  3. {
  4.     PCGComp.SetGraph(RuntimeNewPCGGraph);
  5.     PCGComp.Seed = UE.KismetMathLibrary.RandomInteger(1000);
  6.     PCGComp.Generate(true);
  7. }
复制代码
代码里我做了反复活成第2节的蓝图类对象,然后给此中的PCGComponent的变量Seed一个随机值,为了使每次生成的内容都纷歧样。
4、成果


4.1、运行时创建的PCGGraph内容

我们首先借助UE编纂器的环境,查看一下我们运行时生成的PCGGraph长什么样。

运行时创建的PCGGraph内容
https://www.zhihu.com/video/1664061732761862144
可以看到,我们创建了一个名字叫做PCGGraph_61的资源,名称并不是我起的,而是UE代码中自动设置的。
点开该PCGGraph可以看到,这些PCGNode都挤在一起,这是因为我们在生成PCGNode的时候并没有指定一个它们在蓝图上的坐标,不外这里我们不需要去可视化的看它们,所以也不必设置坐标。这里我前面在连接节点的时候把OutputNdoe也连接上了,懒得断开重录视频了。。
4.2、应用PCGGraph生成内容

打包一个Shipping版本的UE法式,看一下功能。

打包后运行时创建PCGGraph
https://www.zhihu.com/video/1664065635909877760
可以在运行时创建PCGGraph来法式化生成内容,PCG框架的可玩性还是斗劲高的。在运行时写一些UI来配置这些节点的生成,连接,即可模拟一个运行时的PCGGraph蓝图系统。

感觉本文有趣的话请点个附和、存眷吧!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2023-8-15 16:08 | 显示全部楼层
厉害[赞],之前还在群里看别人问,PCG能不能在运行时候编辑,大佬都做出来了
发表于 2023-8-15 16:08 | 显示全部楼层
还是ue给的太慷慨![doge][doge]
发表于 2023-8-15 16:08 | 显示全部楼层
太厉害了,之前一直没找到解决的办法,大佬真的牛👍👍👍
发表于 2023-8-15 16:09 | 显示全部楼层
起来学习[抱抱]
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-4-28 20:09 , Processed in 0.996865 second(s), 27 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表