Config

Config

UnrealEngine3 에는 다양한 설정들을 .ini 파일에 저장하여 사용합니다. 여기서는 설정파일의 구조를 설명하고 엔진에서 이를 다루는 방식에 대해 소개합니다.

설정파일 구조

설정파일은 다음과 같은 일반적인 구조를 가집니다.

; ConfigFile.ini (임의의 설정파일)
 
[Section00]
Property00=Value00
Property01=Value01
 
[Section01]
Property00=Value00
Property01=Value01
 
[Section02]
Property00=Value00
Property01=Value01
Property02=Value02

Window 표준 설정파일의 형식을 가지며 이를 UnrealEngine3 에서도 차용하고 있습니다.

코드 구조

위 구조를 UnrealEngine3 내부적으로 다음과 같은 타입으로 핸들링하고 있습니다.

class FConfigCacheIni      // config 모든 파일을 관장하는 manager 급 클래스 TMap<FFilename, FConfigFile> 로 상속받아 몇가지 method 를 추가
class FConfigFile          // 설정파일. FConfigCacheIni 와 Has-A 관계에 있으며 상기 구조에서 ConfigFile.ini 에 해당
class FConfigSection       // 섹션파일. FConfigFile 과 Has-A 관계에 있으며 상기 구조에서 Section00-02 에 해당
class FString              // 속성파일. FConfigSection 과 Has-A 관계에 있으며 상기 구조에서 Property00=Value00 에 해당

세부적인 코드는 FConfigCacheIni.h & .cpp 에서 확인할 수 있습니다.

설정파일 로드

엔진에서 설정파일을 로드하는데 사용하는 메인함수가 있습니다.

그 함수는 아래와 같습니다.

// UnMisc.cpp
void appCheckIniForOutdatedness(
    const TCHAR* GeneratedIniFile,
    const TCHAR* DefaultIniFile,
    const UBOOL bTryToPreserveContents,
    UINT& YesNoToAll,
    UBOOL bForceReload/*=FALSE*/ )
{
    ...
    GConfig->LoadFile( GeneratedIniFile, &DefaultConfigFile );   // GConfig (FConfigCacheIni) 에 설정파일 내용 저장
}

이 함수가 아래 이어지는 여러 상황에서 호출되어집니다.

엔진에서 설정파일 초기화

  • 엔진 초기화 시
    // UnMisc.cpp
    void appInit( ... )
    {
        ...
        //// Init config.
        GConfig = ConfigFactory();
        ...
    #if WITH_EDITOR
        // 에디터 로드 시
        appCheckInitForOutdateness( GEditrIni, GDefaultEditorIni, FALSE, YesNoToAll );
        appCheckInitForOutdateness( GEditrUserSettingsIni, GDefaultEditorUserSettingsIni, FALSE, YesNoToAll );
    #endif
     
        appCheckInitForOutdateness( GCompatIni, GDefaultCompatIni, FALSE, YesNoToAll );
        appCheckInitForOutdateness( GLightmassIni, GDefaultLightmassIni, FALSE, YesNoToAll );
        appCheckInitForOutdateness( GEngineIni, GDefaultEngineIni, FALSE, YesNoToAll );
        appCheckInitForOutdateness( GGameIni, GDefaultGameIni, FALSE, YesNoToAll );
        appCheckInitForOutdateness( GInputIni, GDefaultInputIni, FALSE, YesNoToAll );
        appCheckInitForOutdateness( GUIIni, GDefaultUIIni, FALSE, YesNoToAll );
        ...
    }
  • UObject 로드 시
    // UnLinker.cpp
     
    // package 파일로부터 오브젝트 파일의 내용을 로드하는 함수
    void ULinkerLoad::Preload( UObject* Object )
    {
        ...
        if ( Object->HasAnyFlags( RF_ClassDefaultObject ) && !GIsUCCMake )
        {
            // config 파일 로드, 그리고 아래 나와있는 UClass::GetConfigName() 으로 이어진다.
            Object->LoadConfig();
        }
        ...
    }
     
    // UnClass.h
    class UClass : public UState
    {
        ...
        // 현 class 가 사용하는 config 파일을 반환하는 함수 (지만 파일명을 조합하고 로드하는 역할도 한다.)
        const FString GetConfigName() const
        {
            if ( ClassConfigName == NAME_Engine )
            {
                return GEngineIni;
            }
            else if ( ... )
            {
                ...
            }
            ...
            else
            {
                FString ConfigGameName      = appGameConfigDir() + FString( GGameName ) + ClassConfigName.ToString() + TEXT( ".ini" );   // user 설정파일과
                FString ConfigDefaultName   = appGameConfigDir() + TEXT( "Default" ) + ClassConfigName.ToString() + TEXT( ".ini" );      // default 설정파일명을 생성
     
                UINT YesNoAll = ART_No;
                const UBOOL bTryToPreserveContents = FALSE;
                appCheckIniForOutdateness( *ConfigGameName, *ConfigDefaultName, bTryToPreserveContents, YesNoAll );        // 파일을 로드
                return ConfigGameName;
            }
        }
        ...
    }

