사용자 설정과 환경 저장
UGameUserSettings를 확장해 그래픽·오디오·키 바인딩 같은 사용자 옵션을 저장하고 적용합니다.
그래픽 품질, 음량, 키 배치, 언어는 진행 상태와 성격이 다른 데이터입니다. 세이브 슬롯마다 따로 있을 필요가 없고, 게임을 켜자마자 메뉴보다 먼저 적용되어야 하며, 옵션 메뉴에서 바꾼 값이 다음 실행에도 남아 있어야 합니다. 또 옵션 메뉴의 슬라이더를 움직였을 때 값이 바뀌는 것과, 그 값이 실제 소리 크기에 적용되는 것과, 파일에 저장되는 것은 서로 다른 일입니다.
언리얼 엔진은 이런 사용자 설정을 위해 UGameUserSettings 클래스를 제공합니다. 이 글에서는 이 클래스를 상속해 게임 고유의 설정을 더하고, 엔진이 그 클래스를 쓰도록 등록한 뒤, 설정 값을 바꾸고 저장하고 적용하는 흐름을 살펴봅니다. 그리고 파일 입출력 및 JSON 처리에서 본 것처럼, 저장과 적용이 서로 다른 단계라는 점을 다시 확인합니다.
UGameUserSettings가 하는 일
UGameUserSettings는 현재 로컬 기기의 게임 설정을 저장하고, 불러오고, 적용하는 시스템입니다. 설정은 각 기기에 .ini 파일로 저장되며, 계정별이나 분할 화면의 플레이어별 프로필을 자동으로 나눠 주지는 않습니다.
해상도와 그래픽 품질 같은 대부분의 기본 설정은 엔진이 알아서 저장하고 불러옵니다. SaveGame과 마찬가지로 여러 플랫폼에서 같은 방식으로 관리되며, 정적 함수 UGameUserSettings::GetGameUserSettings()로 로컬 설정 객체를 얻습니다. 이 객체를 직접 만든 하위 타입으로 캐스팅해 쓸 때는 캐스팅 결과를 확인해야 합니다. 기본 설정 외에 게임 고유의 설정을 더할 수 있고, 바꾼 설정을 바로 적용하거나 임시로 두었다가 한꺼번에 적용할 수도 있습니다.
커스텀 설정 클래스 만들기
대부분의 프로젝트는 기본 UGameUserSettings를 그대로 쓰기보다, 상속해서 게임 고유의 설정 변수를 더합니다. 해상도와 그래픽 품질은 부모 클래스가 이미 다루므로, 하위 클래스에는 음량이나 언어처럼 엔진이 모르는 값만 더합니다. 아래 헤더는 세 부분으로 나눠 읽으면 됩니다. 어디서든 이 클래스의 인스턴스를 얻는 정적 함수, Config를 붙여 .ini에 기록할 설정 변수 네 개, 그리고 그 값을 다루는 setter와 getter와 재정의할 부모 함수들입니다.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameUserSettings.h"
#include "InputCoreTypes.h"
#include "MyGameUserSettings.generated.h"
UCLASS()
class MYPROJECT_API UMyGameUserSettings : public UGameUserSettings
{
GENERATED_BODY()
public:
UMyGameUserSettings();
// 싱글톤 패턴처럼 어디서든 쉽게 접근할 수 있는 헬퍼 함수
UFUNCTION(BlueprintCallable, Category = "Game Settings")
static UMyGameUserSettings* GetMyGameUserSettings();
//========================================
// 커스텀 게임 설정 변수들
// UPROPERTY로 선언해야 직렬화 대상이 됩니다.
//========================================
UPROPERTY(Config) // .ini 파일에 저장될 수 있도록 Config 지정
float MasterVolume;
UPROPERTY(Config)
bool bShowPlayerHealthBar;
UPROPERTY(Config)
FString PlayerPreferredLanguage;
UPROPERTY(Config)
TArray<FKey> CustomAbilityKeyBinds; // 커스텀 능력 키 바인딩 (예시)
//========================================
// 커스텀 설정 제어 함수들
//========================================
UFUNCTION(BlueprintCallable, Category = "Game Settings")
void SetMasterVolume(float NewVolume);
UFUNCTION(BlueprintPure, Category = "Game Settings")
float GetMasterVolume() const;
UFUNCTION(BlueprintCallable, Category = "Game Settings")
void SetShowPlayerHealthBar(bool bShow);
UFUNCTION(BlueprintPure, Category = "Game Settings")
bool GetShowPlayerHealthBar() const;
UFUNCTION(BlueprintCallable, Category = "Game Settings")
void SetPlayerPreferredLanguage(const FString& NewLanguage);
UFUNCTION(BlueprintPure, Category = "Game Settings")
FString GetPlayerPreferredLanguage() const;
// 변경 사항을 .ini 파일에 저장 (부모 클래스 함수 오버라이드)
virtual void SaveSettings() override;
// 저장된 설정을 로드하거나 기본값을 적용 (부모 클래스 함수 오버라이드)
virtual void LoadSettings(bool bForceReload = false) override;
// 설정을 게임에 적용 (그래픽, 사운드 등)
virtual void ApplySettings(bool bCheckForCommandLineOverrides) override;
};저장할 커스텀 변수에는 UPROPERTY(Config)를 붙여 .ini 파일에 기록되게 합니다. UGameUserSettings가 이미 GameUserSettings 설정 파일을 쓰도록 선언되어 있고, 이 설정은 하위 클래스로 이어지므로 따로 지정하지 않아도 같은 파일에 저장됩니다. 그 밖에 정적 접근 함수, 각 설정의 setter와 getter, 그리고 부모의 SaveSettings, LoadSettings, ApplySettings를 재정의할 함수를 선언했습니다.
#include "MyGameUserSettings.h"
#include "Internationalization/Internationalization.h"
UMyGameUserSettings::UMyGameUserSettings()
{
// 기본값 설정
MasterVolume = 0.75f;
bShowPlayerHealthBar = true;
PlayerPreferredLanguage = TEXT("en"); // 기본 언어 영어
CustomAbilityKeyBinds.Add(EKeys::Q); // 기본 Q 키 바인딩
CustomAbilityKeyBinds.Add(EKeys::E); // 기본 E 키 바인딩
}
UMyGameUserSettings* UMyGameUserSettings::GetMyGameUserSettings()
{
// 엔진 설정 클래스 등록과 초기화 상태에 따라 캐스팅은 실패할 수 있습니다.
return Cast<UMyGameUserSettings>(UGameUserSettings::GetGameUserSettings());
}
void UMyGameUserSettings::SetMasterVolume(float NewVolume)
{
MasterVolume = FMath::Clamp(NewVolume, 0.0f, 1.0f);
// 값만 변경합니다. 옵션 UI의 적용 버튼에서 ApplySettings(false)를 호출합니다.
}
float UMyGameUserSettings::GetMasterVolume() const
{
return MasterVolume;
}
void UMyGameUserSettings::SetShowPlayerHealthBar(bool bShow)
{
bShowPlayerHealthBar = bShow;
// UI 관련 설정은 주로 UI 위젯에서 직접 이 변수를 참조하여 처리합니다.
// 즉시 UI 업데이트를 강제하려면 UI 관리자에게 알림을 보내야 합니다.
}
bool UMyGameUserSettings::GetShowPlayerHealthBar() const
{
return bShowPlayerHealthBar;
}
void UMyGameUserSettings::SetPlayerPreferredLanguage(const FString& NewLanguage)
{
PlayerPreferredLanguage = NewLanguage;
// 값만 변경합니다. 실제 문화권 변경은 ApplySettings에서 시도합니다.
}
FString UMyGameUserSettings::GetPlayerPreferredLanguage() const
{
return PlayerPreferredLanguage;
}
void UMyGameUserSettings::SaveSettings()
{
Super::SaveSettings(); // 부모 클래스의 SaveSettings 호출 (실제 파일 저장)
UE_LOG(LogTemp, Warning, TEXT("MyGameUserSettings: SaveSettings returned."));
}
void UMyGameUserSettings::LoadSettings(bool bForceReload)
{
Super::LoadSettings(bForceReload); // 부모 클래스의 LoadSettings 호출
UE_LOG(LogTemp, Warning, TEXT("MyGameUserSettings: LoadSettings returned."));
// 로드된 후 필요에 따라 추가 초기화/유효성 검사 로직
MasterVolume = FMath::Clamp(MasterVolume, 0.0f, 1.0f);
}
void UMyGameUserSettings::ApplySettings(bool bCheckForCommandLineOverrides)
{
// 부모 구현은 기본 설정을 적용하고 SaveSettings도 호출합니다.
Super::ApplySettings(bCheckForCommandLineOverrides);
//========================================
// 커스텀 설정 적용 로직
//========================================
// 마스터 볼륨의 실제 오디오 적용은 미구현입니다.
// 프로젝트의 Sound Class/Mix 또는 오디오 제어 경로에 MasterVolume을 전달해야 합니다.
// 언어 설정 적용
if (!FInternationalization::Get().SetCurrentCulture(PlayerPreferredLanguage))
{
UE_LOG(LogTemp, Warning, TEXT("Culture was not applied: %s"), *PlayerPreferredLanguage);
}
// 키 바인딩 적용 (입력 시스템에 바인딩 로직 필요)
// Project Settings -> Input -> Action Mappings 또는 Axis Mappings을 C++에서 동적으로 변경하는 것은 복잡합니다.
// 보통은 FInputBindings와 같은 커스텀 시스템을 구축합니다.
// 이외의 게임 관련 설정 (예: HUD 체력 바 표시 여부)
// 이 설정은 주로 HUD 위젯에서 GetShowPlayerHealthBar()를 호출하여 조건부로 표시 여부를 결정합니다.
}생성자는 음량 0.75, 체력 바 표시, 언어 en, 능력 키 Q와 E를 기본값으로 정합니다. GetMyGameUserSettings는 엔진의 설정 객체를 이 클래스로 캐스팅해 돌려주는데, 엔진에 이 클래스를 등록하지 않았거나 아직 초기화되지 않았다면 캐스팅이 실패할 수 있습니다.
setter들은 메모리의 값만 바꿉니다. SetMasterVolume은 0에서 1 사이로 제한해 넣고, 언어 setter도 값만 바꿀 뿐 실제 문화권 변경은 ApplySettings에서 시도합니다. LoadSettings는 부모가 파일에서 읽은 뒤 음량이 범위를 벗어났으면 보정합니다. 사용자가 .ini 파일을 직접 고칠 수 있기 때문입니다.
ApplySettings는 먼저 부모 구현을 부르는데, 부모는 해상도 같은 기본 설정을 적용하고 SaveSettings도 함께 호출합니다. 그다음 커스텀 설정을 적용하는데, 실제로 구현된 것은 FInternationalization::Get().SetCurrentCulture로 언어를 바꾸고 실패하면 경고를 남기는 부분뿐입니다. 음량을 실제 오디오에 전달하는 부분은 구현하지 않았으며, 프로젝트의 사운드 클래스나 사운드 믹스 같은 오디오 제어 경로에 MasterVolume을 넘겨야 합니다. 키 바인딩도 배열에 저장만 할 뿐 입력 시스템에 다시 바인딩하지 않습니다. 코드 주석은 레거시 입력의 매핑을 바꾸는 어려움을 적고 있는데, 향상된 입력을 쓴다면 플레이어가 키를 바꾸는 기능을 제공하는 향상된 입력 사용자 설정(Enhanced Input User Settings)을 검토할 수 있습니다. 체력 바 표시 여부는 HUD 위젯이 GetShowPlayerHealthBar()를 읽어 정합니다.
그래서 옵션 메뉴의 적용 버튼은 ApplySettings(false)를 부르고, 이 호출은 부모 구현의 저장까지 함께 실행합니다. 사용자가 편집을 취소할 수 있는 UI를 만들려면 편집하기 전의 값을 따로 보관했다가 되돌려야 합니다. 부모 클래스의 RevertVideoMode는 해상도와 전체 화면 모드를 되돌리는 함수이므로, 직접 만든 음량이나 언어의 취소를 대신하지 않습니다.
엔진에 커스텀 클래스 등록하기
엔진이 UMyGameUserSettings를 쓰도록 알리려면 DefaultEngine.ini 파일을 고칩니다. 프로젝트 루트의 Config/DefaultEngine.ini를 열고(없다면 Config 폴더에 만듭니다) 다음 내용을 추가하거나 수정합니다.
[/Script/Engine.Engine]
GameUserSettingsClassName=/Script/MYPROJECT.MyGameUserSettings여기서 MYPROJECT 자리에는 실제 프로젝트의 모듈 이름을 씁니다. 이렇게 설정하면 엔진은 게임을 시작할 때 UMyGameUserSettings의 인스턴스를 만들고 불러오기와 저장을 관리합니다. 이 등록을 빠뜨리면 엔진은 기본 UGameUserSettings를 만들므로, 앞의 GetMyGameUserSettings 캐스팅이 실패합니다.
더 깊이: 여러 설정 파일 가운데 어느 값이 쓰일까
엔진은 Engine, Game 같은 범주(category)마다 설정 파일 여러 개를 정해진 순서로 읽고, 나중에 읽은 파일의 키와 값이 앞의 것을 덮어씁니다. 순서는 엔진 폴더의 Engine/Config/Base.ini와 Base<CATEGORY>.ini(예: BaseEngine.ini)에서 시작해, 프로젝트의 Config/Default<CATEGORY>.ini, 플랫폼별 파일, 사용자별 User<CATEGORY>.ini로 이어집니다. 위에서 프로젝트의 DefaultEngine.ini에 적은 GameUserSettingsClassName이 엔진의 기본값을 바꿀 수 있는 것도, 이 파일이 BaseEngine.ini보다 뒤에 읽히기 때문입니다.
같은 이유로 플랫폼 폴더의 파일은 프로젝트 기본값을 다시 덮을 수 있습니다. <PROJECT_DIRECTORY>/Config/<PLATFORM>/<PLATFORM><CATEGORY>.ini처럼 플랫폼 디렉터리에 있는 설정은 그 플랫폼에만 적용되며, DefaultEngine.ini보다 뒤에 읽힙니다. 그래서 어느 한 플랫폼에서만 등록한 클래스가 쓰이지 않는다면, 그 플랫폼의 설정 파일에 같은 키가 있는지 확인합니다. 엔진 디렉터리의 설정은 그 엔진을 쓰는 모든 프로젝트에 적용되므로, 한 프로젝트를 위한 값은 프로젝트의 Config 폴더에 둡니다. 배열 값에 +, -, ., ! 접두사를 붙여 앞 파일의 목록에 항목을 더하거나 빼는 규칙도 Configuration Files 문서에 정리되어 있습니다.
설정을 바꾸고 적용하기
C++에서는 정적 함수로 설정 객체를 얻어 setter를 부릅니다. 다음은 AMyPlayerController의 헤더에 SetGameVolume을 선언했다고 가정한 일부 예제입니다.
#include "MyPlayerController.h"
#include "MyGameUserSettings.h"
void AMyPlayerController::SetGameVolume(float NewVolume)
{
UMyGameUserSettings* UserSettings = UMyGameUserSettings::GetMyGameUserSettings();
if (UserSettings)
{
UserSettings->SetMasterVolume(NewVolume);
// 여기서는 편집 값만 변경합니다.
// 옵션 메뉴의 적용 버튼: UserSettings->ApplySettings(false);
}
}이 함수는 편집 값만 바꾸며, 옵션 메뉴의 적용 버튼에서 ApplySettings(false)를 부르는 연결은 UI에서 따로 해야 합니다.
블루프린트에서는 Get My Game User Settings 노드로 인스턴스를 얻은 뒤, 공개한 함수를 불러 설정을 읽거나 바꿉니다. Apply Settings 노드는 기본 설정의 적용과 저장을 함께 하지만, Save Settings만 부르면 파일에 쓰기만 할 뿐 커스텀 설정을 실행 중의 게임에 적용하지는 않습니다.
설정 파일의 위치
UGameUserSettings는 패키징한 Windows 게임에서 C:\Users\[사용자명]\AppData\Local\[프로젝트명]\Saved\Config\Windows\GameUserSettings.ini 같은 위치에 설정을 저장합니다. 에디터에서 실행할 때나 빌드 대상에 따라 디렉터리가 달라지며, Android와 iOS에서는 각 플랫폼의 앱 데이터 디렉터리 안에 저장됩니다. 이 파일은 일반 텍스트 .ini 형식이라 직접 열어 볼 수 있으며, Config 지정자를 붙인 변수들이 클래스 경로 이름의 섹션 아래에 저장됩니다.
; GameUserSettings.ini의 커스텀 속성 일부를 나타낸 형식 예시
[/Script/MYPROJECT.MyGameUserSettings]
MasterVolume=0.650000
bShowPlayerHealthBar=True
PlayerPreferredLanguage=ko저장과 적용은 다른 일이다
설정 값의 저장과 런타임 효과 적용은 다릅니다
setter 메모리 변경과 Super 기본 적용·저장 호출 뒤 문화권 성공/실패를 구분한다. 실제 파일 쓰기 성공은 별도 확인이며 문화권 false 경고에서 앞선 호출 경로가 자동 롤백되지는 않는다.
UNREAL C++ · CH7-4
설정 값의 저장과 런타임 효과 적용은 다릅니다
setter는 메모리 값을 변경합니다. ApplySettings는 부모의 기본 적용·저장 뒤 커스텀 문화권 적용을 시도하지만, 음량·키 재바인딩·HUD 갱신 연결은 아직 구현하지 않았습니다.
setter만 호출한 경로와 이 ApplySettings 경로는 다릅니다. 원 오디오·키 적용은 주석이며, 실패 뒤 취소·원값 복원과 재저장도 프로젝트가 별도로 설계합니다.
| 호출·값 | 현재 구현 | 구별할 점 |
|---|---|---|
| LoadSettings | 부모에서 설정을 읽고 MasterVolume 범위를 보정합니다. | 읽기만으로 이 예제의 커스텀 오디오·언어 적용이 실행되지는 않습니다. |
| ApplySettings | 부모의 기본 설정 적용·저장 뒤 문화권 변경을 시도합니다. | 문화권 적용 실패를 기록하지만 앞선 저장을 되돌리지는 않습니다. 유효한 문화권·리소스 정책은 별도입니다. |
| 음량·체력 바 | 음량 값과 표시 플래그를 보관합니다. | 오디오 제어와 HUD 갱신은 구현하지 않았습니다. 값 변경만으로 해당 효과가 생기지 않습니다. |
| 키 배열 | Config 속성에 FKey 배열을 보관합니다. | 배열 저장은 런타임 입력 재바인딩이 아닙니다. 사용하는 입력 시스템에 별도 적용해야 합니다. |
- LoadSettings
- 현재 구현: 부모에서 설정을 읽고 MasterVolume 범위를 보정합니다.구별할 점: 읽기만으로 이 예제의 커스텀 오디오·언어 적용이 실행되지는 않습니다.
- ApplySettings
- 현재 구현: 부모의 기본 설정 적용·저장 뒤 문화권 변경을 시도합니다.구별할 점: 문화권 적용 실패를 기록하지만 앞선 저장을 되돌리지는 않습니다. 유효한 문화권·리소스 정책은 별도입니다.
- 음량·체력 바
- 현재 구현: 음량 값과 표시 플래그를 보관합니다.구별할 점: 오디오 제어와 HUD 갱신은 구현하지 않았습니다. 값 변경만으로 해당 효과가 생기지 않습니다.
- 키 배열
- 현재 구현: Config 속성에 FKey 배열을 보관합니다.구별할 점: 배열 저장은 런타임 입력 재바인딩이 아닙니다. 사용하는 입력 시스템에 별도 적용해야 합니다.
커스텀 설정 클래스 등록과 캐스트 결과를 확인합니다. 적용 전 편집값 취소는 별도 보관·복원이 필요하며 RevertVideoMode는 음량·언어 취소를 대신하지 않습니다. 저장 로그는 실제 파일/옵션 실행 검증이 아닙니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 관계·조건 설명 | 원 ApplySettings는 부모의 기본 설정 적용과 저장 호출 뒤 커스텀 SetCurrentCulture를 시도한다. 성공하면 문화권이 적용되고, 실패하면 경고를 남기며 앞선 저장을 자동으로 되돌리지 않는다. 파일 쓰기의 실제 성공 여부나 오디오/키/HUD 구현 완료를 보장하지 않는다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 메모리 변경 setter가 설정값을 메모리에 저장한다. |
|
|
| Super 적용·저장 먼저 Super의 기본 적용·저장 호출 경로를 실행하며 실제 파일 성공은 별도로 확인한다. |
|
|
| 문화권 결과 문화권 적용이 false이면 경고하지만 앞선 기본 적용·저장 호출 경로를 자동 롤백하지 않는다. |
|
|
설정 값의 저장과 런타임 효과 적용은 다릅니다
setter 메모리 변경과 Super 기본 적용·저장 호출 뒤 문화권 성공/실패를 구분한다. 실제 파일 쓰기 성공은 별도 확인이며 문화권 false 경고에서 앞선 호출 경로가 자동 롤백되지는 않는다.
UNREAL C++ · CH7-4
설정 값의 저장과 런타임 효과 적용은 다릅니다
setter는 메모리 값을 변경합니다. ApplySettings는 부모의 기본 적용·저장 뒤 커스텀 문화권 적용을 시도하지만, 음량·키 재바인딩·HUD 갱신 연결은 아직 구현하지 않았습니다.
setter만 호출한 경로와 이 ApplySettings 경로는 다릅니다. 원 오디오·키 적용은 주석이며, 실패 뒤 취소·원값 복원과 재저장도 프로젝트가 별도로 설계합니다.
| 호출·값 | 현재 구현 | 구별할 점 |
|---|---|---|
| LoadSettings | 부모에서 설정을 읽고 MasterVolume 범위를 보정합니다. | 읽기만으로 이 예제의 커스텀 오디오·언어 적용이 실행되지는 않습니다. |
| ApplySettings | 부모의 기본 설정 적용·저장 뒤 문화권 변경을 시도합니다. | 문화권 적용 실패를 기록하지만 앞선 저장을 되돌리지는 않습니다. 유효한 문화권·리소스 정책은 별도입니다. |
| 음량·체력 바 | 음량 값과 표시 플래그를 보관합니다. | 오디오 제어와 HUD 갱신은 구현하지 않았습니다. 값 변경만으로 해당 효과가 생기지 않습니다. |
| 키 배열 | Config 속성에 FKey 배열을 보관합니다. | 배열 저장은 런타임 입력 재바인딩이 아닙니다. 사용하는 입력 시스템에 별도 적용해야 합니다. |
- LoadSettings
- 현재 구현: 부모에서 설정을 읽고 MasterVolume 범위를 보정합니다.구별할 점: 읽기만으로 이 예제의 커스텀 오디오·언어 적용이 실행되지는 않습니다.
- ApplySettings
- 현재 구현: 부모의 기본 설정 적용·저장 뒤 문화권 변경을 시도합니다.구별할 점: 문화권 적용 실패를 기록하지만 앞선 저장을 되돌리지는 않습니다. 유효한 문화권·리소스 정책은 별도입니다.
- 음량·체력 바
- 현재 구현: 음량 값과 표시 플래그를 보관합니다.구별할 점: 오디오 제어와 HUD 갱신은 구현하지 않았습니다. 값 변경만으로 해당 효과가 생기지 않습니다.
- 키 배열
- 현재 구현: Config 속성에 FKey 배열을 보관합니다.구별할 점: 배열 저장은 런타임 입력 재바인딩이 아닙니다. 사용하는 입력 시스템에 별도 적용해야 합니다.
커스텀 설정 클래스 등록과 캐스트 결과를 확인합니다. 적용 전 편집값 취소는 별도 보관·복원이 필요하며 RevertVideoMode는 음량·언어 취소를 대신하지 않습니다. 저장 로그는 실제 파일/옵션 실행 검증이 아닙니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 관계·조건 설명 | 원 ApplySettings는 부모의 기본 설정 적용과 저장 호출 뒤 커스텀 SetCurrentCulture를 시도한다. 성공하면 문화권이 적용되고, 실패하면 경고를 남기며 앞선 저장을 자동으로 되돌리지 않는다. 파일 쓰기의 실제 성공 여부나 오디오/키/HUD 구현 완료를 보장하지 않는다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 메모리 변경 setter가 설정값을 메모리에 저장한다. |
|
|
| Super 적용·저장 먼저 Super의 기본 적용·저장 호출 경로를 실행하며 실제 파일 성공은 별도로 확인한다. |
|
|
| 문화권 결과 문화권 적용이 false이면 경고하지만 앞선 기본 적용·저장 호출 경로를 자동 롤백하지 않는다. |
|
|
위 그림의 위쪽은 ApplySettings의 흐름입니다. 편집한 값을 기준으로 ApplySettings를 부르면, 먼저 부모의 기본 설정 적용과 저장이 실행되고, 그 뒤에 문화권 변경을 시도합니다. SetCurrentCulture가 성공하면 문화권이 적용되고, 실패하면 경고를 남기는데, 이때 앞에서 실행한 저장은 그대로 남고 원래 언어 값으로 자동으로 돌아가지도 않습니다. 그림 아래의 설명처럼 setter만 부른 경로와 이 ApplySettings 경로는 서로 다르며, 오디오와 키 적용은 주석이고, 실패한 뒤의 취소와 원래 값 복원, 다시 저장하는 일은 프로젝트가 따로 설계해야 합니다. 이 흐름은 파일 쓰기가 실제로 성공했는지나 오디오, 키, HUD 구현이 끝났는지를 보여 주지 않습니다.
그림의 표는 호출이나 값마다 현재 구현과 구별할 점을 정리합니다. LoadSettings는 부모에서 설정을 읽고 음량 범위를 보정할 뿐, 읽었다고 이 예제의 커스텀 오디오나 언어가 적용되지는 않습니다. ApplySettings는 기본 설정의 적용과 저장 뒤에 문화권 변경을 시도하며, 문화권 적용이 실패해도 앞선 저장을 되돌리지 않고, 어떤 문화권과 리소스를 허용할지는 따로 정해야 합니다. 음량과 체력 바는 값과 표시 플래그만 보관하므로, 오디오 제어와 HUD 갱신이 구현되지 않은 지금은 값을 바꿔도 그 효과가 생기지 않습니다. 키 배열은 Config 속성에 FKey 배열을 보관할 뿐이며, 저장한다고 실행 중의 입력이 다시 바인딩되지는 않습니다.
단계 그림도 같은 순서를 따릅니다. setter가 값을 메모리에 넣고, 부모의 기본 적용과 저장이 실행된 뒤, 문화권 결과가 false이면 경고를 남기지만 앞의 호출을 자동으로 되돌리지 않습니다. 전체 설정이 한 번에 모두 성공하거나 모두 취소되는 처리로 볼 수 없다는 점이 핵심입니다.
구체적인 값으로 따라가 봅시다. 등록한 커스텀 설정 클래스로 캐스팅이 성공했고 MasterVolume이 0.75인 상태에서 SetMasterVolume(1.4)를 부르면, 메모리의 값은 1로 제한됩니다. 이 setter만으로는 파일 저장도 실제 소리 변화도 일어나지 않습니다. SaveSettings를 부르면 저장 경로가 실행되지만, 이 코드에는 오디오 제어로 값을 보내는 부분이 없습니다. ApplySettings(false)를 불러도 주석으로 남은 오디오와 키 적용이 저절로 완성되지는 않습니다.
설정 파일은 사용자가 고칠 수 있으므로 불러온 뒤에는 값을 검증해야 합니다. 이 예제는 음량의 범위만 보정하며, 실제 오디오와 입력 시스템, UI 갱신의 연결은 프로젝트에서 완성해야 합니다.
정리
UGameUserSettings는 로컬 기기의 설정을.ini파일로 저장하고 불러오고 적용하며, 해상도와 그래픽 품질 같은 기본 설정은 엔진이 관리합니다.- 게임 고유의 설정은
UGameUserSettings를 상속한 클래스에UPROPERTY(Config)로 선언하고,DefaultEngine.ini의GameUserSettingsClassName으로 엔진에 등록합니다. - 등록하지 않으면 엔진은 기본 클래스를 만들어 커스텀 타입으로의 캐스팅이 실패합니다.
- setter는 메모리 값만 바꾸고,
SaveSettings는 파일에 쓰며,ApplySettings는 부모의 기본 적용과 저장 뒤 커스텀 적용을 시도합니다. 세 가지는 서로 다른 일입니다. - 커스텀 적용의 일부가 실패해도 앞선 저장은 되돌아가지 않으며, 편집 취소는 이전 값을 따로 보관해 구현합니다.
RevertVideoMode는 해상도와 전체 화면만 되돌립니다. - 사용자가 고칠 수 있는 설정 파일은 불러온 뒤 값을 검증합니다.
연습 문제
1. 문화권 적용이 실패하면
편집하기 전의 언어 값은 en이었고, 새 문자열을 설정한 뒤 ApplySettings(false)를 불렀습니다. 부모의 기본 적용과 저장이 실행되었고 SetCurrentCulture는 false를 돌려주었다고 합시다. 이 코드가 저장한 언어 값을 자동으로 en으로 되돌릴까요?
해설 보기
되돌리지 않습니다. 문화권 변경이 실패하면 경고만 남기며, 앞에서 설정한 언어 문자열을 되돌리거나 이미 실행한 저장을 취소하지 않습니다. 그래서 파일에는 적용되지 않은 새 언어 값이 저장되어 있을 수 있습니다.
실패했을 때 원래 값으로 되돌리려면, 편집 전 값을 보관해 두었다가 실패하면 되돌리고 다시 저장하는 처리를 직접 만들어야 합니다. 이 호출 순서는 코드에 적힌 경로일 뿐이므로, 실제 파일 저장과 문화권 리소스 적용은 따로 확인합니다.
2. 음량 편집 취소하기
옵션 메뉴에서 음량을 바꾼 뒤 취소 버튼을 눌렀습니다. 취소 처리로 RevertVideoMode만 불러도 될까요?
해설 보기
안 됩니다. RevertVideoMode는 해상도와 전체 화면 모드를 되돌리는 함수이므로, 직접 만든 음량이나 언어의 취소를 대신하지 않습니다. 메뉴를 열 때 커스텀 값을 따로 보관했다가 취소할 때 되돌리고, 이미 적용하거나 저장했다면 그 값을 다시 반영하고 저장할지도 정합니다.
3. 슬라이더를 움직였는데 소리가 그대로
옵션 메뉴의 음량 슬라이더가 SetMasterVolume을 부르고 적용 버튼이 ApplySettings(false)를 부릅니다. 적용 버튼까지 눌렀는데 게임의 소리 크기가 바뀌지 않습니다. 원인은 무엇일까요?
해설 보기
이 예제의 ApplySettings에는 MasterVolume을 실제 오디오에 전달하는 코드가 없기 때문입니다. setter는 메모리 값을, 부모의 ApplySettings는 기본 설정의 적용과 저장을 할 뿐, 커스텀 음량을 사운드 시스템에 넘기는 일은 하지 않습니다.
ApplySettings의 커스텀 적용 부분에서 프로젝트의 사운드 클래스나 사운드 믹스 같은 오디오 제어 경로로 MasterVolume을 넘기는 코드를 구현해야 소리가 바뀝니다.
UGameUserSettings의 함수와 동작은 Epic의 UGameUserSettings API 문서에서 확인할 수 있습니다.
이것으로 7장을 마칩니다. 7장에서는 SaveGame으로 진행 상태를 슬롯에 저장하는 방법, 직렬화의 원리와 참조, 버전을 다루는 방법, 파일 입출력과 JSON, Data Asset과 Data Table로 데이터를 주고받는 방법, 그리고 UGameUserSettings로 사용자 설정을 저장하고 적용하는 방법을 살펴봤습니다. 다음 장의 언리얼 네트워킹 기본 개념부터는 서버와 클라이언트 구조, 복제, RPC, 멀티플레이어 최적화로 네트워크 게임을 다룹니다.