UE5 C++开发:GConfig与.ini文件高效读写全解析

📅 2026/8/8 13:41:59
UE5 C++开发:GConfig与.ini文件高效读写全解析
1. 项目概述为什么UE5开发者必须掌握GConfig与.ini文件在UE5的C开发中数据管理是个绕不开的话题。无论是保存玩家的存档进度、配置游戏难度参数还是管理不同平台的图形设置我们都需要一个可靠、高效且易于维护的持久化方案。你可能会想到用JSON、XML甚至自己写个二进制文件格式。但在Unreal Engine的世界里有一个被深度集成、开箱即用且性能经过高度优化的“原生”方案——那就是.ini配置文件以及操作它的核心接口GConfig。很多刚接触UE C的开发者尤其是从其他引擎转过来的容易忽略这套系统觉得它“太老派”或者功能简单。但实际用下来你会发现对于引擎和项目自身的配置管理.ini文件配合GConfig是最高效、最“UE范儿”的选择。它直接与引擎的反射系统、热重载、多平台部署流程深度绑定。简单来说你用UPROPERTY(Config)标记的变量引擎会自动帮你读写到对应的.ini文件里而GConfig则为你提供了在代码中任意位置、以编程方式精细操控这些配置的底层能力。这篇文章我就结合自己十多年的项目经验带你彻底吃透GConfig在UE5 C中对.ini文件的高效读写。我会从最基础的配置文件结构讲起深入到GConfig的每一个核心API的实战用法并分享那些官方文档里不会写的“坑”和最佳实践。目标是让你看完后不仅能熟练使用更能理解其设计哲学在项目中游刃有余地管理所有配置数据。2. .ini文件与GConfig核心机制深度解析2.1 .ini文件UE配置系统的基石.ini文件本质上是一种基于文本的、分段的键值对存储格式。在UE中它不仅仅是简单的文本更是引擎配置层级体系Hierarchical Configuration的物理载体。2.1.1 文件结构与语法一个典型的UE.ini文件长这样[/Script/Engine.GameSession] MaxPlayers100 SessionNameMyAwesomeSession [DarkSoul_Settings] PlayerNameAshenOne DefaultHealth500.0 DifficultyHard MasterVolume0.8 ProhibitedWeaponsDarkSword ProhibitedWeaponsChaosBlade[Section] 方括号内的部分称为“节”或“段”。这是配置的逻辑分组。在UE中对于UClass的配置节名通常是[/Script/ProjectName.ClassName]的形式这是引擎反射系统自动生成的。KeyValue 最基本的键值对。等号两边通常没有空格虽然部分解析器允许但UE内部生成的标准格式没有。KeyValue 加号表示向一个数组TArray追加一个元素。如上例中的ProhibitedWeapons最终在代码中会解析为一个包含DarkSword和ChaosBlade的字符串数组。;或// 注释符号。分号是传统.ini格式的注释双斜杠是UE扩展支持的。2.1.2 UE的配置文件层级与合并规则这是UE配置系统最精妙也最容易让人困惑的地方。UE不会只从一个地方读取配置而是遵循一套严格的优先级合并规则。理解这个才能明白为什么你改了DefaultEngine.ini可能不生效。配置文件主要存放在两个目录引擎目录 ([UE_Install]/Engine/Config/): 包含所有引擎的默认配置Base.ini,DefaultEngine.ini等。项目目录 ([Project]/Config/): 包含你项目的默认配置。当引擎启动时它会按照以下顺序“层叠”地加载和合并配置引擎基础配置- 2.引擎平台配置- 3.项目默认配置- 4.项目派生配置- 5.用户覆盖配置。最终生效的配置是所有这些文件合并后的结果后加载的会覆盖先加载的同名Key。例如项目中的DefaultGame.ini可以覆盖引擎BaseGame.ini里的设置。而运行时生成的Saved/Config/...下的文件用户设置优先级最高。GConfig对象一个FConfigCacheIni全局实例就是管理这个庞大缓存和合并逻辑的核心管理器。你通过它读写配置它帮你透明地处理所有底层的文件查找、解析和合并。2.2 GConfig揭秘全局配置缓存接口GConfig是一个在引擎启动时就被创建的全局变量类型是FConfigCacheIni*。你可以把它理解为一个所有.ini文件内容在内存中的“缓存数据库”。2.2.1 GConfig的工作流程初始化 引擎启动时根据上述层级规则加载所有相关的.ini文件到GConfig缓存中。读取 当你调用GConfig-GetString()时它从内存缓存中查找对应Section和Key的值无需重复读盘。写入 当你调用GConfig-SetString()时它首先更新内存缓存然后根据策略立即或延迟将改动写回磁盘上优先级最高的那个配置文件通常是Saved/Config/下的。刷新Flush()函数强制将内存缓存中的所有脏数据同步到磁盘。2.2.2 关键全局路径变量UE预定义了几个常用的配置文件路径方便你直接使用GGameIni: 指向当前项目的游戏配置文件。注意在编辑器模式下它通常指向[Project]/Saved/Config/[Platform]/Game.ini在打包游戏中则指向用户目录下的对应文件。它是读写游戏逻辑配置最常用的路径。GEngineIni: 指向引擎配置文件。GEditorIni: 指向编辑器配置文件。等等。实操心得 对于项目自定义的游戏性配置优先使用GGameIni。除非你明确要修改引擎或编辑器级别的设置否则不要动GEngineIni或GEditorIni避免影响编辑器稳定性或与其他项目冲突。3. 核心API实战从读取到写入的完整指南理论讲完了我们直接上代码。下面我会分类详解GConfig最常用的API并附上实战中的注意事项。3.1 基础数据类型的读写这是最常用的功能。FConfigCacheIni为每种基础类型都提供了Get和Set方法。3.1.1 读取Get操作所有Get方法签名类似bool GetXxx(const TCHAR* Section, const TCHAR* Key, TYPE OutValue, const FString Filename)。返回bool表示是否成功找到该Key。// 假设我们的.ini文件有如下内容 // [PlayerStats] // NameKratos // Health1500.5 // SoulsCount999999 // HasGodModetrue void UMyGameInstance::LoadPlayerConfiguration() { if (!GConfig) { UE_LOG(LogTemp, Error, TEXT(GConfig is not available!)); return; } FString PlayerName; float PlayerHealth; int32 SoulsCount; bool bHasGodMode; bool bSuccess true; bSuccess GConfig-GetString(TEXT(PlayerStats), TEXT(Name), PlayerName, GGameIni); bSuccess GConfig-GetFloat(TEXT(PlayerStats), TEXT(Health), PlayerHealth, GGameIni); bSuccess GConfig-GetInt(TEXT(PlayerStats), TEXT(SoulsCount), SoulsCount, GGameIni); bSuccess GConfig-GetBool(TEXT(PlayerStats), TEXT(HasGodMode), bHasGodMode, GGameIni); if (bSuccess) { UE_LOG(LogTemp, Log, TEXT(Loaded Player: %s, Health: %.1f, Souls: %d, GodMode: %s), *PlayerName, PlayerHealth, SoulsCount, bHasGodMode ? TEXT(Yes) : TEXT(No)); // 成功加载应用到游戏逻辑... } else { UE_LOG(LogTemp, Warning, TEXT(Failed to load some player stats. Using defaults.)); // 加载失败使用默认值... } }注意事项空值处理 如果配置文件中不存在某个KeyGet函数会返回false并且OutValue参数不会被修改。这意味着你必须先给变量赋一个合理的默认值或者像上面一样检查返回值。路径参数 最后一个Filename参数至关重要。如果你传错了路径例如把项目配置写到了GEngineIni读写会发生在错误的文件上导致配置“消失”。不确定时可以在运行时打印GGameIni等变量的内容看看具体路径。类型安全GetBool对于true的识别很灵活1、True、true、Yes、On都可以。但为了清晰和避免跨平台问题建议统一使用true和false。3.1.2 写入Set操作Set方法用于修改或添加配置。它立即更新内存缓存但默认不会立即写入磁盘。void UMyGameInstance::SavePlayerProgress(const FString NewName, int32 NewSouls) { if (!GConfig) { return; } // 写入新的值到内存缓存 GConfig-SetString(TEXT(PlayerStats), TEXT(Name), *NewName, GGameIni); GConfig-SetInt(TEXT(PlayerStats), TEXT(SoulsCount), NewSouls, GGameIni); // 可选立即将缓存刷新到磁盘。频繁调用会影响性能。 // GConfig-Flush(true, GGameIni); UE_LOG(LogTemp, Log, TEXT(Player progress saved to memory cache.)); }重要提示Set操作只是改了内存里的GConfig缓存。引擎会在适当的时机如关卡切换、程序退出自动调用Flush。如果你需要确保配置立刻持久化例如在崩溃前保存关键设置必须手动调用GConfig-Flush(true, InFilename)。但不要每帧都Flush这会导致不必要的磁盘IO。3.2 复杂数据类型的处理UE的配置系统原生支持FVector、FRotator、FColor乃至TArrayFString等复杂类型的序列化。3.2.1 数组TArray的读写数组的存储格式有两种对应两种API多行格式GetArray/SetArray每个元素占一行Key相同。单行分隔符格式GetSingleLineArray/SetSingleLineArray所有元素在一个字符串内用特定分隔符默认为空格连接。// .ini 文件内容 // [Inventory] // CarriedWeaponsLongsword // CarriedWeaponsShield // CarriedWeaponsEstusFlask // QuickItemsHealing|Stamina|Firebomb // 单行格式用|分隔 void HandleArrays() { TArrayFString WeaponsArray; TArrayFString QuickItemsArray; // 读取多行格式数组 GConfig-GetArray(TEXT(Inventory), TEXT(CarriedWeapons), WeaponsArray, GGameIni); // WeaponsArray 内容: [Longsword, Shield, EstusFlask] // 读取单行格式数组使用默认分隔符空格 // GConfig-GetSingleLineArray(TEXT(Inventory), TEXT(QuickItems), QuickItemsArray, GGameIni); // 但我们的分隔符是|所以需要用GetString然后手动分割或者使用带分隔符参数的版本如果引擎版本支持。 FString QuickItemsString; if(GConfig-GetString(TEXT(Inventory), TEXT(QuickItems), QuickItemsString, GGameIni)) { QuickItemsString.ParseIntoArray(QuickItemsArray, TEXT(|), true); } // QuickItemsArray 内容: [Healing, Stamina, Firebomb] // 写入数组使用多行格式这是最推荐的方式清晰易读 TArrayFString NewWeapons { TEXT(GreatAxe), TEXT(Crossbow) }; GConfig-SetArray(TEXT(Inventory), TEXT(CarriedWeapons), NewWeapons, GGameIni); }避坑技巧 对于数组强烈建议使用GetArray/SetArray对应的多行格式。它在.ini文件中一目了然易于手动编辑和版本控制对比。单行格式虽然紧凑但可读性差且元素内如果包含分隔符会导致解析错误。3.2.2 向量、旋转器等FVector,FRotator,FColor等类型有专用的Get/Set方法它们会处理格式转换。// .ini 文件内容 // [WorldSettings] // SpawnLocation(X100.0,Y200.0,Z50.0) // SpawnRotation(Pitch0.0,Yaw90.0,Roll0.0) // AmbientColor(R64,G128,B255,A255) void HandleStructuredData() { FVector SpawnLoc; FRotator SpawnRot; FColor AmbientColor; if (GConfig-GetVector(TEXT(WorldSettings), TEXT(SpawnLocation), SpawnLoc, GGameIni)) { // 使用 SpawnLoc... } if (GConfig-GetRotator(TEXT(WorldSettings), TEXT(SpawnRotation), SpawnRot, GGameIni)) { // 使用 SpawnRot... } if (GConfig-GetColor(TEXT(WorldSettings), TEXT(AmbientColor), AmbientColor, GGameIni)) { // 使用 AmbientColor... } // 写入 GConfig-SetVector(TEXT(WorldSettings), TEXT(CheckpointLocation), FVector(500, 0, 100), GGameIni); }3.3 高级操作与文件管理3.3.1 检查与操作Section有时你需要动态地管理配置节。// 检查某个Section是否存在 bool bHasSection GConfig-DoesSectionExist(TEXT(ModdedContent), GGameIni); // 获取某个Section下的所有键 TArrayFString AllKeysInSection; if (GConfig-GetSection(TEXT(AudioSettings), AllKeysInSection, GGameIni)) { for (const FString Key : AllKeysInSection) { UE_LOG(LogTemp, Verbose, TEXT(Key in AudioSettings: %s), *Key); // Key的格式是 VolumeMaster0.8需要自己解析 } } // 删除一个Key GConfig-RemoveKey(TEXT(DebugSettings), TEXT(bShowFPS), GGameIni); // 清空整个Section谨慎使用 // GConfig-EmptySection(TEXT(TempData), GGameIni);3.3.2 加载与卸载自定义配置文件你完全可以不使用GGameIni等默认文件而是创建和管理自己的.ini文件。void HandleCustomConfigFile() { FString CustomConfigPath FPaths::ProjectConfigDir() / TEXT(CustomModSettings.ini); // 方法1使用GConfig直接加载文件会加入全局缓存 GConfig-LoadFile(CustomConfigPath); FString ModAuthor; if (GConfig-GetString(TEXT(ModInfo), TEXT(Author), ModAuthor, CustomConfigPath)) { // 从自定义文件读取成功 } // 向自定义文件写入 GConfig-SetInt(TEXT(ModInfo), TEXT(Version), 2, CustomConfigPath); // 将自定义文件的改动刷入磁盘 GConfig-Flush(true, CustomConfigPath); // 当你确定不再需要这个文件时可以卸载它比如Mod被禁用时 // GConfig-UnloadFile(CustomConfigPath); // 方法2使用FConfigFile进行更独立的操作不污染全局GConfig缓存 FConfigFile MyConfigFile; // 读取文件 MyConfigFile.Read(CustomConfigPath); // 获取值 FString Value; if (MyConfigFile.GetString(TEXT(PrivateSection), TEXT(SecretKey), Value)) { // ... } // 修改后写回 MyConfigFile.SetString(TEXT(PrivateSection), TEXT(SecretKey), TEXT(NewSecret)); MyConfigFile.Write(CustomConfigPath); }经验之谈 对于完全独立、与引擎或其他系统无关的配置比如一个独立Mod的配置使用FConfigFile进行独立读写是更干净的选择避免了与全局配置的潜在冲突。而对于需要与引擎配置系统交互、或希望享受引擎自动合并加载机制的配置则应该通过GConfig来操作。4. 与UPROPERTY(Config)的协同工作模式GConfig是底层API而UPROPERTY(Config)是声明式的、与UClass集成的上层用法。两者可以完美配合。4.1 使用UPROPERTY(Config)自动配置这是最UE风格的方式。通过在UClass中标记Config属性引擎会自动在对应配置文件中管理这些变量的值。// MyGameSettings.h UCLASS(ConfigGame, BlueprintType) // ConfigGame 表示使用Game.ini文件 class MYPROJECT_API UMyGameSettings : public UObject { GENERATED_BODY() public: UPROPERTY(Config, BlueprintReadWrite, CategoryGameplay) float MasterVolume; UPROPERTY(Config, BlueprintReadWrite, CategoryGameplay) int32 MaxEnemyCount; UPROPERTY(Config, BlueprintReadWrite, CategoryGraphics) bool bEnableVSync; // 此变量不会保存到配置文件 UPROPERTY(BlueprintReadWrite) FString RuntimeOnlyData; }; // 在代码中或蓝图中你可以通过GetDefaultObject获取配置的默认实例 UMyGameSettings* Settings GetMutableDefaultUMyGameSettings(); float CurrentVolume Settings-MasterVolume; Settings-MasterVolume 0.5f; Settings-SaveConfig(); // 将改动保存到配置文件引擎会在Game.ini中生成如下内容[/Script/MyProject.MyGameSettings] MasterVolume0.5 MaxEnemyCount10 bEnableVSynctrueSaveConfig()和LoadConfig() 这两个函数是UObject的成员函数它们内部其实就是调用了GConfig。SaveConfig()会将当前对象所有Config属性的值写入配置文件LoadConfig()则会从配置文件读取值并覆盖当前对象的属性。4.2 混合使用用GConfig动态覆盖UPROPERTY(Config)一个强大的模式是用UPROPERTY(Config)定义配置的架构和默认值用GConfig在运行时进行动态的、条件性的读写。void ApplyDifficultyModifier(EDifficulty Difficulty) { UMyGameSettings* Settings GetMutableDefaultUMyGameSettings(); // 先从默认配置加载 Settings-LoadConfig(nullptr, nullptr, UE::LCPF_None); // 然后根据难度用GConfig读取特定的覆盖值 FString DifficultySection FString::Printf(TEXT(Difficulty_%s), *UEnum::GetValueAsString(Difficulty)); float DifficultyHealthMultiplier 1.0f; if (GConfig-GetFloat(*DifficultySection, TEXT(PlayerHealthMultiplier), DifficultyHealthMultiplier, GGameIni)) { // 找到了难度特定的配置覆盖默认值 Settings-PlayerHealthMultiplier DifficultyHealthMultiplier; UE_LOG(LogTemp, Log, TEXT(Applied %s health multiplier: %.2f), *DifficultySection, DifficultyHealthMultiplier); } else { // 没找到使用UPROPERTY(Config)里的默认值 UE_LOG(LogTemp, Warning, TEXT(No specific config found for %s, using default.), *DifficultySection); } // 此时Settings对象的值已经是合并后的最终值可用于游戏逻辑 }这种模式非常适合做“默认配置可选覆盖”的系统比如不同的DLC、Mod或用户档案可以提供自己的配置片段在不修改核心默认配置的情况下调整游戏行为。5. 实战场景与性能优化全攻略5.1 典型应用场景剖析场景一游戏设置菜单的保存与加载这是最经典的应用。音频音量、图形质量、键位绑定等都需要持久化。// 保存图形设置 void UGraphicsSettings::SaveSettings() { if (!GConfig) return; // 将UI上的滑块、复选框的值写入GConfig GConfig-SetFloat(TEXT(Graphics), TEXT(ResolutionScale), ResolutionScale, GGameIni); GConfig-SetInt(TEXT(Graphics), TEXT(AntiAliasing), static_castint32(AAMethod), GGameIni); GConfig-SetBool(TEXT(Graphics), TEXT(MotionBlur), bMotionBlur, GGameIni); // 立即保存让玩家感觉设置已生效 GConfig-Flush(true, GGameIni); // 同时可以调用控制台命令或引擎接口立即应用部分设置如分辨率 FString Command FString::Printf(TEXT(r.ScreenPercentage %.1f), ResolutionScale * 100.0f); GEngine-Exec(nullptr, *Command); } // 加载图形设置游戏启动时调用 void UGraphicsSettings::LoadSettings() { // 从GConfig读取如果不存在则使用构造函数中定义的默认值 GConfig-GetFloat(TEXT(Graphics), TEXT(ResolutionScale), ResolutionScale, GGameIni); int32 AATemp; if (GConfig-GetInt(TEXT(Graphics), TEXT(AntiAliasing), AATemp, GGameIni)) { AAMethod static_castEAntiAliasingMethod(AATemp); } GConfig-GetBool(TEXT(Graphics), TEXT(MotionBlur), bMotionBlur, GGameIni); }场景二管理可下载内容DLC或Mod的配置每个DLC/Mod可以有自己的.ini文件主游戏在启动时扫描并加载。void LoadAllModConfigs() { FString ModsDir FPaths::ProjectContentDir() / TEXT(Mods); TArrayFString ModIniFiles; IFileManager::Get().FindFiles(ModIniFiles, *ModsDir, TEXT(.ini)); for (const FString IniFile : ModIniFiles) { FString FullPath ModsDir / IniFile; // 加载到GConfig缓存使用文件全路径作为标识 GConfig-LoadFile(FullPath); // 读取Mod的元信息 FString ModName, ModVersion; if (GConfig-GetString(TEXT(ModMeta), TEXT(Name), ModName, FullPath) GConfig-GetString(TEXT(ModMeta), TEXT(Version), ModVersion, FullPath)) { UE_LOG(LogTemp, Log, TEXT(Loaded Mod: %s (Version: %s)), *ModName, *ModVersion); // 根据配置激活Mod内容... } } }场景三存档系统的一部分虽然完整的游戏存档通常用更结构化的格式如GameplayStatics的SaveGame系统但一些元数据或系统设置可以放在.ini里。// 保存最后一次游玩的关卡和时长 void SaveLastSessionInfo(const FString LastMapName, float PlayTimeHours) { GConfig-SetString(TEXT(Session), TEXT(LastMap), *LastMapName, GGameIni); GConfig-SetFloat(TEXT(Session), TEXT(PlayTimeHours), PlayTimeHours, GGameIni); // 可以设置不立即Flush跟随其他配置一起保存 } // 在主菜单显示“继续游戏”按钮 bool CanContinueLastGame(FString OutMapName) { return GConfig-GetString(TEXT(Session), TEXT(LastMap), OutMapName, GGameIni); }5.2 性能考量与最佳实践缓存是关键GConfig本身就是内存缓存所以重复读取同一个Key几乎没有磁盘开销。但频繁调用Get函数本身有查找开销。对于在Tick中需要读取的配置应该在初始化时读取一次并保存到成员变量中。Flush的时机 这是性能影响最大的操作。Flush会同步所有脏数据到磁盘可能涉及多个文件的写入。避免在循环或每帧中调用。在关卡切换、游戏暂停、退出游戏等自然断点处调用。对于非关键的配置可以依赖引擎的自动保存。配置文件的大小 虽然.ini是文本文件但也不宜过大。如果配置数据非常庞大比如成千上万个物品属性考虑将其拆分为多个专用文件或使用数据库、结构化文件格式。GConfig加载大文件时解析和合并会消耗更多内存和时间。多线程安全FConfigCacheIni的内部操作不是线程安全的。确保从游戏线程访问GConfig。如果必须在异步线程中读写配置考虑将数据先复制到线程安全的结构中或者使用任务队列将读写操作派发到游戏线程执行。默认值策略 总是为配置读取提供安全的默认值。一个健壮的模式是int32 GetConfigValueWithDefault(const FString Section, const FString Key, int32 DefaultValue) { int32 Value DefaultValue; // 先赋默认值 GConfig-GetInt(*Section, *Key, Value, GGameIni); // GetInt失败时Value保持不变 return Value; }6. 常见问题排查与调试技巧即使理解了原理在实际使用中还是会遇到各种奇怪的问题。下面是我踩过的一些坑和解决方法。6.1 问题速查表问题现象可能原因排查步骤与解决方案读取配置总是返回false或默认值1. Section或Key名称拼写错误大小写敏感。2. 使用的配置文件路径GGameIni,GEngineIni不对。3. 配置文件根本不存在或未被引擎加载。1. 打印出你使用的Section、Key和Filename与磁盘上.ini文件的内容仔细比对。2. 在运行时打印GGameIni等全局路径确认它指向的是你期望的文件。3. 检查[Project]/Saved/Config/目录下是否存在对应的文件。编辑器运行时修改的配置通常写在这里。写入配置后重启游戏/编辑器发现没保存1. 没有调用Flush()且程序非正常退出。2. 写入到了错误的配置文件如DefaultGame.ini但引擎加载的是更高优先级的文件如GameUserSettings.ini。1. 在写入关键配置后立即调用GConfig-Flush(true, Filename)。2. 确认你写入的文件是最终生效的文件。在编辑器中用户设置通常保存在Saved/Config/下而不是Config/下。UPROPERTY(Config)变量在打包后不生效1. 在编辑器里修改的是Saved/Config/下的派生文件打包时这些不会被包含。2. 变量的默认值在构造函数中设置覆盖了配置文件中的值。1. 确保项目的Config/目录下的Default*.ini文件中有正确的配置。打包只包含这些默认文件。2. 检查类构造函数确保没有在构造函数里给Config变量赋固定值。Config变量的初始值应从配置文件加载。数组或复杂结构读取出来是空的1. 数组的格式不对多行 vs 单行。2. 对于FVector等.ini文件中的格式不正确。1. 确认你使用的GetArray对应文件中的多行格式。手动检查.ini文件格式。2. 确保向量格式为(X1.0,Y2.0,Z3.0)。最可靠的方法是先用SetVector写入一个样本观察生成的文件格式。修改配置后游戏行为没有实时改变配置值被缓存了。对于UPROPERTY(Config)对象可能持有旧值。对于直接GConfig-Get虽然GConfig缓存会更新但使用该值的代码可能没有重新读取。1. 对于UPROPERTY(Config)调用ReloadConfig()重新从文件加载到对象。2. 对于直接Get的场景确保在需要最新值的时候重新调用GConfig-Get或者建立一种配置变更的通知机制。6.2 高级调试技巧技巧一实时监控配置文件变化在开发期可以用文本编辑器如VSCode、Notepad打开Saved/Config/Windows/Game.ini一边运行游戏一边修改设置观察文件是否被正确写入。注意编辑器可能缓存文件修改后记得在编辑器中刷新。技巧二使用控制台命令UE编辑器控制台提供了强大的配置调试命令ShowConfigFiles: 显示所有已加载的配置文件及其优先级顺序。DisplayAll: 显示所有控制台变量及其当前值其中很多都来自配置文件。EditConfig: 后面跟Section和Key可以实时修改配置并看到效果。例如EditConfig /Script/Engine.GameSession MaxPlayers 16。技巧三在代码中遍历所有配置当你怀疑配置被覆盖或找不到时可以临时写代码遍历GConfig的缓存。// 警告此操作在发布版本中应移除仅用于调试 void DumpAllConfigForSection(const FString Section) { if (!GConfig) return; TArrayFString Files; GConfig-GetConfigFilenames(Files); // 获取所有已加载的配置文件 for (const FString File : Files) { TArrayFString KeyValues; if (GConfig-GetSection(*Section, KeyValues, File)) { if (KeyValues.Num() 0) { UE_LOG(LogTemp, Display, TEXT(--- Section [%s] in File: %s ---), *Section, *File); for (const FString KV : KeyValues) { UE_LOG(LogTemp, Display, TEXT( %s), *KV); } } } } }调用DumpAllConfigForSection(TEXT(MySettings))你可以看到所有已加载配置文件中[MySettings]节下的所有键值对以及它们来自哪个文件这对于诊断配置合并冲突极其有用。技巧四理解配置的继承与覆盖记住这个核心规则后加载的配置覆盖先加载的。当你的配置表现不符合预期时画一个简单的加载顺序图Base.ini-Default*.ini-*.ini-.../Saved/Config/*.ini。 检查你想要修改的Key是否在更早加载的文件中被设置了默认值而在你期望的文件中被意外覆盖或没有覆盖成功。掌握GConfig和.ini文件就像是掌握了UE5配置系统的“源代码”。它没有JSON那样花哨也没有数据库那样强大但它与引擎的集成度是无与伦比的在性能、易用性和工作流支持上达到了完美的平衡。从简单的变量存储到复杂的多层级配置管理这套系统都能稳健地支撑。下次当你需要保存一个简单的开关或者管理成百上千个物品属性时不妨先想想用.ini和GConfig是不是更简单、更“UE”的方式