一 配置总体概述
UE4中,配置文件(Config)其实就是.ini文件。可以用于设置加载时要初始化的属性的值,配置信息按照键值对的格式来实现。虚幻4官方文档只有简单的使用规则,要想深入了解还需要查看源代码才行,所以这里我把自己的学习成果分享给大家。一个完整的配置文件格式如下图1-1所示。
图1-1 配置文件格式示意图
我们所见到的配置文件一般只存在于以下四个路径。
1. \Engine\Config及其子目录
2. \Engine\Saved\Config及其子目录(引擎运行后生成)
3. Projects\[ProjectName]\Config及其子目录
4. Projects\[ProjectName]\Saved\Config及其子目录(游戏项目运行后生成)
如上面所标记的,路径2与路径4的配置信息都是后生成的。
二 概念须知
为了后面的描述清晰,这里需要先简单描述一些基本原理以及一些概念。
2.1概念
Section:如上图1-1,每个配置文件里面有很多模块,每个模块的标题就是一个section。
Flush:在代码里面,类FConfigCacheIni有一个名为Flush()的方法。它的表面意思是冲洗,奔涌,在代码里面表示将内存信息(即缓存的配置信息)准确无误的书写到文件里面。
GConfig:全局空间的一个配置缓存变量。定义如下FConfigCacheIni* GConfig =NULL;在不同的情况下存储不同的信息,如图2-1,图2-2。关于GConfig的内容后面会有较为详细的讲解。
图2-1 运行纯引擎编辑器时GConfig存储的信息
2.2基本原理
2.2.1
配置文件结构
为了对配置文件有一个整体的了解,我们必须要弄清配置文件的结构。
1. 配置分类
Compat(兼容性)
DeviceProfiles(设备概述文件)
Editor(编辑器)
EditorGameAgnostic(编辑器游戏未知的配置信息)
EditorKeyBindings(编辑器按键绑定)
EditorUserSettings(编辑器用户设置)
Engine(引擎)
Game(游戏)
Input(输入)
Lightmass(灯光构建相关)
Scalability(可扩展性)
EditorLayout(编辑器布局)
SourceControlSettings(源码控制设置,只存在于引擎和工程的Save目录)
TemplateDefs(模板定义,只存在于引擎和工程的Save目录)
上面基本描述了所有配置文件的类型,不同类型的配置设置放在不同的文件里面。
2. 文件层次结构
配置文件读入时从Base.ini开始,文件结构中后面文件内的值覆盖之前的值。Engine文
件夹中的文件将应用于所有项目,而针对特定项目的文件应该位于项目目录中的文件内。最后,所有特定项目和特定平台的差异都被保存到 [ProjectDirectory]/Saved/Config/[Platform]/
[Category].ini文件中。下面是配置文件的Engine类的文件层次结构示例。
① Engine/Config/Base.ini 该文件一般是空的
② Engine/Config/BaseEngine.ini
③ Engine/Config/[Platform]/[Platform]Engine.ini
④ [ProjectDirectory]/Config/DefaultEngine.ini
⑤ [ProjectDirectory]/Config/[Platform]/[Platform]Engine.ini
⑥ [ProjectDirectory]/Saved/Config/[Platform]/Engine.ini
对上面的内容作一下解释:我们在读取配置信息的时候,会按照上面的路径读取,因为后面层级的内容对于项目针对性逐步增强,所以层级越高里面的配置信息优先级越高。同时要注意到很多配置文件的名称是固定的(中括号的除外),所以不能随意删除和修改这些配置文件。下一条原理2.2.1会对这里做进一步的阐述。
2.2.1
配置文件目录分析
在总体概述里面,我们简单的描述了配置文件的目录,这里要拿出来作进一步分析。
我们所见到的配置文件一般只存在于以下四个路径。
1. \Engine\Config及其子目录
2. \Engine\Saved\Config及其子目录(引擎运行后生成)
3. Projects\[ProjectName]\Config及其子目录
4. Projects\[ProjectName]\Saved\Config及其子目录(游戏项目运行后生成)
下面先给出4个目录下的文件内容,作一个直观的了解,同时请大家牢牢记住这4个路径以及其对应的编号,后面会多次提到。
图2-2 Engine目录下配置文件的对比图(路径1与2)
图2-3 Project目录下配置文件的对比图(路径3与4)
那么这四个目录之间的关系是什么,有什么作用?
首先我们要知道路径1余路径3中的配置文件在引擎在运行的时候是不会生成的,所以这里面的文件以及信息需要你手动的添加,当然引擎路径1的配置文件都已经写好了一般不需要改动(但是当你新建一个空白项目时,引擎会为你在路径3里自动生成DefaultEngine.ini以及DefaultGame.ini 还可能会生成DefaultEditor.ini,如果使用提供的模板,同时还会生成DefaultEditor.ini和
DefaultInput.ini 配置文件)。
根据我上面给出的对比图,我们可以把引擎和项目分开来看。当你只是打开一个纯引擎的编辑器时,与项目是没有任何关系的。引擎会根据自身的代码逻辑以及路径1中的配置信息来生成路径2中的配置信息。同理,我们在运行工程的时候也会根据代码逻辑和路径3的配置信息来生成路径4的配置信息,不过有一点区别。因为我们刚刚看了原理2.2.1的文件层次结构,所以应该明白工程的配置文件(路径4下的)生成还与路径1中的引擎配置文件有间接关系(与路径2的无关系)。
另外,我们在上面对比的其实是路径3与路径4\windows,而路径4下的结构一般是下面这样的,除了平台文件夹还会有一个CleanSourceConfigs文件夹。
图2-4 Projects\[ProjectName]\Saved\Config目录结构图
我们生成的信息分布在这两个文件夹,CleanSource文件夹里面其实就是路径1和路径3配置信息的总和(也就是文件层次结构⑥之前的配置信息),其他与游戏本身,与平台相关的差异配置信息会最终存储到Windows(对应的平台)目录下。
总结一下,无论是引擎还是项目,在Config下的配置信息都是手动添加的,而Save/Config下的都是引擎或项目运行后生成的。
注意:如果删除了工程下的DefaultEngine.ini,会导致工程无法运行,提示如下的错误。(这表示Engine目录下的配置必须要有 [URL] GameName=ProjectName)。
图2-5 丢失DefaultEngine.ini的报错示意图
三.使用流程
3.1自动配置
在UE4里面,我们利用引擎提供的特性,只需简单做一下配置就可以轻松实现配置信息的保存与读取。基本流程如下所示:
图3-1 自动实现属性配置流程图
1. 为指出应该从哪个配置文件中读取哪个变量,那么在包含这些变量的类的UCLASS
宏中应赋予Config标识符。
注意:必须为 Config 标识符提供类别(比如Game(游戏))。这确定了从哪个配置文件中读取类的变量及将其保存到哪个配置文件中。所有可能的分类都在类ConfigCacheIni(实际是GConfig对象)中进行定义。所有配置文件的分类列表在第二部分里面已经描述。
2. 要想指定读取和保存到配置文件中的某个变量,也必须为该属性UPROPERTY()宏提供Config标识符。如下图所示
对属性的Config标识符不提供任何分类。ExampleVariable属性现在可以从配置文件结构(第二部分有描述)的任意Game配置文件中读取,
只要信息由以下语法所指定(这里面的ModuleName指的是包Package,ExampleClass就是对应的类名。)
3. 这一步在官方文档上没有说明。其实就是让标记config属性的值与构造函数初始化时的值不同。如果你只按照上面两个步骤做,就会发现,对应的配置文件里面还是没有你想要的变量。因为在引擎执行SaveConfig的时候会遍历一遍所有的标记Config的属性,判断当前对象的config属性是否与同属一类CDO的相同属性的值相同,如果相同证明没有任何修改,也就没有必要存储到配置文件里面去。
所以这里我们有两种办法修改属性的值,一是在一个方法里面修改该config属性的值,然后在游戏开始的时候调用。另一种是将这个config属性设置为蓝图可读写,在蓝图配置里面修改。最后,在这些修改操作执行之后一定要调用SaveConfig来保存配置信息,同时就会将信息写到配置文件里面。
注:CDO即ClassDefaultObject。这涉及UE4每个对象的内部结构,暂时可以理解系统在运行时会默认构造一个该类的对象,这个对象只执行了构造函数。
3.2手动编码配置
除了上面描述的自动配置的方法,我们也可以通过编码让配置信息保存到任意的文件里面。这里我们就必须要用到SaveConfig函数,SaveConfig()函数可以在使用配置类修饰符的类上进行调用。一般来说,由SaveConfig()保存的变量位于按照格式
[(package).(classname)] 命名的部分标题中。例如,DefaultEngine.ini中的[/Script/Engine.Engine] 部分指向存储在 Engine包内部的 Engine 类。
图3-3 引擎目录下的Engine包
下面举例在GameMode中如何使用手动编码将信息保存到指定的位置。
1. 首先我们要定义一个方法,用来将某个类型的配置信息保存到GConfig里面。这里
面逻辑是判断当前的值是不是要配置的默认值?是否已经存在与GConfig?是默认的而且不在GConfig里面就执行GConfig->SetBool操作,将该bool变量保存到GConfig。
2. 然后在GameMode初始化阶段执行这个方法并调用SaveConfig保存。
GGameUserSettingsIni是全局的FString,保存的是GameUserSetting配置文件的路径,所以这里我们就可以把GameMode的配置信息保存到GameUserSetting里面了。其他的配置文件路径可以在Core.cpp里面搜索到。
四.配置文件的存储流程
图4-3 配置信息一般存储流程图
注:SaveConfig是UObject的方法,可以在任何标记Config的子类上调用。
五.原理与特性分析
5.1配置文件的意义
在刚接触配置文件的时候,我最大的疑问就是配置文件一般是用来读的,为什么要生成这么多的配置文件以及信息?
这里可以这样理解,
第一次运行游戏项目,引擎会首先读取引擎目录\Config,游戏目录\Config里面的配置信息,以及配置文件路径(运行游戏时的配置路径就是[ProjectDirectory]/Saved/Config/[Platform]/)存储到GConfig里面。然后其他的配置信息会在各个模块执行其SaveConfig()的时候写到GConfig(前面有简单的描述)最后通过GConfig写到生成的配置文件里面。(注:这里生成的文件是指Projects\[ProjectName]\Saved\Config及其子目录,第二章节有讲解)
而第二次运行时,因为之前已经生成了配置文件。引擎首先会把所有的配置文件里面的数据读入到GConfig里面(包括第一次运行生成的),然后如果发现配置文件信息有遗漏或者有改动,就在执行SaveConfig()的时候再次写入到GConfig里面,同时再写到路径4(见2.2.1章节)配置文件里面。
通过上面,我们可以知道。其实我们修改后来生成的配置信息是没什么意义的,因为每次游戏运行的时候我们都想把配置信息存储成我们在代码或者工程想要的样子。这部分生成的配置信息我们可以在游戏运行后读取并加以利用。不过我们可以修改路径3(见2.2.1章节,工程默认配置)里面的信息,这里面的信息是我们想修改后就会立刻生效的。如果你事先把所有信息都写在了路径3下面,就会发现路径4里面的配置信息没有任何内容了。
5.2配置文件的存储原理
GConfig前面已经出现了多次,我们有必要了解一下GConfig到底是什么。其实,GConfig就是一个配置信息缓存量,在运行时里面会一直保存所有的配置信息。
下面是GConfig所属的类以及继承关系图。
图5-1 GConfig继承关系图
从上面的图我们可以看出,GConfig可以说是一个三层结构的Map(最底层为MultiMap),依次存储了配置路径,配置section,配置属性与值。详细的结构我们可以通过下图来进一步了解。
图5-2 GConfig内容结构解析图
这里需要对FConfigFile这个类型做一下解释。从上面两个图可以看出,一个配置文件FConfigFile类由5个成员构成,这个5个成员可以解释前面的很多规则与现象
1.FName 记录这个配置文件的类型
2.SourceIniHierarchy 就如我们在第二章节2.2.1所说明的,这个成员变量描述了文件的层级结构,根据这个结构我们最后的差异信息才会存储到\Windows下面
3.SourceConfigFile 表示从本地最开始加载的配置内容(其实就是加载第二章节2.2.2中所说的路径1和路径4里面的配置信息)
4.dirty 文件是否被修改,在写文件的时候,如果这个值为False就不会重写配置文件
5.NoSave文件是否没有保存
5.3配置文件和继承
Config UCLASS和UPROPERTY标识符都将被子类继承。这表示子类可以读取或保存父类中指定为Config的所有变量,并且它们将会位于相同的配置文件分类中。变量都会位于具有子类名称的部分下。例如,继承 ExampleClass 的 ChildExampleClass 的配置文件信息看起来如下方的代码行,并且将被保存在同一个Config配置文件中。
子类无法否定继承父类的Config标志,但是子类可以通过重新声明config 关键字并指定一个不同的文件名来更改这个.ini文件。
5.4基于每个对象实例的配置
虚幻引擎 4 可以把一个对象的配置信息保存到任何所需的配置文件中。如果 PerObjectConfig 标识符用于 UCLASS 宏,那么这个类的配置信息将会基于每个对象实例进行存储,其中每个对象实例在.ini文件中都有一部分,该文件以这个对象的名字命名,格式如下 [ObjectName ClassName] 。 这个关键字会传递给子类。
这样的例子可以参考UDeviceProfile类,它的前置宏为UCLASS(config=DeviceProfiles, perobjectconfig, Blueprintable)。表示会产生名称为DeviceProfile.ini的配置文件,并且里面的信息如下所示。
[XboxOne DeviceProfile]
Key=Value
[iPhone5S DeviceProfile]
Key=Value
里面的代码细节其实就是在执行SaveConfig的时候,会判断这个当前对象是否用PerObjectConfig 进行标记,是的话就为这个对象单独标记一个配置信息(如Section = L" iPhone5S DeviceProfile "),否则就统一标记到这个类里面(如Section = L"/Script/
UdpMessaging.UdpMessagingSettings")。
六.其他
1.配置文件的生成要远比内容书写早的多,如果是第一次运行,在工程运行的一开始就会通过GenerateDestIniFile生成空的配置文件。
2. FPaths包含了游戏工程的各种目录路径,如EngineDir,GameSavedDir,GameContentDir等,这对于配置信息的存储路径很重要。