首页 > 其他分享 >Unreal中ini配置文件的hierarchy

Unreal中ini配置文件的hierarchy

时间:2023-06-21 20:33:26浏览次数:57  
标签:Engine 配置文件 hierarchy TEXT PLATFORM Unreal ini file Config

Config

UE的很多配置是通过ini文件实现的,相对于二进制文件来说,ini文件的优点是读取、阅读、修改都非常方便,因为所有的文本编辑器都可以修改。但是UE中的ini文件可谓是眼花缭乱,在Engine、project文件夹下,同样的Engine.ini可能存在baseengine.ini、defaultengine.ini、engine.ini,platform下的engine.ini等,这些结构可能对相同的一个key都会有配置,看起来就有些困惑。
最后在UE的官网可以看到这个关于File Hierarchy的说明:大致的意思就是同一个大类的配置项(例如,前面提到的engine相同的base name都是engine,但是存在base、default等不同前、后缀或者不同文件夹下的ini)使用的是一个简单的层级(hierarchy)来实现。这相当于使用了大家熟知的(well-known)的ini结构的基础上,使用文件系统的结构来实现各种定制化。
因为这个概念比较基础,所以把文字摘录一下作为备份:

The configuration file hierarchy is read in starting with Base.ini, with values in later files in the hierarchy overriding earlier values. All files in the Engine folder will be applied to all projects, while project-specific settings should be in files in the project directory. Finally, all project-specific and platform-specific differences are saved out to [Project Directory]/Saved/Config/[Platform]/[Category].ini.

The below file hierarchy example is for the Engine category of configuration files.

Engine/Config/Base.ini

Base.ini is usually empty.

Engine/Config/BaseEngine.ini

Engine/Config/[Platform]/base[Platform]Engine.ini

[Project Directory]/Config/DefaultEngine.ini

Engine/Config/[Platform]/[Platform]Engine.ini

[Project Directory]/Config/[Platform]/[Platform]Engine.ini

The configuration file in the Saved directory only stores the project-specific and platform-specific differences in the stack of configuration files.

创建project

从工程的Templates文件夹生成具体工程(project)。在工程的根目录下存在一个Templates文件夹,该文件夹下存在了很多可以使用的模板,这些模板中包含了基础的Config、Source、uproject文件,这些文件夹的内容将会被拷贝到新创建的project文件中(可能在拷贝的过程中有一些修改?)。

