설정
검증과 변경 이벤트를 갖춘 타입 기반 영속 설정 그룹.
변경 이벤트를 갖춘 타입 기반 영속 설정 그룹 · 모든 쓰기 시 검증 · no-op 대체물:
NullConfiguration
무엇을 하는가
설정 서비스는 게임 설정 — 오디오 볼륨, 그래픽 품질, 입력 리바인드, UI 환경설정 — 을 강타입 그룹(설정 영역당 하나씩의 평범한 직렬화 가능 클래스)으로 저장합니다. 모든 Set은 새 값을 검증하고 캐시합니다. 변경이 언제 디스크에 저장되고 이벤트로 브로드캐스트되는지는 모드에 달려 있습니다: Immediate 모드는 Set 안에서 둘 다 동기적으로 수행하고, Deferred 모드(시작 기본값)는 변경을 대기 큐에 배칭합니다 — FlushPending<TGroup>()이나 FlushAllPending()을 호출하기 전까지는 아무것도 기록되거나 발행되지 않습니다. 배칭은 빠른 UI 슬라이더 드래그가 프레임 히치를 일으키지 않게 해 주는 대신, 소비자가 반응하길 원하는 시점에 명시적인 플러시 호출 하나가 필요합니다.
빠른 예제
현재 오디오 설정을 읽고, 슬라이더로 갱신하고, 이벤트 버스에 발행되는 변경에 반응하는 옵션 패널 스크립트:
using System;
using CommonGameSystem.Core;
using UnityEngine;
using AudioSettings = CommonGameSystem.Core.AudioSettings; // Unity has its own AudioSettings type
[DefaultExecutionOrder(100)]
public class AudioPanel : MonoBehaviour
{
private IConfiguration _cfg;
private IEventBus _bus;
private IDisposable _subscription;
private void Awake()
{
// Resolve and cache once
_cfg = ServiceLocator.Resolve<IConfiguration>();
_bus = ServiceLocator.Resolve<IEventBus>();
// Pull the current value (no event fires on plain loads by default)
var audio = _cfg.Get<AudioSettings>();
UpdateSliders(audio);
// Subscribe for future changes
_subscription = _bus.Subscribe<ConfigurationChanged<AudioSettings>>(OnAudioChanged);
}
private void OnDestroy()
{
_subscription?.Dispose();
}
// Called when the user drags the volume slider
public void OnMasterVolumeSliderChanged(float value)
{
var current = _cfg.Get<AudioSettings>();
current.masterVolume = value;
_cfg.Set(current); // Deferred (default): queues the change only
_cfg.FlushPending<AudioSettings>(); // save + publish NOW so consumers react live
// To batch disk writes during a drag instead, move the FlushPending
// call to the slider's drag-end or Apply-button handler.
}
// Event handler (OldValue is always non-null for UserSet changes)
private void OnAudioChanged(ConfigurationChanged<AudioSettings> evt)
{
if (evt.Source == ConfigurationChangeSource.UserSet)
UpdateAudioMixer(evt.NewValue);
}
private void UpdateSliders(AudioSettings audio) { /* ... */ }
private void UpdateAudioMixer(AudioSettings audio) { /* ... */ }
}
전체 API
핵심 접근자
-
TGroup Get<TGroup>() where TGroup : class, new()— 현재 캐시된 그룹을 가져옵니다. 그룹 타입별 첫 호출은 스토리지에서 로드하고, 이후 호출은 캐시 히트입니다. 아직 저장된 것이 없으면(새 설치, 삭제된 데이터) 그룹의 기본값 —new TGroup(), 또는DefaultsProviders에 등록된 팩토리 — 을 받습니다. 데이터가 없다고 예외를 던지는 일은 없으며, 항상 null이 아닌 인스턴스를 반환합니다. -
void Set<TGroup>(TGroup value) where TGroup : class, new()— 그룹을 교체하고 검증합니다.Immediate모드에서는 같은 호출에서 저장과 변경 이벤트 발행까지 수행합니다.Deferred모드(시작 기본값)에서는 변경을 큐에 넣기만 합니다 — 저장과 발행 모두 플러시를 기다립니다.value가 null이면ArgumentNullException을, 검증기나 변경 이벤트 핸들러 안에서 같은 그룹에 재진입 호출하면InvalidOperationException을 던집니다(무한 set-발행-set 루프에 대한 방어 장치). -
void Reset<TGroup>() where TGroup : class, new()— 그룹 하나를 팩토리 기본값으로 되돌리고, 저장된 데이터를 (모드와 무관하게) 즉시 삭제하며,Source == UserSet인 변경 이벤트를 발행합니다. 이 그룹에 대기 중인 지연Set은 버려집니다. -
void ResetAll()— 모든 그룹을 기본값으로 되돌리고, 저장된 설정 데이터 전부를 즉시 삭제하며, 그룹당 하나의 변경 이벤트를 발행합니다. 대기 중인 지연 항목이 먼저 모두 버려지므로, 직후의FlushAllPending()은 무해한 no-op입니다.
Deferred 모드 플러시 (시작 기본값에서는 필수)
void FlushPending<TGroup>() where TGroup : class, new()— 대기 중인 그룹 하나를 저장하고 발행합니다.Immediate모드이거나 이 그룹에 대기 중인 것이 없으면 no-op입니다. 전형적인 호출 위치: 슬라이더의 드래그 종료 핸들러, 또는 옵션 화면의 Apply 버튼.void FlushAllPending()— 대기 중인 모든 그룹을 한 번에 저장하고 발행합니다. 전형적인 호출 위치: 옵션 화면 닫기, 씬 전환. 애플리케이션 종료 시에도 자동으로 호출되지만 — 그 경로에서는 저장만 합니다; 이벤트는 발행하지 않습니다(동작 및 엣지 케이스 참조).
이벤트 페이로드
이벤트 버스에서 IEventBus.Subscribe<ConfigurationChanged<TGroup>>로 구독하십시오:
ConfigurationChanged<TGroup>.NewValue— 새 설정(항상 null 아님).ConfigurationChanged<TGroup>.OldValue— 이전 설정. 디스크 로드 이벤트에서만, 그것도 옵트인했을 때만 null입니다(아래PublishOnHydrate참조).ConfigurationChanged<TGroup>.Source— 변경의 출처:UserSet— 사용자가 시작한 변경(슬라이더, 키 바인딩 대화상자, Apply 버튼).OldValue는 항상 null이 아닙니다. 평소처럼 처리하십시오.Hydrate— 그룹이 방금 처음으로 스토리지에서 로드되었습니다.PublishOnHydrate가 켜져 있을 때만 발행되며,OldValue는 null입니다. 프레임워크 예약 — 여러분의 코드는 이 값을 발행해서는 안 됩니다.ConfigBackedRebind— 입력 서비스의 키 리바인딩 통합용으로 예약; 입력 관련 핸들러는 피드백 루프를 피하려면 이 출처에서는 재적용을 건너뛰어야 합니다. 프레임워크 예약.Unknown— 폴백;UserSet처럼 다루십시오.
옵션 (시작 시 설정하는 튜닝 값)
PersistCoalesceMode—Immediate(Set안에서 동기적으로 저장 + 발행) 또는Deferred(플러시까지 배칭). 프레임워크는 서비스를Deferred모드로 시작합니다.PublishOnHydrate—true이면 그룹이 스토리지에서 처음 로드될 때ConfigurationChanged이벤트를 발행합니다(OldValue는 null,Source는Hydrate). 기본값false: 의도된 패턴은Awake에서 현재 값을 한 번 당겨오고 이후 변경을 구독하는 것입니다.LoggerWarningThrottleSeconds— 반복되는 검증 경고(클램프되거나 정제된 값)를 필드당 N초에 한 번으로 제한해, 슬라이더 드래그가 콘솔을 도배하지 못하게 합니다. 기본 1초.PlayerPrefsSoftCapBytes— 설정이PlayerPrefs에 저장될 때, 페이로드가PlayerPrefs의 ~64 KB 문자열 한도에 다가가면 경고합니다. 기본 60000.SaveServiceConfigGroupKeyPrefix— 설정이 저장/불러오기 서비스를 통해 저장될 때 쓰는 저장 슬롯 접두사. 기본"config_"이며, 그래서 오디오 그룹은 슬롯config_AudioSettings에 저장됩니다.DefaultsProviders/SetDefaultsProvider<TGroup>(Func<TGroup>)— 그룹별 팩토리 함수. 기본값이new TGroup()이 아니라 디자이너가 작성한 에셋(예: 인스펙터로 연결한 ScriptableObject)에서 와야 할 때 사용합니다.
설정이 저장되는 위치
기본적으로 서비스는 저장/불러오기 서비스를 통해, 그룹당 하나의 저장 슬롯(config_AudioSettings, config_GraphicsSettings, ...)으로 영속화합니다. 백엔드는 시작 시 한 번 선택됩니다: 그 시점에 저장/불러오기 서비스가 활성 상태면 설정이 그 서비스를 거치고, 없거나 이미 NullSaveService로 교체되어 있으면 설정 서비스는 Unity의 PlayerPrefs로 폴백합니다. Play 세션 나중에 저장 서비스를 교체해도 이미 라우팅된 설정은 옮겨지지 않습니다 — 이 선택은 시작 시의 결정입니다.
내장 그룹
프레임워크는 자체 서비스들이 사용하는 세 가지 기성 그룹을 제공합니다: AudioSettings, GraphicsSettings, InputSettings. 여러분의 그룹도 추가할 수 있습니다 — 동작 및 엣지 케이스의 제약을 만족하는 클래스라면 등록 없이 곧바로 Get/Set과 함께 동작합니다.
끄는 방법
// In a test setup, or anywhere before consumers resolve it:
ServiceLocator.Replace<IConfiguration>(new NullConfiguration());
NullConfiguration을 쓰면 모든 Get은 새 기본값을 반환하고, 모든 Set/Reset은 no-op입니다 — 이벤트 발행도, 디스크 쓰기도 없습니다. 헤드리스/CI 빌드나, 플레이어 커스터마이징이 지속되면 안 되는 키오스크 설치에 유용합니다. 소비자는 바뀐 것 없이 계속 동작합니다; 그저 기본값만 보고 변경 이벤트를 받지 않을 뿐입니다.
동작 및 엣지 케이스
-
메인 스레드 전용. 모든 메서드는 메인 스레드에서 실행되는지 assert합니다(검사는 Release 빌드에서 제거됩니다). 워커 스레드에서 호출하지 마십시오; 필요하면 먼저 메인 스레드로 마샬링하십시오.
-
Deferred 모드가 기본값입니다.
Set만으로는 저장도 발행도 되지 않습니다.FlushPending<TGroup>()이나FlushAllPending()을 호출해야 하며, 그러지 않으면 소비자(예: 오디오 서비스의 믹서)는 변경을 결코 보지 못합니다. 애플리케이션 종료 시의 안전망이 대기 중인 값을 저장해 주지만 이벤트는 발행하지 않으므로, 여기에 의존하면 라이브 소비자는 플레이 중에 결코 반응하지 않습니다. 라이브 피드백이 필요하면 변경마다, 배치 쓰기를 원하면 드래그 종료/"Apply"에서 플러시하십시오. -
그룹 타입은 평범한 직렬화 가능 클래스여야 합니다.
[Serializable]을 붙이고 public 필드만 사용하십시오: 자동 프로퍼티 금지,UnityEngine.Object참조 금지, 그리고 매개변수 없는 생성자가 필요합니다. 그 아래의 시리얼라이저인 Unity의JsonUtility는 자동 프로퍼티를 조용히 무시합니다 — 값이 소리 없이 저장에 실패하게 됩니다. -
Unity 타입과의 이름 충돌. 내장 그룹
CommonGameSystem.Core.AudioSettings는UnityEngine.AudioSettings와 이름이 같습니다. 파일이 두 네임스페이스를 모두 임포트한다면using별칭을 추가하십시오(위 예제에 표시됨). -
OldValuenull 체크. 기본 설정에서는 이벤트가 항상 null이 아닌OldValue를 담습니다.PublishOnHydrate를 켠 경우에만(디스크 로드 이벤트에서)OldValue가 null일 수 있습니다 — 그 경우 사용 전에 확인하십시오. -
재진입
Set은 예외를 던집니다. 어떤 그룹의 검증기나 변경 이벤트 핸들러 안에서 그 그룹에Set<TGroup>을 호출하면InvalidOperationException이 발생합니다. 변경에 반응하되, 그 알림 안에서 같은 그룹을 다시 쓰지 마십시오. -
여러분의 그룹 타입과 IL2CPP. 프레임워크는 세 내장 그룹을 IL2CPP 코드 스트리핑에서 보호합니다. IL2CPP로 빌드한다면 여러분의 그룹 타입에
[Preserve]어트리뷰트와 프로젝트의link.xml항목을 추가하십시오.