565 lines
20 KiB
C#
565 lines
20 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Threading;
|
|
using Cysharp.Threading.Tasks;
|
|
using UnityEngine;
|
|
using UVC.Json;
|
|
|
|
namespace UVC.Studio.Config
|
|
{
|
|
/// <summary>
|
|
/// Studio 애플리케이션 설정 관리 클래스
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para><b>[ 개요 ]</b></para>
|
|
/// <para>StreamingAssets/Settings.json 파일을 로드/저장하여 애플리케이션 설정을 관리합니다.</para>
|
|
///
|
|
/// <para><b>[ JSON 파일 구조 ]</b></para>
|
|
/// <code>
|
|
/// Settings.json
|
|
/// ├── database - 데이터베이스 연결 설정
|
|
/// │ ├── ip - DB 서버 IP 주소
|
|
/// │ ├── port - DB 서버 포트
|
|
/// │ ├── id - 접속 계정 ID
|
|
/// │ └── password - 접속 계정 비밀번호
|
|
/// ├── general - 일반 설정
|
|
/// │ ├── autoSaveInterval - 자동 저장 간격 (분)
|
|
/// │ ├── gridSize - 그리드 크기
|
|
/// │ ├── snapPosition - 위치 스냅 단위
|
|
/// │ ├── snapRotation - 회전 스냅 단위 (도)
|
|
/// │ └── snapScale - 스케일 스냅 단위
|
|
/// └── shortcuts - 단축키 설정
|
|
/// ├── menu - 메뉴 단축키 (newProject, openProject, saveProject, ...)
|
|
/// └── tools - 도구 단축키 (select, move, rotate, scale, ...)
|
|
/// </code>
|
|
///
|
|
/// <para><b>[ 사용 예시 (비동기) ]</b></para>
|
|
/// <code>
|
|
/// // 비동기 로드
|
|
/// var setting = new Setting();
|
|
/// await setting.LoadAsync();
|
|
///
|
|
/// // 데이터베이스 설정 접근
|
|
/// string dbIp = setting.Data.database.ip;
|
|
/// int dbPort = setting.Data.database.port;
|
|
///
|
|
/// // 일반 설정 접근
|
|
/// float gridSize = setting.Data.general.gridSize;
|
|
/// float snapPos = setting.Data.general.snapPosition;
|
|
///
|
|
/// // 단축키 설정 접근
|
|
/// string moveKey = setting.Data.shortcuts.tools.move.key; // "2"
|
|
/// string moveLabel = setting.Data.shortcuts.tools.move.label; // "Move Tool"
|
|
/// string saveDisplay = setting.Data.shortcuts.menu.saveProject.GetDisplayString(); // "Ctrl+Shift+S"
|
|
///
|
|
/// // 설정 변경 후 비동기 저장
|
|
/// setting.Data.general.gridSize = 2.0f;
|
|
/// await setting.SaveAsync();
|
|
/// </code>
|
|
///
|
|
/// <para><b>[ 사용 예시 (동기) ]</b></para>
|
|
/// <code>
|
|
/// // 동기 로드
|
|
/// var setting = new Setting();
|
|
/// setting.Load();
|
|
///
|
|
/// // 동기 저장
|
|
/// setting.Save();
|
|
/// </code>
|
|
///
|
|
/// <para><b>[ Injector 등록 및 사용 ]</b></para>
|
|
/// <para>AppContext에서 Setting을 등록하고, 다른 컴포넌트에서 주입받아 사용합니다.</para>
|
|
///
|
|
/// <para><b>1. AppContext에서 등록 (비동기)</b></para>
|
|
/// <code>
|
|
/// public class StudioAppContext : InjectorAppContext
|
|
/// {
|
|
/// protected override async UniTask RegisterServicesAsync()
|
|
/// {
|
|
/// // Setting 생성 및 비동기 로드
|
|
/// var setting = new Setting();
|
|
/// await setting.LoadAsync();
|
|
///
|
|
/// // Injector에 인스턴스 등록
|
|
/// Injector.RegisterInstance<Setting>(setting);
|
|
/// }
|
|
/// }
|
|
/// </code>
|
|
///
|
|
/// <para><b>2. AppContext에서 등록 (동기)</b></para>
|
|
/// <code>
|
|
/// public class StudioAppContext : InjectorAppContext
|
|
/// {
|
|
/// protected override void RegisterServices()
|
|
/// {
|
|
/// // Setting 생성 및 동기 로드
|
|
/// var setting = new Setting();
|
|
/// setting.Load();
|
|
///
|
|
/// // Injector에 인스턴스 등록
|
|
/// Injector.RegisterInstance<Setting>(setting);
|
|
/// }
|
|
/// }
|
|
/// </code>
|
|
///
|
|
/// <para><b>3. 컴포넌트에서 주입받아 사용</b></para>
|
|
/// <code>
|
|
/// public class EditorManager : MonoBehaviour
|
|
/// {
|
|
/// [Inject] private Setting _setting;
|
|
///
|
|
/// private void Start()
|
|
/// {
|
|
/// // 설정 데이터 접근
|
|
/// float gridSize = _setting.Data.general.gridSize;
|
|
/// string moveDisplay = _setting.Data.shortcuts.tools.move.GetDisplayString();
|
|
///
|
|
/// // 설정 변경 및 저장
|
|
/// _setting.Data.general.gridSize = 2.0f;
|
|
/// _setting.SaveAsync().Forget();
|
|
/// }
|
|
/// }
|
|
/// </code>
|
|
///
|
|
/// <para><b>4. 직접 접근 (Inject 없이)</b></para>
|
|
/// <code>
|
|
/// // InjectorAppContext를 통해 직접 접근
|
|
/// var setting = InjectorAppContext.Instance.Get<Setting>();
|
|
/// float gridSize = setting.Data.general.gridSize;
|
|
/// </code>
|
|
/// </remarks>
|
|
public class Setting
|
|
{
|
|
/// <summary>설정 파일 이름</summary>
|
|
private const string FileName = "Settings.json";
|
|
|
|
/// <summary>로드된 설정 데이터</summary>
|
|
public SettingData Data { get; private set; }
|
|
|
|
/// <summary>로드 완료 여부</summary>
|
|
public bool IsLoaded { get; private set; }
|
|
|
|
private bool useAppDataPath = false;
|
|
|
|
/// <summary>
|
|
/// 생성자 - 기본 데이터로 초기화합니다.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 비동기 로드를 사용하려면 생성 후 LoadAsync()를 호출하세요.
|
|
/// 동기 로드를 원하면 Load()를 호출하세요.
|
|
/// </remarks>
|
|
public Setting(bool useAppDataPath = false)
|
|
{
|
|
this.useAppDataPath = useAppDataPath;
|
|
Data = new SettingData();
|
|
IsLoaded = false;
|
|
}
|
|
|
|
#region Async Methods
|
|
|
|
/// <summary>
|
|
/// StreamingAssets/Settings.json 파일에서 설정을 비동기로 로드합니다.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">취소 토큰</param>
|
|
/// <returns>로드 성공 여부</returns>
|
|
/// <remarks>
|
|
/// 파일 읽기와 JSON 파싱을 백그라운드 스레드에서 수행합니다.
|
|
/// 파일이 없거나 파싱 실패 시 기본값으로 초기화됩니다.
|
|
/// </remarks>
|
|
public async UniTask<bool> LoadAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
string path = Path.Combine(Application.streamingAssetsPath, "Studio", FileName);
|
|
if (useAppDataPath) path = Path.Combine(Application.persistentDataPath, FileName);
|
|
|
|
if (!File.Exists(path))
|
|
{
|
|
Debug.LogWarning($"[Setting] File not found: {path}");
|
|
Data = new SettingData();
|
|
IsLoaded = true;
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
// 파일 읽기를 백그라운드 스레드에서 수행
|
|
string json = await UniTask.RunOnThreadPool(
|
|
() => File.ReadAllText(path),
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
// JSON 파싱을 백그라운드 스레드에서 수행
|
|
Data = await UniTask.RunOnThreadPool(
|
|
() => JsonHelper.FromJson<SettingData>(json),
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
IsLoaded = true;
|
|
Debug.Log($"[Setting] Loaded successfully from {path}");
|
|
return true;
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
Debug.Log("[Setting] Load cancelled");
|
|
throw;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError($"[Setting] Failed to load: {e.Message}");
|
|
Data = new SettingData();
|
|
IsLoaded = true;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 현재 설정을 StreamingAssets/Settings.json 파일에 비동기로 저장합니다.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">취소 토큰</param>
|
|
/// <returns>저장 성공 여부</returns>
|
|
public async UniTask<bool> SaveAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
string path = Path.Combine(Application.streamingAssetsPath, "Studio", FileName);
|
|
if (useAppDataPath)
|
|
{
|
|
path = Path.Combine(Application.persistentDataPath, FileName);
|
|
}
|
|
|
|
try
|
|
{
|
|
// JSON 직렬화를 백그라운드 스레드에서 수행 (Newtonsoft.Json 사용으로 float 정밀도 문제 해결)
|
|
string json = await UniTask.RunOnThreadPool(
|
|
() => Newtonsoft.Json.JsonConvert.SerializeObject(Data, Newtonsoft.Json.Formatting.Indented),
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
// 파일 쓰기를 백그라운드 스레드에서 수행
|
|
await UniTask.RunOnThreadPool(
|
|
() => File.WriteAllText(path, json),
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
Debug.Log($"[Setting] Saved successfully to {path}");
|
|
return true;
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
Debug.Log("[Setting] Save cancelled");
|
|
throw;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError($"[Setting] Failed to save: {e.Message}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Sync Methods
|
|
|
|
/// <summary>
|
|
/// StreamingAssets/Studio/Settings.json 파일에서 설정을 동기로 로드합니다.
|
|
/// </summary>
|
|
/// <returns>로드 성공 여부</returns>
|
|
/// <remarks>
|
|
/// 파일이 없거나 파싱 실패 시 기본값으로 초기화됩니다.
|
|
/// 메인 스레드를 블로킹하므로, 가능하면 LoadAsync() 사용을 권장합니다.
|
|
/// </remarks>
|
|
public bool Load()
|
|
{
|
|
string path = Path.Combine(Application.streamingAssetsPath, "Studio", FileName);
|
|
if (useAppDataPath)
|
|
{
|
|
path = Path.Combine(Application.persistentDataPath, FileName);
|
|
}
|
|
|
|
if (!File.Exists(path))
|
|
{
|
|
Debug.LogWarning($"[Setting] File not found: {path}");
|
|
Data = new SettingData();
|
|
IsLoaded = true;
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
string json = File.ReadAllText(path);
|
|
Data = JsonHelper.FromJson<SettingData>(json);
|
|
IsLoaded = true;
|
|
Debug.Log($"[Setting] Loaded successfully from {path}");
|
|
return true;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError($"[Setting] Failed to load: {e.Message}");
|
|
Data = new SettingData();
|
|
IsLoaded = true;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 현재 설정을 StreamingAssets/Settings.json 파일에 동기로 저장합니다.
|
|
/// </summary>
|
|
/// <returns>저장 성공 여부</returns>
|
|
/// <remarks>
|
|
/// 메인 스레드를 블로킹하므로, 가능하면 SaveAsync() 사용을 권장합니다.
|
|
/// </remarks>
|
|
public bool Save()
|
|
{
|
|
string path = Path.Combine(Application.streamingAssetsPath, "Studio", FileName);
|
|
if (useAppDataPath)
|
|
{
|
|
path = Path.Combine(Application.persistentDataPath, FileName);
|
|
}
|
|
try
|
|
{
|
|
// Newtonsoft.Json 사용으로 float 정밀도 문제 해결
|
|
string json = JsonHelper.ToJson(Data);
|
|
File.WriteAllText(path, json);
|
|
Debug.Log($"[Setting] Saved successfully to {path}");
|
|
return true;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError($"[Setting] Failed to save: {e.Message}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
#region Data Classes
|
|
|
|
/// <summary>
|
|
/// Settings.json 루트 데이터 클래스
|
|
/// </summary>
|
|
[Serializable]
|
|
public class SettingData
|
|
{
|
|
/// <summary>데이터베이스 연결 설정</summary>
|
|
public DatabaseSetting database = new();
|
|
|
|
/// <summary>일반 설정 (그리드, 스냅 등)</summary>
|
|
public GeneralSetting general = new();
|
|
|
|
/// <summary>단축키 설정</summary>
|
|
public ShortcutsSetting shortcuts = new();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 데이터베이스 연결 설정
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// MySQL 등 외부 데이터베이스 연결에 필요한 정보를 저장합니다.
|
|
/// </remarks>
|
|
[Serializable]
|
|
public class DatabaseSetting
|
|
{
|
|
/// <summary>데이터베이스 서버 IP 주소 (기본값: 127.0.0.1)</summary>
|
|
public string ip = "127.0.0.1";
|
|
|
|
/// <summary>데이터베이스 서버 포트 (기본값: 3306 - MySQL 기본 포트)</summary>
|
|
public int port = 3306;
|
|
|
|
/// <summary>접속 계정 ID</summary>
|
|
public string id = "admin";
|
|
|
|
/// <summary>접속 계정 비밀번호</summary>
|
|
public string password = "password";
|
|
}
|
|
|
|
/// <summary>
|
|
/// 일반 설정 (에디터 동작 관련)
|
|
/// </summary>
|
|
[Serializable]
|
|
public class GeneralSetting
|
|
{
|
|
/// <summary>자동 저장 간격 (분 단위, 기본값: 5분)</summary>
|
|
public int autoSaveInterval = 5;
|
|
|
|
/// <summary>그리드 표시 크기 (기본값: 1)</summary>
|
|
public float gridSize = 1f;
|
|
|
|
/// <summary>위치 스냅 단위 (기본값: 0.1)</summary>
|
|
public float snapPosition = 0.1f;
|
|
|
|
/// <summary>회전 스냅 단위 (도 단위, 기본값: 5도)</summary>
|
|
public float snapRotation = 5f;
|
|
|
|
/// <summary>스케일 스냅 단위 (기본값: 0.1)</summary>
|
|
public float snapScale = 0.1f;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 단축키 설정 컨테이너
|
|
/// </summary>
|
|
[Serializable]
|
|
public class ShortcutsSetting
|
|
{
|
|
/// <summary>메뉴 관련 단축키 (File, Edit, Create 등)</summary>
|
|
public MenuShortcuts menu = new();
|
|
|
|
/// <summary>도구 관련 단축키 (Select, Move, Rotate 등)</summary>
|
|
public ToolShortcuts tools = new();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 메뉴 단축키 설정
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// File, Edit, Create 메뉴의 단축키를 정의합니다.
|
|
/// </remarks>
|
|
[Serializable]
|
|
public class MenuShortcuts
|
|
{
|
|
/// <summary>새 프로젝트 (File > New Project)</summary>
|
|
public ShortcutItem newProject = new("N", "File > New Project", ctrl: true, shift: true);
|
|
|
|
/// <summary>프로젝트 열기 (File > Open Project)</summary>
|
|
public ShortcutItem openProject = new("O", "File > Open Project", ctrl: true, shift: true);
|
|
|
|
/// <summary>프로젝트 저장 (File > Save Project)</summary>
|
|
public ShortcutItem saveProject = new("S", "File > Save Project", ctrl: true, shift: true);
|
|
|
|
/// <summary>다른 이름으로 저장 (File > Save As...)</summary>
|
|
public ShortcutItem saveAsProject = new("S", "File > Save As...", ctrl: true, shift: true, alt: true);
|
|
|
|
/// <summary>데이터베이스 삽입 (File > Insert Database)</summary>
|
|
public ShortcutItem insertDb = new("I", "File > Insert Database", ctrl: true, shift: true);
|
|
|
|
/// <summary>레이아웃 내보내기 (File > Export > Layout)</summary>
|
|
public ShortcutItem exportLayout = new("L", "File > Export > Layout", ctrl: true, shift: true);
|
|
|
|
/// <summary>메타데이터 내보내기 (File > Export > Metadata)</summary>
|
|
public ShortcutItem exportMeta = new("M", "File > Export > Metadata", ctrl: true, shift: true);
|
|
|
|
/// <summary>glTF 내보내기 (File > Export > glTF)</summary>
|
|
public ShortcutItem exportGltf = new("G", "File > Export > glTF", ctrl: true, shift: true);
|
|
|
|
/// <summary>실행 취소 (Edit > Undo)</summary>
|
|
public ShortcutItem undo = new("Z", "Edit > Undo", ctrl: true, shift: true);
|
|
|
|
/// <summary>다시 실행 (Edit > Redo)</summary>
|
|
public ShortcutItem redo = new("Y", "Edit > Redo", ctrl: true, shift: true);
|
|
|
|
/// <summary>복제 (Edit > Duplicate)</summary>
|
|
public ShortcutItem duplicate = new("D", "Edit > Duplicate", ctrl: true, shift: true);
|
|
|
|
/// <summary>삭제 (Edit > Delete)</summary>
|
|
public ShortcutItem delete = new("DELETE", "Edit > Delete", shift: true);
|
|
|
|
/// <summary>평면 생성 (Create > Plane)</summary>
|
|
public ShortcutItem createPlane = new("V", "Create > Plane", ctrl: true, shift: true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 도구 단축키 설정
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 에디터 도구 전환에 사용되는 단축키를 정의합니다.
|
|
/// </remarks>
|
|
[Serializable]
|
|
public class ToolShortcuts
|
|
{
|
|
/// <summary>선택 도구</summary>
|
|
public ShortcutItem select = new("1", "Select Tool");
|
|
|
|
/// <summary>이동 도구</summary>
|
|
public ShortcutItem move = new("2", "Move Tool");
|
|
|
|
/// <summary>회전 도구</summary>
|
|
public ShortcutItem rotate = new("3", "Rotate Tool");
|
|
|
|
/// <summary>스케일 도구</summary>
|
|
public ShortcutItem scale = new("4", "Scale Tool");
|
|
|
|
/// <summary>스냅 도구</summary>
|
|
public ShortcutItem snap = new("5", "Snap Tool");
|
|
|
|
/// <summary>가이드 도구</summary>
|
|
public ShortcutItem guide = new("6", "Guide Tool");
|
|
|
|
/// <summary>노드 도구</summary>
|
|
public ShortcutItem node = new("7", "Node Tool");
|
|
|
|
/// <summary>링크 도구</summary>
|
|
public ShortcutItem link = new("8", "Link Tool");
|
|
|
|
/// <summary>아크 도구</summary>
|
|
public ShortcutItem arc = new("9", "Arc Tool");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 개별 단축키 항목
|
|
/// </summary>
|
|
[Serializable]
|
|
public class ShortcutItem
|
|
{
|
|
/// <summary>
|
|
/// 단축키 문자 (1글자, 예: "S", "N", "1")
|
|
/// </summary>
|
|
public string key;
|
|
|
|
/// <summary>
|
|
/// Ctrl 키 사용 여부
|
|
/// </summary>
|
|
public bool ctrl;
|
|
|
|
/// <summary>
|
|
/// Shift 키 사용 여부
|
|
/// </summary>
|
|
public bool shift;
|
|
|
|
/// <summary>
|
|
/// Alt 키 사용 여부
|
|
/// </summary>
|
|
public bool alt;
|
|
|
|
/// <summary>
|
|
/// 단축키 설명 레이블
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// UI 표시용 (예: "File > Save Project", "Move Tool")
|
|
/// </remarks>
|
|
public string label;
|
|
|
|
/// <summary>기본 생성자 (JSON 역직렬화용)</summary>
|
|
public ShortcutItem() { }
|
|
|
|
/// <summary>
|
|
/// 단축키 항목 생성
|
|
/// </summary>
|
|
/// <param name="key">단축키 문자 (예: "S")</param>
|
|
/// <param name="label">설명 레이블 (예: "File > Save Project")</param>
|
|
/// <param name="ctrl">Ctrl 키 사용 여부</param>
|
|
/// <param name="shift">Shift 키 사용 여부</param>
|
|
/// <param name="alt">Alt 키 사용 여부</param>
|
|
public ShortcutItem(string key, string label, bool ctrl = false, bool shift = false, bool alt = false)
|
|
{
|
|
this.key = key;
|
|
this.label = label;
|
|
this.ctrl = ctrl;
|
|
this.shift = shift;
|
|
this.alt = alt;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 단축키 조합 문자열을 반환합니다.
|
|
/// </summary>
|
|
/// <returns>표시용 단축키 문자열 (예: "Ctrl+Shift+S")</returns>
|
|
public string GetDisplayString()
|
|
{
|
|
var parts = new System.Collections.Generic.List<string>();
|
|
if (ctrl) parts.Add("Ctrl");
|
|
if (shift) parts.Add("Shift");
|
|
if (alt) parts.Add("Alt");
|
|
parts.Add(key);
|
|
return string.Join("+", parts);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|