///@file: Engine\Source\Editor\GameProjectGeneration\Private\GameProjectUtils.cpp
TOptional<FGuid> GameProjectUtils::CreateProjectFromTemplate(const FProjectInformation& InProjectInfo, FText& OutFailReason, FText& OutFailLog, TArray<FString>* OutCreatedFiles)
{
///...
	FGuid ProjectID = FGuid::NewGuid();
	ConfigValuesToSet.Emplace(TEXT("DefaultGame.ini"), TEXT("/Script/EngineSettings.GeneralProjectSettings"), TEXT("ProjectID"), ProjectID.ToString(), /*InShouldReplaceExistingValue=*/true);

	// Add all classname fixups
	for (const TPair<FString, FString>& Rename : ClassRenames)
	{
		const FString ClassRedirectString = FString::Printf(TEXT("(OldClassName=\"%s\",NewClassName=\"%s\")"), *Rename.Key, *Rename.Value);
		ConfigValuesToSet.Emplace(TEXT("DefaultEngine.ini"), TEXT("/Script/Engine.Engine"), TEXT("+ActiveClassRedirects"), *ClassRedirectString, /*InShouldReplaceExistingValue=*/false);
	}

	SlowTask.EnterProgressFrame();

	if (!SaveConfigValues(InProjectInfo, ConfigValuesToSet, OutFailReason))
	{
		return TOptional<FGuid>();
	}
///...
	// Generate the project file
	{
}

hierarchy

从实现上看,这个层级又分为了静态和动态两个大类,其中的静态就是约定的配置位置及优先级,它们以数组的形式写死在代码中;另一类动态的则是主要给插件使用的配置,它们的动态主要是因为插件是动态的,所以需要在运行时确定。

static

/**
 * Structure to define all the layers of the config system. Layers can be expanded by expansion files (NoRedist, etc), or by ini platform parents
 */
struct FConfigLayer
{
	// Used by the editor to display in the ini-editor
	const TCHAR* EditorName;
	// Path to the ini file (with variables)
	const TCHAR* Path;
	// Special flag
	EConfigLayerFlags Flag;

};

///@file: Engine\Source\Runtime\Core\Public\Misc\ConfigHierarchy.h

// See FConfigContext.cpp for the types here

static FConfigLayer GConfigLayers[] =
{
	/**************************************************
	**** CRITICAL NOTES
	**** If you change this array, you need to also change EnumerateConfigFileLocations() in ConfigHierarchy.cs!!!
	**** And maybe UObject::GetDefaultConfigFilename(), UObject::GetGlobalUserConfigFilename()
	**************************************************/

	// Engine/Base.ini
	{ TEXT("AbsoluteBase"),				TEXT("{ENGINE}/Config/Base.ini"), EConfigLayerFlags::NoExpand},

	// Engine/Base*.ini
	{ TEXT("Base"),						TEXT("{ENGINE}/Config/Base{TYPE}.ini") },
	// Engine/Platform/BasePlatform*.ini
	{ TEXT("BasePlatform"),				TEXT("{ENGINE}/Config/{PLATFORM}/Base{PLATFORM}{TYPE}.ini")  },
	// Project/Default*.ini
	{ TEXT("ProjectDefault"),			TEXT("{PROJECT}/Config/Default{TYPE}.ini"), EConfigLayerFlags::AllowCommandLineOverride },
	// Project/Generated*.ini Reserved for files generated by build process and should never be checked in 
	{ TEXT("ProjectGenerated"),			TEXT("{PROJECT}/Config/Generated{TYPE}.ini") },
	// Project/Custom/CustomConfig/Default*.ini only if CustomConfig is defined
	{ TEXT("CustomConfig"),				TEXT("{PROJECT}/Config/Custom/{CUSTOMCONFIG}/Default{TYPE}.ini"), EConfigLayerFlags::RequiresCustomConfig },
	// Engine/Platform/Platform*.ini
	{ TEXT("EnginePlatform"),			TEXT("{ENGINE}/Config/{PLATFORM}/{PLATFORM}{TYPE}.ini") },
	// Project/Platform/Platform*.ini
	{ TEXT("ProjectPlatform"),			TEXT("{PROJECT}/Config/{PLATFORM}/{PLATFORM}{TYPE}.ini") },
	// Project/Platform/GeneratedPlatform*.ini Reserved for files generated by build process and should never be checked in 
	{ TEXT("ProjectPlatformGenerated"),	TEXT("{PROJECT}/Config/{PLATFORM}/Generated{PLATFORM}{TYPE}.ini") },
	// Project/Platform/Custom/CustomConfig/Platform*.ini only if CustomConfig is defined
	{ TEXT("CustomConfigPlatform"),		TEXT("{PROJECT}/Config/{PLATFORM}/Custom/{CUSTOMCONFIG}/{PLATFORM}{TYPE}.ini"), EConfigLayerFlags::RequiresCustomConfig },
	// UserSettings/.../User*.ini
	{ TEXT("UserSettingsDir"),			TEXT("{USERSETTINGS}Unreal Engine/Engine/Config/User{TYPE}.ini"), EConfigLayerFlags::NoExpand },
	// UserDir/.../User*.ini
	{ TEXT("UserDir"),					TEXT("{USER}Unreal Engine/Engine/Config/User{TYPE}.ini"), EConfigLayerFlags::NoExpand },
	// Project/User*.ini
	{ TEXT("GameDirUser"),				TEXT("{PROJECT}/Config/User{TYPE}.ini"), EConfigLayerFlags::NoExpand },
};


/// <summary>
/// Plugins don't need to look at the same number of insane layers. Here PROJECT is the Plugin dir
/// </summary>
static FConfigLayer GPluginLayers[] =
{
	// Engine/Base.ini
	{ TEXT("AbsoluteBase"),				TEXT("{ENGINE}/Config/Base.ini"), EConfigLayerFlags::NoExpand},

	// Plugin/Base*.ini
	{ TEXT("PluginBase"),				TEXT("{PLUGIN}/Config/Base{TYPE}.ini") },
	// Plugin/Default*.ini (we use Base and Default as we can have both depending on Engine or Project plugin, but going forward we should stick with Default)
	{ TEXT("PluginDefault"),			TEXT("{PLUGIN}/Config/Default{TYPE}.ini") },
	// Plugin/Platform/Platform*.ini
	{ TEXT("PluginPlatform"),			TEXT("{PLUGIN}/Config/{PLATFORM}/{PLATFORM}{TYPE}.ini") },
	// Project/Default.ini
	{ TEXT("ProjectDefault"),			TEXT("{PROJECT}/Config/Default{TYPE}.ini") },
	// Project/Platform/.ini
	{ TEXT("ProjectDefault"),			TEXT("{PROJECT}/Config/{PLATFORM}/{PLATFORM}{TYPE}.ini") },
};



/**************************************************
**** CRITICAL NOTES
**** If you change these arrays, you need to also change EnumerateConfigFileLocations() in ConfigHierarchy.cs!!!
**************************************************/
static FConfigLayerExpansion GConfigExpansions[] =
{
	// No replacements
	{ nullptr, nullptr, nullptr, nullptr, EConfigExpansionFlags::All },

	// Restricted Locations
	{ 
		TEXT("{ENGINE}/"),						TEXT("{ENGINE}/Restricted/NotForLicensees/"),	
		TEXT("{PROJECT}/Config/"),				TEXT("{RESTRICTEDPROJECT_NFL}/Config/"), 
		EConfigExpansionFlags::ForUncooked | EConfigExpansionFlags::ForCooked
	},
	{ 
		TEXT("{ENGINE}/"),						TEXT("{ENGINE}/Restricted/NoRedist/"),			
		TEXT("{PROJECT}/Config/"),				TEXT("{RESTRICTEDPROJECT_NR}/Config/"), 
		EConfigExpansionFlags::ForUncooked 
	},

	// Platform Extensions
	{
		TEXT("{ENGINE}/Config/{PLATFORM}/"),	TEXT("{EXTENGINE}/Config/"),	
		TEXT("{PROJECT}/Config/{PLATFORM}/"),	TEXT("{EXTPROJECT}/Config/"), 
		EConfigExpansionFlags::ForUncooked | EConfigExpansionFlags::ForCooked | EConfigExpansionFlags::ForPlugin
	},

	// Platform Extensions in Restricted Locations
	// 
	// Regarding the commented EConfigExpansionFlags::ForPlugin expansions: in the interest of keeping plugin ini scanning fast,
	// we disable these expansions for plugins because they are not used by Epic, and are unlikely to be used by licensees. If
	// we can make scanning fast (caching what directories exist, etc), then we could turn this back on to be future-proof.
	{
		TEXT("{ENGINE}/Config/{PLATFORM}/"),	TEXT("{ENGINE}/Restricted/NotForLicensees/Platforms/{PLATFORM}/Config/"),	
		TEXT("{PROJECT}/Config/{PLATFORM}/"),	TEXT("{RESTRICTEDPROJECT_NFL}/Platforms/{PLATFORM}/Config/"), 
		EConfigExpansionFlags::ForUncooked | EConfigExpansionFlags::ForCooked // | EConfigExpansionFlags::ForPlugin 
	},
	{
		TEXT("{ENGINE}/Config/{PLATFORM}/"),	TEXT("{ENGINE}/Restricted/NoRedist/Platforms/{PLATFORM}/Config/"),			
		TEXT("{PROJECT}/Config/{PLATFORM}/"),	TEXT("{RESTRICTEDPROJECT_NR}/Platforms/{PLATFORM}/Config/"), 
		EConfigExpansionFlags::ForUncooked // | EConfigExpansionFlags::ForPlugin
	},
};

///@file: Engine\Source\Runtime\Core\Private\Misc\ConfigContext.cpp
void FConfigContext::AddStaticLayersToHierarchy()
{
	// remember where this file was loaded from
	ConfigFile->SourceEngineConfigDir = EngineConfigDir;
	ConfigFile->SourceProjectConfigDir = ProjectConfigDir;

	// string that can have a reference to it, lower down
	const FString DedicatedServerString = IsRunningDedicatedServer() ? TEXT("DedicatedServer") : TEXT("");

	// cache some platform extension information that can be used inside the loops
	const bool bHasCustomConfig = !FConfigCacheIni::GetCustomConfigString().IsEmpty();


	// figure out what layers and expansions we will want
	EConfigExpansionFlags ExpansionMode = EConfigExpansionFlags::ForUncooked;
	FConfigLayer* Layers = GConfigLayers;
	int32 NumLayers = UE_ARRAY_COUNT(GConfigLayers);
	if (FPlatformProperties::RequiresCookedData())
	{
		ExpansionMode = EConfigExpansionFlags::ForCooked;
	}
	if (bIsForPlugin)
	{
		// this has priority over cooked/uncooked
		ExpansionMode = EConfigExpansionFlags::ForPlugin;
		Layers = GPluginLayers;
		NumLayers = UE_ARRAY_COUNT(GPluginLayers);
	}

	// go over all the config layers
	for (int32 LayerIndex = 0; LayerIndex < NumLayers; LayerIndex++)
	{
		const FConfigLayer& Layer = Layers[LayerIndex];

		// skip optional layers
		if (EnumHasAnyFlags(Layer.Flag, EConfigLayerFlags::RequiresCustomConfig) && !bHasCustomConfig)
		{
			continue;
		}

		// start replacing basic variables
		FString LayerPath = PerformBasicReplacements(Layer.Path, *BaseIniName);
		bool bHasPlatformTag = LayerPath.Contains(TEXT("{PLATFORM}"));

		// expand if it it has {ED} or {EF} expansion tags
		if (!EnumHasAnyFlags(Layer.Flag, EConfigLayerFlags::NoExpand))
		{
			// we assume none of the more special tags in expanded ones
			checkfSlow(FCString::Strstr(Layer.Path, TEXT("{USERSETTINGS}")) == nullptr && FCString::Strstr(Layer.Path, TEXT("{USER}")) == nullptr, TEXT("Expanded config %s shouldn't have a {USER*} tags in it"), *Layer.Path);

			// loop over all the possible expansions
			for (int32 ExpansionIndex = 0; ExpansionIndex < UE_ARRAY_COUNT(GConfigExpansions); ExpansionIndex++)
			{
				// does this expansion match our current mode?
				if ((GConfigExpansions[ExpansionIndex].Flags & ExpansionMode) == EConfigExpansionFlags::None)
				{
					continue;
				}

				FString ExpandedPath = PerformExpansionReplacements(GConfigExpansions[ExpansionIndex], LayerPath);

				// if we didn't replace anything, skip it
				if (ExpandedPath.Len() == 0)
				{
					continue;
				}

				// allow for override, only on BASE EXPANSION!
				if (EnumHasAnyFlags(Layer.Flag, EConfigLayerFlags::AllowCommandLineOverride) && ExpansionIndex == 0)
				{
					checkfSlow(!bHasPlatformTag, TEXT("EConfigLayerFlags::AllowCommandLineOverride config %s shouldn't have a PLATFORM in it"), Layer.Path);

					ConditionalOverrideIniFilename(ExpandedPath, *BaseIniName);
				}

				const FDataDrivenPlatformInfo& Info = FDataDrivenPlatformInfoRegistry::GetPlatformInfo(Platform);

				// go over parents, and then this platform, unless there's no platform tag, then we simply want to run through the loop one time to add it to the
				int32 NumPlatforms = bHasPlatformTag ? Info.IniParentChain.Num() + 1 : 1;
				int32 CurrentPlatformIndex = NumPlatforms - 1;
				int32 DedicatedServerIndex = -1;

				// make DedicatedServer another platform
				if (bHasPlatformTag && IsRunningDedicatedServer())
				{
					NumPlatforms++;
					DedicatedServerIndex = CurrentPlatformIndex + 1;
				}

				for (int PlatformIndex = 0; PlatformIndex < NumPlatforms; PlatformIndex++)
				{
					const FString CurrentPlatform =
						(PlatformIndex == DedicatedServerIndex) ? DedicatedServerString :
						(PlatformIndex == CurrentPlatformIndex) ? Platform :
						Info.IniParentChain[PlatformIndex];

					FString PlatformPath = PerformFinalExpansions(ExpandedPath, CurrentPlatform);

					// @todo restricted - ideally, we would move DedicatedServer files into a directory, like platforms are, but for short term compat,
					// convert the path back to the original (DedicatedServer/DedicatedServerEngine.ini -> DedicatedServerEngine.ini)
					if (PlatformIndex == DedicatedServerIndex)
					{
						PlatformPath.ReplaceInline(TEXT("Config/DedicatedServer/"), TEXT("Config/"));
					}

					// if we match the StartSkippingAtFilename, we are done adding to the hierarchy, so just return
					if (PlatformPath == StartSkippingAtFilename)
					{
						return;
					}

					// add this to the list!
					ConfigFile->SourceIniHierarchy.AddStaticLayer(PlatformPath, LayerIndex, ExpansionIndex, PlatformIndex);
				}
			}
		}
		// if no expansion, just process the special tags (assume no PLATFORM tags)
		else
		{
			checkfSlow(!bHasPlatformTag, TEXT("Non-expanded config %s shouldn't have a PLATFORM in it"), *Layer.Path);
			checkfSlow(!EnumHasAnyFlags(Layer.Flag, EConfigLayerFlags::AllowCommandLineOverride), TEXT("Non-expanded config can't have a EConfigLayerFlags::AllowCommandLineOverride"));

			FString FinalPath = PerformFinalExpansions(LayerPath, TEXT(""));

			// if we match the StartSkippingAtFilename, we are done adding to the hierarchy, so just return
			if (FinalPath == StartSkippingAtFilename)
			{
				return;
			}

			// add with no expansion
			ConfigFile->SourceIniHierarchy.AddStaticLayer(FinalPath, LayerIndex);
		}
	}
}

dynamic

这些主要用在插件(plugin)的配置文件中

bool FPluginManager::IntegratePluginsIntoConfig(FConfigCacheIni& ConfigSystem, const TCHAR* EngineIniName, const TCHAR* PlatformName, const TCHAR* StagedPluginsFile)
{
///...
		for (const FString& ConfigFile : PluginConfigs)
		{
			FString BaseConfigFile = *FPaths::GetBaseFilename(ConfigFile);

			// Use GetConfigFilename to find the proper config file to combine into, since it manages command line overrides and path sanitization
			FString PluginConfigFilename = ConfigSystem.GetConfigFilename(*BaseConfigFile);
			FConfigFile* FoundConfig = ConfigSystem.FindConfigFile(PluginConfigFilename);
			if (FoundConfig != nullptr)
			{
				UE_LOG(LogPluginManager, Log, TEXT("Found config from plugin[%s] %s"), *Plugin.GetName(), *PluginConfigFilename);

				FoundConfig->AddDynamicLayerToHierarchy(FPaths::Combine(PluginConfigDir, ConfigFile));
			}
		}
///...
}

runtime

在加载的过程中,会逐层执行ProcessIniContents>>FConfigFile::Combine>>FConfigFile::CombineFromBuffer中进行读取和覆盖。

///@file: Engine\Source\Runtime\Core\Private\Misc\ConfigContext.cpp
/**
 * This will completely load .ini file hierarchy into the passed in FConfigFile. The passed in FConfigFile will then
 * have the data after combining all of those .ini
 *
 * @param FilenameToLoad - this is the path to the file to
 * @param ConfigFile - This is the FConfigFile which will have the contents of the .ini loaded into and Combined()
 *
 **/
static bool LoadIniFileHierarchy(const FConfigFileHierarchy& HierarchyToLoad, FConfigFile& ConfigFile, bool bUseCache, const TSet<FString>* IniCacheSet)
{
	// Traverse ini list back to front, merging along the way.
	for (const TPair<int32, FString>& HierarchyIt : HierarchyToLoad)
	{
		bool bDoCombine = (HierarchyIt.Key != 0);
		const FString& IniFileName = HierarchyIt.Value;

		// skip non-existant files
		if (IsUsingLocalIniFile(*IniFileName, nullptr) && !DoesConfigFileExistWrapper(*IniFileName, IniCacheSet))
		{
			continue;
		}

		bool bDoEmptyConfig = false;
		//UE_LOG(LogConfig, Log,  TEXT( "Combining configFile: %s" ), *IniList(IniIndex) );
		ProcessIniContents(*IniFileName, *IniFileName, &ConfigFile, bDoEmptyConfig, bDoCombine);
	}

	// Set this configs files source ini hierarchy to show where it was loaded from.
	ConfigFile.SourceIniHierarchy = HierarchyToLoad;

	return true;
}

well-knonw配置类型

这里列出了一些熟知配置文件,其中包括了最为常见的Engine、Game两种类型。

///@file: Engine\Source\Runtime\Core\Public\Misc\ConfigCacheIni.h
#define ENUMERATE_KNOWN_INI_FILES(op) \
	op(Engine) \
	op(Game) \
	op(Input) \
	op(DeviceProfiles) \
	op(GameUserSettings) \
	op(Scalability) \
	op(RuntimeOptions) \
	op(InstallBundle) \
	op(Hardware) \
	op(GameplayTags)


#define KNOWN_INI_ENUM(IniName) IniName,

///@file:Engine\Source\Runtime\Core\Private\Misc\ConfigCacheIni.cpp
FConfigCacheIni::FKnownConfigFiles::FKnownConfigFiles()
{
	// set the FNames associated with each file

	// 	Files[(uint8)EKnownIniFile::Engine].IniName = FName("Engine");
	#define SET_KNOWN_NAME(Ini) Files[(uint8)EKnownIniFile::Ini].IniName = FName(#Ini);
		ENUMERATE_KNOWN_INI_FILES(SET_KNOWN_NAME);
	#undef SET_KNOWN_NAME
}

Saved

///@file: Engine\Source\Runtime\Core\Private\Misc\ConfigContext.cpp
bool FConfigContext::PrepareForLoad(bool& bPerformLoad)
{
	checkf(ConfigSystem != nullptr || ConfigFile != nullptr, TEXT("Loading config expects to either have a ConfigFile already passed in, or have a ConfigSystem passed in"));

	if (bForceReload)
	{
		// re-use an existing ConfigFile's Engine/Project directories if we have a config system to look in,
		// or no config system and the platform matches current platform (which will look in GConfig)
		if (ConfigSystem != nullptr || (Platform == FPlatformProperties::IniPlatformName() && GConfig != nullptr))
		{
			bool bNeedRecache = false;
			FConfigCacheIni* SearchSystem = ConfigSystem == nullptr ? GConfig : ConfigSystem;
			FConfigFile* BaseConfigFile = SearchSystem->FindConfigFileWithBaseName(*BaseIniName);
			if (BaseConfigFile != nullptr)
			{
				if (!BaseConfigFile->SourceEngineConfigDir.IsEmpty() && BaseConfigFile->SourceEngineConfigDir != EngineConfigDir)
				{
					EngineConfigDir = BaseConfigFile->SourceEngineConfigDir;
					bNeedRecache = true;
				}
				if (!BaseConfigFile->SourceProjectConfigDir.IsEmpty() && BaseConfigFile->SourceProjectConfigDir != ProjectConfigDir)
				{
					ProjectConfigDir = BaseConfigFile->SourceProjectConfigDir;
					bNeedRecache = true;
				}
				if (bNeedRecache)
				{
					CachePaths();
				}
			}
		}

	}

	// setup for writing out later on
	if (bWriteDestIni || bAllowGeneratedIniWhenCooked || FPlatformProperties::RequiresCookedData())
	{
		// delay filling out GeneratedConfigDir because some early configs can be read in that set -savedir, and 
		// FPaths::GeneratedConfigDir() will permanently cache the value
		if (GeneratedConfigDir.IsEmpty())
		{
			GeneratedConfigDir = FPaths::GeneratedConfigDir();
		}

		// calculate where this file will be saved/generated to (or at least the key to look up in the ConfigSystem)
		DestIniFilename = FConfigCacheIni::GetDestIniFilename(*BaseIniName, *SavePlatform, *GeneratedConfigDir);

		if (bAllowRemoteConfig)
		{
			// Start the loading process for the remote config file when appropriate
			if (FRemoteConfig::Get()->ShouldReadRemoteFile(*DestIniFilename))
			{
				FRemoteConfig::Get()->Read(*DestIniFilename, *BaseIniName);
			}

			FRemoteConfigAsyncIOInfo* RemoteInfo = FRemoteConfig::Get()->FindConfig(*DestIniFilename);
			if (RemoteInfo && (!RemoteInfo->bWasProcessed || !FRemoteConfig::Get()->IsFinished(*DestIniFilename)))
			{
				// Defer processing this remote config file to until it has finish its IO operation
				bPerformLoad = false;
				return false;
			}
		}
	}

	// we can re-use an existing file if:
	//   we are not loading into an existing ConfigFile
	//   we don't want to reload
	//   we found an existing file in the ConfigSystem
	//   the existing file has entries (because Known config files are always going to be found, but they will be empty)
	bool bLookForExistingFile = ConfigFile == nullptr && !bForceReload && ConfigSystem != nullptr;
	if (bLookForExistingFile)
	{
		// look up a file that already exists and matches the name
		FConfigFile* FoundConfigFile = ConfigSystem->KnownFiles.GetMutableFile(*BaseIniName);
		if (FoundConfigFile == nullptr)
		{
			FoundConfigFile = ConfigSystem->FindConfigFile(*DestIniFilename);
			//// @todo: this is test to see if we can simplify this to FindConfigFileWithBaseName always (if it never fires, we can)
			//check(FoundConfigFile == nullptr || FoundConfigFile == ConfigSystem->FindConfigFileWithBaseName(*BaseIniName))
		}

		if (FoundConfigFile != nullptr && FoundConfigFile->Num() > 0)
		{
			ConfigFile = FoundConfigFile;
			bPerformLoad = false;
			return true;
		}
	}

	// setup ConfigFile to read into if one isn't already set
	if (ConfigFile == nullptr)
	{
		// first look for a KnownFile
		ConfigFile = ConfigSystem->KnownFiles.GetMutableFile(*BaseIniName);
		if (ConfigFile == nullptr)
		{
			check(!DestIniFilename.IsEmpty());

			ConfigFile = &ConfigSystem->Add(DestIniFilename, FConfigFile());
		}
	}

	bPerformLoad = true;
	return true;
}

bool FConfigContext::Load(const TCHAR* InBaseIniName, FString& OutFinalFilename)
{
	// for single file loads, just return early of the file doesn't exist
	const bool bBaseIniNameIsFullInIFilePath = FString(InBaseIniName).EndsWith(TEXT(".ini"));
	if (!bIsHierarchicalConfig && bBaseIniNameIsFullInIFilePath && !DoesConfigFileExistWrapper(InBaseIniName, IniCacheSet))
	{
		return false;
	}

	if (bCacheOnNextLoad || BaseIniName != InBaseIniName)
	{
		ResetBaseIni(InBaseIniName);
		CachePaths();
		bCacheOnNextLoad = false;
	}


	bool bPerformLoad;
	if (!PrepareForLoad(bPerformLoad))
	{
		return false;
	}

	// if we are reloading a known ini file (where OutFinalIniFilename already has a value), then we need to leave the OutFinalFilename alone until we can remove LoadGlobalIniFile completely
	if (OutFinalFilename.Len() > 0 && OutFinalFilename == BaseIniName)
	{
		// do nothing
	}
	else
	{
		check(!bWriteDestIni || !DestIniFilename.IsEmpty());

		OutFinalFilename = DestIniFilename;
	}

	// now load if we need (PrepareForLoad may find an existing file and just use it)
	return bPerformLoad ? PerformLoad() : true;
}

由于Generated文件夹默认是存储在Saved文件夹,所以通常可写的内容都是写入到Saved\Config文件夹下,这也是为什么这个文件夹下的Config内容会自动生成的原因。

const TCHAR* FGenericPlatformMisc::GeneratedConfigDir()
{
	static FString Dir = FPaths::ProjectSavedDir() / TEXT("Config/");
	return *Dir;
}
const FString& FPaths::ProjectSavedDir()
{
	FStaticData& StaticData = TLazySingleton<FStaticData>::Get();
	if (!StaticData.bGameSavedDirInitialized)
	{
		StaticData.GameSavedDir = UE4Paths_Private::GameSavedDir();
		StaticData.bGameSavedDirInitialized = true;
	}
	return StaticData.GameSavedDir;
}

栗子

确定ini配置

在启动过程中,使用"EditorSettings"作为ini的base名字,经过配置获得的GEditorSettingsIni变量值为"../../../Engine/Saved/Config/WindowsEditor/EditorSettings.ini"。

///@file: Engine\Source\Runtime\Core\Private\Misc\ConfigCacheIni.cpp
static void LoadRemainingConfigFiles(FConfigContext& Context)
{
	SCOPED_BOOT_TIMING("LoadRemainingConfigFiles");

#if PLATFORM_DESKTOP
	// load some desktop only .ini files
	Context.Load(TEXT("Compat"), GCompatIni);
	Context.Load(TEXT("Lightmass"), GLightmassIni);
#endif

#if WITH_EDITOR
	// load some editor specific .ini files

	Context.Load(TEXT("Editor"), GEditorIni);

	// Upgrade editor user settings before loading the editor per project user settings
	FConfigManifest::MigrateEditorUserSettings();
	Context.Load(TEXT("EditorPerProjectUserSettings"), GEditorPerProjectIni);

	// Project agnostic editor ini files, so save them to a shared location (Engine, not Project)
	Context.GeneratedConfigDir = FPaths::EngineEditorSettingsDir();
	Context.Load(TEXT("EditorSettings"), GEditorSettingsIni);
	Context.Load(TEXT("EditorKeyBindings"), GEditorKeyBindingsIni);
	Context.Load(TEXT("EditorLayout"), GEditorLayoutIni);

#endif

	if (FParse::Param(FCommandLine::Get(), TEXT("dumpconfig")))
	{
		GConfig->Dump(*GLog);
	}
}

读取键值

从全局配置cache中读取

		FString GameEngineClassName;
		GConfig->GetString(TEXT("/Script/Engine.Engine"), TEXT("GameEngine"), GameEngineClassName, GEngineIni);
					
		// Find the editor target
		FString EditorTargetFileName;
		FString DefaultEditorTarget;
		GConfig->GetString(TEXT("/Script/BuildSettings.BuildSettings"), TEXT("DefaultEditorTarget"), DefaultEditorTarget, GEngineIni);

标签:Engine,配置文件,hierarchy,TEXT,PLATFORM,Unreal,ini,file,Config
From: https://www.cnblogs.com/tsecer/p/17497125.html

相关文章

  • Python 修改ha配置文件
    Python修改ha配置文件任务要求1、用户输入字符串{"backend":"test.oldboy.org","record":{"server":"100.1.7.9","weight":20,"maxconn":30}}2、在对应的backend下,添加一条新记录backend不存在时,创建3、删除一条记录ba......
  • 关于在Redhat-7-linux-系统-Apache-2.4.6-版本上部署多个版本的yum仓库-的配置文件写
    背景:云上有一台内部yum服务器,操作系统及版本信息为:RedHatEnterpriseLinuxServerrelease7.9(Maipo)上面每天会同aws仓库官网同步repo,版本也自然是 RedHatEnterpriseLinuxServerrelease7现在需要临时增加Redhat8.的仓库,(默认Redhat8也是有内部repo仓库的,只是在......
  • 多路Qt串口通信源码C++语言接口自定义协议帧Qt读写配置文件ini出售: 可变长定长通信接
    多路Qt串口通信源码C++语言接口自定义协议帧Qt读写配置文件ini出售:可变长定长通信接口协议实现Qt多路串口发送接收SerialProtocol.rar工控自定义报文可用于嵌入式,单片机,ARM,DSP等常见的串口通信中,出售在应用实践中编写总结的源代码,实现自定义的串口通信协议,包括报文头部、长度......
  • 宝塔-ftp配置文件内容
    ###############################################################Configurationfileforpure-ftpdwrappers##################......
  • UnrealEngine:Pawn类
    DefaultPawn#include"CoreMinimal.h"#include"UObject/ObjectMacros.h"#include"GameFramework/Pawn.h"#include"DefaultPawn.generated.h" classUInputComponent;classUPawnMovementComponent;classUSphereComponen......
  • nginx配置多个配置文件,nginx配置多个conf的方式 播报文章
    可以通过在nginx.conf文件中使用include关键字来引入多个子配置文件,从而实现对Nginx的多配置管理。下面是简单的操作步骤:  1.进入Nginx的conf目录(通常是/etc/nginx或者/usr/local/nginx/conf),创建一个名为conf.d的目录,用于存放多个子配置文件:  mkdir......
  • NGINX指定启动的配置文件
    若不指定安装路径,nginx默认安装在/usr/local/nginx路径下。若不指定nginx的配置文件,nginx默认启动找的是同级nginx更路径下的/conf/nginx.conf配置文件 但该配置文件的所在路径以及文件名不是绝对的,可根据需要放置在不同的路径。胡根据业务场景修改配置文件名。 以下是......
  • mycat2配置文件
    1.用户配置xa:分布式事务 各个字段解释:2.数据源配置3.集群的配置 4.逻辑库表   ......
  • java 聚合项目--pom.xml配置文件
    java聚合项目创建聚合项目的2种方式:分层项目开发:1.DAO:java工程项目;(mavenquickstart)2.Service:java工程项目;(mavenquickstart)3.模型:java工程项目;(mavenquickstart)4.共工模块:java工程项目;(mavenquickstart)5.controller+view:webapp:web工程项目(mavenwebapp)工程类型:packing......
  • fastdfs配置文件说明
    参考网址一、tracker.conf#配置tracker.conf文件是否生效false生效true屏蔽disabled=false#程序的监听地址,如果不设定则监听所有地址(0.0.0.0)bind_addr=#tracker监听的端口port=22122#连接超时时间(秒)。#默认值为30。#注意:在内网(LAN)中,2秒就足够......