UE5官方文档(第一人称射击游戏教程)解读 第十章
又到了我们学习的时间了。其实我很喜欢写教程的时间完整的思考和复盘有种很充实的感觉现实里面总是有很多烦心事写教程的时候就不会去乱想。如果有人能够因为我写的教程受益我会很开心。第十章了也感谢所有点赞收藏评论的各位我一直是有拖延症想着可能还有人等着看所以每天一有空就开始写教程。话说要是这个教程写完了我要不要写一个UE5渲染教程但我写的可能还是根据其他优秀教程的补充毕竟我不是专业人士。管理物品和数据Coder 05 Manage Item and Data in an Unreal Engine Game | Unreal Engine 5.7 Documentation | Epic Developer Community在上一章我们彻底完成了输入部分的学习那么这一章我们看标题也知道是背包系统。在开始前我们思考一下如何设计一个背包系统请手动在纸上写写尝试。我们拾取到一个东西的时候我们要把它放入背包不知道大家知不知道MC的怪力史蒂夫可以背n多组石头这个笑话我们可以知道背包系统的显示一般是这样很多个格子格子里放着物品右下角标着物品数。所以如果我们拾取的东西如果在背包里已经有了此种类那么就往对应物品的数量变量做加法如果是丢物品就做减法最后如果数量为零就删除背包里面的这个物品种类。开始本章学习前我们要知道一个事情我们这里不会出现背包的显示界面开发我们只会涉及背包系统的底层逻辑开发请注意。游戏中的数据组织还是跟着官方文档过一遍。大家可能不太能理解官方文档里面的“数据驱动型GamePlay”概念。那我们一步一步来。但我真的完全不是科班出身所以可能会有很多说错的地方首先我们的电脑从硬件层面上来说就是存储数据传递数据根据指令运算的“计算机”。我们的游戏程序会实时读取很多资源实时加载使用。为了防止资源混淆与冗余譬如文档里面提到的角色对话数据我们肯定是不能把角色对话数据一股脑全写入角色类然后用if角色处在某个剧情就现实该对话但是这样的话我们运行时的宝贵内存就完全浪费了虽然省去了从硬盘读取资源的时间但是不用的对话数据也在此时占着茅坑不拉屎所以我们最好的方法就是把对话资源独立成一个单独的文件然后让角色类实时读取虽然增加了从硬盘读取资源文件的的时间但是我们节省了很多宝贵的内存。同时分离时设计方便我们修改对话资源而不影响角色类本身。分离式设计思想真的是无处不在或者说优化本身分成两个大部分便于开发者的优化对于程序本身的性能优化如果可以的话大家可以去了解电脑里所有和存储相关的硬件然后根据它们到运算相关硬件的距离思考。所以在我们本节物品数据会被按照一定规律设计成一个单独的资源文件然后根据这个规律去在游戏运行时读取显示。数据驱动型Gameplay元素还是先跟着官方文档过一遍概念。首先我们如何去理解“物品数据结构体”这个概念假设我们有一个物品它的基础数据是这样的物品名字物品类型物品用处简述。struct 物品{string 物品名字enum 物品类型string 物品用处简述}请不要纠结这个写得很烂的伪代码毕竟只是概念演示没有IDE我已经不会写正常代码了……请思考一下为什么物品类型用的是枚举思考一下我们这个结构体占的总长度是68字节一般string是32字节enum是4字节如果我们一个物品文件存了3个物品总长度是204字节存储是按照我们之前设计好的结构体。如果我们想要从这个物品文件分开读取物品数据该怎么做对喽就是按照我们已知的设计好的规律去反向读取起始字节读68个字节的数据就是我们第一个物品的数据然后在这段数据里面32字节读物品名字数据4字节读物品类型32字节读物品用处简述……以此类推我们就可以完美地读取完所有数据只要知道我们对于此数据的设计。说起来从硬盘读取数据加载的行为称作反序列化而把数据写入硬盘存储的过程叫做序列化。怎么去理解我们的“数据表资产”我们可以把数据表资产简单理解为我们前面提到的存储了所有物品数据的东西。但它不是背包它只是存储了所有物品数据而我们的背包是记录我们有多少种物品每个物品有多少个显示的时候实时读取数据资产表加载对应物品的名字描述类型。这也是一个分离式设计。定义物品数据还是跟着官方文档来一遍这部分没什么好讲的。创建结构体的头文件容器还是先跟着官方文档来一遍。大家有没有发现一个很神奇的东西在我们之前写的角色类游戏模式类都是继承了某些基础类在头文件那里会有我们的core文件父类文件最后一个是生成文件。我们在这里可以看见core有父类文件没有是因为我们这里本来就没继承啥但是生成文件没有了。而在我们的官方文档里面由于我们的这个结构体需要能被数据表资产获取所以需要引入相关头文件但是我们在游戏运行时数据读取是实时的也需要规律所以我们要运行时知晓类信息也就离不开生成文件。然后前向声明就不赘述如果不懂写到后面就懂了。定义物品属性还是跟着官方文档来一遍这是个啥意思呢意思是创建了一个枚举类名字是物品类型在编辑器里面的表现是选择工具or消耗品只能在里面选一个后面会有演示。UMETA里面什么意思呢意思是在编辑器显示的时候对应设置用这两个双引号的名字。但是我们还可以看见枚举前面也有个宏标记毕竟运行时获取类信息真是无处不在。定义物品属性还是根据官方文档来一遍。我们在很早很早之前就讲过结构体也是需要宏标记的。可是我好像漏掉了一个东西GENERATED_BODY宏运行时获取类信息需要生成文件和各种宏标记同时运作包括这个详细的我就不讲了因为我所知道的也只有这些。但是我们看见枚举里面没有这个宏为什么呢枚举其实就相当于一个变量其本质是整型变量但是结构体和类里面存储了许许多多变量登记的时候两者的区别相当于这样我写了一个枚举变量叫xxxx我写了一个结构体xxxx里面有一个变量叫xxxx下一个叫xxxx……。回到我们的结构体本身结构体里面存储了我们的物品名字还有物品描述。作业FString和FText的区别为什么这里要用FText创建物品数据结构体还是跟着官方文档来一遍。我们可以看见这个结构体继承了FTable表Row行Base类也就是说我们这个FItemData类就是我们的数据表资产的行格式。一行里面有四个数据等会儿可以打开查看第一个是ID第二个是物品类型第三个是物品相关文本物品名称物品描述第四个是目前不知道干嘛的UItemDefinition指针变量ItemBase。其实大家还是无法理解这个ID是什么因为我们第三个之前写好的结构体里面明明打包好了物品名称为什么还需要一个唯一的ID。假设我们在场景里面有三朵一模一样的花它们的名字描述类型所有数据都一样我们怎么存入资产表呢ID的作用此刻就体现出来了我们可以叫它们花朵一号花朵二号花朵三号。不过拾取到背包里面肯定就只识别物品名称了。但我们这里就是为了在它们没进背包时在场景区分。总结一下到目前为止我们设置的是资产表的格式而资产表的格式从某种层面上来说是显示给开发者看的方便开发真正的数据存在了我们第四个指针变量指向的数据里面。查找的时候根据ID读取的时候读取指针数据表行里面显示的其他数据本身就是给开发者看的。编译DataAsset物品定义还是根据官方文档过一遍。// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include CoreMinimal.h #include Data/ItemData.h #include ItemDefinition.generated.h /** * Defines a basic item with a static mesh that can be built from the editor. */ UCLASS(BlueprintType, Blueprintable) class FIRSTPERSON_API UItemDefinition : public UDataAsset { GENERATED_BODY() public: // The ID name of this item for referencing in a table row. UPROPERTY(EditAnywhere, Category Item Data) FName ID; // The type of this item. UPROPERTY(EditAnywhere, Category Item Data) EItemType ItemType; // Text struct including the item name and description. UPROPERTY(EditAnywhere, Category Item Data) FItemText ItemText; // The Static Mesh used to display this item in the world. UPROPERTY(EditAnywhere, Category Item Data) TSoftObjectPtrUStaticMesh WorldMesh; };我们之前说过我们资产表里面的各项都是为了方便开发者查看这个类里面包装的才是我们真正的数据。我们多包装了一个静态网格体指针可以理解为是模型数据填充位。为什么是指针呢我们说过是为了优化实时读取而不是急头白脸把所有数据一下子载入而是先载入指针然后根据需要通过指针读取到需要的数据。可能大家还是无法理解一件事情这样一个结构体为什么我们需要继承UDataAssest类。因为继承了这个类你就可以按照你写的模板在编辑器里面编辑这份数据了不需要写代码了。这部分作为一个作业了解。创建数据资产实例还是跟着官方文档来一遍。其实过程就是写一份数据然后把这份数据填充到数据表。没什么好讲的了今天的学习到此为止。