diff --git a/.gitignore b/.gitignore index 485374c..fab0a65 100644 --- a/.gitignore +++ b/.gitignore @@ -119,3 +119,5 @@ InitTestScene*.unity* # 특정 플러그인 /Assets/Plugins/FiveMinuteChat /Assets/Plugins/WebGLTemplates +/Assets/Domain/Character/Model/Chikuwa/ziraitikuwa +/Assets/Domain/Character/Model/Chikuwa/model diff --git a/Assets/App/Scenes/MainSence.unity b/Assets/App/Scenes/MainScene.unity similarity index 97% rename from Assets/App/Scenes/MainSence.unity rename to Assets/App/Scenes/MainScene.unity index 3f2933f..c64d012 100644 --- a/Assets/App/Scenes/MainSence.unity +++ b/Assets/App/Scenes/MainScene.unity @@ -271,7 +271,7 @@ Camera: far clip plane: 1000 field of view: 60 orthographic: 1 - orthographic size: 5 + orthographic size: 1 m_Depth: -1 m_CullingMask: serializedVersion: 2 @@ -378,12 +378,6 @@ MonoBehaviour: m_EditorClassIdentifier: _btnVoice: {fileID: 1411584568} _btnVoiceStop: {fileID: 948060135} - _txtVoiceStatus: {fileID: 1273342020} - _progressBar: {fileID: 0} - _maxRecordingTime: 30 - _voiceStatusRecording: "\uB179\uC74C \uC911..." - _voiceStatusProcessing: "\uC74C\uC131\uC744 \uD14D\uC2A4\uD2B8\uB85C \uBCC0\uD658 - \uC911..." --- !u!114 &332900995 MonoBehaviour: m_ObjectHideFlags: 0 @@ -531,7 +525,7 @@ AudioSource: m_GameObject: {fileID: 622824876} m_Enabled: 1 serializedVersion: 4 - OutputAudioMixerGroup: {fileID: 0} + OutputAudioMixerGroup: {fileID: 24300002, guid: 3bf46f7daa3260847aa1b964ad2b01a7, type: 2} m_audioClip: {fileID: 0} m_Resource: {fileID: 0} m_PlayOnAwake: 1 @@ -714,10 +708,9 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: _chatBubblePanel: {fileID: 2017944365} + _characterManager: {fileID: 873552998} _characterId: 44444444-4444-4444-4444-444444444444 _userId: bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb - _enableMessageQueue: 1 - _maxQueueSize: 100 --- !u!4 &829067253 Transform: m_ObjectHideFlags: 0 @@ -733,6 +726,52 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &873552997 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 873552999} + - component: {fileID: 873552998} + m_Layer: 0 + m_Name: CharacterManager + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &873552998 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 873552997} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 6ae88dd8c9dcaab448ad7d8834e80f8e, type: 3} + m_Name: + m_EditorClassIdentifier: + _modelTransform: {fileID: 873552999} + _modelRegistry: {fileID: 11400000, guid: cf68d9632ab0d2c42a02de12beecadff, type: 2} +--- !u!4 &873552999 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 873552997} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &920517454 GameObject: m_ObjectHideFlags: 0 @@ -1160,17 +1199,6 @@ MonoBehaviour: _audioSource: {fileID: 0} _audioMixerGroup: {fileID: 0} _poolSize: 10 ---- !u!114 &1273342020 stripped -MonoBehaviour: - m_CorrespondingSourceObject: {fileID: 8225492411684342768, guid: 4e9594e3adccfc04793a3b9f79181ebd, type: 3} - m_PrefabInstance: {fileID: 2142640635} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} - m_Name: - m_EditorClassIdentifier: --- !u!224 &1274474669 stripped RectTransform: m_CorrespondingSourceObject: {fileID: 8165641762294667086, guid: cd9756d05ff7ef847b3abde3e768a611, type: 3} @@ -1553,3 +1581,4 @@ SceneRoots: - {fileID: 133449156} - {fileID: 332900996} - {fileID: 622824879} + - {fileID: 873552999} diff --git a/Assets/App/Scenes/MainSence.unity.meta b/Assets/App/Scenes/MainScene.unity.meta similarity index 100% rename from Assets/App/Scenes/MainSence.unity.meta rename to Assets/App/Scenes/MainScene.unity.meta diff --git a/Assets/Core/Audio/AudioManager.cs b/Assets/Core/Audio/AudioManager.cs index f617ed3..b3446c3 100644 --- a/Assets/Core/Audio/AudioManager.cs +++ b/Assets/Core/Audio/AudioManager.cs @@ -44,6 +44,7 @@ public class AudioManager : Singleton public event Action? OnSfxVolumeChanged; public event Action? OnUiVolumeChanged; public event Action? OnVoiceVolumeChanged; + public event Action? OnInitialized; #region Unity Lifecycle @@ -76,6 +77,7 @@ public void Initialize() LoadVolumeSettings(); _isInitialized = true; + OnInitialized?.Invoke(); Debug.Log("[AudioManager] 초기화 완료"); } catch (Exception ex) @@ -249,6 +251,11 @@ public int GetActiveUICount() return 0; } + public VoiceController? GetVoiceController() + { + return _voiceController; + } + #endregion #region Private Methods diff --git a/Assets/Core/Audio/AudioRecorder.cs b/Assets/Core/Audio/AudioRecorder.cs index bb40579..2f65c7e 100644 --- a/Assets/Core/Audio/AudioRecorder.cs +++ b/Assets/Core/Audio/AudioRecorder.cs @@ -98,7 +98,14 @@ public bool StartRecording() _isRecording = true; _recordingStartTime = Time.time; - _recordingClip = Microphone.Start(_currentDevice != null ? _currentDevice : string.Empty, false, _maxRecordingLength, _sampleRate); + _recordingClip = Microphone.Start(_currentDevice ?? string.Empty, false, _maxRecordingLength, _sampleRate); + if (_recordingClip == null) + { + _isRecording = false; + Debug.LogError("[AudioRecorder] 마이크 시작 실패: 반환된 AudioClip이 null입니다."); + OnError?.Invoke("마이크 시작 실패"); + return false; + } Debug.Log($"[AudioRecorder] 음성 녹음 시작됨 (최대 {_maxRecordingLength}초, {_sampleRate}Hz)"); OnRecordingStarted?.Invoke(); @@ -136,7 +143,7 @@ public bool StartRecording() if (_recordingClip != null) { - AudioClip processedClip = ProcessRecordingClip(actualRecordingDuration); + AudioClip? processedClip = ProcessRecordingClip(actualRecordingDuration); if (processedClip != null) { Debug.Log($"[AudioRecorder] 음성 녹음 완료됨 ({actualRecordingDuration:F1}초, {processedClip.samples} 샘플)"); @@ -156,6 +163,11 @@ public bool StartRecording() _isRecording = false; return null; } + finally + { + // 중복 호출 방지를 위해 성공 분기에서 이미 호출했다면 옵저버 측에서 idempotent 처리 가정 + OnRecordingStopped?.Invoke(); + } } /// @@ -316,7 +328,7 @@ private void InitializeMicrophone() // 원본 AudioClip 정리하여 메모리 누수 방지 if (_recordingClip != null) { - DestroyImmediate(_recordingClip); + Destroy(_recordingClip); } _recordingClip = processedClip; diff --git a/Assets/Core/Audio/NewAudioMixer.mixer b/Assets/Core/Audio/NewAudioMixer.mixer index d1bfeb0..c99b10a 100644 --- a/Assets/Core/Audio/NewAudioMixer.mixer +++ b/Assets/Core/Audio/NewAudioMixer.mixer @@ -6,7 +6,7 @@ AudioMixerGroupController: m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_Name: SFX + m_Name: SFXVolume m_AudioMixer: {fileID: 24100000} m_GroupID: 603178d5b1e127d429609f70b6dfaf79 m_Children: [] @@ -128,7 +128,7 @@ AudioMixerGroupController: m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_Name: BGM + m_Name: BGMVolume m_AudioMixer: {fileID: 24100000} m_GroupID: 1bf7479df396fed488843a11e4f16671 m_Children: [] @@ -161,7 +161,7 @@ AudioMixerGroupController: m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_Name: UI + m_Name: UIVolume m_AudioMixer: {fileID: 24100000} m_GroupID: 323361331de8fbf46b26000797bc1794 m_Children: [] @@ -180,7 +180,7 @@ AudioMixerGroupController: m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_Name: Voice + m_Name: VoiceVolume m_AudioMixer: {fileID: 24100000} m_GroupID: 1c2c7c658d2c3e647adee42f03d62f41 m_Children: [] diff --git a/Assets/Core/Managers/SystemManager.cs b/Assets/Core/Managers/SystemManager.cs index 2d76b8c..b33cc2c 100644 --- a/Assets/Core/Managers/SystemManager.cs +++ b/Assets/Core/Managers/SystemManager.cs @@ -20,7 +20,7 @@ public class SystemManager : Singleton [SerializeField] private HttpApiClient? _httpApiClient; [SerializeField] private AudioManager? _audioManager; [SerializeField] private LoadingManager? _loadingManager; - [SerializeField] private ChatManager? _chatManager; + [SerializeField] private ChatSystemManager? _chatManager; [Header("Settings")] @@ -211,7 +211,7 @@ public async UniTask TransitionToMainSceneAsync() { try { - await UnityEngine.SceneManagement.SceneManager.LoadSceneAsync("MainSence"); + await UnityEngine.SceneManagement.SceneManager.LoadSceneAsync("MainScene"); Debug.Log("[SystemManager] MainScene 전환 완료"); } catch (System.Exception ex) diff --git a/Assets/Docs/Guides/Live2D_Resolution_Scaling_Guide.md b/Assets/Docs/Guides/Live2D_Resolution_Scaling_Guide.md new file mode 100644 index 0000000..f00c150 --- /dev/null +++ b/Assets/Docs/Guides/Live2D_Resolution_Scaling_Guide.md @@ -0,0 +1,225 @@ +# Live2D 모델 해상도별 크기 조정 시스템 가이드 + +## 개요 + +이 시스템은 다양한 해상도와 디바이스에서 Live2D 모델의 크기를 자동으로 조정하여 일관된 사용자 경험을 제공합니다. + +## 주요 구성 요소 + +### 1. ResolutionModelScaleConfig +- 해상도별 스케일 규칙을 정의하는 ScriptableObject +- 자동 스케일 계산 로직 포함 +- PC/모바일 구분 지원 + +### 2. Live2DModelScaler +- 개별 Live2D 모델에 스케일을 적용하는 컴포넌트 +- 해상도 변경 시 자동 스케일 조정 +- 원본 크기 보존 및 복원 기능 + +### 3. ResolutionManager +- 전체 시스템을 관리하는 싱글톤 매니저 +- 모든 모델 스케일러를 중앙에서 관리 +- 해상도 변경 감지 및 자동 적용 + +## 사용 방법 + +### 1. 기본 설정 + +1. **ResolutionModelScaleConfig 생성** + ``` + Assets > Create > ProjectVG > Character > Resolution Model Scale Config + ``` + +2. **기본 설정 구성** + - Reference Resolution: 기준 해상도 (예: 1920x1080) + - Reference Scale: 기준 스케일 (예: 1.0) + - Scale Mode: 스케일 계산 방식 선택 + +### 2. 해상도별 규칙 설정 + +```csharp +// 기본 규칙들 (모바일 기준 최적화) +- 모바일 기본: 0.85x 스케일 +- 모바일 세로 (작은 화면): 0.7x 스케일 +- 모바일 가로/태블릿 세로: 0.8x 스케일 +- 태블릿 가로: 0.9x 스케일 +- PC FHD (1920x1080): 1.1x 스케일 +- PC QHD 이상: 1.3x 스케일 +``` + +### 3. 자동 적용 + +1. **CharacterFacade 사용 시** + - 모델 스케일러가 자동으로 추가됨 + - 별도 설정 불필요 + +2. **수동 적용** + ```csharp + // Live2DModelScaler 컴포넌트 추가 + var scaler = modelObject.AddComponent(); + scaler.ApplyScale(); + ``` + +## 해상도별 상세 설정 + +### 모바일 환경 +- **기준 해상도**: 1080x1920 (세로 모드) +- **모바일 기본**: 0.85x 스케일 (대부분의 모바일 기기) +- **작은 화면**: 0.7x 스케일 (480px 이하 너비) +- **가로 모드**: 0.8x 스케일 (481-768px 너비) +- **고해상도**: 0.9x 스케일 (1440px 이상 너비, 2960px 이상 높이) + +### 태블릿 환경 +- **태블릿 세로**: 0.8x 스케일 (481-768px 너비) +- **태블릿 가로**: 0.9x 스케일 (769-1024px 너비) + +### PC 환경 +- **PC FHD**: 1.1x 스케일 (1025-1920px 너비, 1080px 이하 높이) +- **PC QHD+**: 1.3x 스케일 (1921px 이상 너비, 1081px 이상 높이) + +## 스케일 계산 방식 + +### 1. HeightBased +- 화면 높이를 기준으로 스케일 계산 +- 세로 모드에 적합 + +### 2. WidthBased +- 화면 너비를 기준으로 스케일 계산 +- 가로 모드에 적합 + +### 3. AspectRatioBased (권장) +- 종횡비를 고려한 스케일 계산 +- 다양한 화면 비율에 대응 +- 모바일과 PC 환경 모두에 최적화 + +## 설정 예시 + +### 모바일 환경 (기준) +```yaml +Reference Resolution: 1080x1920 +Reference Scale: 1.0 +Scale Mode: AspectRatioBased +Min Scale: 0.6 +Max Scale: 1.5 +``` + +### PC 환경 +```yaml +Reference Resolution: 1920x1080 +Reference Scale: 1.1 +Scale Mode: AspectRatioBased +Min Scale: 0.6 +Max Scale: 1.5 +``` + +## 디버깅 + +### 1. 디버그 정보 활성화 +```csharp +// ResolutionManager에서 +showDebugInfo = true; + +// Live2DModelScaler에서 +showDebugInfo = true; +``` + +### 2. 현재 상태 확인 +```csharp +var info = ResolutionManager.Instance.GetCurrentResolutionInfo(); +Debug.Log($"해상도: {info.Resolution}, 스케일: {info.Scale}"); +``` + +## 고급 기능 + +### 1. 수동 스케일 설정 +```csharp +var scaler = GetComponent(); +scaler.SetManualScale(1.5f); +``` + +### 2. 원본 크기 복원 +```csharp +scaler.ResetToOriginalScale(); +``` + +### 3. 동적 규칙 추가 +```csharp +// 런타임에 규칙 추가 가능 +var config = Resources.Load("ResolutionModelScaleConfig"); +// 규칙 수정 후 적용 +``` + +## 주의사항 + +1. **Resources 폴더** + - ResolutionModelScaleConfig는 반드시 Resources 폴더에 위치해야 함 + - 기본 경로: `Assets/Resources/ResolutionModelScaleConfig.asset` + +2. **성능 고려** + - 해상도 변경 감지는 Update에서 수행 + - 불필요한 경우 `applyOnResolutionChange = false` 설정 + +3. **스케일 범위** + - Min/Max Scale 범위를 적절히 설정 + - 너무 극단적인 값은 피하기 + +## 실제 사용 예시 + +### 일반적인 해상도별 결과 +```csharp +// iPhone SE (375x667) - 모바일 세로 (작은 화면) +// 스케일: 0.7x + +// iPhone 12 (390x844) - 모바일 기본 +// 스케일: 0.85x + +// 모바일 고해상도 (1440x2960) - 모바일 고해상도 +// 스케일: 0.9x + +// iPad (768x1024) - 태블릿 세로 +// 스케일: 0.8x + +// iPad 가로 (1024x768) - 태블릿 가로 +// 스케일: 0.9x + +// PC FHD (1920x1080) - PC FHD +// 스케일: 1.1x + +// PC QHD (2560x1440) - PC QHD+ +// 스케일: 1.3x +``` + +### 디버그 정보 확인 +```csharp +// 현재 해상도 정보 출력 +var info = ResolutionManager.Instance.GetCurrentResolutionInfo(); +Debug.Log($"현재 해상도: {info.Resolution}"); +Debug.Log($"적용된 스케일: {info.Scale:F2}"); +Debug.Log($"종횡비: {info.AspectRatio:F2}"); +Debug.Log($"모바일 여부: {info.IsMobile}"); +``` + +## 문제 해결 + +### 1. 스케일이 적용되지 않는 경우 +- ResolutionModelScaleConfig가 Resources 폴더에 있는지 확인 +- Live2DModelScaler 컴포넌트가 추가되었는지 확인 +- 디버그 정보를 활성화하여 로그 확인 + +### 2. 스케일이 예상과 다른 경우 +- Reference Resolution과 Reference Scale 설정 확인 +- Scale Mode 설정 확인 +- 해상도별 규칙 우선순위 확인 + +### 3. 성능 문제 +- `autoDetectScalers = false` 설정 +- 필요한 경우에만 수동으로 스케일러 등록 + +### 4. 특정 해상도에서 문제가 있는 경우 +```csharp +// 특정 해상도에 대한 스케일 확인 +var config = Resources.Load("ResolutionModelScaleConfig"); +var testResolution = new Vector2(1920, 1080); +var scale = config.CalculateScaleForResolution(testResolution); +Debug.Log($"1920x1080에서의 스케일: {scale:F2}"); +``` diff --git a/Assets/Resources/Character.meta b/Assets/Docs/Guides/Live2D_Resolution_Scaling_Guide.md.meta similarity index 57% rename from Assets/Resources/Character.meta rename to Assets/Docs/Guides/Live2D_Resolution_Scaling_Guide.md.meta index 892336f..fd60952 100644 --- a/Assets/Resources/Character.meta +++ b/Assets/Docs/Guides/Live2D_Resolution_Scaling_Guide.md.meta @@ -1,7 +1,6 @@ fileFormatVersion: 2 -guid: 33c3ee053842bbb4a8f0c00ad6fbf92a -folderAsset: yes -DefaultImporter: +guid: 0c70642f3f9f98841a9d5145ecf5a822 +TextScriptImporter: externalObjects: {} userData: assetBundleName: diff --git a/Assets/Docs/ProjectVG_Structure_Guide.md b/Assets/Docs/ProjectVG_Structure_Guide.md new file mode 100644 index 0000000..7f0b643 --- /dev/null +++ b/Assets/Docs/ProjectVG_Structure_Guide.md @@ -0,0 +1,100 @@ + +# 📁 ProjectVG Unity 클라이언트 구조 가이드 + +이 문서는 `Assets/` 디렉토리 기준의 Unity 클라이언트 프로젝트 구조 및 디렉토리 용도/명명 규칙을 설명합니다. + +--- + +## 🗂 전체 구조 + +``` +Assets/ +├── App/ # 진입점, 전체 앱 설정 +│ ├── Scenes/ # 메인 씬, 로딩 씬 등 +│ └── App.cs # 앱 초기화/엔트리 진입 + +├── Core/ # 전역 공통 기능 +│ ├── Input/ # 입력 처리 (Touch, Mouse, Key 등) +│ ├── Audio/ # BGM, SFX, 오디오 매니저 +│ ├── Localization/ # 다국어 지원 +│ ├── Time/ # 시간 유틸 (타이머, 딜레이 등) +│ ├── Extensions/ # Unity, LINQ, System 확장 메서드 +│ └── Utils/ # 범용 유틸리티 클래스 + +├── Infrastructure/ # 외부 서비스, 저장소, 네트워크 +│ ├── Data/ # 로컬/클라우드 저장소 (SaveData, PlayerPrefs) +│ ├── Network/ # 서버 API 호출, Response DTO +│ └── Bridge/ # Native, 외부 SDK 연동 (예: Android, iOS 기능) + +├── Domain/ # 도메인(기능)별 묶음 +│ ├── Character/ +│ │ ├── Model/ +│ │ ├── View/ +│ │ ├── Script/ +│ │ └── Animation/ +│ ├── Chat/ +│ │ ├── Script/ +│ │ ├── View/ +│ │ └── Model/ +│ ├── Popup/ +│ │ └── System/ # 팝업 매니저, 팝업 큐 +│ │ └── Instances/ # 실제 팝업 프리팹들 +│ └── System/ # 게임 로직 컨트롤러, FSM 등 (선택) + +├── UI/ # UI 공통 요소 +│ ├── Prefabs/ # 공용 UI 프리팹 (버튼, 아이콘, 툴팁 등) +│ ├── Panels/ # HUD, 메인패널 등 +│ ├── Scripts/ # UI 상호작용 스크립트 +│ ├── Transitions/ # UI 전환 효과 (페이드, 이동 등) +│ └── Fonts/ # UI 폰트 + +├── Resources/ # Resources.Load() 로드 대상 +│ ├── Configs/ # Json 기반 설정파일 +│ └── AddressablesDummy/ # Addressable 사용 안 할 때 대체 + +├── Addressables/ # 어드레서블 관리용 분리 리소스 (선택) +│ ├── UI/ +│ ├── Characters/ +│ └── Scenes/ + +├── Plugins/ # 외부 라이브러리/Live2D SDK +│ └── Live2D/ +│ └── CubismSdkForUnity/ + +├── Editor/ # 커스텀 에디터 코드 (.cs만 가능) +│ └── Inspectors/ +│ └── PropertyDrawers/ +│ └── MenuItems/ + +├── Tests/ # 테스트 +│ ├── Editor/ # Editor Test +│ └── Runtime/ # PlayMode Test, 유닛 테스트 + +├── Art/ # 디자인 원본 (PSD, AI 등) - 버전 관리 제외 가능 + +└── Docs/ # 문서 (설계, 흐름도, README 등) +``` + +--- + +## 📌 네이밍 가이드 요약 + +- 디렉토리 및 클래스명: **PascalCase** +- 변수 및 메서드: **camelCase** +- JSON 및 설정파일: **snake_case** +- Addressable 키: `/` 구분, 예: `UI/PanelChat` +- UI 오브젝트 접두어: `Panel`, `Btn`, `Txt`, `Img`, `Group` 등 + +--- + +## ✅ 활용 예시 + +- `Domain/Character/Model/` → Live2D `.moc3`, `.json`, 모션 설정 +- `Core/Audio/AudioManager.cs` → SFX, BGM 전역 제어 +- `Infrastructure/Network/ApiClient.cs` → REST API 통신 처리 +- `UI/Panels/PanelMain.prefab` → 메인 화면 UI +- `Tests/Runtime/CharacterMotionTest.cs` → 캐릭터 모션 유닛 테스트 + +--- + +> 이 문서는 신규 팀원이 구조를 빠르게 이해하고 규칙대로 작업할 수 있도록 작성된 **구조 및 명명 가이드라인**입니다. diff --git a/Assets/Docs/ProjectVG_Structure_Guide.md.meta b/Assets/Docs/ProjectVG_Structure_Guide.md.meta new file mode 100644 index 0000000..446b47b --- /dev/null +++ b/Assets/Docs/ProjectVG_Structure_Guide.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 04397c9c761edea4a83a995e11537cd9 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Domain/Character/Script/Controller.meta b/Assets/Domain/Character/Model/Chikuwa.meta similarity index 77% rename from Assets/Domain/Character/Script/Controller.meta rename to Assets/Domain/Character/Model/Chikuwa.meta index 830fe1c..9b5cda9 100644 --- a/Assets/Domain/Character/Script/Controller.meta +++ b/Assets/Domain/Character/Model/Chikuwa.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 37c695efe237c2a47888f4ce37fa681e +guid: c4265468216d110429e94c574b954da1 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/Domain/Character/Model/Chikuwa/Character-Zero.asset b/Assets/Domain/Character/Model/Chikuwa/Character-Zero.asset new file mode 100644 index 0000000..d5b8bda --- /dev/null +++ b/Assets/Domain/Character/Model/Chikuwa/Character-Zero.asset @@ -0,0 +1,41 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f22d39f6e7c71524d911cb4b658d7fd9, type: 3} + m_Name: Character-Zero + m_EditorClassIdentifier: + characterId: zero + characterName: "\uC81C\uB85C" + characterPrefab: {fileID: 8051974178353762967, guid: 9be43d9f8e24d634186c522ebce74618, type: 3} + thumbnail: {fileID: 2800000, guid: e029dae0a0b46124ba7b0b6b2ac00a76, type: 3} + characterDescription: "\uCE58\uC640\uC9F1 \uBAA8\uB378 \uAE30\uBC18" + emotionMappings: + - emotionKey: + expressionName: + defaultIntensity: 0 + defaultDurationMs: 0 + actionMappings: + - actionKey: + motionGroup: + motionName: + isLockAtActive: 0 + lookSensitivity: 1 + lockAtDamping: 0 + useLipSync: 1 + gain: 10 + smoothing: 1 + useAutoEyeBlink: 1 + eyeBlinkMean: 2.5 + eyeBlinkMaximumDeviation: 2 + eyeBlinkTimescale: 10 + eyeBlinkClosingSeconds: 1 + eyeBlinkClosedSeconds: 0.5 + eyeBlinkOpeningSeconds: 1.5 diff --git a/Assets/Resources/Character/Model/CharacterZero.asset.meta b/Assets/Domain/Character/Model/Chikuwa/Character-Zero.asset.meta similarity index 79% rename from Assets/Resources/Character/Model/CharacterZero.asset.meta rename to Assets/Domain/Character/Model/Chikuwa/Character-Zero.asset.meta index 9d69fe5..14845cd 100644 --- a/Assets/Resources/Character/Model/CharacterZero.asset.meta +++ b/Assets/Domain/Character/Model/Chikuwa/Character-Zero.asset.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 4c6d1f5cb9556f24c843f3e9fe14d49e +guid: 58c40b616ae7d944c91fbc00193d5d3b NativeFormatImporter: externalObjects: {} mainObjectFileID: 11400000 diff --git a/Assets/Domain/Character/Script/Manager.meta b/Assets/Domain/Character/Model/Chikuwa/model.meta similarity index 77% rename from Assets/Domain/Character/Script/Manager.meta rename to Assets/Domain/Character/Model/Chikuwa/model.meta index d2debce..a05dce7 100644 --- a/Assets/Domain/Character/Script/Manager.meta +++ b/Assets/Domain/Character/Model/Chikuwa/model.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: a07cad638f33a9e448551011b2f085bf +guid: 2fc0b961763d08c47b49035b4dfb295d folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/Domain/Character/Model/Model.fadeMotionList.asset b/Assets/Domain/Character/Model/Model.fadeMotionList.asset new file mode 100644 index 0000000..798e9bd --- /dev/null +++ b/Assets/Domain/Character/Model/Model.fadeMotionList.asset @@ -0,0 +1,17 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 403ae2dd693bb1d4b924f6b8d206b053, type: 3} + m_Name: Model.fadeMotionList + m_EditorClassIdentifier: + MotionInstanceIds: + CubismFadeMotionObjects: + - {fileID: 0} diff --git a/Assets/Voice.mixer.meta b/Assets/Domain/Character/Model/Model.fadeMotionList.asset.meta similarity index 64% rename from Assets/Voice.mixer.meta rename to Assets/Domain/Character/Model/Model.fadeMotionList.asset.meta index 3f7bc74..9b3ec5d 100644 --- a/Assets/Voice.mixer.meta +++ b/Assets/Domain/Character/Model/Model.fadeMotionList.asset.meta @@ -1,8 +1,8 @@ fileFormatVersion: 2 -guid: 3bf46f7daa3260847aa1b964ad2b01a7 +guid: b1083908ab87d904a95806ad80fc75eb NativeFormatImporter: externalObjects: {} - mainObjectFileID: 24100000 + mainObjectFileID: 11400000 userData: assetBundleName: assetBundleVariant: diff --git a/Assets/Domain/Character/Script/Test.meta b/Assets/Domain/Character/Model/Sample.meta similarity index 77% rename from Assets/Domain/Character/Script/Test.meta rename to Assets/Domain/Character/Model/Sample.meta index 793641e..6c9c9f0 100644 --- a/Assets/Domain/Character/Script/Test.meta +++ b/Assets/Domain/Character/Model/Sample.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: c762a71a10e06714a96c237780ce3100 +guid: 58240e09a72cdad44921af9fe40f68bc folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/Domain/Character/Model/natoriConfig.asset b/Assets/Domain/Character/Model/Sample/natoriConfig.asset similarity index 100% rename from Assets/Domain/Character/Model/natoriConfig.asset rename to Assets/Domain/Character/Model/Sample/natoriConfig.asset diff --git a/Assets/Domain/Character/Model/natoriConfig.asset.meta b/Assets/Domain/Character/Model/Sample/natoriConfig.asset.meta similarity index 100% rename from Assets/Domain/Character/Model/natoriConfig.asset.meta rename to Assets/Domain/Character/Model/Sample/natoriConfig.asset.meta diff --git a/Assets/Domain/Character/Model/natori_pro_Sample.prefab b/Assets/Domain/Character/Model/Sample/natori_pro_Sample.prefab similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_Sample.prefab rename to Assets/Domain/Character/Model/Sample/natori_pro_Sample.prefab diff --git a/Assets/Domain/Character/Model/natori_pro_Sample.prefab.meta b/Assets/Domain/Character/Model/Sample/natori_pro_Sample.prefab.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_Sample.prefab.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_Sample.prefab.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/ReadMe.txt b/Assets/Domain/Character/Model/Sample/natori_pro_en/ReadMe.txt similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/ReadMe.txt rename to Assets/Domain/Character/Model/Sample/natori_pro_en/ReadMe.txt diff --git a/Assets/Domain/Character/Model/natori_pro_en/ReadMe.txt.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/ReadMe.txt.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/ReadMe.txt.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/ReadMe.txt.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/natori_pro_exp_t03.can3 b/Assets/Domain/Character/Model/Sample/natori_pro_en/natori_pro_exp_t03.can3 similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/natori_pro_exp_t03.can3 rename to Assets/Domain/Character/Model/Sample/natori_pro_en/natori_pro_exp_t03.can3 diff --git a/Assets/Domain/Character/Model/natori_pro_en/natori_pro_exp_t03.can3.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/natori_pro_exp_t03.can3.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/natori_pro_exp_t03.can3.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/natori_pro_exp_t03.can3.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/natori_pro_motions_t03.can3 b/Assets/Domain/Character/Model/Sample/natori_pro_en/natori_pro_motions_t03.can3 similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/natori_pro_motions_t03.can3 rename to Assets/Domain/Character/Model/Sample/natori_pro_en/natori_pro_motions_t03.can3 diff --git a/Assets/Domain/Character/Model/natori_pro_en/natori_pro_motions_t03.can3.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/natori_pro_motions_t03.can3.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/natori_pro_motions_t03.can3.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/natori_pro_motions_t03.can3.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/natori_pro_t06.cmo3 b/Assets/Domain/Character/Model/Sample/natori_pro_en/natori_pro_t06.cmo3 similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/natori_pro_t06.cmo3 rename to Assets/Domain/Character/Model/Sample/natori_pro_en/natori_pro_t06.cmo3 diff --git a/Assets/Domain/Character/Model/natori_pro_en/natori_pro_t06.cmo3.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/natori_pro_t06.cmo3.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/natori_pro_t06.cmo3.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/natori_pro_t06.cmo3.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Angry.exp3.asset b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Angry.exp3.asset similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Angry.exp3.asset rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Angry.exp3.asset diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Angry.exp3.asset.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Angry.exp3.asset.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Angry.exp3.asset.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Angry.exp3.asset.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Angry.exp3.json b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Angry.exp3.json similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Angry.exp3.json rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Angry.exp3.json diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Angry.exp3.json.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Angry.exp3.json.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Angry.exp3.json.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Angry.exp3.json.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Blushing.exp3.asset b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Blushing.exp3.asset similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Blushing.exp3.asset rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Blushing.exp3.asset diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Blushing.exp3.asset.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Blushing.exp3.asset.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Blushing.exp3.asset.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Blushing.exp3.asset.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Blushing.exp3.json b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Blushing.exp3.json similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Blushing.exp3.json rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Blushing.exp3.json diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Blushing.exp3.json.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Blushing.exp3.json.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Blushing.exp3.json.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Blushing.exp3.json.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Normal.exp3.asset b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Normal.exp3.asset similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Normal.exp3.asset rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Normal.exp3.asset diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Normal.exp3.asset.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Normal.exp3.asset.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Normal.exp3.asset.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Normal.exp3.asset.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Normal.exp3.json b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Normal.exp3.json similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Normal.exp3.json rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Normal.exp3.json diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Normal.exp3.json.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Normal.exp3.json.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Normal.exp3.json.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Normal.exp3.json.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Sad.exp3.asset b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Sad.exp3.asset similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Sad.exp3.asset rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Sad.exp3.asset diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Sad.exp3.asset.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Sad.exp3.asset.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Sad.exp3.asset.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Sad.exp3.asset.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Sad.exp3.json b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Sad.exp3.json similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Sad.exp3.json rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Sad.exp3.json diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Sad.exp3.json.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Sad.exp3.json.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Sad.exp3.json.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Sad.exp3.json.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Smile.exp3.asset b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Smile.exp3.asset similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Smile.exp3.asset rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Smile.exp3.asset diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Smile.exp3.asset.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Smile.exp3.asset.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Smile.exp3.asset.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Smile.exp3.asset.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Smile.exp3.json b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Smile.exp3.json similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Smile.exp3.json rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Smile.exp3.json diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Smile.exp3.json.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Smile.exp3.json.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Smile.exp3.json.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Smile.exp3.json.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Surprised.exp3.asset b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Surprised.exp3.asset similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Surprised.exp3.asset rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Surprised.exp3.asset diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Surprised.exp3.asset.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Surprised.exp3.asset.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Surprised.exp3.asset.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Surprised.exp3.asset.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Surprised.exp3.json b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Surprised.exp3.json similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Surprised.exp3.json rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Surprised.exp3.json diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Surprised.exp3.json.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Surprised.exp3.json.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/Surprised.exp3.json.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/Surprised.exp3.json.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_01.exp3.asset b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_01.exp3.asset similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_01.exp3.asset rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_01.exp3.asset diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_01.exp3.asset.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_01.exp3.asset.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_01.exp3.asset.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_01.exp3.asset.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_01.exp3.json b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_01.exp3.json similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_01.exp3.json rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_01.exp3.json diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_01.exp3.json.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_01.exp3.json.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_01.exp3.json.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_01.exp3.json.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_02.exp3.asset b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_02.exp3.asset similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_02.exp3.asset rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_02.exp3.asset diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_02.exp3.asset.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_02.exp3.asset.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_02.exp3.asset.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_02.exp3.asset.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_02.exp3.json b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_02.exp3.json similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_02.exp3.json rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_02.exp3.json diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_02.exp3.json.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_02.exp3.json.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_02.exp3.json.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_02.exp3.json.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_03.exp3.asset b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_03.exp3.asset similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_03.exp3.asset rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_03.exp3.asset diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_03.exp3.asset.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_03.exp3.asset.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_03.exp3.asset.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_03.exp3.asset.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_03.exp3.json b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_03.exp3.json similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_03.exp3.json rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_03.exp3.json diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_03.exp3.json.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_03.exp3.json.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_03.exp3.json.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_03.exp3.json.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_04.exp3.asset b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_04.exp3.asset similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_04.exp3.asset rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_04.exp3.asset diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_04.exp3.asset.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_04.exp3.asset.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_04.exp3.asset.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_04.exp3.asset.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_04.exp3.json b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_04.exp3.json similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_04.exp3.json rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_04.exp3.json diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_04.exp3.json.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_04.exp3.json.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_04.exp3.json.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_04.exp3.json.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_05.exp3.asset b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_05.exp3.asset similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_05.exp3.asset rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_05.exp3.asset diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_05.exp3.asset.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_05.exp3.asset.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_05.exp3.asset.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_05.exp3.asset.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_05.exp3.json b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_05.exp3.json similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_05.exp3.json rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_05.exp3.json diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_05.exp3.json.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_05.exp3.json.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/exp/exp_05.exp3.json.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/exp/exp_05.exp3.json.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_00.anim b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_00.anim similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_00.anim rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_00.anim diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_00.anim.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_00.anim.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_00.anim.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_00.anim.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_00.fade.asset b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_00.fade.asset similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_00.fade.asset rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_00.fade.asset diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_00.fade.asset.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_00.fade.asset.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_00.fade.asset.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_00.fade.asset.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_00.motion3.json b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_00.motion3.json similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_00.motion3.json rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_00.motion3.json diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_00.motion3.json.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_00.motion3.json.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_00.motion3.json.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_00.motion3.json.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_01.anim b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_01.anim similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_01.anim rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_01.anim diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_01.anim.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_01.anim.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_01.anim.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_01.anim.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_01.fade.asset b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_01.fade.asset similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_01.fade.asset rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_01.fade.asset diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_01.fade.asset.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_01.fade.asset.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_01.fade.asset.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_01.fade.asset.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_01.motion3.json b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_01.motion3.json similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_01.motion3.json rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_01.motion3.json diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_01.motion3.json.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_01.motion3.json.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_01.motion3.json.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_01.motion3.json.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_02.anim b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_02.anim similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_02.anim rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_02.anim diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_02.anim.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_02.anim.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_02.anim.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_02.anim.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_02.fade.asset b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_02.fade.asset similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_02.fade.asset rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_02.fade.asset diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_02.fade.asset.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_02.fade.asset.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_02.fade.asset.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_02.fade.asset.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_02.motion3.json b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_02.motion3.json similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_02.motion3.json rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_02.motion3.json diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_02.motion3.json.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_02.motion3.json.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_02.motion3.json.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_02.motion3.json.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_03.anim b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_03.anim similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_03.anim rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_03.anim diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_03.anim.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_03.anim.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_03.anim.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_03.anim.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_03.fade.asset b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_03.fade.asset similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_03.fade.asset rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_03.fade.asset diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_03.fade.asset.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_03.fade.asset.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_03.fade.asset.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_03.fade.asset.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_03.motion3.json b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_03.motion3.json similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_03.motion3.json rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_03.motion3.json diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_03.motion3.json.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_03.motion3.json.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_03.motion3.json.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_03.motion3.json.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_04.anim b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_04.anim similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_04.anim rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_04.anim diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_04.anim.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_04.anim.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_04.anim.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_04.anim.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_04.fade.asset b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_04.fade.asset similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_04.fade.asset rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_04.fade.asset diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_04.fade.asset.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_04.fade.asset.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_04.fade.asset.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_04.fade.asset.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_04.motion3.json b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_04.motion3.json similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_04.motion3.json rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_04.motion3.json diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_04.motion3.json.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_04.motion3.json.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_04.motion3.json.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_04.motion3.json.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_05.anim b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_05.anim similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_05.anim rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_05.anim diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_05.anim.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_05.anim.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_05.anim.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_05.anim.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_05.fade.asset b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_05.fade.asset similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_05.fade.asset rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_05.fade.asset diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_05.fade.asset.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_05.fade.asset.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_05.fade.asset.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_05.fade.asset.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_05.motion3.json b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_05.motion3.json similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_05.motion3.json rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_05.motion3.json diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_05.motion3.json.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_05.motion3.json.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_05.motion3.json.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_05.motion3.json.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_06.anim b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_06.anim similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_06.anim rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_06.anim diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_06.anim.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_06.anim.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_06.anim.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_06.anim.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_06.fade.asset b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_06.fade.asset similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_06.fade.asset rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_06.fade.asset diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_06.fade.asset.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_06.fade.asset.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_06.fade.asset.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_06.fade.asset.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_06.motion3.json b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_06.motion3.json similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_06.motion3.json rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_06.motion3.json diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_06.motion3.json.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_06.motion3.json.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_06.motion3.json.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_06.motion3.json.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_07.anim b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_07.anim similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_07.anim rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_07.anim diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_07.anim.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_07.anim.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_07.anim.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_07.anim.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_07.fade.asset b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_07.fade.asset similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_07.fade.asset rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_07.fade.asset diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_07.fade.asset.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_07.fade.asset.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_07.fade.asset.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_07.fade.asset.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_07.motion3.json b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_07.motion3.json similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_07.motion3.json rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_07.motion3.json diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_07.motion3.json.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_07.motion3.json.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/motions/mtn_07.motion3.json.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/motions/mtn_07.motion3.json.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/natori.pose3.json b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori.pose3.json similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/natori.pose3.json rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori.pose3.json diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/natori.pose3.json.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori.pose3.json.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/natori.pose3.json.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori.pose3.json.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.4096.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.4096.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.4096.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.4096.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.4096/texture_00.png b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.4096/texture_00.png similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.4096/texture_00.png rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.4096/texture_00.png diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.4096/texture_00.png.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.4096/texture_00.png.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.4096/texture_00.png.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.4096/texture_00.png.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.asset b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.asset similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.asset rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.asset diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.asset.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.asset.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.asset.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.asset.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.cdi3.json b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.cdi3.json similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.cdi3.json rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.cdi3.json diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.cdi3.json.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.cdi3.json.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.cdi3.json.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.cdi3.json.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.controller b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.controller similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.controller rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.controller diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.controller.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.controller.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.controller.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.controller.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.moc3 b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.moc3 similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.moc3 rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.moc3 diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.moc3.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.moc3.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.moc3.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.moc3.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.model3.json b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.model3.json similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.model3.json rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.model3.json diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.model3.json.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.model3.json.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.model3.json.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.model3.json.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.physics3.json b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.physics3.json similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.physics3.json rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.physics3.json diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.physics3.json.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.physics3.json.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.physics3.json.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.physics3.json.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.prefab b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.prefab similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.prefab rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.prefab diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.prefab.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.prefab.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06.prefab.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06.prefab.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06MaskTexture.asset b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06MaskTexture.asset similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06MaskTexture.asset rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06MaskTexture.asset diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06MaskTexture.asset.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06MaskTexture.asset.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/natori_pro_t06MaskTexture.asset.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/natori_pro_t06MaskTexture.asset.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/runtime.expressionList.asset b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/runtime.expressionList.asset similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/runtime.expressionList.asset rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/runtime.expressionList.asset diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/runtime.expressionList.asset.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/runtime.expressionList.asset.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/runtime.expressionList.asset.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/runtime.expressionList.asset.meta diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/runtime.fadeMotionList.asset b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/runtime.fadeMotionList.asset similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/runtime.fadeMotionList.asset rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/runtime.fadeMotionList.asset diff --git a/Assets/Domain/Character/Model/natori_pro_en/runtime/runtime.fadeMotionList.asset.meta b/Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/runtime.fadeMotionList.asset.meta similarity index 100% rename from Assets/Domain/Character/Model/natori_pro_en/runtime/runtime.fadeMotionList.asset.meta rename to Assets/Domain/Character/Model/Sample/natori_pro_en/runtime/runtime.fadeMotionList.asset.meta diff --git a/Assets/Domain/Character/Script/CharacterActionController.cs b/Assets/Domain/Character/Script/CharacterActionController.cs new file mode 100644 index 0000000..5949ce3 --- /dev/null +++ b/Assets/Domain/Character/Script/CharacterActionController.cs @@ -0,0 +1,111 @@ +#nullable enable +using UnityEngine; +using ProjectVG.Domain.Chat.Model; + +namespace ProjectVG.Domain.Character.Service +{ + + public enum CharacterActionType + { + Idle, + Listen, + Talk + } + + public class CharacterActionController : MonoBehaviour + { + private Animator? _animator; + private bool _isPlaying = false; + private CharacterActionType _currentAction = CharacterActionType.Idle; + + /// + /// 서비스를 초기화한다. + /// + /// 캐릭터의 Animator + public void Initialize(Animator animator) + { + _animator = animator; + _isPlaying = false; + _currentAction = CharacterActionType.Idle; + } + + /// + /// 액션을 실행한다. + /// + /// 액션 타입 + public void PlayAction(CharacterActionType actionType) + { + if (_animator == null) + { + Debug.LogWarning("[CharacterActionController] Animator가 초기화되지 않았습니다."); + return; + } + + try + { + _currentAction = actionType; + + Debug.Log(actionType); + + switch (actionType) + { + case CharacterActionType.Idle: + _animator.SetTrigger("Idle"); + _animator.SetBool("Talk", false); + _isPlaying = false; + break; + + case CharacterActionType.Listen: + _animator.SetTrigger("Listen"); + _animator.SetBool("Talk", false); + _isPlaying = true; + break; + + case CharacterActionType.Talk: + _animator.SetBool("Talk", true); + _isPlaying = true; + break; + } + + Debug.Log($"[CharacterActionController] 액션 재생: {actionType}"); + } + catch (System.Exception ex) + { + Debug.LogError($"[CharacterActionController] 액션 재생 실패: {ex.Message}"); + _isPlaying = false; + } + } + + /// + /// 현재 재생 중인 액션을 중지한다. + /// + public void StopCurrentAction() + { + if (_animator == null) return; + + _animator.SetTrigger("Idle"); + _animator.SetBool("Talk", false); + _isPlaying = false; + _currentAction = CharacterActionType.Idle; + Debug.Log("[CharacterActionController] 액션 중지"); + } + + /// + /// 액션이 재생 중인지 확인한다. + /// + /// 액션 재생 중이면 true + public bool IsPlaying() + { + return _isPlaying && _animator != null; + } + + /// + /// 현재 액션 타입을 반환한다. + /// + /// 현재 액션 타입 + public CharacterActionType GetCurrentAction() + { + return _currentAction; + } + } +} diff --git a/Assets/Domain/Character/Script/CharacterActionController.cs.meta b/Assets/Domain/Character/Script/CharacterActionController.cs.meta new file mode 100644 index 0000000..d2bbf75 --- /dev/null +++ b/Assets/Domain/Character/Script/CharacterActionController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f43ea94184a478548b8bd29496a6070a \ No newline at end of file diff --git a/Assets/Domain/Character/Script/CharacterManager.cs b/Assets/Domain/Character/Script/CharacterManager.cs new file mode 100644 index 0000000..d1066ca --- /dev/null +++ b/Assets/Domain/Character/Script/CharacterManager.cs @@ -0,0 +1,188 @@ +using ProjectVG.Domain.Character.Live2D.Model; +using UnityEngine; +using ProjectVG.Domain.Character.Component; +using ProjectVG.Core.Audio; +using ProjectVG.Domain.Chat.Model; +using System.Threading.Tasks; +using System.Threading; + +namespace ProjectVG.Domain.Character.Service +{ + /// + /// 캐릭터 제어를 단일 진입점으로 제공하는 파사드 구현체. + /// + public class CharacterManager : MonoBehaviour + { + [SerializeField] private Transform _modelTransform; + [SerializeField] private Live2DModelRegistry _modelRegistry; + + private CharacterModelLoader _modelLoader; + private GameObject _currentCharacter; + private CharacterActionController _currentActionService; + private int _loadVersion = 0; + + #region Unity Lifecycle + void Start() + { + Initialize(); + } + + void OnDestroy() + { + Shutdown(); + } + public void Initialize() + { + _modelLoader = GetComponent(); + if (_modelLoader == null) { + _modelLoader = gameObject.AddComponent(); + Debug.Log($"[CharacterManager] CharacterModelLoader가 자동으로 추가되었습니다: {gameObject.name}"); + } + + if (_modelRegistry == null) + { + Debug.LogError("[CharacterManager] Live2DModelRegistry가 할당되지 않았습니다. Inspector에서 설정하세요."); + return; + } + + // AudioManager에서 Voice AudioSource 가져오기 + var voiceAudioSource = GetVoiceAudioSource(); + + _modelLoader.Initialize(_modelRegistry, voiceAudioSource); + + // 임시로 zero 캐릭터 로드 + LoadCharacter("zero"); + + if (_modelTransform != null) { + var scaler = _modelTransform.GetComponent(); + if (scaler == null) { + scaler = _modelTransform.gameObject.AddComponent(); + Debug.Log($"[CharacterManager] Live2DModelScaler가 자동으로 추가되었습니다: {_modelTransform.name}"); + } + } + } + public void Shutdown() + { + UnloadCurrentCharacter(); + } + + #endregion + + #region Public Methods + + /// + /// 캐릭터를 로드하고 활성화한다. + /// + public async Task LoadCharacter(string characterId) + { + if (_modelLoader == null) return; + + // 요청 버전 증가(새 요청 식별자) + var requestId = Interlocked.Increment(ref _loadVersion); + + // 기존 캐릭터 제거 + UnloadCurrentCharacter(); + + // 새 캐릭터 로드 + var newCharacter = await _modelLoader.LoadAndInitializeModelAsync(characterId, _modelTransform); + + // 더 최신 요청이 진행되었다면 현재 결과는 폐기 + if (requestId != _loadVersion) + { + if (newCharacter != null) Destroy(newCharacter); + return; + } + + if (newCharacter != null) + { + _currentCharacter = newCharacter; + _currentActionService = _currentCharacter.GetComponent(); + _currentCharacter.SetActive(true); + Debug.Log($"[CharacterManager] 캐릭터 로드 완료: {characterId}"); + } + } + + /// + /// 현재 캐릭터를 언로드한다. + /// + public void UnloadCurrentCharacter() + { + if (_currentCharacter != null) + { + Destroy(_currentCharacter); + _currentCharacter = null; + _currentActionService = null; + } + } + + /// + /// 액션을 실행한다. + /// + /// 액션 데이터 + public void PlayAction(CharacterActionData actionData) + { + if (_currentActionService != null && actionData.HasAction()) + { + _currentActionService.PlayAction(actionData.ActionType); + } + } + + /// + /// 현재 액션을 중지한다. + /// + public void StopCurrentAction() + { + _currentActionService?.StopCurrentAction(); + } + + /// + /// 액션이 재생 중인지 확인한다. + /// + /// 액션 재생 중이면 true + public bool IsActionPlaying() + { + return _currentActionService?.IsPlaying() ?? false; + } + + /// + /// 현재 캐릭터가 로드되어 있는지 확인한다. + /// + /// 캐릭터가 로드되어 있으면 true + public bool HasCharacter() + { + return _currentCharacter != null; + } + + #endregion + + #region Private Methods + + /// + /// AudioManager에서 Voice AudioSource를 가져온다. + /// + private AudioSource GetVoiceAudioSource() + { + var audioManager = AudioManager.Instance; + if (audioManager != null && audioManager.IsInitialized) + { + var voiceController = audioManager.GetVoiceController(); + if (voiceController != null) + { + var audioSource = voiceController.GetAudioSource(); + if (audioSource != null) + { + Debug.Log("[CharacterManager] Voice AudioSource 가져오기 완료"); + return audioSource; + } + } + } + + Debug.LogWarning("[CharacterManager] AudioManager가 초기화되지 않았거나 Voice AudioSource를 찾을 수 없습니다."); + return null; + } + + #endregion + } +} + + diff --git a/Assets/Domain/Character/Script/CharacterManager.cs.meta b/Assets/Domain/Character/Script/CharacterManager.cs.meta new file mode 100644 index 0000000..ae723b5 --- /dev/null +++ b/Assets/Domain/Character/Script/CharacterManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6ae88dd8c9dcaab448ad7d8834e80f8e \ No newline at end of file diff --git a/Assets/Domain/Character/Script/CharacterModelLoader.cs b/Assets/Domain/Character/Script/CharacterModelLoader.cs new file mode 100644 index 0000000..c7f4cea --- /dev/null +++ b/Assets/Domain/Character/Script/CharacterModelLoader.cs @@ -0,0 +1,184 @@ +using Cysharp.Threading.Tasks; +using Live2D.Cubism.Framework; +using Live2D.Cubism.Framework.MouthMovement; +using Live2D.Cubism.Core; +using ProjectVG.Core.Audio; +using ProjectVG.Domain.Character.Live2D.Model; +using System.Collections.Generic; +using UnityEngine; + +namespace ProjectVG.Domain.Character.Service +{ + /// + /// Live2D 모델 로딩 및 초기화를 담당하는 매니저 + /// + public class CharacterModelLoader : MonoBehaviour + { + + private Live2DModelRegistry _modelRegistry; + private AudioSource _voiceAudioSource; + + #region Unity Lifecycle + + #endregion + + #region Public Methods + + /// + /// 로더를 초기화한다 + /// + public void Initialize(Live2DModelRegistry modelRegistry, AudioSource voiceAudioSource) + { + _modelRegistry = modelRegistry; + _voiceAudioSource = voiceAudioSource; + } + + /// + /// 캐릭터 모델을 로드하고 초기화된 인스턴스를 반환한다 + /// + /// 캐릭터 ID + /// 부모 Transform + /// 초기화된 모델 인스턴스 + public async UniTask LoadAndInitializeModelAsync(string characterId, Transform parent = null) + { + var config = GetCharacterConfig(characterId); + if (config == null) { + Debug.LogError($"[CharacterModelLoader] 캐릭터 '{characterId}'의 설정을 찾을 수 없습니다."); + return null; + } + + return await CreateModelInstanceAsync(config, characterId, parent); + } + + #endregion + + #region Private Methods + + /// + /// 캐릭터 설정을 가져온다 + /// + private Live2DModelConfig GetCharacterConfig(string characterId) + { + if (_modelRegistry != null && _modelRegistry.TryGetConfig(characterId, out var config)) { + return config; + } + + return null; + } + + /// + /// 모델 인스턴스를 생성한다 + /// + private async UniTask CreateModelInstanceAsync(Live2DModelConfig config, string characterId, Transform parent = null) + { + if (config == null || config.CharacterPrefab == null) { + Debug.LogError($"[CharacterModelLoader] 캐릭터 '{characterId}'의 프리팹이 null입니다."); + return null; + } + + var targetParent = parent != null ? parent : transform; + + var instance = Instantiate(config.CharacterPrefab, targetParent); + instance.name = characterId; + instance.SetActive(false); + + SetupModelComponents(instance, config); + await UniTask.Yield(); + return instance; + } + + /// + /// 모델에 필요한 컴포넌트들을 설정한다 + /// + private void SetupModelComponents(GameObject modelInstance, Live2DModelConfig config) + { + SetupLipSync(modelInstance, config); + SetupAutoEyeBlink(modelInstance, config); + SetupActionController(modelInstance); + } + + /// + /// 립싱크 컴포넌트를 설정한다 + /// + private void SetupLipSync(GameObject modelInstance, Live2DModelConfig config) + { + if (!config.UseLipSync) { + Debug.Log($"[CharacterModelLoader] 립싱크가 비활성화되어 있습니다: {modelInstance.name}"); + return; + } + + var mouthController = modelInstance.GetComponent(); + if (mouthController == null) { + mouthController = modelInstance.AddComponent(); + Debug.Log($"[CharacterModelLoader] CubismAudioMouthInput 컴포넌트를 추가했습니다: {modelInstance.name}"); + } + + if (_voiceAudioSource == null) { + Debug.LogWarning($"[CharacterModelLoader] Voice AudioSource가 null입니다. 립싱크가 동작하지 않을 수 있습니다: {modelInstance.name}"); + } else { + Debug.Log($"[CharacterModelLoader] Voice AudioSource 설정 완료: {modelInstance.name}, AudioSource: {_voiceAudioSource.name}"); + mouthController.AudioInput = _voiceAudioSource; + } + mouthController.Gain = config.Gain; + mouthController.Smoothing = config.Smoothing; + + // Live2D 모델에 Mouth 파라미터가 있는지 확인 + var model = modelInstance.GetComponent(); + if (model != null) { + var mouthParameter = model.Parameters.FindById("ParamMouthOpenY"); + if (mouthParameter != null) { + Debug.Log($"[CharacterModelLoader] Mouth 파라미터 발견: {mouthParameter.Id}, 현재값: {mouthParameter.Value}"); + } else { + Debug.LogWarning($"[CharacterModelLoader] Mouth 파라미터(ParamMouthOpenY)를 찾을 수 없습니다: {modelInstance.name}"); + } + } + + Debug.Log($"[CharacterModelLoader] 립싱크 설정 완료: {modelInstance.name}, Gain: {config.Gain}, Smoothing: {config.Smoothing}"); + } + + /// + /// 자동 눈 깜빡임 컴포넌트를 설정한다 + /// + private void SetupAutoEyeBlink(GameObject modelInstance, Live2DModelConfig config) + { + if (!config.UseAutoEyeBlink) { + return; + } + + var eyeBlinkController = modelInstance.GetComponent(); + if (eyeBlinkController == null) { + eyeBlinkController = modelInstance.AddComponent(); + } + + eyeBlinkController.Mean = config.EyeBlinkMean; + eyeBlinkController.MaximumDeviation = config.EyeBlinkMaximumDeviation; + eyeBlinkController.Timescale = config.EyeBlinkTimescale; + eyeBlinkController.SetBlinkingSettings( + config.EyeBlinkClosingSeconds, + config.EyeBlinkClosedSeconds, + config.EyeBlinkOpeningSeconds + ); + } + + /// + /// 액션 서비스를 설정한다 + /// + private void SetupActionController(GameObject modelInstance) + { + var actionService = modelInstance.GetComponent(); + if (actionService == null) { + actionService = modelInstance.AddComponent(); + } + + var animator = modelInstance.GetComponent(); + if (animator == null) { + Debug.LogWarning($"[CharacterModelLoader] Animator를 찾을 수 없습니다: {modelInstance.name}"); + return; + } + actionService.Initialize(animator); + Debug.Log($"[CharacterModelLoader] CharacterActionController 초기화 완료: {modelInstance.name}"); + } + + #endregion + } +} diff --git a/Assets/Domain/Character/Script/CharacterModelLoader.cs.meta b/Assets/Domain/Character/Script/CharacterModelLoader.cs.meta new file mode 100644 index 0000000..7e23c15 --- /dev/null +++ b/Assets/Domain/Character/Script/CharacterModelLoader.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 739215a2e50c1c64f91ed2aaa05127c2 \ No newline at end of file diff --git a/Assets/Domain/Character/Script/Component/CameraResolutionScaler.cs b/Assets/Domain/Character/Script/Component/CameraResolutionScaler.cs new file mode 100644 index 0000000..78918b3 --- /dev/null +++ b/Assets/Domain/Character/Script/Component/CameraResolutionScaler.cs @@ -0,0 +1,214 @@ +using UnityEngine; +using ProjectVG.Domain.Character.Config; + +namespace ProjectVG.Domain.Character.Component +{ + /// + /// 해상도에 따라 카메라의 Orthographic Size를 자동 조정하는 컴포넌트 + /// + public class CameraResolutionScaler : MonoBehaviour + { + [Header("설정")] + [SerializeField] private ResolutionModelScaleConfig scaleConfig; + [SerializeField] private bool applyOnStart = true; + [SerializeField] private bool applyOnResolutionChange = true; + [SerializeField] private float baseOrthographicSize = 5f; + [SerializeField] private Vector2 baseResolution = new Vector2(1080, 1920); + + [Header("디버그")] + [SerializeField] private bool showDebugInfo = true; + + private Camera targetCamera; + private Vector2 lastResolution; + private float originalOrthographicSize; + + #region Unity Lifecycle + + private void Start() + { + Initialize(); + } + + private void Update() + { + if (applyOnResolutionChange && HasResolutionChanged()) + { + ApplyCameraScale(); + } + } + + #endregion + + #region Public Methods + + /// + /// 컴포넌트를 초기화한다. + /// + public void Initialize() + { + targetCamera = GetComponent(); + if (targetCamera == null) + { + Debug.LogError("[CameraResolutionScaler] Camera 컴포넌트를 찾을 수 없습니다."); + return; + } + + if (scaleConfig == null) + { + scaleConfig = Resources.Load("ResolutionModelScaleConfig"); + if (scaleConfig == null) + { + Debug.LogWarning("[CameraResolutionScaler] ResolutionModelScaleConfig를 찾을 수 없습니다."); + } + } + + originalOrthographicSize = targetCamera.orthographicSize; + lastResolution = new Vector2(Screen.width, Screen.height); + + if (applyOnStart) + { + ApplyCameraScale(); + } + + if (showDebugInfo) + { + Debug.Log($"[CameraResolutionScaler] 초기화 완료. 카메라: {targetCamera.name}"); + } + } + + /// + /// 카메라 스케일을 적용한다. + /// + public void ApplyCameraScale() + { + if (targetCamera == null) + { + Debug.LogWarning("[CameraResolutionScaler] 카메라가 없습니다."); + return; + } + + var currentResolution = new Vector2(Screen.width, Screen.height); + var scale = CalculateCameraScale(currentResolution); + + targetCamera.orthographicSize = baseOrthographicSize * scale; + lastResolution = currentResolution; + + if (showDebugInfo) + { + var info = scaleConfig?.GetCurrentResolutionInfo(); + Debug.Log($"[CameraResolutionScaler] 카메라 스케일 적용 완료: {targetCamera.name}\n" + + $"=== 해상도 정보 ===\n" + + $"현재 해상도: {currentResolution.x} x {currentResolution.y}\n" + + $"기준 해상도: {baseResolution.x} x {baseResolution.y}\n" + + $"플랫폼: {(info?.IsMobile == true ? "모바일" : "PC")}\n" + + $"=== 카메라 설정 ===\n" + + $"원본 Orthographic Size: {originalOrthographicSize:F2}\n" + + $"계산된 스케일: {scale:F3}배\n" + + $"적용된 Orthographic Size: {targetCamera.orthographicSize:F2}"); + } + } + + /// + /// 원본 크기로 되돌린다. + /// + public void ResetToOriginalSize() + { + if (targetCamera != null) + { + targetCamera.orthographicSize = originalOrthographicSize; + + if (showDebugInfo) + { + Debug.Log($"[CameraResolutionScaler] 원본 크기로 복원: {targetCamera.name}"); + } + } + } + + /// + /// 현재 카메라 스케일을 반환한다. + /// + public float GetCurrentCameraScale() + { + if (targetCamera == null) return 1f; + return targetCamera.orthographicSize / baseOrthographicSize; + } + + #endregion + + #region Private Methods + + /// + /// 해상도가 변경되었는지 확인한다. + /// + private bool HasResolutionChanged() + { + var currentResolution = new Vector2(Screen.width, Screen.height); + return currentResolution != lastResolution; + } + + /// + /// 카메라 스케일을 계산한다. + /// + private float CalculateCameraScale(Vector2 resolution) + { + // ResolutionModelScaleConfig에서 스케일 가져오기 + if (scaleConfig != null) + { + var configScale = scaleConfig.CalculateScale(); + + // 카메라 스케일은 해상도 비율과 설정 스케일을 조합 + var resolutionRatio = CalculateResolutionRatio(resolution); + var finalScale = resolutionRatio * configScale; + + if (showDebugInfo) + { + Debug.Log($"[CameraResolutionScaler] 스케일 계산:\n" + + $"해상도 비율: {resolutionRatio:F3}\n" + + $"설정 스케일: {configScale:F3}\n" + + $"최종 스케일: {finalScale:F3}"); + } + + return finalScale; + } + + // 설정이 없으면 해상도 비율만 사용 + return CalculateResolutionRatio(resolution); + } + + /// + /// 해상도 비율을 계산한다. + /// + private float CalculateResolutionRatio(Vector2 resolution) + { + // 세로 기준으로 비율 계산 (모바일 세로 모드 고려) + var currentAspect = resolution.x / resolution.y; + var baseAspect = baseResolution.x / baseResolution.y; + + // 세로 모드인 경우 높이 기준, 가로 모드인 경우 너비 기준 + if (currentAspect < 1.0f) // 세로 모드 + { + return resolution.y / baseResolution.y; + } + else // 가로 모드 + { + return resolution.x / baseResolution.x; + } + } + + #endregion + + #region Editor Methods + + #if UNITY_EDITOR + private void OnValidate() + { + if (scaleConfig == null) + { + scaleConfig = Resources.Load("ResolutionModelScaleConfig"); + } + } + #endif + + #endregion + } +} diff --git a/Assets/Domain/Character/Script/Component/CameraResolutionScaler.cs.meta b/Assets/Domain/Character/Script/Component/CameraResolutionScaler.cs.meta new file mode 100644 index 0000000..a033eff --- /dev/null +++ b/Assets/Domain/Character/Script/Component/CameraResolutionScaler.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a172350175672e545ba7e4abb18f0b59 \ No newline at end of file diff --git a/Assets/Domain/Character/Script/Component/CubismHitHandler.cs b/Assets/Domain/Character/Script/Component/CubismHitHandler.cs deleted file mode 100644 index 8b066ba..0000000 --- a/Assets/Domain/Character/Script/Component/CubismHitHandler.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections; -using Live2D.Cubism.Framework.Expression; -using Live2D.Cubism.Framework.Raycasting; -using UnityEngine; - -public class CubismHitHandler : MonoBehaviour -{ - private CubismRaycaster _raycaster = null; - private CubismExpressionController _expressionController = null; - - public void Initialize() - { - _raycaster = GetComponent(); - _expressionController = GetComponent(); - - if (_raycaster != null && ScreenTapManager.Instance != null) - { - ScreenTapManager.Instance.SetRaycaster(_raycaster); - } - else - { - Debug.LogWarning("[CubismHitHandler] Raycaster 또는 ScreenTapManager가 null입니다."); - } - } - - private void Update() - { - if (ScreenTapManager.Instance == null) - { - return; - } - - if (ScreenTapManager.Instance.TryGetTapUpPosition(out var hits)) - { - foreach (var hit in hits) - { - if(hit.Drawable is null) continue; - HandleHit(hit.Drawable.name); - } - } - } - - private void HandleHit(string drawableName) - { - // 여기서 터치된 파츠별로 반응 - switch (drawableName) - { - case "HitAreaHead": - Debug.Log("머리 터치 → 표정 변경 or 모션 재생"); - ExpressionChange(); - break; - case "HitAreaBody": - Debug.Log("몸통 터치 → 다른 반응"); - break; - } - } - - // TODO : 추후 표정 관리 클래스로 분리 - private void ExpressionChange() - { - _expressionController.CurrentExpressionIndex = - GetNextExpressionIndex(_expressionController.CurrentExpressionIndex, 0, - _expressionController.ExpressionsList.CubismExpressionObjects.Length); - } - - private int GetNextExpressionIndex(int current, int min, int max) - { - return ((current - min + 1) % (max - min + 1)) + min; - } - - public void ExpressionChange_Btn() - { - ExpressionChange(); - } -} diff --git a/Assets/Domain/Character/Script/Component/CubismHitHandler.cs.meta b/Assets/Domain/Character/Script/Component/CubismHitHandler.cs.meta deleted file mode 100644 index 284028f..0000000 --- a/Assets/Domain/Character/Script/Component/CubismHitHandler.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 49951858c8af46f4c85cecf4a03db08b \ No newline at end of file diff --git a/Assets/Domain/Character/Script/Component/CubismLookTarget.cs b/Assets/Domain/Character/Script/Component/CubismLookTarget.cs deleted file mode 100644 index 5e515c8..0000000 --- a/Assets/Domain/Character/Script/Component/CubismLookTarget.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Live2D.Cubism.Framework.LookAt; -using UnityEngine; - -public class CubismLookTarget : MonoBehaviour, ICubismLookTarget -{ - private ModelConfig _modelConfig; - - public void Initialize(ModelConfig modelConfig) - { - _modelConfig = modelConfig; - } - - public Vector3 GetPosition() - { - if (!ScreenTapManager.Instance.TryGetLookDirection(out var lookDir)) - return Vector3.zero; - - return lookDir * _modelConfig.LookSensitivity; - } - - public bool IsActive() - { - return _modelConfig.IsLockAtActive; - } -} diff --git a/Assets/Domain/Character/Script/Component/CubismLookTarget.cs.meta b/Assets/Domain/Character/Script/Component/CubismLookTarget.cs.meta deleted file mode 100644 index 4230601..0000000 --- a/Assets/Domain/Character/Script/Component/CubismLookTarget.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 8c57f28f2e390324eb9e52c4fd0c3dde \ No newline at end of file diff --git a/Assets/Domain/Character/Script/Component/Live2DModelScaler.cs b/Assets/Domain/Character/Script/Component/Live2DModelScaler.cs new file mode 100644 index 0000000..50a71b5 --- /dev/null +++ b/Assets/Domain/Character/Script/Component/Live2DModelScaler.cs @@ -0,0 +1,238 @@ +using UnityEngine; +using ProjectVG.Domain.Character.Config; + +namespace ProjectVG.Domain.Character.Component +{ + /// + /// Live2D 모델 크기를 해상도에 맞게 자동 조정하는 컴포넌트 + /// + public class Live2DModelScaler : MonoBehaviour + { + [Header("설정")] + [SerializeField] private ResolutionModelScaleConfig scaleConfig; + [SerializeField] private bool applyOnStart = true; + [SerializeField] private bool applyOnResolutionChange = true; + + [Header("디버그")] + [SerializeField] private bool showDebugInfo = true; + + private Vector2 lastResolution; + private float currentScale; + private Vector3 originalScale; + + #region Unity Lifecycle + + private void Start() + { + if (applyOnStart) + { + ApplyScale(); + } + + // ResolutionManager에 자동 등록 + RegisterToResolutionManager(); + } + + private void OnDestroy() + { + // ResolutionManager에서 제거 + UnregisterFromResolutionManager(); + } + + private void Update() + { + if (applyOnResolutionChange && HasResolutionChanged()) + { + ApplyScale(); + } + } + + #endregion + + #region Public Methods + + /// + /// 현재 해상도에 맞는 스케일을 적용한다. + /// + public void ApplyScale() + { + if (scaleConfig == null) + { + Debug.LogWarning("[Live2DModelScaler] 스케일 설정이 없습니다."); + return; + } + + // 현재 해상도에 맞는 스케일 계산 + var scale = scaleConfig.CalculateScale(); + ApplyScaleWithPreCalculatedScale(scale); + } + + /// + /// 미리 계산된 스케일을 적용한다. + /// + public void ApplyScaleWithPreCalculatedScale(float scale) + { + if (scaleConfig == null) + { + Debug.LogWarning("[Live2DModelScaler] 스케일 설정이 없습니다."); + return; + } + + // 원본 스케일 저장 (최초 1회만) + if (originalScale == Vector3.zero) + { + originalScale = transform.localScale; + } + + // 스케일 적용 + currentScale = scale; + transform.localScale = originalScale * currentScale; + + // 해상도 정보 업데이트 + lastResolution = new Vector2(Screen.width, Screen.height); + + if (showDebugInfo) + { + var info = scaleConfig.GetCurrentResolutionInfo(); + var originalSize = originalScale; + var newSize = transform.localScale; + var scaleMultiplier = currentScale; + + Debug.Log($"[Live2DModelScaler] 스케일 적용 완료: {gameObject.name}\n" + + $"=== 해상도 정보 ===\n" + + $"현재 해상도: {info.Resolution.x} x {info.Resolution.y}\n" + + $"종횡비: {info.AspectRatio:F3}\n" + + $"플랫폼: {(info.IsMobile ? "모바일" : "PC")}\n" + + $"=== 스케일 정보 ===\n" + + $"적용된 스케일: {scaleMultiplier:F3}배\n" + + $"원본 크기: {originalSize}\n" + + $"변경된 크기: {newSize}\n" + + $"크기 변화: {scaleMultiplier:F1}배"); + } + } + + /// + /// 원본 크기로 되돌린다. + /// + public void ResetToOriginalScale() + { + if (originalScale != Vector3.zero) + { + transform.localScale = originalScale; + currentScale = 1.0f; + + if (showDebugInfo) + { + Debug.Log($"[Live2DModelScaler] 원본 크기로 복원: {gameObject.name}"); + } + } + } + + /// + /// 수동으로 스케일을 설정한다. + /// + public void SetManualScale(float scale) + { + if (originalScale == Vector3.zero) + { + originalScale = transform.localScale; + } + + currentScale = scale; + transform.localScale = originalScale * scale; + + if (showDebugInfo) + { + Debug.Log($"[Live2DModelScaler] 수동 스케일 설정: {gameObject.name} -> {scale:F2}"); + } + } + + /// + /// 현재 스케일을 반환한다. + /// + public float GetCurrentScale() + { + return currentScale; + } + + /// + /// 원본 스케일을 반환한다. + /// + public Vector3 GetOriginalScale() + { + return originalScale; + } + + #endregion + + #region Private Methods + + /// + /// 해상도가 변경되었는지 확인한다. + /// + private bool HasResolutionChanged() + { + var currentResolution = new Vector2(Screen.width, Screen.height); + return currentResolution != lastResolution; + } + + /// + /// ResolutionManager에 등록한다. + /// + private void RegisterToResolutionManager() + { + try + { + var manager = ProjectVG.Domain.Character.Manager.ResolutionManager.Instance; + if (manager != null) + { + manager.RegisterModelScaler(this); + } + } + catch (System.Exception ex) + { + Debug.LogWarning($"[Live2DModelScaler] ResolutionManager 등록 실패: {ex.Message}"); + } + } + + /// + /// ResolutionManager에서 제거한다. + /// + private void UnregisterFromResolutionManager() + { + try + { + var manager = ProjectVG.Domain.Character.Manager.ResolutionManager.Instance; + if (manager != null) + { + manager.UnregisterModelScaler(this); + } + } + catch (System.Exception ex) + { + Debug.LogWarning($"[Live2DModelScaler] ResolutionManager 제거 실패: {ex.Message}"); + } + } + + #endregion + + #region Editor Methods + + #if UNITY_EDITOR + private void OnValidate() + { + if (scaleConfig == null) + { + // 기본 설정 자동 생성 + scaleConfig = Resources.Load("ResolutionModelScaleConfig"); + if (scaleConfig == null) + { + Debug.LogWarning("[Live2DModelScaler] Resources 폴더에 ResolutionModelScaleConfig가 없습니다."); + } + } + } + #endif + + #endregion + } +} diff --git a/Assets/Domain/Character/Script/Component/Live2DModelScaler.cs.meta b/Assets/Domain/Character/Script/Component/Live2DModelScaler.cs.meta new file mode 100644 index 0000000..ff87c26 --- /dev/null +++ b/Assets/Domain/Character/Script/Component/Live2DModelScaler.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5ba83c8a090667149ad21fedc63b1fa9 \ No newline at end of file diff --git a/Assets/Domain/Character/Script/Component/ResolutionManager.cs b/Assets/Domain/Character/Script/Component/ResolutionManager.cs new file mode 100644 index 0000000..3c29307 --- /dev/null +++ b/Assets/Domain/Character/Script/Component/ResolutionManager.cs @@ -0,0 +1,233 @@ +using UnityEngine; +using System.Collections.Generic; +using System.Linq; +using ProjectVG.Domain.Character.Component; +using ProjectVG.Domain.Character.Config; + +namespace ProjectVG.Domain.Character.Manager +{ + /// + /// 해상도 변경을 감지하고 Live2D 모델 크기를 자동 조정하는 매니저 + /// + public class ResolutionManager : MonoBehaviour + { + [Header("설정")] + [SerializeField] private ResolutionModelScaleConfig scaleConfig; + [SerializeField] private bool autoDetectScalers = true; + [SerializeField] private bool applyOnResolutionChange = true; + + [Header("디버그")] + [SerializeField] private bool showDebugInfo = true; + + private Vector2 lastResolution; + private List modelScalers = new List(); + + #region Unity Lifecycle + + private void Start() + { + Initialize(); + } + + private void Update() + { + if (applyOnResolutionChange && HasResolutionChanged()) + { + ApplyScaleToAllModels(); + } + } + + #endregion + + #region Public Methods + + /// + /// 매니저를 초기화한다. + /// + public void Initialize() + { + if (scaleConfig == null) + { + scaleConfig = Resources.Load("ResolutionModelScaleConfig"); + if (scaleConfig == null) + { + Debug.LogError("[ResolutionManager] ResolutionModelScaleConfig를 찾을 수 없습니다."); + return; + } + } + + lastResolution = new Vector2(Screen.width, Screen.height); + + if (autoDetectScalers) + { + FindAllModelScalers(); + } + + // 초기 스케일 적용 + ApplyScaleToAllModels(); + + if (showDebugInfo) + { + Debug.Log($"[ResolutionManager] 초기화 완료. 발견된 스케일러: {modelScalers.Count}개"); + } + } + + /// + /// 모든 모델 스케일러를 찾는다. + /// + public void FindAllModelScalers() + { + modelScalers.Clear(); + var scalers = FindObjectsOfType(); + modelScalers.AddRange(scalers); + + if (showDebugInfo) + { + Debug.Log($"[ResolutionManager] {modelScalers.Count}개의 모델 스케일러를 발견했습니다."); + } + } + + /// + /// 모든 모델에 스케일을 적용한다. + /// + public void ApplyScaleToAllModels() + { + if (scaleConfig == null) + { + Debug.LogWarning("[ResolutionManager] 스케일 설정이 없습니다."); + return; + } + + // 자동 감지가 활성화되어 있으면 스케일러 다시 찾기 + if (autoDetectScalers) + { + FindAllModelScalers(); + } + + var currentResolution = new Vector2(Screen.width, Screen.height); + + // 스케일 계산은 한 번만 수행 + var scale = scaleConfig.CalculateScale(); + + foreach (var scaler in modelScalers) + { + if (scaler != null) + { + // 스케일러에 미리 계산된 스케일을 전달 + scaler.ApplyScaleWithPreCalculatedScale(scale); + } + } + + lastResolution = currentResolution; + + if (showDebugInfo) + { + var info = scaleConfig.GetCurrentResolutionInfo(); + Debug.Log($"[ResolutionManager] 모든 모델에 스케일 적용 완료\n" + + $"=== 해상도 설정 ===\n" + + $"현재 해상도: {info.Resolution.x} x {info.Resolution.y}\n" + + $"종횡비: {info.AspectRatio:F3}\n" + + $"플랫폼: {(info.IsMobile ? "모바일" : "PC")}\n" + + $"=== 스케일 설정 ===\n" + + $"적용된 스케일: {scale:F3}배\n" + + $"적용된 모델 수: {modelScalers.Count}개\n" + + $"=== 모델 목록 ===\n" + + string.Join("\n", modelScalers.Select(s => $"- {s.name}: {s.GetCurrentScale():F3}배"))); + } + } + + /// + /// 특정 모델 스케일러를 등록한다. + /// + public void RegisterModelScaler(Live2DModelScaler scaler) + { + if (scaler != null && !modelScalers.Contains(scaler)) + { + modelScalers.Add(scaler); + + if (showDebugInfo) + { + Debug.Log($"[ResolutionManager] 모델 스케일러 등록: {scaler.name}"); + } + } + } + + /// + /// 특정 모델 스케일러를 제거한다. + /// + public void UnregisterModelScaler(Live2DModelScaler scaler) + { + if (modelScalers.Remove(scaler) && showDebugInfo) + { + Debug.Log($"[ResolutionManager] 모델 스케일러 제거: {scaler.name}"); + } + } + + /// + /// 현재 해상도 정보를 반환한다. + /// + public ResolutionModelScaleConfig.ResolutionInfo GetCurrentResolutionInfo() + { + return scaleConfig?.GetCurrentResolutionInfo(); + } + + /// + /// 등록된 모델 스케일러 수를 반환한다. + /// + public int GetRegisteredScalerCount() + { + return modelScalers.Count; + } + + #endregion + + #region Private Methods + + /// + /// 해상도가 변경되었는지 확인한다. + /// + private bool HasResolutionChanged() + { + var currentResolution = new Vector2(Screen.width, Screen.height); + return currentResolution != lastResolution; + } + + #endregion + + #region Singleton Pattern + + private static ResolutionManager _instance; + public static ResolutionManager Instance + { + get + { + if (_instance == null) + { + _instance = FindObjectOfType(); + if (_instance == null) + { + var go = new GameObject("ResolutionManager"); + _instance = go.AddComponent(); + DontDestroyOnLoad(go); + } + } + return _instance; + } + } + + private void Awake() + { + if (_instance == null) + { + _instance = this; + DontDestroyOnLoad(gameObject); + } + else if (_instance != this) + { + Destroy(gameObject); + } + } + + #endregion + } +} diff --git a/Assets/Domain/Character/Script/Component/ResolutionManager.cs.meta b/Assets/Domain/Character/Script/Component/ResolutionManager.cs.meta new file mode 100644 index 0000000..56bfa4a --- /dev/null +++ b/Assets/Domain/Character/Script/Component/ResolutionManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a1d6440929102a34b926d1c2477daec1 \ No newline at end of file diff --git a/Assets/Domain/Character/Script/Config/Live2DModelConfig.cs b/Assets/Domain/Character/Script/Config/Live2DModelConfig.cs index 4c1c54b..cee40fa 100644 --- a/Assets/Domain/Character/Script/Config/Live2DModelConfig.cs +++ b/Assets/Domain/Character/Script/Config/Live2DModelConfig.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using UnityEngine; +using UnityEngine.Serialization; namespace ProjectVG.Domain.Character.Live2D.Model { @@ -75,7 +76,7 @@ public class ActionMapping [Space(2)] [Tooltip("시선 추적 사용 여부")] - [SerializeField] private bool isLockAtActive = true; + [SerializeField] private bool isLookAtActive = true; [Tooltip("시선 민감도 (값이 클수록 회전이 커짐)")] [Range(0f, 30f)] @@ -83,13 +84,17 @@ public class ActionMapping [Tooltip("시선 반응 속도 (값이 작을수록 빠름)")] [Range(0f, 5f)] - [SerializeField] private float lockAtDamping = 0.0f; + [SerializeField, FormerlySerializedAs("lockAtDamping")] + private float lookAtDamping = 0.0f; [Space(5)] [Header("────────────────────────────────────────")] [Header("[ 립싱크 설정 ]")] [Space(2)] + [Tooltip("립싱크 사용 여부")] + [SerializeField] private bool useLipSync = true; + [Tooltip("음량 배수 (1 = 기본)")] [Range(1f, 10f)] [SerializeField] private float gain = 1f; @@ -98,6 +103,40 @@ public class ActionMapping [Range(0f, 1f)] [SerializeField] private float smoothing = 1f; + [Space(5)] + [Header("────────────────────────────────────────")] + [Header("[ 자동 애니메이션 설정 ]")] + [Space(2)] + + [Tooltip("자동 눈 깜빡임 사용 여부")] + [SerializeField] private bool useAutoEyeBlink = true; + + [Header("눈 깜빡임 타이밍 설정")] + [Tooltip("눈 깜빡임 간격의 평균 시간 (초)")] + [Range(1f, 10f)] + [SerializeField] private float eyeBlinkMean = 2.5f; + + [Tooltip("평균에서의 최대 편차 (초)")] + [Range(0.5f, 5f)] + [SerializeField] private float eyeBlinkMaximumDeviation = 2f; + + [Tooltip("눈 깜빡임 시간 스케일")] + [Range(1f, 20f)] + [SerializeField] private float eyeBlinkTimescale = 10f; + + [Header("눈 깜빡임 동작 세부 설정")] + [Tooltip("눈을 감는 동작 시간 (초)")] + [Range(0.1f, 3f)] + [SerializeField] private float eyeBlinkClosingSeconds = 1.0f; + + [Tooltip("눈이 감긴 상태 지속 시간 (초)")] + [Range(0.1f, 2f)] + [SerializeField] private float eyeBlinkClosedSeconds = 0.5f; + + [Tooltip("눈을 여는 동작 시간 (초)")] + [Range(0.1f, 3f)] + [SerializeField] private float eyeBlinkOpeningSeconds = 1.5f; + // 캐릭터 기본정보 public string CharacterId => characterId; public string CharacterName => characterName; @@ -115,11 +154,23 @@ public class ActionMapping public List ActionMappings => actionMappings; // 세부 설정 - public bool IsLockAtActive => isLockAtActive; + public bool IsLookAtActive => isLookAtActive; public float LookSensitivity => lookSensitivity; - public float LockAtDamping => lockAtDamping; + [Obsolete("Use LookAtDamping instead.")] + public float LockAtDamping => lookAtDamping; + public float LookAtDamping => lookAtDamping; public float Gain => gain; public float Smoothing => smoothing; + public bool UseLipSync => useLipSync; + public bool UseAutoEyeBlink => useAutoEyeBlink; + + // 눈 깜빡임 설정 + public float EyeBlinkMean => eyeBlinkMean; + public float EyeBlinkMaximumDeviation => eyeBlinkMaximumDeviation; + public float EyeBlinkTimescale => eyeBlinkTimescale; + public float EyeBlinkClosingSeconds => eyeBlinkClosingSeconds; + public float EyeBlinkClosedSeconds => eyeBlinkClosedSeconds; + public float EyeBlinkOpeningSeconds => eyeBlinkOpeningSeconds; } } diff --git a/Assets/Domain/Character/Script/Config/ResolutionModelScaleConfig.cs b/Assets/Domain/Character/Script/Config/ResolutionModelScaleConfig.cs new file mode 100644 index 0000000..1b85f7e --- /dev/null +++ b/Assets/Domain/Character/Script/Config/ResolutionModelScaleConfig.cs @@ -0,0 +1,290 @@ +using UnityEngine; +using System; +using System.Collections.Generic; + +namespace ProjectVG.Domain.Character.Config +{ + /// + /// 해상도별 Live2D 모델 크기 조정 설정 + /// + [CreateAssetMenu(fileName = "ResolutionModelScaleConfig", menuName = "ProjectVG/Character/Resolution Model Scale Config")] + public class ResolutionModelScaleConfig : ScriptableObject + { + [Header("기본 설정")] + [SerializeField] private Vector2 referenceResolution = new Vector2(1920, 1080); + [SerializeField] private float referenceScale = 1.0f; + + public Vector2 ReferenceResolution => referenceResolution; + public float ReferenceScale => referenceScale; + + [Header("해상도별 스케일 설정")] + [SerializeField] private List scaleRules = new List(); + + [Header("자동 조정 설정")] + [SerializeField] private bool enableAutoScale = true; + [SerializeField] private float minScale = 0.5f; + [SerializeField] private float maxScale = 2.0f; + [SerializeField] private ScaleMode scaleMode = ScaleMode.HeightBased; + + /// + /// 현재 해상도에 맞는 스케일을 계산한다. + /// + public float CalculateScale() + { + if (!enableAutoScale) + { + return referenceScale; + } + + var currentResolution = new Vector2(Screen.width, Screen.height); + var scale = CalculateScaleForResolution(currentResolution); + var clampedScale = Mathf.Clamp(scale, minScale, maxScale); + + return clampedScale; + } + + /// + /// 특정 해상도에 대한 스케일을 계산한다. + /// + public float CalculateScaleForResolution(Vector2 resolution) + { + // 최적의 규칙을 찾기 + var bestRule = FindBestMatchingRule(resolution); + + if (bestRule != null) + { + return bestRule.Scale; + } + + // 자동 계산 + switch (scaleMode) + { + case ScaleMode.HeightBased: + return CalculateHeightBasedScale(resolution); + case ScaleMode.WidthBased: + return CalculateWidthBasedScale(resolution); + case ScaleMode.AspectRatioBased: + return CalculateAspectRatioBasedScale(resolution); + default: + return referenceScale; + } + } + + /// + /// 주어진 해상도에 가장 적합한 규칙을 찾는다. + /// + private ResolutionScaleRule FindBestMatchingRule(Vector2 resolution) + { + var matchingRules = new List(); + + // 조건을 만족하는 모든 규칙을 찾기 + foreach (var rule in scaleRules) + { + if (rule.Matches(resolution)) + { + matchingRules.Add(rule); + } + } + + if (matchingRules.Count == 0) + { + return null; + } + + if (matchingRules.Count == 1) + { + return matchingRules[0]; + } + + // 여러 규칙이 매칭되는 경우 최적의 규칙 선택 + return FindOptimalRule(resolution, matchingRules); + } + + /// + /// 여러 매칭 규칙 중에서 최적의 규칙을 선택한다. + /// + private ResolutionScaleRule FindOptimalRule(Vector2 resolution, List matchingRules) + { + ResolutionScaleRule bestRule = null; + float bestScore = float.MaxValue; + + foreach (var rule in matchingRules) + { + var score = CalculateRuleFitnessScore(resolution, rule); + + if (score < bestScore) + { + bestScore = score; + bestRule = rule; + } + } + + return bestRule; + } + + /// + /// 규칙의 적합도를 계산한다. 점수가 낮을수록 더 적합함. + /// + private float CalculateRuleFitnessScore(Vector2 resolution, ResolutionScaleRule rule) + { + var ruleCenterWidth = (rule.MinWidth + rule.MaxWidth) / 2f; + var ruleCenterHeight = (rule.MinHeight + rule.MaxHeight) / 2f; + + var widthDistance = Mathf.Abs(resolution.x - ruleCenterWidth); + var heightDistance = Mathf.Abs(resolution.y - ruleCenterHeight); + + var distance = Mathf.Sqrt(widthDistance * widthDistance + heightDistance * heightDistance); + + var ruleWidthRange = rule.MaxWidth - rule.MinWidth; + var ruleHeightRange = rule.MaxHeight - rule.MinHeight; + var ruleArea = ruleWidthRange * ruleHeightRange; + + var specificityBonus = Mathf.Max(0, 1000f - ruleArea) / 1000f; + + var finalScore = distance - specificityBonus; + + return finalScore; + } + + /// + /// 높이 기반 스케일 계산 + /// + private float CalculateHeightBasedScale(Vector2 resolution) + { + float heightRatio = resolution.y / referenceResolution.y; + return referenceScale * heightRatio; + } + + /// + /// 너비 기반 스케일 계산 + /// + private float CalculateWidthBasedScale(Vector2 resolution) + { + float widthRatio = resolution.x / referenceResolution.x; + return referenceScale * widthRatio; + } + + /// + /// 종횡비 기반 스케일 계산 + /// + private float CalculateAspectRatioBasedScale(Vector2 resolution) + { + float currentAspect = resolution.x / resolution.y; + float referenceAspect = referenceResolution.x / referenceResolution.y; + float aspectRatio = currentAspect / referenceAspect; + + // 종횡비가 크면 너비 기준, 작으면 높이 기준 + if (aspectRatio > 1.0f) + { + return CalculateWidthBasedScale(resolution); + } + else + { + return CalculateHeightBasedScale(resolution); + } + } + + /// + /// 현재 해상도 정보를 반환한다. + /// + public ResolutionInfo GetCurrentResolutionInfo() + { + var resolution = new Vector2(Screen.width, Screen.height); + var scale = CalculateScale(); + + return new ResolutionInfo + { + Resolution = resolution, + Scale = scale, + AspectRatio = resolution.x / resolution.y, + IsMobile = IsMobilePlatform(resolution) + }; + } + + /// + /// 해상도를 기반으로 모바일 플랫폼인지 판단한다. + /// + private bool IsMobilePlatform(Vector2 resolution) + { + // Unity 에디터에서도 모바일 해상도로 테스트할 수 있도록 해상도 기반 판단 + var aspectRatio = resolution.x / resolution.y; + + // 세로 모드 (종횡비 < 1)이고 높이가 1000px 이상이면 모바일로 판단 + if (aspectRatio < 1.0f && resolution.y >= 1000f) + { + return true; + } + + // Unity의 기본 플랫폼 감지도 함께 사용 + return Application.isMobilePlatform; + } + + /// + /// 스케일 모드 + /// + public enum ScaleMode + { + HeightBased, // 높이 기준 + WidthBased, // 너비 기준 + AspectRatioBased // 종횡비 기준 + } + + /// + /// 해상도별 스케일 규칙 + /// + [Serializable] + public class ResolutionScaleRule + { + [Header("해상도 조건")] + [SerializeField] private int minWidth = 0; + [SerializeField] private int maxWidth = 9999; + [SerializeField] private int minHeight = 0; + [SerializeField] private int maxHeight = 9999; + [SerializeField] private bool isMobileOnly = false; + + [Header("스케일 설정")] + [SerializeField] private float scale = 1.0f; + [SerializeField] private string description = ""; + + public float Scale => scale; + public string Description => description; + public int MinWidth => minWidth; + public int MaxWidth => maxWidth; + public int MinHeight => minHeight; + public int MaxHeight => maxHeight; + + /// + /// 해상도가 이 규칙에 맞는지 확인한다. + /// + public bool Matches(Vector2 resolution) + { + // 모바일 전용 규칙인 경우 해상도 기반으로도 판단 + if (isMobileOnly) + { + var aspectRatio = resolution.x / resolution.y; + var isMobileByResolution = aspectRatio < 1.0f && resolution.y >= 1000f; + + if (!Application.isMobilePlatform && !isMobileByResolution) + { + return false; + } + } + + return resolution.x >= minWidth && resolution.x <= maxWidth && + resolution.y >= minHeight && resolution.y <= maxHeight; + } + } + + /// + /// 해상도 정보 + /// + [Serializable] + public class ResolutionInfo + { + public Vector2 Resolution; + public float Scale; + public float AspectRatio; + public bool IsMobile; + } + } +} diff --git a/Assets/Domain/Character/Script/Config/ResolutionModelScaleConfig.cs.meta b/Assets/Domain/Character/Script/Config/ResolutionModelScaleConfig.cs.meta new file mode 100644 index 0000000..5aa6c94 --- /dev/null +++ b/Assets/Domain/Character/Script/Config/ResolutionModelScaleConfig.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2f1d81d7191952e419c1237b0fe91ebb \ No newline at end of file diff --git a/Assets/Domain/Character/Script/Controller/ActionController.cs b/Assets/Domain/Character/Script/Controller/ActionController.cs deleted file mode 100644 index f87b4c2..0000000 --- a/Assets/Domain/Character/Script/Controller/ActionController.cs +++ /dev/null @@ -1,20 +0,0 @@ -using UnityEngine; - -namespace ProjectVG.Domain.Character.Service -{ - /// - /// 행동 → 모션/파라미터 트리거를 구현하는 컨트롤러의 스켈레톤. - /// - public class ActionController : MonoBehaviour, IActionController - { - public void Initialize() - { - } - - public void TriggerAction(string action, object args = null) - { - } - } -} - - diff --git a/Assets/Domain/Character/Script/Controller/ActionController.cs.meta b/Assets/Domain/Character/Script/Controller/ActionController.cs.meta deleted file mode 100644 index 1cc9337..0000000 --- a/Assets/Domain/Character/Script/Controller/ActionController.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 7ea94bb5086c84841b65fdc5cf3c652b \ No newline at end of file diff --git a/Assets/Domain/Character/Script/Controller/DTOs.cs b/Assets/Domain/Character/Script/Controller/DTOs.cs deleted file mode 100644 index 4816e2d..0000000 --- a/Assets/Domain/Character/Script/Controller/DTOs.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ProjectVG.Domain.Character.Service -{ - /// - /// 감정/행동 데이터 전달을 위한 단순 DTO. - /// - public struct EmotionData { public string Emotion; public float Intensity; public int DurationMs; } - public struct ActionData { public string Action; public object Args; } -} - - diff --git a/Assets/Domain/Character/Script/Controller/DTOs.cs.meta b/Assets/Domain/Character/Script/Controller/DTOs.cs.meta deleted file mode 100644 index 266eeca..0000000 --- a/Assets/Domain/Character/Script/Controller/DTOs.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 91104183254896440a378f856ed66e43 \ No newline at end of file diff --git a/Assets/Domain/Character/Script/Controller/EmotionController.cs b/Assets/Domain/Character/Script/Controller/EmotionController.cs deleted file mode 100644 index 16ef56a..0000000 --- a/Assets/Domain/Character/Script/Controller/EmotionController.cs +++ /dev/null @@ -1,24 +0,0 @@ -using UnityEngine; - -namespace ProjectVG.Domain.Character.Service -{ - /// - /// 감정 → Expression 맵핑과 블렌딩을 구현하는 컨트롤러의 스켈레톤. - /// - public class EmotionController : MonoBehaviour, IEmotionController - { - public void Initialize() - { - } - - public void SetEmotion(string emotion, float intensity, int durationMs) - { - } - - public void ClearEmotion() - { - } - } -} - - diff --git a/Assets/Domain/Character/Script/Controller/EmotionController.cs.meta b/Assets/Domain/Character/Script/Controller/EmotionController.cs.meta deleted file mode 100644 index af39ad2..0000000 --- a/Assets/Domain/Character/Script/Controller/EmotionController.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: d1977c0cf1b32534daaeb00c3f777fe8 \ No newline at end of file diff --git a/Assets/Domain/Character/Script/Controller/IActionController.cs b/Assets/Domain/Character/Script/Controller/IActionController.cs deleted file mode 100644 index df24f71..0000000 --- a/Assets/Domain/Character/Script/Controller/IActionController.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace ProjectVG.Domain.Character.Service -{ - /// - /// 행동 → 모션/파라미터 트리거를 담당한다. - /// - public interface IActionController - { - void Initialize(); - void TriggerAction(string action, object args = null); - } -} - - diff --git a/Assets/Domain/Character/Script/Controller/IActionController.cs.meta b/Assets/Domain/Character/Script/Controller/IActionController.cs.meta deleted file mode 100644 index a8816b8..0000000 --- a/Assets/Domain/Character/Script/Controller/IActionController.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 2d7f3153340a3c444996d299a6f29f3a \ No newline at end of file diff --git a/Assets/Domain/Character/Script/Controller/IEmotionController.cs b/Assets/Domain/Character/Script/Controller/IEmotionController.cs deleted file mode 100644 index 3232beb..0000000 --- a/Assets/Domain/Character/Script/Controller/IEmotionController.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace ProjectVG.Domain.Character.Service -{ - /// - /// 감정 → Expression 맵핑과 블렌딩을 담당한다. - /// - public interface IEmotionController - { - void Initialize(); - void SetEmotion(string emotion, float intensity, int durationMs); - void ClearEmotion(); - } -} - - diff --git a/Assets/Domain/Character/Script/Controller/IEmotionController.cs.meta b/Assets/Domain/Character/Script/Controller/IEmotionController.cs.meta deleted file mode 100644 index b4e8673..0000000 --- a/Assets/Domain/Character/Script/Controller/IEmotionController.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 7ef1b6519703d4244af92ace857a98ce \ No newline at end of file diff --git a/Assets/Domain/Character/Script/Controller/ILive2DModelApplier.cs b/Assets/Domain/Character/Script/Controller/ILive2DModelApplier.cs deleted file mode 100644 index 6420840..0000000 --- a/Assets/Domain/Character/Script/Controller/ILive2DModelApplier.cs +++ /dev/null @@ -1,12 +0,0 @@ -using UnityEngine; - -namespace ProjectVG.Domain.Character.Service -{ - public interface ILive2DModelApplier - { - /** 활성 모델과 구성에 대해 LookAt, LipSync, 썸네일 등 시각 설정을 적용한다. */ - void Apply(GameObject activeModel, ProjectVG.Domain.Character.Live2D.Model.Live2DModelConfig characterConfig); - } -} - - diff --git a/Assets/Domain/Character/Script/Controller/ILive2DModelApplier.cs.meta b/Assets/Domain/Character/Script/Controller/ILive2DModelApplier.cs.meta deleted file mode 100644 index 29399d9..0000000 --- a/Assets/Domain/Character/Script/Controller/ILive2DModelApplier.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 2234415ca6f22a14e9e1db4b6ca01091 \ No newline at end of file diff --git a/Assets/Domain/Character/Script/Controller/Live2DModelApplier.cs b/Assets/Domain/Character/Script/Controller/Live2DModelApplier.cs deleted file mode 100644 index 43dd699..0000000 --- a/Assets/Domain/Character/Script/Controller/Live2DModelApplier.cs +++ /dev/null @@ -1,13 +0,0 @@ -using UnityEngine; - -namespace ProjectVG.Domain.Character.Service -{ - public class Live2DModelApplier : MonoBehaviour, ILive2DModelApplier - { - public void Apply(GameObject activeModel, ProjectVG.Domain.Character.Live2D.Model.Live2DModelConfig characterConfig) - { - } - } -} - - diff --git a/Assets/Domain/Character/Script/Controller/Live2DModelApplier.cs.meta b/Assets/Domain/Character/Script/Controller/Live2DModelApplier.cs.meta deleted file mode 100644 index 132d5ba..0000000 --- a/Assets/Domain/Character/Script/Controller/Live2DModelApplier.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 599c8846d73d1244a80788addacb4285 \ No newline at end of file diff --git a/Assets/Domain/Character/Script/Controller/Live2DParameterController.cs b/Assets/Domain/Character/Script/Controller/Live2DParameterController.cs deleted file mode 100644 index a0877c1..0000000 --- a/Assets/Domain/Character/Script/Controller/Live2DParameterController.cs +++ /dev/null @@ -1,24 +0,0 @@ -using UnityEngine; - -namespace ProjectVG.Domain.Character.Service -{ - /// - /// Live2D 파라미터를 직접 제어하거나 프리셋을 적용하는 컨트롤러의 스켈레톤. - /// - public class Live2DParameterController : MonoBehaviour - { - public void Initialize() - { - } - - public void ApplyPreset(string presetKey) - { - } - - public void SetParameter(string parameterId, float value) - { - } - } -} - - diff --git a/Assets/Domain/Character/Script/Controller/Live2DParameterController.cs.meta b/Assets/Domain/Character/Script/Controller/Live2DParameterController.cs.meta deleted file mode 100644 index 6e1f953..0000000 --- a/Assets/Domain/Character/Script/Controller/Live2DParameterController.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 2c84ef8b52117614e806503794e80e87 \ No newline at end of file diff --git a/Assets/Domain/Character/Script/Manager/ILive2DCharacterManager.cs b/Assets/Domain/Character/Script/Manager/ILive2DCharacterManager.cs deleted file mode 100644 index 9e3f518..0000000 --- a/Assets/Domain/Character/Script/Manager/ILive2DCharacterManager.cs +++ /dev/null @@ -1,17 +0,0 @@ -using UnityEngine; - -namespace ProjectVG.Domain.Character.Service -{ - /// - /// 모델 전반의 상태를 단일 진입점에서 관리한다. - /// - public interface ILive2DModelManagerFacade - { - void Initialize(); - void ApplyReaction(EmotionData emotionData, ActionData actionData); - void OnVoiceStarted(); - void OnVoiceFinished(); - } -} - - diff --git a/Assets/Domain/Character/Script/Manager/ILive2DCharacterManager.cs.meta b/Assets/Domain/Character/Script/Manager/ILive2DCharacterManager.cs.meta deleted file mode 100644 index 3fd378f..0000000 --- a/Assets/Domain/Character/Script/Manager/ILive2DCharacterManager.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: ea75b23f7c651a24c9e0055ae07e8234 \ No newline at end of file diff --git a/Assets/Domain/Character/Script/Manager/Live2DCharacterManager.cs b/Assets/Domain/Character/Script/Manager/Live2DCharacterManager.cs deleted file mode 100644 index f11edc4..0000000 --- a/Assets/Domain/Character/Script/Manager/Live2DCharacterManager.cs +++ /dev/null @@ -1,28 +0,0 @@ -using UnityEngine; - -namespace ProjectVG.Domain.Character.Service -{ - /// - /// 상위 조정자. 모델 관리자/파라미터 컨트롤러를 묶어 감정/행동 반응을 일관되게 적용한다. - /// - public class Live2DModelManagerFacade : MonoBehaviour, ILive2DModelManagerFacade - { - public void Initialize() - { - } - - public void ApplyReaction(EmotionData emotionData, ActionData actionData) - { - } - - public void OnVoiceStarted() - { - } - - public void OnVoiceFinished() - { - } - } -} - - diff --git a/Assets/Domain/Character/Script/Manager/Live2DCharacterManager.cs.meta b/Assets/Domain/Character/Script/Manager/Live2DCharacterManager.cs.meta deleted file mode 100644 index c06ba09..0000000 --- a/Assets/Domain/Character/Script/Manager/Live2DCharacterManager.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: aeee24d07701ad747a92d82afe08caa6 \ No newline at end of file diff --git a/Assets/Domain/Character/Script/Manager/Live2DModelManager.cs b/Assets/Domain/Character/Script/Manager/Live2DModelManager.cs deleted file mode 100644 index 8e390b0..0000000 --- a/Assets/Domain/Character/Script/Manager/Live2DModelManager.cs +++ /dev/null @@ -1,377 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; -using Cysharp.Threading.Tasks; -using ProjectVG.Domain.Character.Live2D.Model; -using Live2D.Cubism.Framework.LookAt; -using Live2D.Cubism.Framework.MouthMovement; - -namespace ProjectVG.Domain.Character.Service -{ - public class Live2DModelManager : Singleton - { - [Header("설정")] - [SerializeField] private Transform _modelRoot; - [SerializeField] private Live2DModelRegistry _modelRegistry; - - private readonly Dictionary _characterIdToInstance = new Dictionary(); - private string _activeCharacterId; - - protected override void Awake() - { - base.Awake(); - } - - #region Public Methods - - /// - /// 캐릭터 모델을 로드하고 설정을 적용한다. - /// - public GameObject LoadCharacter(string characterId, bool activateImmediately = false) - { - if (string.IsNullOrEmpty(characterId)) - { - Debug.LogWarning("[Live2DModelManager] 캐릭터 ID가 null입니다."); - return null; - } - - // 이미 로드된 모델이 있는지 확인 - if (_characterIdToInstance.TryGetValue(characterId, out var existing)) - { - Debug.Log($"[Live2DModelManager] 캐릭터 '{characterId}'가 이미 로드되어 있습니다."); - if (activateImmediately) - { - ActivateCharacter(characterId); - } - return existing; - } - - // 설정 가져오기 - var config = GetCharacterConfig(characterId); - if (config == null) - { - Debug.LogError($"[Live2DModelManager] 캐릭터 '{characterId}'의 설정을 찾을 수 없습니다."); - return null; - } - - // 모델 인스턴스 생성 - var instance = CreateModelInstance(config, characterId); - if (instance == null) - { - return null; - } - - // 설정 적용 - ApplyCharacterConfig(instance, config); - - // 딕셔너리에 저장 - _characterIdToInstance[characterId] = instance; - - // 즉시 활성화 옵션 - if (activateImmediately) - { - ActivateCharacter(characterId); - } - - Debug.Log($"[Live2DModelManager] 캐릭터 '{characterId}' 로드 완료"); - return instance; - } - - /// - /// 캐릭터 모델을 비동기로 로드하고 설정을 적용한다. - /// - public async UniTask LoadCharacterAsync(string characterId, bool activateImmediately = false) - { - if (string.IsNullOrEmpty(characterId)) - { - Debug.LogWarning("[Live2DModelManager] 캐릭터 ID가 null입니다."); - return null; - } - - // 이미 로드된 모델이 있는지 확인 - if (_characterIdToInstance.TryGetValue(characterId, out var existing)) - { - Debug.Log($"[Live2DModelManager] 캐릭터 '{characterId}'가 이미 로드되어 있습니다."); - if (activateImmediately) - { - ActivateCharacter(characterId); - } - return existing; - } - - // 설정 가져오기 - var config = GetCharacterConfig(characterId); - if (config == null) - { - Debug.LogError($"[Live2DModelManager] 캐릭터 '{characterId}'의 설정을 찾을 수 없습니다."); - return null; - } - - // 모델 인스턴스 생성 (비동기) - var instance = await CreateModelInstanceAsync(config, characterId); - if (instance == null) - { - return null; - } - - // 설정 적용 - ApplyCharacterConfig(instance, config); - - // 딕셔너리에 저장 - _characterIdToInstance[characterId] = instance; - - // 즉시 활성화 옵션 - if (activateImmediately) - { - ActivateCharacter(characterId); - } - - Debug.Log($"[Live2DModelManager] 캐릭터 '{characterId}' 비동기 로드 완료"); - return instance; - } - - /// - /// 캐릭터를 활성화한다. - /// - public void ActivateCharacter(string characterId) - { - if (!_characterIdToInstance.TryGetValue(characterId, out var target)) - { - Debug.LogWarning($"[Live2DModelManager] 캐릭터 '{characterId}'가 로드되지 않았습니다."); - return; - } - - // 모든 모델 비활성화 - DeactivateAllModels(); - - // 대상 모델 활성화 - target.SetActive(true); - _activeCharacterId = characterId; - - Debug.Log($"[Live2DModelManager] 캐릭터 '{characterId}' 활성화"); - } - - /// - /// 현재 활성 캐릭터를 반환한다. - /// - public GameObject GetActiveCharacter() - { - if (string.IsNullOrEmpty(_activeCharacterId)) - { - return null; - } - - _characterIdToInstance.TryGetValue(_activeCharacterId, out var character); - return character; - } - - /// - /// 캐릭터가 로드되어 있는지 확인한다. - /// - public bool HasCharacter(string characterId) - { - return !string.IsNullOrEmpty(characterId) && _characterIdToInstance.ContainsKey(characterId); - } - - /// - /// 캐릭터를 언로드한다. - /// - public void UnloadCharacter(string characterId) - { - if (!_characterIdToInstance.TryGetValue(characterId, out var instance)) - { - return; - } - - if (instance != null) - { - Destroy(instance); - } - - _characterIdToInstance.Remove(characterId); - - // 현재 활성 캐릭터였다면 활성 ID 초기화 - if (_activeCharacterId == characterId) - { - _activeCharacterId = null; - } - - Debug.Log($"[Live2DModelManager] 캐릭터 '{characterId}' 언로드 완료"); - } - - /// - /// 모든 캐릭터를 언로드한다. - /// - public void UnloadAllCharacters() - { - foreach (var kvp in _characterIdToInstance) - { - if (kvp.Value != null) - { - Destroy(kvp.Value); - } - } - - _characterIdToInstance.Clear(); - _activeCharacterId = null; - - Debug.Log("[Live2DModelManager] 모든 캐릭터 언로드 완료"); - } - - /// - /// 활성 캐릭터의 가시성을 설정한다. - /// - public void SetCharacterVisibility(bool isVisible) - { - var active = GetActiveCharacter(); - if (active == null) - { - Debug.LogWarning("[Live2DModelManager] 활성 캐릭터가 없습니다."); - return; - } - - active.SetActive(isVisible); - } - - #endregion - - #region Private Methods - - /// - /// 캐릭터 설정을 가져온다. - /// - private Live2DModelConfig GetCharacterConfig(string characterId) - { - // 레지스트리에서 먼저 찾기 - if (_modelRegistry != null && _modelRegistry.TryGetConfig(characterId, out var config)) - { - return config; - } - - // 설정을 찾을 수 없음 - Debug.LogError($"[Live2DModelManager] 캐릭터 '{characterId}'의 설정을 찾을 수 없습니다. 레지스트리를 확인해주세요."); - return null; - } - - /// - /// 모델 인스턴스를 생성한다. - /// - private GameObject CreateModelInstance(Live2DModelConfig config, string characterId) - { - if (config.CharacterPrefab == null) - { - Debug.LogError($"[Live2DModelManager] 캐릭터 '{characterId}'의 프리팹이 null입니다."); - return null; - } - - var parent = _modelRoot != null ? _modelRoot : transform; - var instance = Instantiate(config.CharacterPrefab, parent); - instance.name = characterId; - instance.SetActive(false); - - return instance; - } - - /// - /// 모델 인스턴스를 비동기로 생성한다. - /// - private async UniTask CreateModelInstanceAsync(Live2DModelConfig config, string characterId) - { - if (config.CharacterPrefab == null) - { - Debug.LogError($"[Live2DModelManager] 캐릭터 '{characterId}'의 프리팹이 null입니다."); - return null; - } - - var parent = _modelRoot != null ? _modelRoot : transform; - var instance = Instantiate(config.CharacterPrefab, parent); - instance.name = characterId; - instance.SetActive(false); - - // 비동기 작업을 위한 지연 (필요시) - await UniTask.Yield(); - - return instance; - } - - /// - /// 캐릭터 설정을 적용한다. - /// - private void ApplyCharacterConfig(GameObject character, Live2DModelConfig config) - { - if (character == null || config == null) - { - return; - } - - ApplyLookAtSettings(character, config); - ApplyLipSyncSettings(character, config); - InitializeHitHandler(character); - } - - /// - /// 시선 추적 설정을 적용한다. - /// - private void ApplyLookAtSettings(GameObject character, Live2DModelConfig config) - { - var lookController = character.GetComponent(); - if (lookController == null) - { - Debug.LogWarning($"[Live2DModelManager] CubismLookController를 찾을 수 없습니다: {character.name}"); - return; - } - - lookController.Target = null; // TODO: 시선 타겟 설정 필요 - lookController.Damping = config.LockAtDamping; - } - - /// - /// 립싱크 설정을 적용한다. - /// - private void ApplyLipSyncSettings(GameObject character, Live2DModelConfig config) - { - var mouthInput = character.GetComponent(); - if (mouthInput == null) - { - Debug.LogWarning($"[Live2DModelManager] CubismAudioMouthInput을 찾을 수 없습니다: {character.name}"); - return; - } - - mouthInput.AudioInput = null; // TODO: 오디오 소스 설정 필요 - mouthInput.Gain = config.Gain; - mouthInput.Smoothing = config.Smoothing; - } - - /// - /// 터치 핸들러를 초기화한다. - /// - private void InitializeHitHandler(GameObject character) - { - var hitHandler = character.GetComponent(); - if (hitHandler == null) - { - Debug.LogWarning($"[Live2DModelManager] CubismHitHandler를 찾을 수 없습니다: {character.name}"); - return; - } - - hitHandler.Initialize(); - } - - /// - /// 모든 모델을 비활성화한다. - /// - private void DeactivateAllModels() - { - foreach (var kvp in _characterIdToInstance) - { - if (kvp.Value != null) - { - kvp.Value.SetActive(false); - } - } - } - - #endregion - } -} - - diff --git a/Assets/Domain/Character/Script/Manager/Live2DModelManager.cs.meta b/Assets/Domain/Character/Script/Manager/Live2DModelManager.cs.meta deleted file mode 100644 index 58c5d74..0000000 --- a/Assets/Domain/Character/Script/Manager/Live2DModelManager.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: bbd7bfb36017db743b3b7cab74183970 \ No newline at end of file diff --git a/Assets/Domain/Character/Script/Test/Live2DModelTest.cs b/Assets/Domain/Character/Script/Test/Live2DModelTest.cs deleted file mode 100644 index 94be95e..0000000 --- a/Assets/Domain/Character/Script/Test/Live2DModelTest.cs +++ /dev/null @@ -1,180 +0,0 @@ -using UnityEngine; -using UnityEngine.UI; -using ProjectVG.Domain.Character.Service; -using ProjectVG.Domain.Character.Live2D.Model; - -namespace ProjectVG.Domain.Character.Test -{ - /// - /// Live2DModelManager 테스트를 위한 컴포넌트 - /// - public class Live2DModelTest : MonoBehaviour - { - [Header("테스트 설정")] - [SerializeField] private string _testCharacterId = "test_character"; - - [Header("UI 요소")] - [SerializeField] private Button _loadCharacterBtn; - [SerializeField] private Button _activateCharacterBtn; - [SerializeField] private Button _unloadCharacterBtn; - [SerializeField] private Button _unloadAllBtn; - [SerializeField] private Button _changeVisibilityBtn; - [SerializeField] private InputField _characterIdInput; - - [Header("테스트 결과")] - [SerializeField] private Text _statusText; - - private bool _isCharacterVisible = true; - - private void Start() - { - SetupUI(); - - // Live2DModelManager 초기화 확인 - if (Live2DModelManager.Instance != null) - { - UpdateStatus("Live2DModelManager 준비 완료"); - } - else - { - UpdateStatus("Live2DModelManager 초기화 실패"); - } - } - - /// - /// UI 요소들을 설정한다. - /// - private void SetupUI() - { - if (_loadCharacterBtn != null) - { - _loadCharacterBtn.onClick.RemoveAllListeners(); - _loadCharacterBtn.onClick.AddListener(OnLoadCharacterClicked); - } - - if (_activateCharacterBtn != null) - { - _activateCharacterBtn.onClick.RemoveAllListeners(); - _activateCharacterBtn.onClick.AddListener(OnActivateCharacterClicked); - } - - if (_unloadCharacterBtn != null) - { - _unloadCharacterBtn.onClick.RemoveAllListeners(); - _unloadCharacterBtn.onClick.AddListener(OnUnloadCharacterClicked); - } - - if (_unloadAllBtn != null) - { - _unloadAllBtn.onClick.RemoveAllListeners(); - _unloadAllBtn.onClick.AddListener(OnUnloadAllClicked); - } - - if (_changeVisibilityBtn != null) - { - _changeVisibilityBtn.onClick.RemoveAllListeners(); - _changeVisibilityBtn.onClick.AddListener(OnChangeVisibilityClicked); - } - - if (_characterIdInput != null) - { - _characterIdInput.text = _testCharacterId; - _characterIdInput.onValueChanged.AddListener(OnCharacterIdChanged); - } - - UpdateStatus("테스트 준비 완료"); - } - - /// - /// 캐릭터 로드 버튼 클릭 시 호출된다. - /// - private async void OnLoadCharacterClicked() - { - UpdateStatus($"캐릭터 로드 시작: {_testCharacterId}"); - - var character = await Live2DModelManager.Instance.LoadCharacterAsync(_testCharacterId, activateImmediately: true); - if (character != null) - { - UpdateStatus($"캐릭터 로드 및 활성화 완료: {_testCharacterId}"); - } - else - { - UpdateStatus($"캐릭터 로드 실패: {_testCharacterId}"); - } - } - - /// - /// 캐릭터 활성화 버튼 클릭 시 호출된다. - /// - private void OnActivateCharacterClicked() - { - Live2DModelManager.Instance.ActivateCharacter(_testCharacterId); - UpdateStatus($"캐릭터 활성화: {_testCharacterId}"); - } - - /// - /// 캐릭터 언로드 버튼 클릭 시 호출된다. - /// - private void OnUnloadCharacterClicked() - { - Live2DModelManager.Instance.UnloadCharacter(_testCharacterId); - UpdateStatus($"캐릭터 언로드 완료: {_testCharacterId}"); - } - - /// - /// 모든 캐릭터 언로드 버튼 클릭 시 호출된다. - /// - private void OnUnloadAllClicked() - { - Live2DModelManager.Instance.UnloadAllCharacters(); - UpdateStatus("모든 캐릭터 언로드 완료"); - } - - /// - /// 가시성 변경 버튼 클릭 시 호출된다. - /// - private void OnChangeVisibilityClicked() - { - _isCharacterVisible = !_isCharacterVisible; - Live2DModelManager.Instance.SetCharacterVisibility(_isCharacterVisible); - UpdateStatus($"캐릭터 가시성 변경: {(_isCharacterVisible ? "보임" : "숨김")}"); - } - - /// - /// 캐릭터 ID 입력 변경 시 호출된다. - /// - private void OnCharacterIdChanged(string newId) - { - _testCharacterId = newId; - } - - /// - /// 상태 텍스트를 업데이트한다. - /// - private void UpdateStatus(string message) - { - Debug.Log($"[Live2DModelTest] {message}"); - - if (_statusText != null) - { - _statusText.text = message; - } - } - - /// - /// 현재 상태 정보를 출력한다. - /// - [ContextMenu("현재 상태 출력")] - private void PrintCurrentStatus() - { - var activeCharacter = Live2DModelManager.Instance.GetActiveCharacter(); - var hasCharacter = Live2DModelManager.Instance.HasCharacter(_testCharacterId); - - Debug.Log($"[Live2DModelTest] 현재 상태:"); - Debug.Log($" - 테스트 캐릭터 ID: {_testCharacterId}"); - Debug.Log($" - 캐릭터 로드 여부: {hasCharacter}"); - Debug.Log($" - 활성 캐릭터: {(activeCharacter != null ? activeCharacter.name : "없음")}"); - } - } -} - diff --git a/Assets/Domain/Character/Script/Test/Live2DModelTest.cs.meta b/Assets/Domain/Character/Script/Test/Live2DModelTest.cs.meta deleted file mode 100644 index d21d899..0000000 --- a/Assets/Domain/Character/Script/Test/Live2DModelTest.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 7fb1442983837c140abae42a85733fe6 \ No newline at end of file diff --git a/Assets/Domain/Character/Script/Test/TestVoice.cs b/Assets/Domain/Character/Script/Test/TestVoice.cs deleted file mode 100644 index 4cefb39..0000000 --- a/Assets/Domain/Character/Script/Test/TestVoice.cs +++ /dev/null @@ -1,14 +0,0 @@ -using UnityEngine; - -[RequireComponent(typeof(AudioSource))] -public class TestVoice : MonoBehaviour -{ - [SerializeField] private AudioClip audioClip; - private AudioSource _audioSource; - - void Awake() - { - _audioSource = GetComponent(); - _audioSource.clip = audioClip; - } -} diff --git a/Assets/Domain/Character/Script/Test/TestVoice.cs.meta b/Assets/Domain/Character/Script/Test/TestVoice.cs.meta deleted file mode 100644 index 2abfbe6..0000000 --- a/Assets/Domain/Character/Script/Test/TestVoice.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: ff42adec7c6fd2f4f972f5cffb445dc2 \ No newline at end of file diff --git a/Assets/Domain/Chat/Model/Actor.cs b/Assets/Domain/Chat/Model/Actor.cs index a3a0f48..64305f8 100644 --- a/Assets/Domain/Chat/Model/Actor.cs +++ b/Assets/Domain/Chat/Model/Actor.cs @@ -5,4 +5,4 @@ public enum Actor User, Character } -} \ No newline at end of file +} diff --git a/Assets/Domain/Chat/Model/CharacterActionData.cs b/Assets/Domain/Chat/Model/CharacterActionData.cs new file mode 100644 index 0000000..816e0ba --- /dev/null +++ b/Assets/Domain/Chat/Model/CharacterActionData.cs @@ -0,0 +1,57 @@ +#nullable enable +using UnityEngine; +using ProjectVG.Domain.Character.Service; + +namespace ProjectVG.Domain.Chat.Model +{ + public class CharacterActionData + { + /// + /// 캐릭터가 수행할 행동 타입 + /// + public CharacterActionType ActionType { get; set; } = CharacterActionType.Talk; + + /// + /// 액션 타입으로 초기화 + /// + /// 액션 타입 + public CharacterActionData(CharacterActionType actionType = CharacterActionType.Talk) + { + ActionType = actionType; + } + + /// + /// 행동 문자열로 초기화 + /// + /// 행동 문자열 + public CharacterActionData(string? action = null) + { + ActionType = ParseActionString(action); + } + + /// + /// 행동이 설정되어 있는지 확인 + /// + /// 행동이 설정되어 있으면 true + public bool HasAction() => true; + + /// + /// 문자열을 액션 타입으로 파싱 + /// + /// 액션 문자열 + /// 파싱된 액션 타입 + private CharacterActionType ParseActionString(string? actionString) + { + if (string.IsNullOrEmpty(actionString)) + return CharacterActionType.Talk; + + return actionString.ToLower() switch + { + "idle" => CharacterActionType.Idle, + "listen" => CharacterActionType.Listen, + "talk" => CharacterActionType.Talk, + _ => CharacterActionType.Idle + }; + } + } +} \ No newline at end of file diff --git a/Assets/Domain/Chat/Model/CharacterActionData.cs.meta b/Assets/Domain/Chat/Model/CharacterActionData.cs.meta new file mode 100644 index 0000000..2981f77 --- /dev/null +++ b/Assets/Domain/Chat/Model/CharacterActionData.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 333379fbd87c0304db4ae39689691795 \ No newline at end of file diff --git a/Assets/Domain/Chat/Model/ChatMessage.cs b/Assets/Domain/Chat/Model/ChatMessage.cs index 136d278..e70e31e 100644 --- a/Assets/Domain/Chat/Model/ChatMessage.cs +++ b/Assets/Domain/Chat/Model/ChatMessage.cs @@ -1,6 +1,5 @@ #nullable enable using System; -using System.Collections.Generic; using UnityEngine; using ProjectVG.Infrastructure.Network.DTOs.Chat; @@ -12,17 +11,17 @@ public class ChatMessage public string SessionId { get; set; } = string.Empty; public string? Text { get; set; } public VoiceData? VoiceData { get; set; } + public CharacterActionData? ActionData { get; set; } + public CostInfo? CostInfo { get; set; } public DateTime Timestamp { get; set; } = DateTime.UtcNow; - public Dictionary? Metadata { get; set; } public static ChatMessage FromChatResponse(ChatResponse response) { - var chatMessage = new ChatMessage - { + var chatMessage = new ChatMessage { SessionId = response.SessionId, Text = response.Text, Timestamp = response.Timestamp, - Metadata = response.Metadata + ActionData = new CharacterActionData(response.Action) }; if (!string.IsNullOrEmpty(response.AudioData)) @@ -30,13 +29,23 @@ public static ChatMessage FromChatResponse(ChatResponse response) chatMessage.VoiceData = VoiceData.FromBase64(response.AudioData, response.AudioFormat); } + if ((response.UsedCost ?? 0) > 0 || (response.RemainingCost ?? 0) > 0) + { + chatMessage.CostInfo = new CostInfo(response.UsedCost ?? 0f, response.RemainingCost ?? 0f); + } + return chatMessage; } public bool HasVoiceData() => VoiceData != null && VoiceData.IsPlayable(); public bool HasTextData() => !string.IsNullOrEmpty(Text); + + public bool HasActionData() => ActionData != null && ActionData.HasAction(); + + public bool HasCostInfo() => CostInfo != null && CostInfo.HasCostInfo(); public AudioClip? GetAudioClip() => VoiceData?.AudioClip; + } } \ No newline at end of file diff --git a/Assets/Domain/Chat/Model/CostInfo.cs b/Assets/Domain/Chat/Model/CostInfo.cs new file mode 100644 index 0000000..65811c1 --- /dev/null +++ b/Assets/Domain/Chat/Model/CostInfo.cs @@ -0,0 +1,31 @@ +#nullable enable +using System; + +namespace ProjectVG.Domain.Chat.Model +{ + + [Serializable] + public class CostInfo + { + + public float UsedCost { get; set; } = 0f; + + public float RemainingCost { get; set; } = 0f; + + public CostInfo() { } + + public CostInfo(float usedCost, float remainingCost) + { + UsedCost = usedCost; + RemainingCost = remainingCost; + } + + public bool HasCostInfo() => UsedCost > 0 || RemainingCost > 0; + + + public override string ToString() + { + return $"Used: {UsedCost} Token, Remaining: {RemainingCost} Token"; + } + } +} diff --git a/Assets/Domain/Chat/Model/CostInfo.cs.meta b/Assets/Domain/Chat/Model/CostInfo.cs.meta new file mode 100644 index 0000000..2e9316f --- /dev/null +++ b/Assets/Domain/Chat/Model/CostInfo.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e8dc30eb8caa6a341bced27e0eb9e218 \ No newline at end of file diff --git a/Assets/Domain/Chat/Model/VoiceData.cs b/Assets/Domain/Chat/Model/VoiceData.cs index 9e8b434..98a41a1 100644 --- a/Assets/Domain/Chat/Model/VoiceData.cs +++ b/Assets/Domain/Chat/Model/VoiceData.cs @@ -17,10 +17,6 @@ public VoiceData(AudioClip audioClip, float length, string format = "wav") Format = format; } - public VoiceData() - { - } - public static VoiceData FromBase64(string base64Data, string format = "wav") { if (string.IsNullOrEmpty(base64Data)) diff --git a/Assets/Domain/Chat/Service/ChatManager.cs b/Assets/Domain/Chat/Service/ChatManager.cs deleted file mode 100644 index a018824..0000000 --- a/Assets/Domain/Chat/Service/ChatManager.cs +++ /dev/null @@ -1,325 +0,0 @@ -#nullable enable -using System; -using System.Collections; -using System.Collections.Generic; -using UnityEngine; -using Cysharp.Threading.Tasks; -using ProjectVG.Core.Audio; -using ProjectVG.Domain.Chat.Model; -using ProjectVG.Infrastructure.Network.WebSocket; -using ProjectVG.Infrastructure.Network.Services; -using ProjectVG.Domain.Chat.View; - - -namespace ProjectVG.Domain.Chat.Service -{ - public class ChatManager : MonoBehaviour - { - [Header("Components")] - [SerializeField] private ChatBubblePanel? _chatBubblePanel; - - - [Header("Chat Settings")] - [SerializeField] private string _characterId = "44444444-4444-4444-4444-444444444444"; - [SerializeField] private string _userId = "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"; - - [Header("Message Queue Settings")] - [SerializeField] private bool _enableMessageQueue = true; - [SerializeField] private int _maxQueueSize = 100; - - private bool _isConnected = false; - private bool _isInitialized = false; - private bool _isProcessing = false; - - private WebSocketManager? _webSocketManager; - private AudioManager? _audioManager; - - private readonly Queue _messageQueue = new Queue(); - private readonly object _queueLock = new object(); - - public bool IsConnected => _isConnected; - public bool IsInitialized => _isInitialized; - public int QueueCount => _messageQueue.Count; - - public event Action? OnChatMessageReceived; - public event Action? OnError; - - #region Unity Lifecycle - - private static ChatManager? _instance; - public static ChatManager Instance - { - get - { - if (_instance == null) - { - _instance = FindAnyObjectByType(); - } - return _instance; - } - } - - private void Awake() - { - if (_instance != null && _instance != this) - { - Destroy(gameObject); - return; - } - - _instance = this; - } - - private void Start() - { - // UI 컴포넌트들이 모두 생성된 후 초기화 - StartCoroutine(InitializeWhenReady()); - } - - private IEnumerator InitializeWhenReady() - { - float timeout = 5f; - float startTime = Time.realtimeSinceStartup; - - // ChatBubblePanel이 준비될 때까지 대기 - while (_chatBubblePanel == null && (Time.realtimeSinceStartup - startTime) < timeout) - { - yield return new WaitForSecondsRealtime(0.1f); - } - - if (_chatBubblePanel == null) - { - Debug.LogError("[ChatManager] ChatBubblePanel 초기화 타임아웃"); - yield break; - } - - Initialize(); - } - - private void OnDestroy() - { - if (_webSocketManager != null) - { - _webSocketManager.OnChatMessageReceived -= HandleChatMessageReceived; - } - } - - #endregion - - #region Public Methods - - public void Initialize() - { - if (_isInitialized) - return; - - try - { - // 기본 매니저들 초기화 - _webSocketManager = WebSocketManager.Instance; - _audioManager = AudioManager.Instance; - - if (_webSocketManager != null) - { - _webSocketManager.OnChatMessageReceived += HandleChatMessageReceived; - } - - _isInitialized = true; - _isConnected = true; - Debug.Log("[ChatManager] 초기화 완료"); - } - catch (Exception ex) - { - Debug.LogError($"[ChatManager] 초기화 실패: {ex.Message}"); - OnError?.Invoke($"초기화 실패: {ex.Message}"); - } - } - - public async void SendUserMessage(string message) - { - if (!ValidateUserInput(message)) - return; - - try - { - if (_chatBubblePanel != null) - { - _chatBubblePanel.CreateBubble(Actor.User, message); - } - - var chatService = ApiServiceManager.Instance.Chat; - var response = await chatService.SendChatAsync( - message: message, - characterId: _characterId, - userId: _userId - ); - - if (response == null) - { - Debug.LogWarning("[ChatManager] 채팅 응답이 null입니다."); - } - } - catch (Exception ex) - { - Debug.LogError($"[ChatManager] 메시지 전송 실패: {ex.Message}"); - OnError?.Invoke($"메시지 전송 실패: {ex.Message}"); - } - } - - public void ProcessCharacterMessage(ChatMessage chatMessage) - { - if (chatMessage == null) - { - Debug.LogWarning("[ChatManager] 빈 채팅 메시지를 받았습니다."); - return; - } - - if (!_enableMessageQueue) - { - ProcessMessageImmediately(chatMessage); - return; - } - - lock (_queueLock) - { - if (_messageQueue.Count >= _maxQueueSize) - { - Debug.LogWarning($"[ChatManager] 메시지 큐가 가득 찼습니다. (최대: {_maxQueueSize})"); - return; - } - - _messageQueue.Enqueue(chatMessage); - } - - ProcessMessageQueueAsync().Forget(); - } - - public void ClearMessageQueue() - { - lock (_queueLock) - { - _messageQueue.Clear(); - } - } - - - - - - #endregion - - #region Private Methods - - private async UniTaskVoid ProcessMessageQueueAsync() - { - if (_isProcessing) - return; - - _isProcessing = true; - - try - { - while (true) - { - ChatMessage message; - - lock (_queueLock) - { - if (_messageQueue.Count == 0) - { - break; - } - message = _messageQueue.Dequeue(); - } - - if (message != null) - { - await ProcessMessageImmediatelyAsync(message); - } - } - } - catch (Exception ex) - { - Debug.LogError($"[ChatManager] 메시지 큐 처리 중 오류: {ex.Message}"); - OnError?.Invoke($"메시지 큐 처리 실패: {ex.Message}"); - } - finally - { - _isProcessing = false; - } - } - - private void ProcessMessageImmediately(ChatMessage chatMessage) - { - try - { - OnChatMessageReceived?.Invoke(chatMessage); - - // 캐릭터 메시지를 버블로 표시 - if (_chatBubblePanel != null && !string.IsNullOrEmpty(chatMessage.Text)) - { - _chatBubblePanel.CreateBubble(Actor.Character, chatMessage.Text); - } - - if (chatMessage.VoiceData != null && _audioManager != null) - { - _audioManager.PlayVoice(chatMessage.VoiceData); - } - } - catch (Exception ex) - { - Debug.LogError($"[ChatManager] 캐릭터 메시지 처리 실패: {ex.Message}"); - OnError?.Invoke($"메시지 처리 실패: {ex.Message}"); - } - } - - private async UniTask ProcessMessageImmediatelyAsync(ChatMessage chatMessage) - { - try - { - OnChatMessageReceived?.Invoke(chatMessage); - - if (_chatBubblePanel != null && !string.IsNullOrEmpty(chatMessage.Text)) - { - _chatBubblePanel.CreateBubble(Actor.Character, chatMessage.Text); - } - - if (chatMessage.VoiceData != null && _audioManager != null) - { - await _audioManager.PlayVoiceAsync(chatMessage.VoiceData); - } - } - catch (Exception ex) - { - Debug.LogError($"[ChatManager] 캐릭터 메시지 처리 실패: {ex.Message}"); - OnError?.Invoke($"메시지 처리 실패: {ex.Message}"); - } - } - - private bool ValidateUserInput(string message) - { - if (string.IsNullOrWhiteSpace(message)) - { - Debug.LogWarning("[ChatManager] 빈 메시지는 전송할 수 없습니다."); - return false; - } - - if (message.Length > 1000) - { - Debug.LogWarning("[ChatManager] 메시지가 너무 깁니다. (최대 1000자)"); - return false; - } - - return true; - } - - - - private void HandleChatMessageReceived(ChatMessage chatMessage) - { - ProcessCharacterMessage(chatMessage); - } - - #endregion - } -} \ No newline at end of file diff --git a/Assets/Domain/Chat/Service/ChatMessageQueue.cs b/Assets/Domain/Chat/Service/ChatMessageQueue.cs new file mode 100644 index 0000000..f96e7ca --- /dev/null +++ b/Assets/Domain/Chat/Service/ChatMessageQueue.cs @@ -0,0 +1,143 @@ +#nullable enable +using System; +using System.Collections.Generic; +using UnityEngine; +using Cysharp.Threading.Tasks; +using ProjectVG.Domain.Chat.Model; + +namespace ProjectVG.Domain.Chat.Service +{ + /// + /// 채팅 메시지 큐를 관리하는 클래스 + /// + public class ChatMessageQueue + { + private readonly Queue _messageQueue = new Queue(); + private readonly object _queueLock = new object(); + private readonly int _maxQueueSize; + private bool _isProcessing = false; + + public int Count => _messageQueue.Count; + public bool IsProcessing => _isProcessing; + public bool IsEmpty => _messageQueue.Count == 0; + + public event Action? OnMessageProcessed; + public event Action? OnError; + + public ChatMessageQueue(int maxQueueSize = 100) + { + _maxQueueSize = maxQueueSize; + } + + /// + /// 메시지를 큐에 추가한다 + /// + /// 추가할 메시지 + /// 추가 성공 여부 + public bool Enqueue(ChatMessage chatMessage) + { + if (chatMessage == null) + return false; + + lock (_queueLock) { + if (_messageQueue.Count >= _maxQueueSize) { + Debug.LogWarning($"[ChatMessageQueue] 메시지 큐가 가득 찼습니다. (최대: {_maxQueueSize})"); + return false; + } + + _messageQueue.Enqueue(chatMessage); + } + + return true; + } + + /// + /// 큐에서 메시지를 제거하고 반환한다 + /// + /// 제거된 메시지, 큐가 비어있으면 null + public ChatMessage? Dequeue() + { + lock (_queueLock) { + return _messageQueue.Count > 0 ? _messageQueue.Dequeue() : null; + } + } + + /// + /// 큐의 모든 메시지를 제거한다 + /// + public void Clear() + { + lock (_queueLock) { + _messageQueue.Clear(); + } + } + + /// + /// 메시지 큐를 순차적으로 처리한다 + /// + /// 메시지 처리 액션 + /// + public async UniTaskVoid ProcessQueueAsync(Func processAction) + { + lock (_queueLock) + { + if (_isProcessing) + return; + _isProcessing = true; + } + + try { + while (true) { + ChatMessage? message = Dequeue(); + + if (message == null) { + break; + } + + try { + await processAction(message); + OnMessageProcessed?.Invoke(message); + } + catch (Exception ex) { + Debug.LogError($"[ChatMessageQueue] 메시지 처리 중 오류: {ex.Message}"); + OnError?.Invoke($"메시지 처리 실패: {ex.Message}"); + } + } + } + catch (Exception ex) { + Debug.LogError($"[ChatMessageQueue] 큐 처리 중 오류: {ex.Message}"); + OnError?.Invoke($"큐 처리 실패: {ex.Message}"); + } + finally { + _isProcessing = false; + } + } + + /// + /// 큐의 상태 정보를 반환한다 + /// + /// 큐 상태 정보 + public QueueStatus GetStatus() + { + lock (_queueLock) { + return new QueueStatus { + Count = _messageQueue.Count, + MaxSize = _maxQueueSize, + IsProcessing = _isProcessing, + IsFull = _messageQueue.Count >= _maxQueueSize + }; + } + } + } + + /// + /// 큐 상태 정보 + /// + public struct QueueStatus + { + public int Count; + public int MaxSize; + public bool IsProcessing; + public bool IsFull; + } +} diff --git a/Assets/Domain/Chat/Service/ChatMessageQueue.cs.meta b/Assets/Domain/Chat/Service/ChatMessageQueue.cs.meta new file mode 100644 index 0000000..a88db9d --- /dev/null +++ b/Assets/Domain/Chat/Service/ChatMessageQueue.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 30c1af92fefd50c43a58245bea18e854 \ No newline at end of file diff --git a/Assets/Domain/Chat/Service/ChatSystemManager.cs b/Assets/Domain/Chat/Service/ChatSystemManager.cs new file mode 100644 index 0000000..de919b9 --- /dev/null +++ b/Assets/Domain/Chat/Service/ChatSystemManager.cs @@ -0,0 +1,255 @@ +#nullable enable +using System; +using System.Collections; +using System.Threading; +using UnityEngine; +using Cysharp.Threading.Tasks; +using ProjectVG.Core.Audio; +using ProjectVG.Domain.Chat.Model; +using ProjectVG.Infrastructure.Network.WebSocket; +using ProjectVG.Infrastructure.Network.Services; +using ProjectVG.Domain.Chat.View; +using ProjectVG.Domain.Character.Service; + + +namespace ProjectVG.Domain.Chat.Service +{ + /// + /// 대화 메시지를 전송하고 받으면 처리한다. + /// 대화 메시지를 처리할때 S시간 동안 액션과 Voice를 점유하고 다음 행동을 수행한다. + /// + public class ChatSystemManager : MonoBehaviour + { + [Header("Components")] + [SerializeField] private ChatBubblePanel? _chatBubblePanel; + [SerializeField] private CharacterManager? _characterManager; + + [Header("Chat Settings")] + [SerializeField] private string _characterId = "44444444-4444-4444-4444-444444444444"; + [SerializeField] private string _userId = "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"; + + private WebSocketManager? _webSocketManager; + private AudioManager? _audioManager; + private ChatMessageQueue? _messageQueue; + private ChatApiService? _chatApiService; + + private bool _isInitialized = false; + private CancellationTokenSource _cts = new(); + public bool IsInitialized => _isInitialized; + + public event Action? OnError; + public event Action? OnConversationEnd; + + #region Unity Lifecycle + + private static ChatSystemManager? _instance; + public static ChatSystemManager Instance { + get { + if (_instance == null) { + _instance = FindAnyObjectByType(); + } + return _instance; + } + } + + private void Awake() + { + if (_instance != null && _instance != this) { + Destroy(gameObject); + return; + } + _instance = this; + } + + private void Start() + { + StartCoroutine(InitializeWhenReady()); + } + + private IEnumerator InitializeWhenReady() + { + float timeout = 5f; + float startTime = Time.realtimeSinceStartup; + + while (_chatBubblePanel == null && (Time.realtimeSinceStartup - startTime) < timeout) { + yield return new WaitForSecondsRealtime(0.1f); + } + + if (_chatBubblePanel == null) { + Debug.LogError("[ChatSystemManager] ChatBubblePanel 초기화 타임아웃"); + yield break; + } + + Initialize(); + } + public void Initialize() + { + if (_isInitialized) return; + + try { + _webSocketManager = WebSocketManager.Instance; + _audioManager = AudioManager.Instance; + _messageQueue = new ChatMessageQueue(); + _chatApiService = ApiServiceManager.Instance.Chat; + + if (_messageQueue != null) { + _messageQueue.OnError += (msg) => OnError?.Invoke(msg); + } + + if (_webSocketManager != null) { + _webSocketManager.OnChatMessageReceived += ProcessCharacterMessage; + } + + _isInitialized = true; + Debug.Log("[ChatSystemManager] 초기화 완료"); + } + catch (Exception ex) { + Debug.LogError($"[ChatSystemManager] 초기화 실패: {ex.Message}"); + } + } + + private void OnDestroy() + { + _cts.Cancel(); + _cts.Dispose(); + if (_webSocketManager != null) { + _webSocketManager.OnChatMessageReceived -= ProcessCharacterMessage; + } + } + + #endregion + + #region Public Methods + + /// + /// 유저 메시지를 처리한다 + /// + /// User 메시지 + public async void SendUserMessage(string message) + { + if (!ValidateUserInput(message)) { return; } + + try { + // 유저 메시지 전송 시 캐릭터를 Listen 상태로 변경 + if (_characterManager != null) { + var listenAction = new CharacterActionData(CharacterActionType.Listen); + _characterManager.PlayAction(listenAction); + } + + if (_chatApiService != null) { + var response = await _chatApiService.SendChatAsync( + message: message, + characterId: _characterId, + userId: _userId + ); + if (response == null) { + Debug.LogWarning("[ChatSystemManager] 채팅 응답이 null입니다."); + } + } + if (_chatBubblePanel != null) { + _chatBubblePanel.CreateBubble(Actor.User, message); + } + } + catch (Exception ex) { + // todo : 메시지 실패 처리 + Debug.LogError($"[ChatSystemManager] 메시지 전송 실패: {ex.Message}"); + } + } + + /// + /// 캐릭터 메시지를 처리한다 + /// + /// 캐릭터 메시지 + public void ProcessCharacterMessage(ChatMessage chatMessage) + { + if (chatMessage == null) { + Debug.LogWarning("[ChatSystemManager] 빈 채팅 메시지를 받았습니다."); + return; + } + + if (_messageQueue == null) { + Debug.LogError("[ChatSystemManager] 메시지 큐가 초기화되지 않았습니다."); + return; + } + + if (_messageQueue.Enqueue(chatMessage)) { + _messageQueue.ProcessQueueAsync(ProcessMessageAsync).Forget(); + } + } + + #endregion + + #region Private Methods + + /// + /// 메시지를 비동기적으로 처리한다 + /// + /// + /// + private async UniTask ProcessMessageAsync(ChatMessage chatMessage) + { + try { + if (_chatBubblePanel != null && !string.IsNullOrEmpty(chatMessage.Text)) { + _chatBubblePanel.CreateBubble(Actor.Character, chatMessage.Text); + } + + if (_characterManager != null) { + _characterManager.PlayAction(chatMessage.ActionData); + } + + + if (chatMessage.VoiceData != null && _audioManager != null) { + _audioManager.PlayVoiceAsync(chatMessage.VoiceData).Forget(); + } + + float waitTime = CalculateConversationWaitTime(chatMessage); + await UniTask.Delay(TimeSpan.FromSeconds(waitTime), cancellationToken: _cts.Token); + + // 대화 종료 시 캐릭터를 Idle 상태로 변경 + if (_characterManager != null) { + var idleAction = new CharacterActionData(CharacterActionType.Idle); + _characterManager.PlayAction(idleAction); + } + + OnConversationEnd?.Invoke(); + } + catch (Exception ex) { + Debug.LogError($"[ChatSystemManager] 캐릭터 메시지 처리 실패: {ex.Message}"); + OnError?.Invoke($"메시지 처리 실패: {ex.Message}"); + } + } + + /// + /// 대화 대기 시간을 계산한다 + /// + private float CalculateConversationWaitTime(ChatMessage chatMessage) + { + float baseTime = 0f; + + if (chatMessage.VoiceData != null && chatMessage.VoiceData.IsPlayable()) { + baseTime = chatMessage.VoiceData.Length; + } + + if (baseTime <= 0f) { + baseTime = 2f; + } + + return baseTime + 0.5f; + } + + private bool ValidateUserInput(string message) + { + if (string.IsNullOrWhiteSpace(message)) { + Debug.LogWarning("[ChatSystemManager] 빈 메시지는 전송할 수 없습니다."); + return false; + } + if (message.Length > 1000) { + Debug.LogWarning("[ChatSystemManager] 메시지가 너무 깁니다. (최대 1000자)"); + return false; + } + return true; + } + + #endregion + } +} diff --git a/Assets/Domain/Chat/Service/ChatManager.cs.meta b/Assets/Domain/Chat/Service/ChatSystemManager.cs.meta similarity index 100% rename from Assets/Domain/Chat/Service/ChatManager.cs.meta rename to Assets/Domain/Chat/Service/ChatSystemManager.cs.meta diff --git a/Assets/Domain/Chat/View/TextInputView.cs b/Assets/Domain/Chat/View/TextInputView.cs index 142ce66..781fe17 100644 --- a/Assets/Domain/Chat/View/TextInputView.cs +++ b/Assets/Domain/Chat/View/TextInputView.cs @@ -13,7 +13,7 @@ public class TextInputView : MonoBehaviour [SerializeField] private TMP_InputField? _inputField; [SerializeField] private Button? _btnSend; - private ChatManager? _chatManager; + private ChatSystemManager? _chatManager; private bool _isProcessingSubmit = false; public event Action? OnTextMessageSent; @@ -37,7 +37,7 @@ private void Initialize() SetupChatManager(); } - public void SetChatManager(ChatManager chatManager) + public void SetChatManager(ChatSystemManager chatManager) { _chatManager = chatManager; } @@ -77,7 +77,7 @@ private void SetupChatManager() { if (_chatManager == null) { - _chatManager = FindAnyObjectByType(); + _chatManager = FindAnyObjectByType(); if (_chatManager == null) { Debug.LogWarning("[TextInputView] ChatManager를 찾을 수 없습니다. 수동으로 SetChatManager를 호출해주세요."); diff --git a/Assets/Domain/Chat/View/VoiceInputView.cs b/Assets/Domain/Chat/View/VoiceInputView.cs index 1bbe81b..c5713ce 100644 --- a/Assets/Domain/Chat/View/VoiceInputView.cs +++ b/Assets/Domain/Chat/View/VoiceInputView.cs @@ -19,7 +19,7 @@ public class VoiceInputView : MonoBehaviour [SerializeField] private Button? _btnVoiceStop; - private ChatManager? _chatManager; + private ChatSystemManager? _chatManager; private AudioRecorder? _audioRecorder; private STTService? _sttService; private bool _isRecording = false; @@ -72,7 +72,7 @@ private void Initialize() SetupChatManager(); } - public void SetChatManager(ChatManager chatManager) + public void SetChatManager(ChatSystemManager chatManager) { _chatManager = chatManager; } @@ -239,7 +239,7 @@ private void SetupChatManager() { if (_chatManager == null) { - _chatManager = FindAnyObjectByType(); + _chatManager = FindAnyObjectByType(); if (_chatManager == null) { Debug.LogWarning("[VoiceInputView] ChatManager를 찾을 수 없습니다. 수동으로 SetChatManager를 호출해주세요."); diff --git a/Assets/Infrastructure/Network/DTOs/Chat/ChatResponse.cs b/Assets/Infrastructure/Network/DTOs/Chat/ChatResponse.cs index d8ddb32..88a1421 100644 --- a/Assets/Infrastructure/Network/DTOs/Chat/ChatResponse.cs +++ b/Assets/Infrastructure/Network/DTOs/Chat/ChatResponse.cs @@ -19,7 +19,10 @@ public class ChatResponse [JsonProperty("text")] public string? Text { get; set; } - + + [JsonProperty("action")] + public string? Action { get; set; } + [JsonProperty("audio_data")] public string? AudioData { get; set; } @@ -28,11 +31,14 @@ public class ChatResponse [JsonProperty("audio_length")] public float? AudioLength { get; set; } - + + [JsonProperty("used_cost")] + public float? UsedCost { get; set; } + + [JsonProperty("remaining_cost")] + public float? RemainingCost { get; set; } + [JsonProperty("timestamp")] public DateTime Timestamp { get; set; } = DateTime.UtcNow; - - [JsonProperty("metadata")] - public Dictionary? Metadata { get; set; } } } \ No newline at end of file diff --git a/Assets/Resources/Character/Model.meta b/Assets/Resources/Character/Model.meta deleted file mode 100644 index cea515c..0000000 --- a/Assets/Resources/Character/Model.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: c4f92c17fdb8d804492c23f2446382db -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Character/Model/CharacterZero.asset b/Assets/Resources/Character/Model/CharacterZero.asset deleted file mode 100644 index 09aed52..0000000 --- a/Assets/Resources/Character/Model/CharacterZero.asset +++ /dev/null @@ -1,33 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!114 &11400000 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 2a91fff055b553542acd1857fd9bbf0f, type: 3} - m_Name: CharacterZero - m_EditorClassIdentifier: - characterId: zero - characterName: "\uC81C\uB85C" - characterPrefab: {fileID: 6181935751025943507, guid: 43e77a085e1072e4dbc6393f20643f3b, type: 3} - thumbnail: {fileID: 2800000, guid: 7ee051ec4bca36046934826113d64c32, type: 3} - characterDescription: "\uCE90\uB9AD\uD130 \uC81C\uB85C" - emotionMappings: - - emotionKey: - expressionName: - defaultIntensity: 0 - defaultDurationMs: 0 - actionMappings: - - actionKey: - motionGroup: - motionName: - isLockAtActive: 1 - lookSensitivity: 1 - lockAtDamping: 0 - gain: 1 - smoothing: 1 diff --git a/Assets/Resources/Character/Live2DModelRegistry.asset b/Assets/Resources/Live2DModelRegistry.asset similarity index 83% rename from Assets/Resources/Character/Live2DModelRegistry.asset rename to Assets/Resources/Live2DModelRegistry.asset index 72bdee3..65a8162 100644 --- a/Assets/Resources/Character/Live2DModelRegistry.asset +++ b/Assets/Resources/Live2DModelRegistry.asset @@ -14,4 +14,4 @@ MonoBehaviour: m_EditorClassIdentifier: _entries: - characterId: zero - characterConfig: {fileID: 11400000, guid: 4c6d1f5cb9556f24c843f3e9fe14d49e, type: 2} + characterConfig: {fileID: 11400000, guid: 58c40b616ae7d944c91fbc00193d5d3b, type: 2} diff --git a/Assets/Resources/Character/Live2DModelRegistry.asset.meta b/Assets/Resources/Live2DModelRegistry.asset.meta similarity index 100% rename from Assets/Resources/Character/Live2DModelRegistry.asset.meta rename to Assets/Resources/Live2DModelRegistry.asset.meta diff --git a/Assets/Resources/ResolutionModelScaleConfig.asset b/Assets/Resources/ResolutionModelScaleConfig.asset new file mode 100644 index 0000000..d471886 --- /dev/null +++ b/Assets/Resources/ResolutionModelScaleConfig.asset @@ -0,0 +1,84 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2f1d81d7191952e419c1237b0fe91ebb, type: 3} + m_Name: ResolutionModelScaleConfig + m_EditorClassIdentifier: + referenceResolution: {x: 1080, y: 1920} + referenceScale: 1 + scaleRules: + - minWidth: 1440 + maxWidth: 1440 + minHeight: 2960 + maxHeight: 2960 + isMobileOnly: 0 + scale: 1.5 + description: "\uBAA8\uBC14\uC77C \uACE0\uD574\uC0C1\uB3C4 (1440x2960)" + - minWidth: 2560 + maxWidth: 9999 + minHeight: 1440 + maxHeight: 9999 + isMobileOnly: 0 + scale: 1.4 + description: "PC QHD+ (2560x1440 \uC774\uC0C1)" + - minWidth: 1921 + maxWidth: 9999 + minHeight: 1081 + maxHeight: 9999 + isMobileOnly: 0 + scale: 1.3 + description: "PC QHD \uC774\uC0C1" + - minWidth: 1025 + maxWidth: 1920 + minHeight: 0 + maxHeight: 1080 + isMobileOnly: 0 + scale: 1.1 + description: PC FHD + - minWidth: 769 + maxWidth: 1024 + minHeight: 0 + maxHeight: 9999 + isMobileOnly: 0 + scale: 0.9 + description: "\uD0DC\uBE14\uB9BF \uAC00\uB85C" + - minWidth: 481 + maxWidth: 768 + minHeight: 0 + maxHeight: 9999 + isMobileOnly: 0 + scale: 0.8 + description: "\uBAA8\uBC14\uC77C \uAC00\uB85C/\uD0DC\uBE14\uB9BF \uC138\uB85C" + - minWidth: 0 + maxWidth: 480 + minHeight: 0 + maxHeight: 9999 + isMobileOnly: 0 + scale: 0.7 + description: "\uBAA8\uBC14\uC77C \uC138\uB85C (\uC791\uC740 \uD654\uBA74)" + - minWidth: 0 + maxWidth: 9999 + minHeight: 0 + maxHeight: 9999 + isMobileOnly: 0 + scale: 0.85 + description: "\uBAA8\uBC14\uC77C \uAE30\uBCF8" + - minWidth: 0 + maxWidth: 9999 + minHeight: 0 + maxHeight: 9999 + isMobileOnly: 0 + scale: 1 + description: "\uAE30\uBCF8 \uADDC\uCE59" + enableAutoScale: 1 + minScale: 0.1 + maxScale: 10 + scaleMode: 1 diff --git a/Assets/Resources/ResolutionModelScaleConfig.asset.meta b/Assets/Resources/ResolutionModelScaleConfig.asset.meta new file mode 100644 index 0000000..1386f0a --- /dev/null +++ b/Assets/Resources/ResolutionModelScaleConfig.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0f80d8435f4ccee4184aa458f6b86634 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Samples.meta b/Assets/Samples.meta deleted file mode 100644 index 1790a84..0000000 --- a/Assets/Samples.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 7e93090ffb9d8aa47ab8da6804c4f7ca -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Samples/Core.meta b/Assets/Samples/Core.meta deleted file mode 100644 index 9210e88..0000000 --- a/Assets/Samples/Core.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 25ed4b2e8a2f9fc48af8e926e17a17c0 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Samples/Core/Managers.meta b/Assets/Samples/Core/Managers.meta deleted file mode 100644 index ce6405e..0000000 --- a/Assets/Samples/Core/Managers.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: bd11cb6c95a02a94faa52c02ded0b378 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Samples/Core/Managers/SampleSystemManager.cs b/Assets/Samples/Core/Managers/SampleSystemManager.cs deleted file mode 100644 index 3a49e09..0000000 --- a/Assets/Samples/Core/Managers/SampleSystemManager.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Live2D.Cubism.Framework.LookAt; -using Live2D.Cubism.Framework.MouthMovement; -using UnityEngine; -using UnityEngine.UI; -using ProjectVG.Core.Audio; - -public class SampleSystemManager : Singleton -{ - [SerializeField] private CubismLookTarget cubismLookTarget = null; - [SerializeField] private Camera mCamera = null; - [SerializeField] private ModelConfig initModelConfig = null; - - - - private GameObject _currentModel = null; - - // TODO : test 추후 삭제 - [SerializeField] private AudioSource voiceSource = null; - [SerializeField] private Button expressionChangeBtn = null; - - private void Start() - { - ScreenTapManager.Instance.Initialize(mCamera); - AudioManager.Instance.Initialize(); - - ModelInit(initModelConfig); - } - - private void ModelInit(ModelConfig modelConfig) - { - if (_currentModel != null) - { - Destroy(_currentModel); - } - - _currentModel = Instantiate(modelConfig.ModelPrefab); - - // LockAt 설정 - SetLockAt(modelConfig); - - // LipSync 설정 - SetLipSync(modelConfig); - - // raycast 설정 - SetRayCast(modelConfig); - } - - private void SetLockAt(ModelConfig modelConfig) - { - var lookController = _currentModel.GetComponent(); - lookController.Target = cubismLookTarget.gameObject; - lookController.Damping = modelConfig.LockAtDamping; - - cubismLookTarget.Initialize(modelConfig); - } - - private void SetLipSync(ModelConfig modelConfig) - { - var mouthController = _currentModel.GetComponent(); - mouthController.AudioInput = voiceSource; - mouthController.Gain = modelConfig.Gain; - mouthController.Smoothing = modelConfig.Smoothing; - } - - private void SetRayCast(ModelConfig modelConfig) - { - var hitHandler = _currentModel.GetComponent(); - hitHandler.Initialize(); - expressionChangeBtn.onClick.AddListener(hitHandler.ExpressionChange_Btn); - } -} diff --git a/Assets/Samples/Core/Managers/SampleSystemManager.cs.meta b/Assets/Samples/Core/Managers/SampleSystemManager.cs.meta deleted file mode 100644 index e960c9c..0000000 --- a/Assets/Samples/Core/Managers/SampleSystemManager.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 9224e8bfc209a8b488ae41e6b188b048 \ No newline at end of file diff --git a/Assets/Voice.mixer b/Assets/Voice.mixer deleted file mode 100644 index 84960c3..0000000 --- a/Assets/Voice.mixer +++ /dev/null @@ -1,69 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!241 &24100000 -AudioMixerController: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: Voice - m_OutputGroup: {fileID: 0} - m_MasterGroup: {fileID: 24300002} - m_Snapshots: - - {fileID: 24500006} - m_StartSnapshot: {fileID: 24500006} - m_SuspendThreshold: -80 - m_EnableSuspend: 1 - m_UpdateMode: 0 - m_ExposedParameters: [] - m_AudioMixerGroupViews: - - guids: - - 0f47c3379c5b66e458d9740a30fc267a - name: View - m_CurrentViewIndex: 0 - m_TargetSnapshot: {fileID: 24500006} ---- !u!243 &24300002 -AudioMixerGroupController: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: Master - m_AudioMixer: {fileID: 24100000} - m_GroupID: 0f47c3379c5b66e458d9740a30fc267a - m_Children: [] - m_Volume: 583a709ad5a7180498f0cda7cd1091b2 - m_Pitch: 7ea55f842e0513b43902be8341087012 - m_Send: 00000000000000000000000000000000 - m_Effects: - - {fileID: 24400004} - m_UserColorIndex: 0 - m_Mute: 0 - m_Solo: 0 - m_BypassEffects: 0 ---- !u!244 &24400004 -AudioMixerEffectController: - m_ObjectHideFlags: 3 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: - m_EffectID: 9f0119b8c97725546ac7b269861f6c67 - m_EffectName: Attenuation - m_MixLevel: b67c869ef3e747e4fad8fd66b2f91bf9 - m_Parameters: [] - m_SendTarget: {fileID: 0} - m_EnableWetMix: 0 - m_Bypass: 0 ---- !u!245 &24500006 -AudioMixerSnapshotController: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: Snapshot - m_AudioMixer: {fileID: 24100000} - m_SnapshotID: 028d43bb053f091429fb3985cb191113 - m_FloatValues: - 583a709ad5a7180498f0cda7cd1091b2: 2.850226 - m_TransitionOverrides: {} diff --git a/ProjectSettings/EditorBuildSettings.asset b/ProjectSettings/EditorBuildSettings.asset index 7bb2c5f..aae99c7 100644 --- a/ProjectSettings/EditorBuildSettings.asset +++ b/ProjectSettings/EditorBuildSettings.asset @@ -6,7 +6,7 @@ EditorBuildSettings: serializedVersion: 2 m_Scenes: - enabled: 1 - path: Assets/App/Scenes/MainSence.unity + path: Assets/App/Scenes/MainScene.unity guid: 0845d716b4db3d745a759f81d547dea6 - enabled: 1 path: Assets/App/Scenes/StartSence.unity