diff --git a/Assets/Resources/Settings/NotionSyncSettings.asset b/Assets/Resources/Settings/NotionSyncSettings.asset
new file mode 100644
index 00000000..e31d14e5
--- /dev/null
+++ b/Assets/Resources/Settings/NotionSyncSettings.asset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:18c621c74aaad475f8f0305201cf98e497be47cf7ea066c1e7ce288260168b35
+size 751
diff --git a/Assets/Resources/Settings/NotionSyncSettings.asset.meta b/Assets/Resources/Settings/NotionSyncSettings.asset.meta
new file mode 100644
index 00000000..83de1ff2
--- /dev/null
+++ b/Assets/Resources/Settings/NotionSyncSettings.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: a1b2c3d4e5f6789012345678abcdef01
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Background/BackgroundSceneLoader.cs b/Assets/Scripts/Streamingle/StreamingleControl/Background/BackgroundSceneLoader.cs
index 29a20c0e..1d68f452 100644
--- a/Assets/Scripts/Streamingle/StreamingleControl/Background/BackgroundSceneLoader.cs
+++ b/Assets/Scripts/Streamingle/StreamingleControl/Background/BackgroundSceneLoader.cs
@@ -88,6 +88,102 @@ namespace Streamingle.Background
}
}
+ private void Start()
+ {
+ // 시작 시 현재 상태 동기화
+ SyncCurrentSceneStatus();
+ }
+
+ ///
+ /// 현재 로드된 배경 씬 상태 동기화 (리컴파일/재시작 후 복원용)
+ ///
+ public void SyncCurrentSceneStatus()
+ {
+ if (sceneDatabase == null) return;
+
+ var loadedBackgroundScenes = new List();
+
+ for (int i = 0; i < SceneManager.sceneCount; i++)
+ {
+ var scene = SceneManager.GetSceneAt(i);
+ if (!scene.isLoaded) continue;
+
+ var sceneInfo = sceneDatabase.FindByPath(scene.path);
+ if (sceneInfo != null)
+ {
+ loadedBackgroundScenes.Add(scene);
+ }
+ }
+
+ // 중복 배경 씬 처리 (첫 번째만 유지)
+ if (loadedBackgroundScenes.Count > 1)
+ {
+ UnityEngine.Debug.LogWarning($"[Runtime] 중복 배경 씬 감지: {loadedBackgroundScenes.Count}개. 첫 번째만 유지합니다.");
+
+ for (int i = 1; i < loadedBackgroundScenes.Count; i++)
+ {
+ var duplicateScene = loadedBackgroundScenes[i];
+ UnityEngine.Debug.Log($"[Runtime] 중복 씬 언로드: {duplicateScene.name}");
+ SceneManager.UnloadSceneAsync(duplicateScene);
+ }
+ }
+
+ // 상태 업데이트
+ if (loadedBackgroundScenes.Count > 0)
+ {
+ _currentBackgroundScene = loadedBackgroundScenes[0];
+ UnityEngine.Debug.Log($"[Runtime] 배경 씬 상태 동기화됨: {_currentBackgroundScene.Value.name}");
+ }
+ else
+ {
+ _currentBackgroundScene = null;
+ }
+ }
+
+ ///
+ /// 특정 씬이 이미 로드되어 있는지 확인
+ ///
+ private bool IsSceneAlreadyLoaded(string scenePath)
+ {
+ for (int i = 0; i < SceneManager.sceneCount; i++)
+ {
+ var scene = SceneManager.GetSceneAt(i);
+ if (scene.path == scenePath && scene.isLoaded)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ ///
+ /// 현재 로드된 모든 배경 씬 언로드
+ ///
+ private void UnloadAllBackgroundScenesImmediate()
+ {
+ if (sceneDatabase == null) return;
+
+ var scenesToUnload = new List();
+
+ for (int i = 0; i < SceneManager.sceneCount; i++)
+ {
+ var scene = SceneManager.GetSceneAt(i);
+ if (!scene.isLoaded) continue;
+
+ var sceneInfo = sceneDatabase.FindByPath(scene.path);
+ if (sceneInfo != null)
+ {
+ scenesToUnload.Add(scene);
+ }
+ }
+
+ foreach (var scene in scenesToUnload)
+ {
+ UnityEngine.Debug.Log($"[Runtime] 기존 배경 씬 언로드: {scene.name}");
+ SceneManager.UnloadSceneAsync(scene);
+ }
+ }
+
///
/// 현재 활성 씬의 Directional Light와 NiloToonOverrider 백업 및 비활성화
///
@@ -232,25 +328,53 @@ namespace Streamingle.Background
yield break;
}
+ // 1. 같은 씬이 이미 로드되어 있는지 확인
+ if (IsSceneAlreadyLoaded(sceneInfo.scenePath))
+ {
+ UnityEngine.Debug.Log($"[Runtime] 이미 로드된 씬입니다: {sceneInfo.sceneName}");
+ _currentBackgroundScene = SceneManager.GetSceneByPath(sceneInfo.scenePath);
+ OnSceneLoadCompleted?.Invoke(sceneInfo);
+ yield break;
+ }
+
_isLoading = true;
OnSceneLoadStarted?.Invoke(sceneInfo);
- // 현재 씬 언로드 (새 배경 로드 시에는 라이팅 복원하지 않음)
- if (unloadCurrent && _currentBackgroundScene.HasValue && _currentBackgroundScene.Value.isLoaded)
+ // 2. 기존 모든 배경 씬 언로드 (새 배경 로드 시에는 라이팅 복원하지 않음)
+ if (unloadCurrent)
{
- var unloadOp = SceneManager.UnloadSceneAsync(_currentBackgroundScene.Value);
- if (unloadOp != null)
+ // 모든 배경 씬 찾아서 언로드
+ var scenesToUnload = new List();
+ for (int i = 0; i < SceneManager.sceneCount; i++)
{
- while (!unloadOp.isDone)
+ var scene = SceneManager.GetSceneAt(i);
+ if (!scene.isLoaded) continue;
+
+ var existingSceneInfo = sceneDatabase?.FindByPath(scene.path);
+ if (existingSceneInfo != null)
{
- yield return null;
+ scenesToUnload.Add(scene);
}
}
- OnSceneUnloaded?.Invoke(_currentBackgroundScene.Value.name);
+
+ foreach (var scene in scenesToUnload)
+ {
+ UnityEngine.Debug.Log($"[Runtime] 기존 배경 씬 언로드: {scene.name}");
+ var unloadOp = SceneManager.UnloadSceneAsync(scene);
+ if (unloadOp != null)
+ {
+ while (!unloadOp.isDone)
+ {
+ yield return null;
+ }
+ }
+ OnSceneUnloaded?.Invoke(scene.name);
+ }
+
_currentBackgroundScene = null;
}
- // 새 씬 로드
+ // 3. 새 씬 로드
#if UNITY_EDITOR
// 에디터에서는 EditorSceneManager 사용 (별도 처리 필요)
if (!Application.isPlaying)
diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/BackgroundSceneLoaderWindow.cs b/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/BackgroundSceneLoaderWindow.cs
index 8553fd9e..ff4debfb 100644
--- a/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/BackgroundSceneLoaderWindow.cs
+++ b/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/BackgroundSceneLoaderWindow.cs
@@ -306,6 +306,16 @@ namespace Streamingle.Background.Editor
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
UnityEditor.SceneManagement.EditorSceneManager.sceneOpened += OnSceneOpened;
UnityEditor.SceneManagement.EditorSceneManager.sceneClosed += OnSceneClosed;
+
+ // 리컴파일 후에도 현재 상태 복원
+ UpdateCurrentSceneStatus();
+ }
+
+ private void OnFocus()
+ {
+ // 윈도우가 포커스될 때마다 상태 동기화
+ UpdateCurrentSceneStatus();
+ Repaint();
}
private void OnDisable()
@@ -485,6 +495,33 @@ namespace Streamingle.Background.Editor
_thumbnailCache.Clear();
}
+ ///
+ /// 16:9 (또는 다른 비율) 이미지를 1:1로 중앙 크롭하여 표시
+ ///
+ private void DrawCroppedThumbnail(Rect displayRect, Texture2D texture)
+ {
+ if (texture == null) return;
+
+ float textureAspect = (float)texture.width / texture.height;
+
+ // 정사각형이거나 세로가 더 긴 경우 기존 방식
+ if (textureAspect <= 1.0f)
+ {
+ GUI.DrawTexture(displayRect, texture, ScaleMode.ScaleToFit);
+ return;
+ }
+
+ // 16:9 등 가로가 더 긴 경우: 중앙에서 정사각형으로 크롭
+ // UV 좌표 계산: 세로는 전체, 가로는 중앙 부분만
+ float cropWidth = (float)texture.height / texture.width; // 정사각형 비율
+ float uvOffsetX = (1f - cropWidth) / 2f; // 중앙 정렬
+
+ // texCoords: (x, y, width, height) - UV 공간에서의 영역
+ Rect texCoords = new Rect(uvOffsetX, 0f, cropWidth, 1f);
+
+ GUI.DrawTextureWithTexCoords(displayRect, texture, texCoords);
+ }
+
///
/// 특정 썸네일 캐시 무효화 및 새로고침
///
@@ -869,7 +906,8 @@ namespace Streamingle.Background.Editor
if (thumbnail != null)
{
- GUI.DrawTexture(thumbnailRect, thumbnail, ScaleMode.ScaleToFit);
+ // 16:9 이미지를 1:1로 중앙 크롭하여 표시
+ DrawCroppedThumbnail(thumbnailRect, thumbnail);
}
else
{
@@ -934,7 +972,8 @@ namespace Streamingle.Background.Editor
if (thumbnail != null)
{
- GUI.DrawTexture(thumbRect, thumbnail, ScaleMode.ScaleToFit);
+ // 16:9 이미지를 1:1로 중앙 크롭하여 표시
+ DrawCroppedThumbnail(thumbRect, thumbnail);
}
else
{
@@ -1032,17 +1071,23 @@ namespace Streamingle.Background.Editor
private void LoadSceneEditor(BackgroundSceneInfo sceneInfo, LoadSceneMode mode)
{
- // 기존 배경 씬 언로드 (Additive 모드일 때)
- if (mode == LoadSceneMode.Additive && _loadedBackgroundScene.HasValue)
+ // 1. 같은 씬이 이미 로드되어 있는지 확인
+ if (IsSceneAlreadyLoaded(sceneInfo.scenePath))
{
- if (_loadedBackgroundScene.Value.isLoaded)
- {
- // 자동 복원 처리 (새 배경 로드 시에는 복원하지 않음 - 새 배경 라이팅이 덮어씀)
- EditorSceneManager.CloseScene(_loadedBackgroundScene.Value, true);
- }
+ UnityEngine.Debug.Log($"[BackgroundSceneLoader] 이미 로드된 씬입니다: {sceneInfo.sceneName}");
+ var existingScene = SceneManager.GetSceneByPath(sceneInfo.scenePath);
+ _loadedBackgroundScene = existingScene;
+ _currentSceneName = sceneInfo.sceneName;
+ return;
}
- // 씬 로드
+ // 2. Additive 모드일 때 기존 모든 배경 씬 언로드
+ if (mode == LoadSceneMode.Additive)
+ {
+ UnloadAllBackgroundScenes();
+ }
+
+ // 3. 씬 로드
var openMode = mode == LoadSceneMode.Additive
? OpenSceneMode.Additive
: OpenSceneMode.Single;
@@ -1082,13 +1127,23 @@ namespace Streamingle.Background.Editor
private void LoadSceneRuntime(BackgroundSceneInfo sceneInfo, LoadSceneMode mode)
{
- // 기존 배경 씬 언로드
- if (mode == LoadSceneMode.Additive && _loadedBackgroundScene.HasValue && _loadedBackgroundScene.Value.isLoaded)
+ // 1. 같은 씬이 이미 로드되어 있는지 확인
+ if (IsSceneAlreadyLoaded(sceneInfo.scenePath))
{
- SceneManager.UnloadSceneAsync(_loadedBackgroundScene.Value);
+ UnityEngine.Debug.Log($"[BackgroundSceneLoader] [PlayMode] 이미 로드된 씬입니다: {sceneInfo.sceneName}");
+ var existingScene = SceneManager.GetSceneByPath(sceneInfo.scenePath);
+ _loadedBackgroundScene = existingScene;
+ _currentSceneName = sceneInfo.sceneName;
+ return;
}
- // 라이팅 백업 및 비활성화
+ // 2. Additive 모드일 때 기존 모든 배경 씬 언로드
+ if (mode == LoadSceneMode.Additive)
+ {
+ UnloadAllBackgroundScenes();
+ }
+
+ // 3. 라이팅 백업 및 비활성화
if (mode == LoadSceneMode.Additive && _copyLighting)
{
var activeScene = SceneManager.GetActiveScene();
@@ -1163,21 +1218,109 @@ namespace Streamingle.Background.Editor
private void UpdateCurrentSceneStatus()
{
- // 현재 로드된 배경 씬 확인
+ if (_database == null) return;
+
+ // 현재 로드된 모든 배경 씬 찾기
+ var loadedBackgroundScenes = new List();
+
for (int i = 0; i < SceneManager.sceneCount; i++)
{
var scene = SceneManager.GetSceneAt(i);
- var sceneInfo = _database?.FindByPath(scene.path);
+ if (!scene.isLoaded) continue;
+
+ var sceneInfo = _database.FindByPath(scene.path);
if (sceneInfo != null)
{
- _loadedBackgroundScene = scene;
- _currentSceneName = sceneInfo.sceneName;
- return;
+ loadedBackgroundScenes.Add(scene);
}
}
- _loadedBackgroundScene = null;
- _currentSceneName = "없음";
+ // 배경 씬이 여러 개 로드된 경우 경고 (첫 번째만 유지)
+ if (loadedBackgroundScenes.Count > 1)
+ {
+ UnityEngine.Debug.LogWarning($"[BackgroundSceneLoader] 중복 배경 씬 감지: {loadedBackgroundScenes.Count}개. 첫 번째만 유지합니다.");
+
+ // 첫 번째를 제외하고 모두 언로드
+ for (int i = 1; i < loadedBackgroundScenes.Count; i++)
+ {
+ var duplicateScene = loadedBackgroundScenes[i];
+ UnityEngine.Debug.Log($"[BackgroundSceneLoader] 중복 씬 언로드: {duplicateScene.name}");
+
+ if (Application.isPlaying)
+ {
+ SceneManager.UnloadSceneAsync(duplicateScene);
+ }
+ else
+ {
+ EditorSceneManager.CloseScene(duplicateScene, true);
+ }
+ }
+ }
+
+ // 상태 업데이트
+ if (loadedBackgroundScenes.Count > 0)
+ {
+ var firstScene = loadedBackgroundScenes[0];
+ _loadedBackgroundScene = firstScene;
+ _currentSceneName = _database.FindByPath(firstScene.path)?.sceneName ?? firstScene.name;
+ }
+ else
+ {
+ _loadedBackgroundScene = null;
+ _currentSceneName = "없음";
+ }
+ }
+
+ ///
+ /// 현재 로드된 모든 배경 씬 언로드
+ ///
+ private void UnloadAllBackgroundScenes()
+ {
+ if (_database == null) return;
+
+ var scenesToUnload = new List();
+
+ for (int i = 0; i < SceneManager.sceneCount; i++)
+ {
+ var scene = SceneManager.GetSceneAt(i);
+ if (!scene.isLoaded) continue;
+
+ var sceneInfo = _database.FindByPath(scene.path);
+ if (sceneInfo != null)
+ {
+ scenesToUnload.Add(scene);
+ }
+ }
+
+ foreach (var scene in scenesToUnload)
+ {
+ UnityEngine.Debug.Log($"[BackgroundSceneLoader] 기존 배경 씬 언로드: {scene.name}");
+
+ if (Application.isPlaying)
+ {
+ SceneManager.UnloadSceneAsync(scene);
+ }
+ else
+ {
+ EditorSceneManager.CloseScene(scene, true);
+ }
+ }
+ }
+
+ ///
+ /// 특정 씬이 이미 로드되어 있는지 확인
+ ///
+ private bool IsSceneAlreadyLoaded(string scenePath)
+ {
+ for (int i = 0; i < SceneManager.sceneCount; i++)
+ {
+ var scene = SceneManager.GetSceneAt(i);
+ if (scene.path == scenePath && scene.isLoaded)
+ {
+ return true;
+ }
+ }
+ return false;
}
private void SelectInProject(BackgroundSceneInfo sceneInfo)
diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/BackgroundThumbnailCapture.cs b/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/BackgroundThumbnailCapture.cs
index d2e47efe..5dd1474d 100644
--- a/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/BackgroundThumbnailCapture.cs
+++ b/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/BackgroundThumbnailCapture.cs
@@ -10,12 +10,15 @@ namespace Streamingle.Background.Editor
///
public class BackgroundThumbnailCapture : EditorWindow
{
- private const int DEFAULT_WIDTH = 512;
- private const int DEFAULT_HEIGHT = 512;
+ // 16:9 기본값 (Notion용)
+ private const int DEFAULT_WIDTH = 1920;
+ private const int DEFAULT_HEIGHT = 1080;
private Camera _selectedCamera;
private int _width = DEFAULT_WIDTH;
private int _height = DEFAULT_HEIGHT;
+
+ private NotionSyncSettings _notionSettings;
private string _savePath;
private string _fileName;
private bool _useSceneCamera = true;
@@ -39,6 +42,17 @@ namespace Streamingle.Background.Editor
_database = AssetDatabase.LoadAssetAtPath(
"Assets/Resources/Settings/BackgroundSceneDatabase.asset");
+ // Notion 설정 로드
+ _notionSettings = AssetDatabase.LoadAssetAtPath(
+ "Assets/Resources/Settings/NotionSyncSettings.asset");
+
+ // 설정에서 해상도 가져오기
+ if (_notionSettings != null)
+ {
+ _width = _notionSettings.thumbnailWidth;
+ _height = _notionSettings.thumbnailHeight;
+ }
+
// 현재 로드된 배경 씬 찾기
FindCurrentBackgroundScene();
@@ -319,15 +333,12 @@ namespace Streamingle.Background.Editor
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
- if (GUILayout.Button("256x256")) { _width = 256; _height = 256; }
- if (GUILayout.Button("512x512")) { _width = 512; _height = 512; }
- if (GUILayout.Button("1024x1024")) { _width = 1024; _height = 1024; }
+ if (GUILayout.Button("1920x1080 (권장)")) { _width = 1920; _height = 1080; }
+ if (GUILayout.Button("1280x720")) { _width = 1280; _height = 720; }
+ if (GUILayout.Button("960x540")) { _width = 960; _height = 540; }
EditorGUILayout.EndHorizontal();
- EditorGUILayout.BeginHorizontal();
- if (GUILayout.Button("16:9")) { _width = 1920; _height = 1080; }
- if (GUILayout.Button("4:3")) { _width = 1024; _height = 768; }
- EditorGUILayout.EndHorizontal();
+ EditorGUILayout.HelpBox("16:9 비율 썸네일이 권장됩니다.\n에디터에서는 1:1로 크롭되어 표시되고, Notion에는 16:9 원본이 업로드됩니다.", MessageType.Info);
EditorGUILayout.Space(10);
@@ -569,9 +580,33 @@ namespace Streamingle.Background.Editor
loaderWindow.RefreshThumbnail(assetPath);
}
+ // Notion 자동 동기화
+ if (_notionSettings != null && _notionSettings.autoSyncOnCapture && _currentSceneInfo != null)
+ {
+ SyncToNotionAsync(_currentSceneInfo);
+ }
+
EditorUtility.DisplayDialog("완료", $"썸네일이 저장되었습니다.\n{fullPath}", "확인");
}
+ private async void SyncToNotionAsync(BackgroundSceneInfo sceneInfo)
+ {
+ if (_notionSettings == null || !_notionSettings.IsValid())
+ {
+ UnityEngine.Debug.LogWarning("[Notion] 설정이 올바르지 않아 동기화를 건너뜁니다.");
+ return;
+ }
+
+ var sync = new NotionBackgroundSync(_notionSettings, _database);
+
+ sync.OnProgress += (msg) => UnityEngine.Debug.Log($"[Notion] {msg}");
+ sync.OnError += (msg) => UnityEngine.Debug.LogError($"[Notion] {msg}");
+ sync.OnCompleted += () => UnityEngine.Debug.Log("[Notion] 동기화 완료");
+
+ await sync.SyncSingleToNotion(sceneInfo);
+ sync.Dispose();
+ }
+
private void CaptureAllBackgroundScenes()
{
if (_database == null)
diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionBackgroundSync.cs b/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionBackgroundSync.cs
new file mode 100644
index 00000000..4e708add
--- /dev/null
+++ b/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionBackgroundSync.cs
@@ -0,0 +1,803 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEditor;
+using UnityEngine;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Streamingle.Background.Editor
+{
+ ///
+ /// Notion 배경 씬 동기화 매니저
+ ///
+ public class NotionBackgroundSync
+ {
+ private const string NOTION_API_VERSION = "2022-06-28";
+ private const string NOTION_API_URL = "https://api.notion.com/v1";
+
+ private readonly NotionSyncSettings _settings;
+ private readonly BackgroundSceneDatabase _database;
+ private readonly HttpClient _httpClient;
+
+ public event Action OnProgress;
+ public event Action OnError;
+ public event Action OnCompleted;
+
+ public NotionBackgroundSync(NotionSyncSettings settings, BackgroundSceneDatabase database)
+ {
+ _settings = settings;
+ _database = database;
+ _httpClient = new HttpClient();
+ _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_settings.notionApiToken}");
+ _httpClient.DefaultRequestHeaders.Add("Notion-Version", NOTION_API_VERSION);
+ }
+
+ ///
+ /// 모든 배경 씬을 Notion에 동기화
+ ///
+ public async Task SyncAllToNotion()
+ {
+ if (!_settings.IsValid())
+ {
+ OnError?.Invoke("Notion 설정이 올바르지 않습니다. 설정을 확인하세요.");
+ return;
+ }
+
+ try
+ {
+ OnProgress?.Invoke("데이터베이스 속성 확인 중...");
+
+ // 0. 데이터베이스 속성 확인 및 생성
+ await EnsureDatabaseProperties();
+
+ OnProgress?.Invoke("기존 Notion 항목 조회 중...");
+
+ // 1. 기존 Notion 데이터베이스 항목 조회
+ var existingPages = await GetExistingPages();
+
+ int total = _database.scenes.Count;
+ int current = 0;
+
+ foreach (var sceneInfo in _database.scenes)
+ {
+ current++;
+ OnProgress?.Invoke($"동기화 중: {sceneInfo.sceneName} ({current}/{total})");
+
+ // 기존 페이지가 있는지 확인
+ string existingPageId = FindExistingPage(existingPages, sceneInfo.scenePath);
+
+ if (existingPageId != null)
+ {
+ // 업데이트
+ await UpdatePage(existingPageId, sceneInfo);
+ }
+ else
+ {
+ // 새로 생성
+ await CreatePage(sceneInfo);
+ }
+
+ // API Rate Limit 방지
+ await Task.Delay(350);
+ }
+
+ OnProgress?.Invoke("동기화 완료!");
+ OnCompleted?.Invoke();
+ }
+ catch (Exception ex)
+ {
+ OnError?.Invoke($"동기화 실패: {ex.Message}");
+ UnityEngine.Debug.LogException(ex);
+ }
+ }
+
+ ///
+ /// 단일 배경 씬을 Notion에 동기화
+ ///
+ public async Task SyncSingleToNotion(BackgroundSceneInfo sceneInfo)
+ {
+ if (!_settings.IsValid())
+ {
+ OnError?.Invoke("Notion 설정이 올바르지 않습니다.");
+ return;
+ }
+
+ try
+ {
+ OnProgress?.Invoke("데이터베이스 속성 확인 중...");
+ await EnsureDatabaseProperties();
+
+ OnProgress?.Invoke($"동기화 중: {sceneInfo.sceneName}");
+
+ var existingPages = await GetExistingPages();
+ string existingPageId = FindExistingPage(existingPages, sceneInfo.scenePath);
+
+ if (existingPageId != null)
+ {
+ await UpdatePage(existingPageId, sceneInfo);
+ }
+ else
+ {
+ await CreatePage(sceneInfo);
+ }
+
+ OnProgress?.Invoke("동기화 완료!");
+ OnCompleted?.Invoke();
+ }
+ catch (Exception ex)
+ {
+ OnError?.Invoke($"동기화 실패: {ex.Message}");
+ }
+ }
+
+ ///
+ /// 데이터베이스 속성 확인 및 누락된 속성 생성
+ ///
+ private async Task EnsureDatabaseProperties()
+ {
+ // 데이터베이스 정보 조회
+ var response = await _httpClient.GetAsync($"{NOTION_API_URL}/databases/{_settings.notionDatabaseId}");
+ var responseText = await response.Content.ReadAsStringAsync();
+
+ if (!response.IsSuccessStatusCode)
+ {
+ throw new Exception($"데이터베이스 조회 실패: {responseText}");
+ }
+
+ var dbInfo = JObject.Parse(responseText);
+ var existingProperties = dbInfo["properties"] as JObject;
+
+ // 필요한 속성들 정의
+ var requiredProperties = new Dictionary
+ {
+ // Title 속성은 이미 존재하므로 이름만 확인
+ ["카테고리"] = new JObject { ["select"] = new JObject { ["options"] = new JArray() } },
+ ["씬 경로"] = new JObject { ["rich_text"] = new JObject() },
+ ["폴더 이름"] = new JObject { ["rich_text"] = new JObject() },
+ ["썸네일"] = new JObject { ["url"] = new JObject() },
+ ["마지막 동기화"] = new JObject { ["date"] = new JObject() },
+ ["마지막 사용"] = new JObject { ["date"] = new JObject() }
+ };
+
+ // 누락된 속성 찾기
+ var propertiesToAdd = new JObject();
+ foreach (var prop in requiredProperties)
+ {
+ bool found = false;
+ foreach (var existing in existingProperties)
+ {
+ if (existing.Key == prop.Key)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ propertiesToAdd[prop.Key] = prop.Value;
+ UnityEngine.Debug.Log($"[Notion] 속성 추가 예정: {prop.Key}");
+ }
+ }
+
+ // 누락된 속성이 있으면 추가
+ if (propertiesToAdd.Count > 0)
+ {
+ OnProgress?.Invoke($"누락된 속성 {propertiesToAdd.Count}개 생성 중...");
+
+ var updateBody = new JObject
+ {
+ ["properties"] = propertiesToAdd
+ };
+
+ var content = new StringContent(updateBody.ToString(), Encoding.UTF8, "application/json");
+ var request = new HttpRequestMessage(new HttpMethod("PATCH"), $"{NOTION_API_URL}/databases/{_settings.notionDatabaseId}")
+ {
+ Content = content
+ };
+
+ var updateResponse = await _httpClient.SendAsync(request);
+ var updateResponseText = await updateResponse.Content.ReadAsStringAsync();
+
+ if (!updateResponse.IsSuccessStatusCode)
+ {
+ UnityEngine.Debug.LogWarning($"[Notion] 속성 추가 실패 (무시하고 계속): {updateResponseText}");
+ }
+ else
+ {
+ UnityEngine.Debug.Log($"[Notion] 데이터베이스 속성 {propertiesToAdd.Count}개 추가됨");
+ }
+ }
+
+ // Title 속성 이름 확인 (기본 "이름" 또는 "Name")
+ _titlePropertyName = FindTitlePropertyName(existingProperties);
+ UnityEngine.Debug.Log($"[Notion] Title 속성 이름: {_titlePropertyName}");
+ }
+
+ private string _titlePropertyName = "이름";
+
+ ///
+ /// Title 타입 속성의 이름 찾기
+ ///
+ private string FindTitlePropertyName(JObject properties)
+ {
+ foreach (var prop in properties)
+ {
+ var propType = prop.Value["type"]?.Value();
+ if (propType == "title")
+ {
+ return prop.Key;
+ }
+ }
+ return "이름"; // 기본값
+ }
+
+ ///
+ /// 기존 Notion 페이지 목록 조회
+ ///
+ private async Task> GetExistingPages()
+ {
+ var pages = new List();
+ string cursor = null;
+
+ do
+ {
+ var requestBody = new JObject
+ {
+ ["page_size"] = 100
+ };
+
+ if (cursor != null)
+ {
+ requestBody["start_cursor"] = cursor;
+ }
+
+ var content = new StringContent(requestBody.ToString(), Encoding.UTF8, "application/json");
+ var response = await _httpClient.PostAsync(
+ $"{NOTION_API_URL}/databases/{_settings.notionDatabaseId}/query",
+ content);
+
+ var responseText = await response.Content.ReadAsStringAsync();
+
+ if (!response.IsSuccessStatusCode)
+ {
+ throw new Exception($"Notion API 오류: {responseText}");
+ }
+
+ var result = JObject.Parse(responseText);
+ var results = result["results"] as JArray;
+
+ if (results != null)
+ {
+ foreach (var page in results)
+ {
+ pages.Add(page as JObject);
+ }
+ }
+
+ cursor = result["has_more"]?.Value() == true
+ ? result["next_cursor"]?.Value()
+ : null;
+
+ } while (cursor != null);
+
+ return pages;
+ }
+
+ ///
+ /// 씬 경로로 기존 페이지 찾기
+ ///
+ private string FindExistingPage(List pages, string scenePath)
+ {
+ foreach (var page in pages)
+ {
+ var properties = page["properties"] as JObject;
+ if (properties == null) continue;
+
+ // "씬 경로" 또는 "ScenePath" 속성에서 검색
+ var pathProperty = properties["씬 경로"] ?? properties["ScenePath"];
+ if (pathProperty == null) continue;
+
+ var richText = pathProperty["rich_text"] as JArray;
+ if (richText != null && richText.Count > 0)
+ {
+ var text = richText[0]["plain_text"]?.Value();
+ if (text == scenePath)
+ {
+ return page["id"]?.Value();
+ }
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// 새 페이지 생성
+ ///
+ private async Task CreatePage(BackgroundSceneInfo sceneInfo)
+ {
+ var properties = BuildProperties(sceneInfo);
+
+ var requestBody = new JObject
+ {
+ ["parent"] = new JObject { ["database_id"] = _settings.notionDatabaseId },
+ ["properties"] = properties
+ };
+
+ // 썸네일이 있으면 커버 이미지로 추가
+ if (!string.IsNullOrEmpty(sceneInfo.thumbnailPath))
+ {
+ // Git 커밋 상태 확인
+ bool isCommitted = CheckIfFileIsCommitted(sceneInfo.thumbnailPath);
+ if (!isCommitted)
+ {
+ UnityEngine.Debug.LogWarning($"[Notion] 썸네일이 Git에 커밋되지 않음: {sceneInfo.thumbnailPath}\n" +
+ "Notion에서 이미지가 표시되지 않을 수 있습니다. Git commit & push 후 다시 동기화하세요.");
+ }
+
+ string imageUrl = _settings.GetGitRawUrl(sceneInfo.thumbnailPath);
+ requestBody["cover"] = new JObject
+ {
+ ["type"] = "external",
+ ["external"] = new JObject { ["url"] = imageUrl }
+ };
+ }
+
+ var content = new StringContent(requestBody.ToString(), Encoding.UTF8, "application/json");
+ var response = await _httpClient.PostAsync($"{NOTION_API_URL}/pages", content);
+
+ if (!response.IsSuccessStatusCode)
+ {
+ var responseText = await response.Content.ReadAsStringAsync();
+ throw new Exception($"페이지 생성 실패: {responseText}");
+ }
+
+ UnityEngine.Debug.Log($"[Notion] 페이지 생성됨: {sceneInfo.sceneName}");
+ }
+
+ ///
+ /// 기존 페이지 업데이트
+ ///
+ private async Task UpdatePage(string pageId, BackgroundSceneInfo sceneInfo)
+ {
+ var properties = BuildProperties(sceneInfo);
+
+ var requestBody = new JObject
+ {
+ ["properties"] = properties
+ };
+
+ // 썸네일이 있으면 커버 이미지도 업데이트
+ if (!string.IsNullOrEmpty(sceneInfo.thumbnailPath))
+ {
+ // Git 커밋 상태 확인
+ bool isCommitted = CheckIfFileIsCommitted(sceneInfo.thumbnailPath);
+ if (!isCommitted)
+ {
+ UnityEngine.Debug.LogWarning($"[Notion] 썸네일이 Git에 커밋되지 않음: {sceneInfo.thumbnailPath}\n" +
+ "Notion에서 이미지가 표시되지 않을 수 있습니다. Git commit & push 후 다시 동기화하세요.");
+ }
+
+ string imageUrl = _settings.GetGitRawUrl(sceneInfo.thumbnailPath);
+ requestBody["cover"] = new JObject
+ {
+ ["type"] = "external",
+ ["external"] = new JObject { ["url"] = imageUrl }
+ };
+ }
+
+ var content = new StringContent(requestBody.ToString(), Encoding.UTF8, "application/json");
+ var request = new HttpRequestMessage(new HttpMethod("PATCH"), $"{NOTION_API_URL}/pages/{pageId}")
+ {
+ Content = content
+ };
+
+ var response = await _httpClient.SendAsync(request);
+
+ if (!response.IsSuccessStatusCode)
+ {
+ var responseText = await response.Content.ReadAsStringAsync();
+ throw new Exception($"페이지 업데이트 실패: {responseText}");
+ }
+
+ UnityEngine.Debug.Log($"[Notion] 페이지 업데이트됨: {sceneInfo.sceneName}");
+ }
+
+ ///
+ /// Notion 속성 빌드
+ ///
+ private JObject BuildProperties(BackgroundSceneInfo sceneInfo)
+ {
+ var properties = new JObject();
+
+ // 이름 (Title) - 동적으로 찾은 속성 이름 사용
+ properties[_titlePropertyName] = new JObject
+ {
+ ["title"] = new JArray
+ {
+ new JObject
+ {
+ ["text"] = new JObject { ["content"] = sceneInfo.sceneName }
+ }
+ }
+ };
+
+ // 카테고리 (Select)
+ string category = ExtractCategory(sceneInfo.categoryName);
+ properties["카테고리"] = new JObject
+ {
+ ["select"] = new JObject { ["name"] = category }
+ };
+
+ // 씬 경로 (Rich Text)
+ properties["씬 경로"] = new JObject
+ {
+ ["rich_text"] = new JArray
+ {
+ new JObject
+ {
+ ["text"] = new JObject { ["content"] = sceneInfo.scenePath }
+ }
+ }
+ };
+
+ // 썸네일 URL (URL)
+ if (!string.IsNullOrEmpty(sceneInfo.thumbnailPath))
+ {
+ string imageUrl = _settings.GetGitRawUrl(sceneInfo.thumbnailPath);
+ properties["썸네일"] = new JObject
+ {
+ ["url"] = imageUrl
+ };
+ }
+
+ // 폴더 이름 (Rich Text)
+ properties["폴더 이름"] = new JObject
+ {
+ ["rich_text"] = new JArray
+ {
+ new JObject
+ {
+ ["text"] = new JObject { ["content"] = sceneInfo.categoryName }
+ }
+ }
+ };
+
+ // 마지막 동기화 (Date)
+ properties["마지막 동기화"] = new JObject
+ {
+ ["date"] = new JObject
+ {
+ ["start"] = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss")
+ }
+ };
+
+ return properties;
+ }
+
+ ///
+ /// 카테고리 이름에서 태그 부분 추출 (예: "[공용]농구장" → "공용")
+ ///
+ private string ExtractCategory(string folderName)
+ {
+ if (string.IsNullOrEmpty(folderName)) return "기타";
+
+ int startBracket = folderName.IndexOf('[');
+ int endBracket = folderName.IndexOf(']');
+
+ if (startBracket >= 0 && endBracket > startBracket)
+ {
+ return folderName.Substring(startBracket + 1, endBracket - startBracket - 1);
+ }
+
+ return folderName;
+ }
+
+ ///
+ /// 파일이 Git에 커밋되어 있는지 확인
+ ///
+ /// Unity Asset 경로 (예: Assets/ResourcesData/...)
+ /// 커밋 여부 (true: 커밋됨, false: 커밋 안됨 또는 오류)
+ private bool CheckIfFileIsCommitted(string assetPath)
+ {
+ try
+ {
+ // Unity 프로젝트 루트 경로
+ string projectPath = Path.GetDirectoryName(Application.dataPath);
+ string filePath = Path.Combine(projectPath, assetPath).Replace("\\", "/");
+
+ // 파일이 존재하는지 먼저 확인
+ if (!File.Exists(filePath))
+ {
+ return false;
+ }
+
+ // git ls-files로 파일이 추적되고 있는지 확인
+ var lsFilesProcess = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ FileName = "git",
+ Arguments = $"ls-files \"{assetPath}\"",
+ WorkingDirectory = projectPath,
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ CreateNoWindow = true,
+ StandardOutputEncoding = Encoding.UTF8
+ }
+ };
+
+ lsFilesProcess.Start();
+ string lsFilesOutput = lsFilesProcess.StandardOutput.ReadToEnd().Trim();
+ lsFilesProcess.WaitForExit();
+
+ // 파일이 Git에 추적되고 있지 않으면
+ if (string.IsNullOrEmpty(lsFilesOutput))
+ {
+ return false;
+ }
+
+ // git status로 변경 사항 확인 (커밋되지 않은 변경이 있는지)
+ var statusProcess = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ FileName = "git",
+ Arguments = $"status --porcelain \"{assetPath}\"",
+ WorkingDirectory = projectPath,
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ CreateNoWindow = true,
+ StandardOutputEncoding = Encoding.UTF8
+ }
+ };
+
+ statusProcess.Start();
+ string statusOutput = statusProcess.StandardOutput.ReadToEnd().Trim();
+ statusProcess.WaitForExit();
+
+ // status 출력이 비어있으면 커밋된 상태 (변경 없음)
+ // 출력이 있으면 modified/added/untracked 등의 상태
+ return string.IsNullOrEmpty(statusOutput);
+ }
+ catch (Exception ex)
+ {
+ UnityEngine.Debug.LogWarning($"[Notion] Git 상태 확인 실패: {ex.Message}");
+ return false;
+ }
+ }
+
+ ///
+ /// 사용 이력 기록 (Notion에)
+ ///
+ public async Task RecordUsage(BackgroundSceneInfo sceneInfo)
+ {
+ if (!_settings.trackUsageHistory || !_settings.IsValid()) return;
+
+ try
+ {
+ var existingPages = await GetExistingPages();
+ string pageId = FindExistingPage(existingPages, sceneInfo.scenePath);
+
+ if (pageId != null)
+ {
+ // "마지막 사용" 날짜 업데이트
+ var properties = new JObject
+ {
+ ["마지막 사용"] = new JObject
+ {
+ ["date"] = new JObject
+ {
+ ["start"] = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss")
+ }
+ }
+ };
+
+ var requestBody = new JObject { ["properties"] = properties };
+ var content = new StringContent(requestBody.ToString(), Encoding.UTF8, "application/json");
+ var request = new HttpRequestMessage(new HttpMethod("PATCH"), $"{NOTION_API_URL}/pages/{pageId}")
+ {
+ Content = content
+ };
+
+ await _httpClient.SendAsync(request);
+ UnityEngine.Debug.Log($"[Notion] 사용 이력 기록됨: {sceneInfo.sceneName}");
+ }
+ }
+ catch (Exception ex)
+ {
+ UnityEngine.Debug.LogWarning($"[Notion] 사용 이력 기록 실패: {ex.Message}");
+ }
+ }
+
+ public void Dispose()
+ {
+ _httpClient?.Dispose();
+ }
+ }
+
+ ///
+ /// Notion 동기화 에디터 윈도우
+ ///
+ public class NotionSyncWindow : EditorWindow
+ {
+ private NotionSyncSettings _settings;
+ private BackgroundSceneDatabase _database;
+ private string _statusMessage = "";
+ private bool _isSyncing;
+ private float _progress;
+
+ [MenuItem("Streamingle/Notion Background Sync")]
+ public static void ShowWindow()
+ {
+ var window = GetWindow("Notion 동기화");
+ window.minSize = new Vector2(400, 350);
+ window.Show();
+ }
+
+ private void OnEnable()
+ {
+ LoadSettings();
+ }
+
+ private void LoadSettings()
+ {
+ _settings = AssetDatabase.LoadAssetAtPath(
+ "Assets/Resources/Settings/NotionSyncSettings.asset");
+
+ _database = AssetDatabase.LoadAssetAtPath(
+ "Assets/Resources/Settings/BackgroundSceneDatabase.asset");
+ }
+
+ private void OnGUI()
+ {
+ EditorGUILayout.Space(10);
+ EditorGUILayout.LabelField("Notion 배경 씬 동기화", EditorStyles.boldLabel);
+ EditorGUILayout.Space(10);
+
+ // 설정 확인
+ if (_settings == null)
+ {
+ EditorGUILayout.HelpBox("NotionSyncSettings을 찾을 수 없습니다.\n" +
+ "Assets/Resources/Settings/NotionSyncSettings.asset을 생성하세요.", MessageType.Warning);
+
+ if (GUILayout.Button("설정 파일 생성"))
+ {
+ CreateSettingsFile();
+ }
+ return;
+ }
+
+ // 설정 표시
+ EditorGUILayout.BeginVertical(EditorStyles.helpBox);
+ EditorGUILayout.LabelField("설정", EditorStyles.boldLabel);
+
+ EditorGUI.BeginChangeCheck();
+
+ _settings.notionApiToken = EditorGUILayout.PasswordField("Notion API Token", _settings.notionApiToken);
+ _settings.notionDatabaseId = EditorGUILayout.TextField("Database ID", _settings.notionDatabaseId);
+
+ EditorGUILayout.Space(5);
+
+ _settings.gitServerUrl = EditorGUILayout.TextField("Git Server URL", _settings.gitServerUrl);
+ _settings.gitRepoPath = EditorGUILayout.TextField("Repo Path", _settings.gitRepoPath);
+ _settings.gitBranch = EditorGUILayout.TextField("Branch", _settings.gitBranch);
+
+ EditorGUILayout.Space(5);
+
+ _settings.autoSyncOnCapture = EditorGUILayout.Toggle("썸네일 캡처 시 자동 동기화", _settings.autoSyncOnCapture);
+ _settings.trackUsageHistory = EditorGUILayout.Toggle("사용 이력 추적", _settings.trackUsageHistory);
+
+ if (EditorGUI.EndChangeCheck())
+ {
+ EditorUtility.SetDirty(_settings);
+ AssetDatabase.SaveAssets();
+ }
+
+ EditorGUILayout.EndVertical();
+
+ // 유효성 검사
+ EditorGUILayout.Space(10);
+
+ if (!_settings.HasNotionToken)
+ {
+ EditorGUILayout.HelpBox("Notion API Token이 필요합니다.\n" +
+ "https://www.notion.so/my-integrations 에서 발급하세요.", MessageType.Warning);
+ }
+
+ if (!_settings.HasValidDatabaseId)
+ {
+ EditorGUILayout.HelpBox("Notion Database ID가 필요합니다.\n" +
+ "데이터베이스 URL에서 32자리 ID를 복사하세요.", MessageType.Warning);
+ }
+
+ // Git URL 미리보기
+ EditorGUILayout.Space(10);
+ EditorGUILayout.LabelField("이미지 URL 예시:", EditorStyles.boldLabel);
+ string exampleUrl = _settings.GetGitRawUrl("Assets/ResourcesData/Background/Example/Scene/Example.png");
+ EditorGUILayout.SelectableLabel(exampleUrl, EditorStyles.miniLabel, GUILayout.Height(20));
+
+ // 동기화 버튼
+ EditorGUILayout.Space(20);
+
+ GUI.enabled = _settings.IsValid() && !_isSyncing && _database != null;
+
+ if (GUILayout.Button("전체 동기화", GUILayout.Height(35)))
+ {
+ SyncAll();
+ }
+
+ GUI.enabled = true;
+
+ // 상태 메시지
+ if (!string.IsNullOrEmpty(_statusMessage))
+ {
+ EditorGUILayout.Space(10);
+ EditorGUILayout.HelpBox(_statusMessage, MessageType.Info);
+ }
+
+ // 데이터베이스 정보
+ if (_database != null)
+ {
+ EditorGUILayout.Space(10);
+ EditorGUILayout.LabelField($"배경 씬 수: {_database.scenes.Count}개", EditorStyles.miniLabel);
+ }
+ }
+
+ private void CreateSettingsFile()
+ {
+ string dirPath = "Assets/Resources/Settings";
+ if (!Directory.Exists(dirPath))
+ {
+ Directory.CreateDirectory(dirPath);
+ }
+
+ _settings = ScriptableObject.CreateInstance();
+ AssetDatabase.CreateAsset(_settings, $"{dirPath}/NotionSyncSettings.asset");
+ AssetDatabase.SaveAssets();
+
+ UnityEngine.Debug.Log("NotionSyncSettings 파일이 생성되었습니다.");
+ }
+
+ private async void SyncAll()
+ {
+ _isSyncing = true;
+ _statusMessage = "동기화 시작...";
+ Repaint();
+
+ var sync = new NotionBackgroundSync(_settings, _database);
+
+ sync.OnProgress += (msg) =>
+ {
+ _statusMessage = msg;
+ Repaint();
+ };
+
+ sync.OnError += (msg) =>
+ {
+ _statusMessage = $"오류: {msg}";
+ _isSyncing = false;
+ Repaint();
+ };
+
+ sync.OnCompleted += () =>
+ {
+ _statusMessage = "동기화 완료!";
+ _isSyncing = false;
+ Repaint();
+ };
+
+ await sync.SyncAllToNotion();
+
+ sync.Dispose();
+ }
+ }
+}
diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionBackgroundSync.cs.meta b/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionBackgroundSync.cs.meta
new file mode 100644
index 00000000..865884df
--- /dev/null
+++ b/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionBackgroundSync.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: e384f532f7d5f744083ecba75388fde0
\ No newline at end of file
diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionSyncSettings.cs b/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionSyncSettings.cs
new file mode 100644
index 00000000..c1364ddc
--- /dev/null
+++ b/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionSyncSettings.cs
@@ -0,0 +1,76 @@
+using UnityEngine;
+
+namespace Streamingle.Background.Editor
+{
+ ///
+ /// Notion 동기화 설정을 저장하는 ScriptableObject
+ ///
+ [CreateAssetMenu(fileName = "NotionSyncSettings", menuName = "Streamingle/Notion Sync Settings")]
+ public class NotionSyncSettings : ScriptableObject
+ {
+ [Header("Notion API 설정")]
+ [Tooltip("Notion Integration Token (ntn_xxx... 또는 secret_xxx...)")]
+ public string notionApiToken = "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";
+
+ [Tooltip("Notion Database ID (32자리 hex) - 배경 일람")]
+ public string notionDatabaseId = "25ea7d46b958805f88fcf127979934bf";
+
+ [Header("Git 설정")]
+ [Tooltip("Git 서버 URL (예: https://kindnick-git.duckdns.org)")]
+ public string gitServerUrl = "https://kindnick-git.duckdns.org";
+
+ [Tooltip("Git 리포지토리 경로 (예: kindnick/Streamingle_URP)")]
+ public string gitRepoPath = "kindnick/Streamingle_URP";
+
+ [Tooltip("Git 브랜치 (예: main)")]
+ public string gitBranch = "main";
+
+ [Header("동기화 설정")]
+ [Tooltip("썸네일 캡처 시 자동 동기화")]
+ public bool autoSyncOnCapture = false;
+
+ [Tooltip("배경 씬 로드 시 사용 이력 기록")]
+ public bool trackUsageHistory = true;
+
+ [Header("썸네일 설정")]
+ [Tooltip("썸네일 너비")]
+ public int thumbnailWidth = 1920;
+
+ [Tooltip("썸네일 높이 (16:9 비율)")]
+ public int thumbnailHeight = 1080;
+
+ ///
+ /// Git Raw 파일 URL 생성 (Gitea/Gogs 형식)
+ ///
+ public string GetGitRawUrl(string assetPath)
+ {
+ // Assets/ResourcesData/Background/... 형식의 경로를 Git Raw URL로 변환
+ // Gitea Raw URL 형식: {serverUrl}/{repoPath}/raw/branch/{branch}/{filePath}
+ string relativePath = assetPath.Replace("\\", "/");
+
+ return $"{gitServerUrl}/{gitRepoPath}/raw/branch/{gitBranch}/{relativePath}";
+ }
+
+ ///
+ /// 설정이 유효한지 확인
+ ///
+ public bool IsValid()
+ {
+ return !string.IsNullOrEmpty(notionApiToken) &&
+ !string.IsNullOrEmpty(notionDatabaseId) &&
+ !string.IsNullOrEmpty(gitServerUrl) &&
+ !string.IsNullOrEmpty(gitRepoPath);
+ }
+
+ ///
+ /// Notion API Token이 설정되었는지 확인
+ ///
+ public bool HasNotionToken => !string.IsNullOrEmpty(notionApiToken) &&
+ (notionApiToken.StartsWith("secret_") || notionApiToken.StartsWith("ntn_"));
+
+ ///
+ /// Database ID가 유효한 형식인지 확인
+ ///
+ public bool HasValidDatabaseId => !string.IsNullOrEmpty(notionDatabaseId) && notionDatabaseId.Length >= 32;
+ }
+}
diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionSyncSettings.cs.meta b/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionSyncSettings.cs.meta
new file mode 100644
index 00000000..c140b1fc
--- /dev/null
+++ b/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionSyncSettings.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 0d02f18044447044a927d2258edc4286
\ No newline at end of file