설정파일 리로드

클라이언트를 재부팅하지 않아도 설정파일을 다시 로드할 수 있는 커맨드가 존재한다. reloadcfgreloadconfig 가 그것이다. TAB 이나 '`' 를 누르면 나오는 콘솔창으로 입력하는 이 커맨드의 시그니쳐는 다음과 같다.

reloadcfg <Class/Object>              // reloadcfg 혹은 reloadconfig 다음의 파라메터는 class 이름이나 object 이름이 될 수 있다.

이 커맨드의 의미는 실시간 modify 가 아닌 restore 의 개념이다. 무슨 말인고 하니, 설정파일을 고치고 저 커맨드를 실행해도 반영되지 않는다. 엔진이 부트업되는 시점 딱 한번 설정파일들을 Heap에 올려놓은 후 그 값으로 다시 복원restore한다는 개념이다.1) 때문에 잘 알고서 사용해야 한다.

(계속 설명을 이어가자면) 의미론적으로 class 는 변수 type 에 해당하며 Object 는 instance 에 해당된다. 따라서 실제 게임 내 실시간 반영시키려면 Object 이름을 넣어야 한다.

Object 이름은 프로그래머가 아니라면 알기 힘들다. 왜냐면 UnrealEngine3 내부적으로 Object, 즉 instance 의 이름은 [class이름+넘버링] 으로 조합되기 때문이다. 예를 들어 UTPawn class 인스턴스는 UTPawn_0, UTPawn_1, … , UTPawn_999 등의 순서로 생성된다.

레벨이 다시 로드된다거나 죽어서 리스폰된다거나 하면 기존 instance 는 소멸되고 넘버링이 증가된 새 instance 를 생성한다. (UnrealEngine3 내부적으로..) 따라가 debugger 로 실시간 instance 이름 확인이 가능한 프로그래머가 아닌 다른 직군들은 Object 의 이름을 제대로 알기 힘들다.

실시간 설정파일 리로드

위에서 엔진이 기본 제공하는 reloadcfg 는 우리가 원하는 기능을 하지 않는다는 것을 알았다. 이제 restore 기능이 아닌 change & adjust 기능을 하는 치트키를 실제 구현해보기로 한다.

크게 다음과 같은 단계로 구현될 것이다.

  1. config 파일의 변경 여부를 검사한다.
    appCheckIniForOutdatedness 함수를 사용하여 이를 검사한다. 예제는 UClass::GetConfigName 함수를 참조.
  2. 변경되었다면 다시 읽어들이고 게임 내 모든 instance 에게 적용한다.
    모든 Object 를 순회하며 해당 class 의 인스턴스들의 설정을 config 파일로부터 다시 읽어온다. UObject::ReloadConfig 함수를 사용하면 된다.

이를 구현한 코드조각은 아래와 같다.

void UMyCheatManager::RelCfgNative( FName ClassName )
{
    // 원본 클래스가 존재하는지 확인
    UClass* pOrigClass = FindObject<UClass>( ANY_PACKAGE, *ClassName.ToString() );
    if ( !pOrigClass )
    {
        debugf( TEXT( "class not found ( name : %s )" ), *ClassName.ToString() );
        return;
    }
 
    // config 파일 (.ini) 다시 로드
    const FString ConfigFileName = pOrigClass->GetConfigName();
    if ( GConfig->FindConfigFile( *ConfigFileName ) )
    {
        FString ConfigGameName     = appGameConfigDir() + FString( GGameName ) + pOrigClass->ClassConfigName.ToString() + TEXT( ".ini" );
        FString ConfigDefaultName   = appGameConfigDir() + TEXT( "Default" ) + pOrigClass->ClassConfigName.ToString() + TEXT( ".ini" );
 
        UINT YesNoAll = ART_No;
        const UBOOL bTryToPreserveContents = FALSE;
        appCheckIniForOutdatedness( *ConfigGameName, *ConfigDefaultName, bTryToPreserveContents, YesNoAll, TRUE );
    }
 
    // 루프를 돌며 Config 를 다시 읽어들여 적용
    for ( TObjectIterator<UObject> It; It; ++It )
    {
        if ( It->IsA( pOrigClass ) )
        {
            It->ReloadConfig();
        }
    }
}

위 네이티브 코드를 스크립트의 exec 함수로 호출하는 형태면 좋다.

exec function RelCfg( Name ClassName )
{
    if (
        Role == ROLE_Authority &&
        WorldInfo.NetMode != NM_Client &&
        Pawn.IsLocallyControlled()
    )
    {
        RelCfgNative( ClassName );
    }
}

만약 MyPawn.uc 파일에서 config 로부터 불러오는 속성을 DefaultPawn.ini 에서 수정한 후 인게임에 바로 적용하려면 틸트( ` ) 나 탭을 눌러 콘솔창을 열고 relcfg MyPawn 을 입력하면 된다.

참고

1) GConfig (FConfigCacheIni형) 에 이미 로드된 내용이 다시 로드된다.