diff --git a/.coderabbit.yaml b/.coderabbit.yaml deleted file mode 100644 index 065526b..0000000 --- a/.coderabbit.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# .coderabbit.yaml -language: "ko-KR" -early_access: false -reviews: - profile: "chill" - request_changes_workflow: false - high_level_summary: true - poem: true - review_status: true - collapse_walkthrough: false - auto_review: - enabled: true - drafts: false -chat: - auto_reply: true \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..c9b8903 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,18 @@ +## 제목 +- `type(scope): subject` + +## 변경 의도 +- 왜 이 변경이 필요한가 + +## 변경 내용 요약 +- 핵심 변경 1~3줄 + +## 영향 범위 +- 시스템/모듈/성능/보안 영향 + +## 검증 방법 +- 재현/확인 절차 + +## 관련 링크 +- 이슈: # +- 문서: Docs/... diff --git a/.gitignore b/.gitignore index 540e128..485374c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ -# This .gitignore file should be placed at the root of your Unity project directory -# -# Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore -# +# Unity 프로젝트 .gitignore +# https://github.com/github/gitignore/blob/main/Unity.gitignore + +# Unity 기본 디렉토리 .utmp/ /[Ll]ibrary/ /[Tt]emp/ @@ -12,32 +12,29 @@ /[Uu]ser[Ss]ettings/ *.log -# By default unity supports Blender asset imports, *.blend1 blender files do not need to be commited to version control. +# Blender 파일 *.blend1 *.blend1.meta -# MemoryCaptures can get excessive in size. -# They also could contain extremely sensitive data +# 메모리 캡처 (크기가 크고 민감한 데이터 포함) /[Mm]emoryCaptures/ -# Recordings can get excessive in size +# 녹화 파일 (크기가 큼) /[Rr]ecordings/ -# Uncomment this line if you wish to ignore the asset store tools plugin +# Asset Store 도구 플러그인 # /[Aa]ssets/AssetStoreTools* -# Autogenerated Jetbrains Rider plugin +# Jetbrains Rider 플러그인 /[Aa]ssets/Plugins/Editor/JetBrains* -# Jetbrains Rider personal-layer settings *.DotSettings.user -# Visual Studio cache directory +# IDE 캐시 디렉토리 .vs/ - -# Gradle cache directory .gradle/ +.idea/ -# Autogenerated VS/MD/Consulo solution and project files +# IDE 생성 파일 ExportedObj/ .consulo/ *.csproj @@ -54,56 +51,50 @@ ExportedObj/ *.mdb *.opendb *.VC.db +*.vsconfig -# Unity3D generated meta files +# Unity 메타 파일 *.pidb.meta *.pdb.meta *.mdb.meta -# Unity3D generated file on crash reports +# 크래시 리포트 sysinfo.txt - -# Mono auto generated files mono_crash.* -# Builds +# 빌드 파일 *.apk *.aab *.unitypackage *.unitypackage.meta *.app -# Crashlytics generated file +# Crashlytics crashlytics-build.properties -# TestRunner generated files +# 테스트 파일 InitTestScene*.unity* -# Addressables default ignores, before user customizations +# Addressables /ServerData /[Aa]ssets/StreamingAssets/aa* /[Aa]ssets/AddressableAssetsData/link.xml* /[Aa]ssets/Addressables_Temp* -# By default, Addressables content builds will generate addressables_content_state.bin -# files in platform-specific subfolders, for example: -# /Assets/AddressableAssetsData/OSX/addressables_content_state.bin /[Aa]ssets/AddressableAssetsData/*/*.bin* -# Visual Scripting auto-generated files +# Visual Scripting 자동 생성 파일 /[Aa]ssets/Unity.VisualScripting.Generated/VisualScripting.Flow/UnitOptions.db /[Aa]ssets/Unity.VisualScripting.Generated/VisualScripting.Flow/UnitOptions.db.meta /[Aa]ssets/Unity.VisualScripting.Generated/VisualScripting.Core/Property Providers /[Aa]ssets/Unity.VisualScripting.Generated/VisualScripting.Core/Property Providers.meta -# Auto-generated scenes by play mode tests +# 테스트 씬 /[Aa]ssets/[Ii]nit[Tt]est[Ss]cene*.unity* - -# Add +# 추가 항목 /.plastic/ *.stacktrace *.crash -.idea/ *.iml *.xcworkspace *.xcuserdata @@ -111,9 +102,7 @@ InitTestScene*.unity* *.mode1v3 *.mode2v3 -# Resources Large Files -*.wav -*.mp3 +# 대용량 미디어 파일 *.mp4 *.avi *.mov @@ -126,9 +115,7 @@ InitTestScene*.unity* *.m4b *.m4r *.m4p -*.m4v -*.m4b -*.m4r -*.m4p + +# 특정 플러그인 /Assets/Plugins/FiveMinuteChat /Assets/Plugins/WebGLTemplates diff --git a/.plasticignore b/.plasticignore index c0cb5ef..3481751 100644 --- a/.plasticignore +++ b/.plasticignore @@ -22,6 +22,7 @@ Desktop.ini *.tmp *.user *.userprefs +*.vsconfig # macOS 관련 *.DS_Store @@ -41,3 +42,46 @@ Desktop.ini *.log *.bak *.meta~ # 임시 메타파일 + +# 추가된 항목들 +*.blend1 +*.blend1.meta +/[Rr]ecordings/ +*.DotSettings.user +mono_crash.* +*.unitypackage.meta +InitTestScene*.unity* +/ServerData +/[Aa]ssets/AddressableAssetsData/link.xml* +/[Aa]ssets/Addressables_Temp* +*.stacktrace +*.crash +*.xcuserdata +*.pbxuser +*.mode1v3 +*.mode2v3 +*.private +*.private.meta +^*.private.[0-9]+$ +^*.private.[0-9]+.meta$ +~UnityDirMonSyncFile~* +**/Assets/StreamingAssets/aa.meta +**/assets/streamingassets/*/aa/* + +# 대용량 미디어 파일 +*.mp4 +*.avi +*.mov +*.wmv +*.flv +*.mkv +*.webm +*.m4a +*.m4v +*.m4b +*.m4r +*.m4p + +# 특정 플러그인 +/Assets/Plugins/FiveMinuteChat +/Assets/Plugins/WebGLTemplates diff --git a/Assets/App/Scenes/Live2d Sence.unity b/Assets/App/Scenes/Live2d Sence.unity new file mode 100644 index 0000000..449d304 --- /dev/null +++ b/Assets/App/Scenes/Live2d Sence.unity @@ -0,0 +1,918 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 10 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 3 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 0 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 1 + m_PVRFilteringGaussRadiusAO: 1 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &194973787 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 194973789} + - component: {fileID: 194973788} + m_Layer: 0 + m_Name: GameObject (1) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &194973788 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 194973787} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 220500989a5e4164381615ebdf1a6e33, type: 3} + m_Name: + m_EditorClassIdentifier: + _enableDebugLog: 1 + _enableDebugRay: 1 + _debugRayColor: {r: 1, g: 0, b: 0, a: 1} + _debugRayDuration: 2 +--- !u!4 &194973789 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 194973787} + 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 &893152814 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 893152817} + - component: {fileID: 893152816} + - component: {fileID: 893152815} + - component: {fileID: 893152818} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &893152815 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 893152814} + m_Enabled: 1 +--- !u!20 &893152816 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 893152814} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 1 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &893152817 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 893152814} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: -10} + 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!114 &893152818 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 893152814} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_RenderShadows: 1 + m_RequiresDepthTextureOption: 2 + m_RequiresOpaqueTextureOption: 2 + m_CameraType: 0 + m_Cameras: [] + m_RendererIndex: -1 + m_VolumeLayerMask: + serializedVersion: 2 + m_Bits: 1 + m_VolumeTrigger: {fileID: 0} + m_VolumeFrameworkUpdateModeOption: 2 + m_RenderPostProcessing: 0 + m_Antialiasing: 0 + m_AntialiasingQuality: 2 + m_StopNaN: 0 + m_Dithering: 0 + m_ClearDepth: 1 + m_AllowXRRendering: 1 + m_AllowHDROutput: 1 + m_UseScreenCoordOverride: 0 + m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0} + m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0} + m_RequiresDepthTexture: 0 + m_RequiresColorTexture: 0 + m_Version: 2 + m_TaaSettings: + m_Quality: 3 + m_FrameInfluence: 0.1 + m_JitterScale: 1 + m_MipBias: 0 + m_VarianceClampScale: 0.9 + m_ContrastAdaptiveSharpening: 0 +--- !u!1 &1211836786 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1211836790} + - component: {fileID: 1211836789} + - component: {fileID: 1211836788} + - component: {fileID: 1211836787} + m_Layer: 5 + m_Name: Canvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1211836787 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1211836786} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &1211836788 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1211836786} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 0 +--- !u!223 &1211836789 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1211836786} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 0 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 + m_AdditionalShaderChannelsFlag: 25 + m_UpdateRectTransformForStandalone: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &1211836790 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1211836786} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1594706922} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!1 &1303424300 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1303424301} + - component: {fileID: 1303424303} + - component: {fileID: 1303424302} + m_Layer: 5 + m_Name: Text (TMP) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1303424301 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1303424300} + 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: 1594706922} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1303424302 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1303424300} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: Button + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4281479730 + m_fontColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 24 + m_fontSizeBase: 24 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 2 + m_VerticalAlignment: 512 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_TextWrappingMode: 1 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 0 + m_ActiveFontFeatures: 6e72656b + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_EmojiFallbackSupport: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!222 &1303424303 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1303424300} + m_CullTransparentMesh: 1 +--- !u!1 &1594706921 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1594706922} + - component: {fileID: 1594706925} + - component: {fileID: 1594706924} + - component: {fileID: 1594706923} + m_Layer: 5 + m_Name: Button + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1594706922 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1594706921} + 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: + - {fileID: 1303424301} + m_Father: {fileID: 1211836790} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: -640, y: -1465} + m_SizeDelta: {x: 160, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1594706923 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1594706921} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 1594706924} + m_OnClick: + m_PersistentCalls: + m_Calls: [] +--- !u!114 &1594706924 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1594706921} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &1594706925 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1594706921} + m_CullTransparentMesh: 1 +--- !u!1 &1607417702 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1607417703} + m_Layer: 0 + m_Name: CharacterRoot + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1607417703 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1607417702} + 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 &1916112489 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1916112490} + - component: {fileID: 1916112492} + - component: {fileID: 1916112491} + - component: {fileID: 1916112493} + m_Layer: 0 + m_Name: Live2D Manager + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1916112490 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1916112489} + 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!114 &1916112491 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1916112489} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 599c8846d73d1244a80788addacb4285, type: 3} + m_Name: + m_EditorClassIdentifier: + _autoApplyOnEnable: 1 +--- !u!114 &1916112492 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1916112489} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bbd7bfb36017db743b3b7cab74183970, type: 3} + m_Name: + m_EditorClassIdentifier: + _modelRoot: {fileID: 1607417703} + _modelRegistry: {fileID: 11400000, guid: cf68d9632ab0d2c42a02de12beecadff, type: 2} +--- !u!114 &1916112493 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1916112489} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c31e1b9082df088489ede1181874cde4, type: 3} + m_Name: + m_EditorClassIdentifier: + _initializationManager: {fileID: 0} + _managerRegistry: {fileID: 0} + _dependencyManager: {fileID: 0} + _autoInitializeOnStart: 1 + _createManagersIfNotExist: 1 + _autoUpdateCameraOnSceneChange: 1 + _camera: {fileID: 0} +--- !u!1 &2028963604 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2028963606} + - component: {fileID: 2028963605} + m_Layer: 0 + m_Name: GameObject + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &2028963605 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2028963604} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 7fb1442983837c140abae42a85733fe6, type: 3} + m_Name: + m_EditorClassIdentifier: + _testCharacterId: zero + _loadCharacterBtn: {fileID: 1594706923} + _activateCharacterBtn: {fileID: 0} + _unloadCharacterBtn: {fileID: 0} + _unloadAllBtn: {fileID: 0} + _changeVisibilityBtn: {fileID: 0} + _characterIdInput: {fileID: 0} + _statusText: {fileID: 0} +--- !u!4 &2028963606 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2028963604} + 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 &2140621153 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2140621156} + - component: {fileID: 2140621155} + - component: {fileID: 2140621154} + m_Layer: 0 + m_Name: EventSystem + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &2140621154 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2140621153} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 01614664b831546d2ae94a42149d80ac, type: 3} + m_Name: + m_EditorClassIdentifier: + m_SendPointerHoverToParent: 1 + m_MoveRepeatDelay: 0.5 + m_MoveRepeatRate: 0.1 + m_XRTrackingOrigin: {fileID: 0} + m_ActionsAsset: {fileID: -944628639613478452, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_PointAction: {fileID: -1654692200621890270, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_MoveAction: {fileID: -8784545083839296357, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_SubmitAction: {fileID: 392368643174621059, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_CancelAction: {fileID: 7727032971491509709, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_LeftClickAction: {fileID: 3001919216989983466, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_MiddleClickAction: {fileID: -2185481485913320682, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_RightClickAction: {fileID: -4090225696740746782, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_ScrollWheelAction: {fileID: 6240969308177333660, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_TrackedDevicePositionAction: {fileID: 6564999863303420839, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_TrackedDeviceOrientationAction: {fileID: 7970375526676320489, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_DeselectOnBackgroundClick: 1 + m_PointerBehavior: 0 + m_CursorLockBehavior: 0 + m_ScrollDeltaPerTick: 6 +--- !u!114 &2140621155 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2140621153} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_FirstSelected: {fileID: 0} + m_sendNavigationEvents: 1 + m_DragThreshold: 10 +--- !u!4 &2140621156 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2140621153} + 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!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 893152817} + - {fileID: 1916112490} + - {fileID: 1607417703} + - {fileID: 2028963606} + - {fileID: 1211836790} + - {fileID: 2140621156} + - {fileID: 194973789} diff --git a/Assets/App/Scenes/Live2d Sence.unity.meta b/Assets/App/Scenes/Live2d Sence.unity.meta new file mode 100644 index 0000000..1e774b7 --- /dev/null +++ b/Assets/App/Scenes/Live2d Sence.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 834aa5ca0d5c48149b8c17792ca7ae9a +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/App/Scenes/MainSence.unity b/Assets/App/Scenes/MainSence.unity index 2d854cc..3f2933f 100644 --- a/Assets/App/Scenes/MainSence.unity +++ b/Assets/App/Scenes/MainSence.unity @@ -379,6 +379,7 @@ MonoBehaviour: _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 @@ -422,6 +423,53 @@ RectTransform: m_CorrespondingSourceObject: {fileID: 2925110611670761417, guid: 4ad5b82186255a54aafec3328e7a1737, type: 3} m_PrefabInstance: {fileID: 1348947105} m_PrefabAsset: {fileID: 0} +--- !u!1 &416852388 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 416852389} + - component: {fileID: 416852390} + m_Layer: 0 + m_Name: VoiceController + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &416852389 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 416852388} + 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: 622824879} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &416852390 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 416852388} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3032c507d42ef0740bf28ca870b8fb21, type: 3} + m_Name: + m_EditorClassIdentifier: + _audioSource: {fileID: 0} + _audioMixerGroup: {fileID: 0} + _autoPlay: 1 --- !u!114 &577588901 stripped MonoBehaviour: m_CorrespondingSourceObject: {fileID: 2620430970088811223, guid: cd9756d05ff7ef847b3abde3e768a611, type: 3} @@ -433,7 +481,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} m_Name: m_EditorClassIdentifier: ---- !u!1 &829067250 +--- !u!1 &622824876 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -441,38 +489,218 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 829067253} - - component: {fileID: 829067252} - - component: {fileID: 829067251} + - component: {fileID: 622824879} + - component: {fileID: 622824877} + - component: {fileID: 622824878} m_Layer: 0 - m_Name: ChatManager + m_Name: Audio Manager m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!114 &829067251 +--- !u!114 &622824877 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 829067250} + m_GameObject: {fileID: 622824876} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5a710e93b242df6468dad9017ee5bc47, type: 3} + m_Name: + m_EditorClassIdentifier: + _bgmController: {fileID: 709008645} + _voiceController: {fileID: 416852390} + _sfxController: {fileID: 1145687257} + _uiController: {fileID: 920517456} + _audioMixer: {fileID: 24100000, guid: 5661f475a706360419d3419e026775a1, type: 2} + _masterGroup: {fileID: 24300002, guid: 5661f475a706360419d3419e026775a1, type: 2} + _bgmGroup: {fileID: 3421493782693753967, guid: 5661f475a706360419d3419e026775a1, type: 2} + _sfxGroup: {fileID: -7380437828635221995, guid: 5661f475a706360419d3419e026775a1, type: 2} + _uiGroup: {fileID: 4821037570395430284, guid: 5661f475a706360419d3419e026775a1, type: 2} + _voiceGroup: {fileID: 5131122028680581099, guid: 5661f475a706360419d3419e026775a1, type: 2} + _masterVolume: 1 +--- !u!82 &622824878 +AudioSource: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 622824876} + m_Enabled: 1 + serializedVersion: 4 + OutputAudioMixerGroup: {fileID: 0} + m_audioClip: {fileID: 0} + m_Resource: {fileID: 0} + m_PlayOnAwake: 1 + m_Volume: 1 + m_Pitch: 1 + Loop: 0 + Mute: 0 + Spatialize: 0 + SpatializePostEffects: 0 + Priority: 128 + DopplerLevel: 1 + MinDistance: 1 + MaxDistance: 500 + Pan2D: 0 + rolloffMode: 0 + BypassEffects: 0 + BypassListenerEffects: 0 + BypassReverbZones: 0 + rolloffCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + - serializedVersion: 3 + time: 1 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + panLevelCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + spreadCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + reverbZoneMixCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 +--- !u!4 &622824879 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 622824876} + 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: + - {fileID: 709008644} + - {fileID: 416852389} + - {fileID: 1145687256} + - {fileID: 920517455} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &709008643 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 709008644} + - component: {fileID: 709008645} + m_Layer: 0 + m_Name: BGMController + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &709008644 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 709008643} + 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: 622824879} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &709008645 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 709008643} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: bebe894334d166c44b4b4e1c755d03ae, type: 3} + m_Script: {fileID: 11500000, guid: 6b0432b7302b2834f916352bcb369cd2, type: 3} m_Name: m_EditorClassIdentifier: - _scrollRect: {fileID: 1636555668} - _gridLayoutGroup: {fileID: 1863899999} - _contentSizeFitter: {fileID: 1863899998} - _chatBubblePrefab: {fileID: 3575337311992857127, guid: 7151716ef12e2424d866e4db41b1f6f9, type: 3} - _bubbleContainer: {fileID: 1863899997} - _enableQueueAnimation: 1 - _queueAnimationDelay: 0.1 - _maxBubbles: 20 - _autoCleanup: 1 - _cleanupThreshold: 15 + _audioSource: {fileID: 0} + _audioMixerGroup: {fileID: 0} +--- !u!1 &829067250 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 829067253} + - component: {fileID: 829067252} + m_Layer: 0 + m_Name: ChatManager + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 --- !u!114 &829067252 MonoBehaviour: m_ObjectHideFlags: 0 @@ -485,9 +713,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 2b142d4545b9a4f43841c4893e72c04f, type: 3} m_Name: m_EditorClassIdentifier: - _webSocketManager: {fileID: 0} - _voiceManager: {fileID: 0} - _chatBubbleManager: {fileID: 829067251} + _chatBubblePanel: {fileID: 2017944365} _characterId: 44444444-4444-4444-4444-444444444444 _userId: bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb _enableMessageQueue: 1 @@ -507,6 +733,53 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &920517454 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 920517455} + - component: {fileID: 920517456} + m_Layer: 0 + m_Name: UIController + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &920517455 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 920517454} + 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: 622824879} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &920517456 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 920517454} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 547f477965608824f9b820652903230a, type: 3} + m_Name: + m_EditorClassIdentifier: + _audioSource: {fileID: 0} + _audioMixerGroup: {fileID: 0} + _poolSize: 5 --- !u!114 &948060135 stripped MonoBehaviour: m_CorrespondingSourceObject: {fileID: 5682896619326521631, guid: 4e9594e3adccfc04793a3b9f79181ebd, type: 3} @@ -689,31 +962,14 @@ GameObject: serializedVersion: 6 m_Component: - component: {fileID: 1104447313} - - component: {fileID: 1104447312} + - component: {fileID: 1104447314} m_Layer: 0 - m_Name: GameManager + m_Name: SystemManager m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!114 &1104447312 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1104447311} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: f2343a97be172e748a6501089d9e3c0a, type: 3} - m_Name: - m_EditorClassIdentifier: - _initializationManager: {fileID: 0} - _managerRegistry: {fileID: 0} - _dependencyManager: {fileID: 0} - _autoInitializeOnStart: 1 - _createManagersIfNotExist: 1 --- !u!4 &1104447313 Transform: m_ObjectHideFlags: 0 @@ -730,6 +986,28 @@ Transform: - {fileID: 1087467995} m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1104447314 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1104447311} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c31e1b9082df088489ede1181874cde4, type: 3} + m_Name: + m_EditorClassIdentifier: + _webSocketManager: {fileID: 0} + _sessionManager: {fileID: 0} + _httpApiClient: {fileID: 0} + _audioManager: {fileID: 0} + _loadingManager: {fileID: 0} + _chatManager: {fileID: 829067252} + _autoInitializeOnStart: 1 + _createManagersIfNotExist: 1 + _autoUpdateCameraOnSceneChange: 1 + _camera: {fileID: 0} --- !u!1 &1145326583 GameObject: m_ObjectHideFlags: 0 @@ -835,6 +1113,53 @@ RectTransform: m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: 0, y: 0} m_Pivot: {x: 0, y: 0} +--- !u!1 &1145687255 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1145687256} + - component: {fileID: 1145687257} + m_Layer: 0 + m_Name: SFXController + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1145687256 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1145687255} + 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: 622824879} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1145687257 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1145687255} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 54c04a926f0c6b44fa67847d6c530907, type: 3} + m_Name: + m_EditorClassIdentifier: + _audioSource: {fileID: 0} + _audioMixerGroup: {fileID: 0} + _poolSize: 10 --- !u!114 &1273342020 stripped MonoBehaviour: m_CorrespondingSourceObject: {fileID: 8225492411684342768, guid: 4e9594e3adccfc04793a3b9f79181ebd, type: 3} @@ -1093,17 +1418,6 @@ RectTransform: m_CorrespondingSourceObject: {fileID: 1193639241046649271, guid: 290ac1b848f75584f9077b5dd03337f6, type: 3} m_PrefabInstance: {fileID: 1636555666} m_PrefabAsset: {fileID: 0} ---- !u!114 &1636555668 stripped -MonoBehaviour: - m_CorrespondingSourceObject: {fileID: 900044648925520396, guid: 290ac1b848f75584f9077b5dd03337f6, type: 3} - m_PrefabInstance: {fileID: 1636555666} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 1aa08ab6e0800fa44ae55d278d1423e3, type: 3} - m_Name: - m_EditorClassIdentifier: --- !u!114 &1695405118 stripped MonoBehaviour: m_CorrespondingSourceObject: {fileID: 3254242542015821905, guid: cd9756d05ff7ef847b3abde3e768a611, type: 3} @@ -1115,31 +1429,15 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 2da0c512f12947e489f739169773d7ca, type: 3} m_Name: m_EditorClassIdentifier: ---- !u!224 &1863899997 stripped -RectTransform: - m_CorrespondingSourceObject: {fileID: 5196526590166986169, guid: 290ac1b848f75584f9077b5dd03337f6, type: 3} - m_PrefabInstance: {fileID: 1636555666} - m_PrefabAsset: {fileID: 0} ---- !u!114 &1863899998 stripped -MonoBehaviour: - m_CorrespondingSourceObject: {fileID: 2327760123492423530, guid: 290ac1b848f75584f9077b5dd03337f6, type: 3} - m_PrefabInstance: {fileID: 1636555666} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3} - m_Name: - m_EditorClassIdentifier: ---- !u!114 &1863899999 stripped +--- !u!114 &2017944365 stripped MonoBehaviour: - m_CorrespondingSourceObject: {fileID: 6992329501726019127, guid: 290ac1b848f75584f9077b5dd03337f6, type: 3} + m_CorrespondingSourceObject: {fileID: 7731201516897828228, guid: 290ac1b848f75584f9077b5dd03337f6, type: 3} m_PrefabInstance: {fileID: 1636555666} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 8a8695521f0d02e499659fee002a26c2, type: 3} + m_Script: {fileID: 11500000, guid: 30700e2214ca700469760eef5b0069ea, type: 3} m_Name: m_EditorClassIdentifier: --- !u!224 &2076648994 stripped @@ -1254,3 +1552,4 @@ SceneRoots: - {fileID: 829067253} - {fileID: 133449156} - {fileID: 332900996} + - {fileID: 622824879} diff --git a/Assets/App/Scenes/StartSence.unity b/Assets/App/Scenes/StartSence.unity index 81246aa..373ae08 100644 --- a/Assets/App/Scenes/StartSence.unity +++ b/Assets/App/Scenes/StartSence.unity @@ -128,7 +128,7 @@ GameObject: serializedVersion: 6 m_Component: - component: {fileID: 177257299} - - component: {fileID: 177257298} + - component: {fileID: 177257300} m_Layer: 0 m_Name: GameSystem m_TagString: Untagged @@ -136,23 +136,6 @@ GameObject: m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!114 &177257298 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 177257297} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: f2343a97be172e748a6501089d9e3c0a, type: 3} - m_Name: - m_EditorClassIdentifier: - _initializationManager: {fileID: 0} - _managerRegistry: {fileID: 0} - _dependencyManager: {fileID: 0} - _autoInitializeOnStart: 1 - _createManagersIfNotExist: 1 --- !u!4 &177257299 Transform: m_ObjectHideFlags: 0 @@ -166,10 +149,30 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: - - {fileID: 573203869} - {fileID: 1015393258} m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &177257300 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 177257297} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c31e1b9082df088489ede1181874cde4, type: 3} + m_Name: + m_EditorClassIdentifier: + _webSocketManager: {fileID: 0} + _sessionManager: {fileID: 0} + _httpApiClient: {fileID: 0} + _audioManager: {fileID: 0} + _loadingManager: {fileID: 0} + _autoInitializeOnStart: 1 + _createManagersIfNotExist: 1 + _autoUpdateCameraOnSceneChange: 1 + _camera: {fileID: 0} --- !u!1 &193680551 GameObject: m_ObjectHideFlags: 0 @@ -414,78 +417,6 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 359102246} m_CullTransparentMesh: 1 ---- !u!1 &573203868 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 573203869} - - component: {fileID: 573203872} - - component: {fileID: 573203871} - - component: {fileID: 573203870} - m_Layer: 0 - m_Name: Network Managers - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!4 &573203869 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 573203868} - 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: 177257299} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!114 &573203870 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 573203868} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 8e1977f0484f5f84684a077ad63409b5, type: 3} - m_Name: - m_EditorClassIdentifier: ---- !u!114 &573203871 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 573203868} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 00fbb8efd091db646bc0653b6295222f, type: 3} - m_Name: - m_EditorClassIdentifier: ---- !u!114 &573203872 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 573203868} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: f0048fe563a65f94a90a49760ba27126, type: 3} - m_Name: - m_EditorClassIdentifier: - _currentSessionId: - _isInitialized: 0 --- !u!1 &648783742 GameObject: m_ObjectHideFlags: 0 @@ -827,6 +758,8 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: _loadingUI: {fileID: 193680555} + _autoProgressMax: 0.9 + _autoProgressSpeed: 0.25 --- !u!4 &915844785 Transform: m_ObjectHideFlags: 0 diff --git a/Assets/Core/Attributes/InjectAttribute.cs b/Assets/Core/Attributes/InjectAttribute.cs deleted file mode 100644 index 743c9a3..0000000 --- a/Assets/Core/Attributes/InjectAttribute.cs +++ /dev/null @@ -1,17 +0,0 @@ -using UnityEngine; - -namespace ProjectVG.Core.Attributes -{ - /// - /// 의존성 주입을 위한 커스텀 어트리뷰트 - /// - public class InjectAttribute : PropertyAttribute - { - public string DependencyName { get; } - - public InjectAttribute(string dependencyName = "") - { - DependencyName = dependencyName; - } - } -} \ No newline at end of file diff --git a/Assets/Core/Attributes/InjectAttribute.cs.meta b/Assets/Core/Attributes/InjectAttribute.cs.meta deleted file mode 100644 index f186bc4..0000000 --- a/Assets/Core/Attributes/InjectAttribute.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 2aa07c71a3e7eb2429aeca8359d31418 \ No newline at end of file diff --git a/Assets/Core/Audio/AudioControllerCore.cs b/Assets/Core/Audio/AudioControllerCore.cs new file mode 100644 index 0000000..ac38814 --- /dev/null +++ b/Assets/Core/Audio/AudioControllerCore.cs @@ -0,0 +1,82 @@ +#nullable enable +using UnityEngine; +using UnityEngine.Audio; + +namespace ProjectVG.Core.Audio +{ + public class AudioControllerCore : MonoBehaviour + { + [SerializeField] protected AudioSource? _audioSource; + [SerializeField] protected AudioMixerGroup? _audioMixerGroup; + + protected float _volume = 1f; + protected string _volumeParameterName = ""; + + public virtual void Initialize(string volumeParameterName) + { + _volumeParameterName = volumeParameterName; + + // AudioSource가 없으면 자동 생성 + if (_audioSource == null) + { + _audioSource = gameObject.AddComponent(); + _audioSource.playOnAwake = false; + _audioSource.loop = false; + _audioSource.volume = _volume; + } + + // AudioMixerGroup이 설정되지 않은 경우 AudioManager에서 자동 할당 + if (_audioMixerGroup == null) + { + var group = GetAudioMixerGroupFromManager(); + if (group != null) + { + _audioMixerGroup = group; + } + } + + if (_audioSource != null && _audioMixerGroup != null) + { + _audioSource.outputAudioMixerGroup = _audioMixerGroup; + } + } + + protected virtual AudioMixerGroup? GetAudioMixerGroupFromManager() + { + // 하위 클래스에서 오버라이드하여 적절한 그룹 반환 + return null; + } + + public virtual void SetVolume(float volume) + { + _volume = Mathf.Clamp01(volume); + + if (_audioMixerGroup != null && !string.IsNullOrEmpty(_volumeParameterName)) + { + float dbValue = _volume > 0 ? 20f * Mathf.Log10(_volume) : -80f; + _audioMixerGroup.audioMixer.SetFloat(_volumeParameterName, dbValue); + } + } + + public virtual void Stop() + { + if (_audioSource != null) + { + _audioSource.Stop(); + } + } + + public virtual bool IsPlaying() + { + return _audioSource != null && _audioSource.isPlaying; + } + + public virtual float GetVolume() + { + return _volume; + } + + public AudioSource? GetAudioSource() => _audioSource; + public AudioMixerGroup? GetAudioMixerGroup() => _audioMixerGroup; + } +} diff --git a/Assets/Core/Audio/AudioControllerCore.cs.meta b/Assets/Core/Audio/AudioControllerCore.cs.meta new file mode 100644 index 0000000..783245d --- /dev/null +++ b/Assets/Core/Audio/AudioControllerCore.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8b7ab8e5a4fd02b448fdc3f34d05ea54 \ No newline at end of file diff --git a/Assets/Core/Audio/AudioManager.cs b/Assets/Core/Audio/AudioManager.cs index eb4c761..f617ed3 100644 --- a/Assets/Core/Audio/AudioManager.cs +++ b/Assets/Core/Audio/AudioManager.cs @@ -1,12 +1,335 @@ +#nullable enable +using System; using UnityEngine; using UnityEngine.Audio; -using UnityEngine.Serialization; +using Cysharp.Threading.Tasks; +using ProjectVG.Domain.Chat.Model; +using ProjectVG.Core.Utils; -public class AudioManager : Singleton +namespace ProjectVG.Core.Audio { - [SerializeField] private AudioSource voiceSource; - [SerializeField] private AudioSource bgmSource; - [SerializeField] private AudioSource sfxSource; - - public void Initialize() { } + public class AudioManager : Singleton + { + [Header("Audio Controllers")] + [SerializeField] private BGMController? _bgmController; + [SerializeField] private VoiceController? _voiceController; + [SerializeField] private SFXController? _sfxController; + [SerializeField] private UIController? _uiController; + + [Header("Audio Mixer")] + [SerializeField] private AudioMixer? _audioMixer; + + [Header("Audio Mixer Groups")] + [SerializeField] public AudioMixerGroup? _masterGroup; + [SerializeField] public AudioMixerGroup? _bgmGroup; + [SerializeField] public AudioMixerGroup? _sfxGroup; + [SerializeField] public AudioMixerGroup? _uiGroup; + [SerializeField] public AudioMixerGroup? _voiceGroup; + + [Header("Volume Settings")] + [SerializeField] private float _masterVolume = 1f; + + private bool _isInitialized = false; + private bool _isLoadingSettings = false; + + public bool IsInitialized => _isInitialized; + public float MasterVolume => _masterVolume; + public float BgmVolume => _bgmController?.GetVolume() ?? 1f; + public float SfxVolume => _sfxController?.GetVolume() ?? 1f; + public float UiVolume => _uiController?.GetVolume() ?? 1f; + public float VoiceVolume => _voiceController?.GetVolume() ?? 1f; + + public event Action? OnMasterVolumeChanged; + public event Action? OnBgmVolumeChanged; + public event Action? OnSfxVolumeChanged; + public event Action? OnUiVolumeChanged; + public event Action? OnVoiceVolumeChanged; + + #region Unity Lifecycle + + protected override void Awake() + { + base.Awake(); + } + + private void Start() + { + Initialize(); + } + + private void OnDestroy() + { + Cleanup(); + } + + #endregion + + #region Public Methods + + public void Initialize() + { + if (_isInitialized) return; + + try + { + InitializeControllers(); + LoadVolumeSettings(); + + _isInitialized = true; + Debug.Log("[AudioManager] 초기화 완료"); + } + catch (Exception ex) + { + Debug.LogError($"[AudioManager] 초기화 실패: {ex.Message}"); + } + } + + public void PlayBGM(AudioClip? clip, bool loop = true) + { + _bgmController?.PlayBGM(clip, loop); + } + + public void StopBGM() + { + _bgmController?.StopBGM(); + } + + public void PlayVoice(AudioClip? clip) + { + _voiceController?.PlayVoice(clip); + } + + public void PlayVoice(VoiceData voiceData) + { + _voiceController?.PlayVoice(voiceData); + } + + public async UniTask PlayVoiceAsync(VoiceData voiceData) + { + if (_voiceController != null) + { + await _voiceController.PlayVoiceAsync(voiceData); + } + } + + public void StopVoice() + { + _voiceController?.StopVoice(); + } + + public void PauseVoice() + { + _voiceController?.PauseVoice(); + } + + public void ResumeVoice() + { + _voiceController?.ResumeVoice(); + } + + public void PlaySFX(AudioClip? clip, Vector3? position = null) + { + _sfxController?.PlaySFX(clip, position); + } + + public void PlayUI(AudioClip? clip) + { + _uiController?.PlayUI(clip); + } + + public void SetMasterVolume(float volume) + { + _masterVolume = Mathf.Clamp01(volume); + + if (_audioMixer != null) + { + float dbValue = _masterVolume > 0 ? 20f * Mathf.Log10(_masterVolume) : -80f; + _audioMixer.SetFloat("MasterVolume", dbValue); + } + + OnMasterVolumeChanged?.Invoke(_masterVolume); + SaveVolumeSettings(); + } + + public void SetBGMVolume(float volume) + { + _bgmController?.SetVolume(volume); + OnBgmVolumeChanged?.Invoke(volume); + SaveVolumeSettings(); + } + + public void SetSFXVolume(float volume) + { + _sfxController?.SetVolume(volume); + OnSfxVolumeChanged?.Invoke(volume); + SaveVolumeSettings(); + } + + public void SetUIVolume(float volume) + { + _uiController?.SetVolume(volume); + OnUiVolumeChanged?.Invoke(volume); + SaveVolumeSettings(); + } + + public void SetVoiceVolume(float volume) + { + _voiceController?.SetVolume(volume); + OnVoiceVolumeChanged?.Invoke(volume); + SaveVolumeSettings(); + } + + public void SaveVolumeSettings() + { + if (_isLoadingSettings) return; + + PlayerPrefs.SetFloat("Audio_MasterVolume", _masterVolume); + PlayerPrefs.SetFloat("Audio_BGMVolume", BgmVolume); + PlayerPrefs.SetFloat("Audio_SFXVolume", SfxVolume); + PlayerPrefs.SetFloat("Audio_UIVolume", UiVolume); + PlayerPrefs.SetFloat("Audio_VoiceVolume", VoiceVolume); + PlayerPrefs.Save(); + } + + public void LoadVolumeSettings() + { + _isLoadingSettings = true; + + _masterVolume = PlayerPrefs.GetFloat("Audio_MasterVolume", 1f); + float bgmVolume = PlayerPrefs.GetFloat("Audio_BGMVolume", 1f); + float sfxVolume = PlayerPrefs.GetFloat("Audio_SFXVolume", 1f); + float uiVolume = PlayerPrefs.GetFloat("Audio_UIVolume", 1f); + float voiceVolume = PlayerPrefs.GetFloat("Audio_VoiceVolume", 1f); + + SetMasterVolume(_masterVolume); + SetBGMVolume(bgmVolume); + SetSFXVolume(sfxVolume); + SetUIVolume(uiVolume); + SetVoiceVolume(voiceVolume); + + _isLoadingSettings = false; + } + + public void ResetVolumeSettings() + { + SetMasterVolume(1f); + SetBGMVolume(1f); + SetSFXVolume(1f); + SetUIVolume(1f); + SetVoiceVolume(1f); + } + + public void StopAllAudio() + { + _bgmController?.Stop(); + _voiceController?.Stop(); + _sfxController?.Stop(); + _uiController?.Stop(); + } + + public bool IsBGMPlaying() + { + return _bgmController?.IsPlaying() ?? false; + } + + public bool IsVoicePlaying() + { + return _voiceController?.IsPlaying() ?? false; + } + + public int GetActiveSFXCount() + { + // SFXController에서 활성 소스 개수 반환 메서드 추가 필요 + return 0; + } + + public int GetActiveUICount() + { + // UIController에서 활성 소스 개수 반환 메서드 추가 필요 + return 0; + } + + #endregion + + #region Private Methods + + private void InitializeControllers() + { + SetupAudioMixerGroups(); + + _bgmController?.Initialize(); + _voiceController?.Initialize(); + _sfxController?.Initialize(); + _uiController?.Initialize(); + } + + private void SetupAudioMixerGroups() + { + if (_audioMixer == null) + { + Debug.LogWarning("[AudioManager] AudioMixer가 설정되지 않았습니다!"); + return; + } + + // 그룹이 설정되지 않은 경우 동적으로 생성 + if (_masterGroup == null) + _masterGroup = CreateOrFindGroup("Master"); + if (_bgmGroup == null) + _bgmGroup = CreateOrFindGroup("BGM"); + if (_sfxGroup == null) + _sfxGroup = CreateOrFindGroup("SFX"); + if (_uiGroup == null) + _uiGroup = CreateOrFindGroup("UI"); + if (_voiceGroup == null) + _voiceGroup = CreateOrFindGroup("Voice"); + + // 각 컨트롤러에 그룹 할당 + AssignGroupToController(_bgmController, _bgmGroup); + AssignGroupToController(_voiceController, _voiceGroup); + AssignGroupToController(_sfxController, _sfxGroup); + AssignGroupToController(_uiController, _uiGroup); + } + + private AudioMixerGroup? CreateOrFindGroup(string groupName) + { + if (_audioMixer == null) return null; + + // 기존 그룹 찾기 + AudioMixerGroup[] groups = _audioMixer.FindMatchingGroups(groupName); + if (groups.Length > 0) + { + Debug.Log($"[AudioManager] 기존 그룹 사용: {groupName}"); + return groups[0]; + } + + // 그룹이 없으면 생성 (Unity 에디터에서만 가능) + Debug.LogWarning($"[AudioManager] 그룹 '{groupName}'을 찾을 수 없습니다. AudioMixer에서 수동으로 생성해주세요."); + return null; + } + + private void AssignGroupToController(AudioControllerCore? controller, AudioMixerGroup? group) + { + if (controller == null || group == null) return; + + // AudioControllerCore의 _audioMixerGroup 필드에 할당 + var audioSource = controller.GetAudioSource(); + if (audioSource != null) + { + audioSource.outputAudioMixerGroup = group; + Debug.Log($"[AudioManager] {controller.GetType().Name}에 {group.name} 그룹 할당"); + } + } + + private void Cleanup() + { + _bgmController?.Stop(); + _voiceController?.Stop(); + _sfxController?.Stop(); + _uiController?.Stop(); + + Debug.Log("[AudioManager] 정리 완료"); + } + + #endregion + } } diff --git a/Assets/Core/Audio/AudioRecorder.cs b/Assets/Core/Audio/AudioRecorder.cs index 6e26ac0..bb40579 100644 --- a/Assets/Core/Audio/AudioRecorder.cs +++ b/Assets/Core/Audio/AudioRecorder.cs @@ -14,7 +14,6 @@ public class AudioRecorder : Singleton { [Header("Recording Settings")] [SerializeField] private int _sampleRate = 44100; - [SerializeField] private int _channels = 1; [SerializeField] private int _maxRecordingLength = 30; // 최대 녹음 시간 (초) [Header("Audio Processing")] @@ -99,8 +98,7 @@ public bool StartRecording() _isRecording = true; _recordingStartTime = Time.time; - // 최대 녹음 시간만큼 버퍼 할당 - _recordingClip = Microphone.Start(_currentDevice ?? string.Empty, false, _maxRecordingLength, _sampleRate); + _recordingClip = Microphone.Start(_currentDevice != null ? _currentDevice : string.Empty, false, _maxRecordingLength, _sampleRate); Debug.Log($"[AudioRecorder] 음성 녹음 시작됨 (최대 {_maxRecordingLength}초, {_sampleRate}Hz)"); OnRecordingStarted?.Invoke(); @@ -134,7 +132,7 @@ public bool StartRecording() _recordingEndTime = Time.time; float actualRecordingDuration = _recordingEndTime - _recordingStartTime; - Microphone.End(_currentDevice ?? string.Empty); + Microphone.End(_currentDevice != null ? _currentDevice : string.Empty); if (_recordingClip != null) { diff --git a/Assets/Core/Audio/BGMController.cs b/Assets/Core/Audio/BGMController.cs new file mode 100644 index 0000000..4fec16d --- /dev/null +++ b/Assets/Core/Audio/BGMController.cs @@ -0,0 +1,53 @@ +#nullable enable +using UnityEngine; +using UnityEngine.Audio; + +namespace ProjectVG.Core.Audio +{ + public class BGMController : AudioControllerCore, IAudioController + { + public string GetControllerName() => "BGM"; + + public void Initialize() + { + base.Initialize("BGMVolume"); + } + + protected override AudioMixerGroup? GetAudioMixerGroupFromManager() + { + return AudioManager.Instance._bgmGroup; + } + + public void PlayBGM(AudioClip? clip, bool loop = true) + { + if (_audioSource == null) + { + Debug.LogWarning("[BGMController] AudioSource가 설정되지 않았습니다!"); + return; + } + + if (clip == null) + { + Debug.LogWarning("[BGMController] BGM 클립이 null입니다!"); + return; + } + + _audioSource.clip = clip; + _audioSource.loop = loop; + _audioSource.Play(); + + Debug.Log($"[BGMController] BGM 재생 시작: {clip.name}"); + } + + public void StopBGM() + { + Stop(); + Debug.Log("[BGMController] BGM 재생 중지"); + } + + public AudioClip? GetCurrentClip() + { + return _audioSource?.clip; + } + } +} diff --git a/Assets/Core/Audio/BGMController.cs.meta b/Assets/Core/Audio/BGMController.cs.meta new file mode 100644 index 0000000..b468143 --- /dev/null +++ b/Assets/Core/Audio/BGMController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6b0432b7302b2834f916352bcb369cd2 \ No newline at end of file diff --git a/Assets/Core/Audio/IAudioController.cs b/Assets/Core/Audio/IAudioController.cs new file mode 100644 index 0000000..ff7274b --- /dev/null +++ b/Assets/Core/Audio/IAudioController.cs @@ -0,0 +1,15 @@ +#nullable enable +using UnityEngine; + +namespace ProjectVG.Core.Audio +{ + public interface IAudioController + { + void Initialize(); + void SetVolume(float volume); + void Stop(); + bool IsPlaying(); + float GetVolume(); + string GetControllerName(); + } +} diff --git a/Assets/Core/Audio/IAudioController.cs.meta b/Assets/Core/Audio/IAudioController.cs.meta new file mode 100644 index 0000000..d72acc5 --- /dev/null +++ b/Assets/Core/Audio/IAudioController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f638fbd296a39e04ab1e48c5432c5c7c \ No newline at end of file diff --git a/Assets/Core/Audio/NewAudioMixer.mixer b/Assets/Core/Audio/NewAudioMixer.mixer new file mode 100644 index 0000000..d1bfeb0 --- /dev/null +++ b/Assets/Core/Audio/NewAudioMixer.mixer @@ -0,0 +1,209 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!243 &-7380437828635221995 +AudioMixerGroupController: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: SFX + m_AudioMixer: {fileID: 24100000} + m_GroupID: 603178d5b1e127d429609f70b6dfaf79 + m_Children: [] + m_Volume: a14a87239cbc80d4ea3cc25c732ce286 + m_Pitch: 92aa377384123554fbf27fe9a957eeda + m_Send: 00000000000000000000000000000000 + m_Effects: + - {fileID: 4715039332668670441} + m_UserColorIndex: 0 + m_Mute: 0 + m_Solo: 0 + m_BypassEffects: 0 +--- !u!244 &-6908518189966514031 +AudioMixerEffectController: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_EffectID: 1f54a9280deea2a419a46fe74ebc4362 + m_EffectName: Attenuation + m_MixLevel: bc8ed2f555610cf4abeae1663db50a9b + m_Parameters: [] + m_SendTarget: {fileID: 0} + m_EnableWetMix: 0 + m_Bypass: 0 +--- !u!244 &-5248543927617172087 +AudioMixerEffectController: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_EffectID: 7bc61aa520f02174f9e4ed6ec4caca15 + m_EffectName: Attenuation + m_MixLevel: 52e4d977f715cbf4f8bf6a64c5e2462a + m_Parameters: [] + m_SendTarget: {fileID: 0} + m_EnableWetMix: 0 + m_Bypass: 0 +--- !u!241 &24100000 +AudioMixerController: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: NewAudioMixer + 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: + - 78fb5b14484baf24d8e876ea9cd4e22a + - 1bf7479df396fed488843a11e4f16671 + - 1c2c7c658d2c3e647adee42f03d62f41 + - 603178d5b1e127d429609f70b6dfaf79 + - 323361331de8fbf46b26000797bc1794 + 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: 78fb5b14484baf24d8e876ea9cd4e22a + m_Children: + - {fileID: 3421493782693753967} + - {fileID: 5131122028680581099} + - {fileID: -7380437828635221995} + - {fileID: 4821037570395430284} + m_Volume: d683a197e89cb63419aa87d5e9ffd201 + m_Pitch: 57058f4e3f8db9b45bf44904130fd94e + 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: 5b1fad1651498f64e896a200c3460279 + m_EffectName: Attenuation + m_MixLevel: 5634d4ae45b4e3b46a1238a75adf6559 + 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: 72350169c21947e4eab158741fc20486 + m_FloatValues: + d683a197e89cb63419aa87d5e9ffd201: 0.13552584 + m_TransitionOverrides: {} +--- !u!243 &3421493782693753967 +AudioMixerGroupController: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: BGM + m_AudioMixer: {fileID: 24100000} + m_GroupID: 1bf7479df396fed488843a11e4f16671 + m_Children: [] + m_Volume: e16781d3daa6c0e45adbff52097c39f1 + m_Pitch: ba3dd39f4f9857f4da2a595243444152 + m_Send: 00000000000000000000000000000000 + m_Effects: + - {fileID: -5248543927617172087} + m_UserColorIndex: 0 + m_Mute: 0 + m_Solo: 0 + m_BypassEffects: 0 +--- !u!244 &4715039332668670441 +AudioMixerEffectController: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_EffectID: 9663680121f2eec4da8b34409c293da5 + m_EffectName: Attenuation + m_MixLevel: 0acb42f468b80d949b814f51086d2244 + m_Parameters: [] + m_SendTarget: {fileID: 0} + m_EnableWetMix: 0 + m_Bypass: 0 +--- !u!243 &4821037570395430284 +AudioMixerGroupController: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: UI + m_AudioMixer: {fileID: 24100000} + m_GroupID: 323361331de8fbf46b26000797bc1794 + m_Children: [] + m_Volume: 5b2b28ee812512d479114f20e49b7c3e + m_Pitch: defdbe6e61c7b9742a79170f86807b5f + m_Send: 00000000000000000000000000000000 + m_Effects: + - {fileID: -6908518189966514031} + m_UserColorIndex: 0 + m_Mute: 0 + m_Solo: 0 + m_BypassEffects: 0 +--- !u!243 &5131122028680581099 +AudioMixerGroupController: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Voice + m_AudioMixer: {fileID: 24100000} + m_GroupID: 1c2c7c658d2c3e647adee42f03d62f41 + m_Children: [] + m_Volume: ebec21cb4c0d05b4bae9b518144b691f + m_Pitch: 137240dd54422f64a9ea7cfc5414197c + m_Send: 00000000000000000000000000000000 + m_Effects: + - {fileID: 8918031699990245807} + m_UserColorIndex: 0 + m_Mute: 0 + m_Solo: 0 + m_BypassEffects: 0 +--- !u!244 &8918031699990245807 +AudioMixerEffectController: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_EffectID: 4a3aa06ad866be94dba9d6c66dbb5bde + m_EffectName: Attenuation + m_MixLevel: 837f9927fc6868841a1f2bfd3338aac1 + m_Parameters: [] + m_SendTarget: {fileID: 0} + m_EnableWetMix: 0 + m_Bypass: 0 diff --git a/Assets/Readme.asset.meta b/Assets/Core/Audio/NewAudioMixer.mixer.meta similarity index 64% rename from Assets/Readme.asset.meta rename to Assets/Core/Audio/NewAudioMixer.mixer.meta index fce2d34..17f1ffa 100644 --- a/Assets/Readme.asset.meta +++ b/Assets/Core/Audio/NewAudioMixer.mixer.meta @@ -1,8 +1,8 @@ fileFormatVersion: 2 -guid: 7f2c382dc2c22446db59030979f6e495 +guid: 5661f475a706360419d3419e026775a1 NativeFormatImporter: externalObjects: {} - mainObjectFileID: 0 + mainObjectFileID: 24100000 userData: assetBundleName: assetBundleVariant: diff --git a/Assets/Core/Audio/SFXController.cs b/Assets/Core/Audio/SFXController.cs new file mode 100644 index 0000000..c1a8bf4 --- /dev/null +++ b/Assets/Core/Audio/SFXController.cs @@ -0,0 +1,135 @@ +#nullable enable +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Audio; + +namespace ProjectVG.Core.Audio +{ + public class SFXController : AudioControllerCore, IAudioController + { + [Header("SFX Settings")] + [SerializeField] private int _poolSize = 10; + + private Queue _pool = new Queue(); + private List _activeSources = new List(); + + public string GetControllerName() => "SFX"; + + public void Initialize() + { + base.Initialize("SFXVolume"); + CreatePool(); + } + + protected override AudioMixerGroup? GetAudioMixerGroupFromManager() + { + return AudioManager.Instance._sfxGroup; + } + + public override void Stop() + { + foreach (var source in _activeSources.ToArray()) + { + if (source != null) + { + source.Stop(); + ReturnToPool(source); + } + } + } + + public override bool IsPlaying() + { + return _activeSources.Count > 0; + } + + public void PlaySFX(AudioClip? clip, Vector3? position = null) + { + if (clip == null) + { + Debug.LogWarning("[SFXController] SFX 클립이 null입니다!"); + return; + } + + AudioSource? audioSource = GetPooledSource(); + if (audioSource == null) + { + Debug.LogWarning("[SFXController] 사용 가능한 AudioSource가 없습니다!"); + return; + } + + audioSource.clip = clip; + audioSource.loop = false; + + if (position.HasValue) + { + audioSource.transform.position = position.Value; + audioSource.spatialBlend = 1f; + } + else + { + audioSource.spatialBlend = 0f; + } + + audioSource.Play(); + + StartCoroutine(ReturnToPoolWhenFinished(audioSource)); + + Debug.Log($"[SFXController] SFX 재생: {clip.name}"); + } + + private void CreatePool() + { + for (int i = 0; i < _poolSize; i++) + { + GameObject poolObject = new GameObject($"PooledSFXSource_{i}"); + poolObject.transform.SetParent(transform); + + AudioSource audioSource = poolObject.AddComponent(); + audioSource.playOnAwake = false; + audioSource.outputAudioMixerGroup = GetAudioMixerGroupFromManager(); + + _pool.Enqueue(audioSource); + } + + Debug.Log($"[SFXController] SFX AudioSource 풀 생성 완료: {_poolSize}개"); + } + + private AudioSource? GetPooledSource() + { + if (_pool.Count > 0) + { + AudioSource audioSource = _pool.Dequeue(); + _activeSources.Add(audioSource); + return audioSource; + } + + return null; + } + + private void ReturnToPool(AudioSource audioSource) + { + audioSource.Stop(); + audioSource.clip = null; + audioSource.volume = 1f; + audioSource.pitch = 1f; + + _activeSources.Remove(audioSource); + _pool.Enqueue(audioSource); + } + + private IEnumerator ReturnToPoolWhenFinished(AudioSource audioSource) + { + while (audioSource != null && audioSource.isPlaying) + { + yield return null; + } + + if (audioSource != null) + { + ReturnToPool(audioSource); + } + } + } +} diff --git a/Assets/Core/Audio/SFXController.cs.meta b/Assets/Core/Audio/SFXController.cs.meta new file mode 100644 index 0000000..80ae809 --- /dev/null +++ b/Assets/Core/Audio/SFXController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 54c04a926f0c6b44fa67847d6c530907 \ No newline at end of file diff --git a/Assets/Core/Audio/UIController.cs b/Assets/Core/Audio/UIController.cs new file mode 100644 index 0000000..01b47d0 --- /dev/null +++ b/Assets/Core/Audio/UIController.cs @@ -0,0 +1,126 @@ +#nullable enable +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Audio; + +namespace ProjectVG.Core.Audio +{ + public class UIController : AudioControllerCore, IAudioController + { + [Header("UI Settings")] + [SerializeField] private int _poolSize = 5; + + private Queue _pool = new Queue(); + private List _activeSources = new List(); + + public string GetControllerName() => "UI"; + + public void Initialize() + { + base.Initialize("UIVolume"); + CreatePool(); + } + + protected override AudioMixerGroup? GetAudioMixerGroupFromManager() + { + return AudioManager.Instance._uiGroup; + } + + public override void Stop() + { + foreach (var source in _activeSources.ToArray()) + { + if (source != null) + { + source.Stop(); + ReturnToPool(source); + } + } + } + + public override bool IsPlaying() + { + return _activeSources.Count > 0; + } + + public void PlayUI(AudioClip? clip) + { + if (clip == null) + { + Debug.LogWarning("[UIController] UI 클립이 null입니다!"); + return; + } + + AudioSource? audioSource = GetPooledSource(); + if (audioSource == null) + { + Debug.LogWarning("[UIController] 사용 가능한 AudioSource가 없습니다!"); + return; + } + + audioSource.clip = clip; + audioSource.loop = false; + audioSource.spatialBlend = 0f; + + audioSource.Play(); + + StartCoroutine(ReturnToPoolWhenFinished(audioSource)); + + Debug.Log($"[UIController] UI 효과음 재생: {clip.name}"); + } + + private void CreatePool() + { + for (int i = 0; i < _poolSize; i++) + { + GameObject poolObject = new GameObject($"PooledUISource_{i}"); + poolObject.transform.SetParent(transform); + + AudioSource audioSource = poolObject.AddComponent(); + audioSource.playOnAwake = false; + audioSource.outputAudioMixerGroup = GetAudioMixerGroupFromManager(); + + _pool.Enqueue(audioSource); + } + + Debug.Log($"[UIController] UI AudioSource 풀 생성 완료: {_poolSize}개"); + } + + private AudioSource? GetPooledSource() + { + if (_pool.Count > 0) + { + AudioSource audioSource = _pool.Dequeue(); + _activeSources.Add(audioSource); + return audioSource; + } + + return null; + } + + private void ReturnToPool(AudioSource audioSource) + { + audioSource.Stop(); + audioSource.clip = null; + audioSource.volume = 1f; + audioSource.pitch = 1f; + + _activeSources.Remove(audioSource); + _pool.Enqueue(audioSource); + } + + private IEnumerator ReturnToPoolWhenFinished(AudioSource audioSource) + { + while (audioSource != null && audioSource.isPlaying) + { + yield return null; + } + + if (audioSource != null) + { + ReturnToPool(audioSource); + } + } + } +} diff --git a/Assets/Core/Audio/UIController.cs.meta b/Assets/Core/Audio/UIController.cs.meta new file mode 100644 index 0000000..839f71c --- /dev/null +++ b/Assets/Core/Audio/UIController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 547f477965608824f9b820652903230a \ No newline at end of file diff --git a/Assets/Core/Audio/VoiceController.cs b/Assets/Core/Audio/VoiceController.cs new file mode 100644 index 0000000..19d9003 --- /dev/null +++ b/Assets/Core/Audio/VoiceController.cs @@ -0,0 +1,217 @@ +#nullable enable +using System; +using UnityEngine; +using UnityEngine.Audio; +using ProjectVG.Domain.Chat.Model; +using Cysharp.Threading.Tasks; + +namespace ProjectVG.Core.Audio +{ + public class VoiceController : AudioControllerCore, IAudioController + { + [Header("Voice Settings")] + [SerializeField] private bool _autoPlay = true; + + private VoiceData? _currentVoice; + private bool _isPlaying = false; + + public string GetControllerName() => "Voice"; + public VoiceData? CurrentVoice => _currentVoice; + public bool AutoPlay => _autoPlay; + + public event Action? OnVoiceFinished; + public event Action? OnVoiceStarted; + public event Action? OnVoiceStopped; + + #region Unity Lifecycle + + private void Update() + { + if (_isPlaying && _audioSource != null && !_audioSource.isPlaying && _audioSource.clip != null) + { + _isPlaying = false; + OnVoiceFinished?.Invoke(); + } + } + + private void OnDestroy() + { + StopVoice(); + } + + #endregion + + #region IAudioController Implementation + + public void Initialize() + { + base.Initialize("VoiceVolume"); + SetVolume(_volume); + } + + protected override AudioMixerGroup? GetAudioMixerGroupFromManager() + { + return AudioManager.Instance._voiceGroup; + } + + public override void SetVolume(float volume) + { + base.SetVolume(volume); + + if (_audioSource != null) + { + _audioSource.volume = _volume; + } + } + + public override void Stop() + { + StopVoice(); + } + + public override bool IsPlaying() + { + return _isPlaying; + } + + #endregion + + #region Public Methods + + public void PlayVoice(AudioClip? clip) + { + if (_audioSource == null) + { + Debug.LogWarning("[VoiceController] AudioSource가 설정되지 않았습니다!"); + return; + } + + if (clip == null) + { + Debug.LogWarning("[VoiceController] Voice 클립이 null입니다!"); + return; + } + + PrepareAudioSource(); + + _audioSource.clip = clip; + _audioSource.Play(); + _isPlaying = true; + + Debug.Log($"[VoiceController] Voice 재생 시작: {clip.name}"); + } + + public async void PlayVoice(VoiceData voiceData) + { + if (voiceData == null || !voiceData.IsPlayable()) + { + Debug.LogWarning("[VoiceController] 재생할 수 있는 VoiceData가 없습니다."); + return; + } + + PrepareAudioSource(); + + await UniTask.Delay(50); + + _currentVoice = voiceData; + if (_audioSource != null) + { + _audioSource.clip = voiceData.AudioClip; + _audioSource.volume = _volume; + } + + if (_autoPlay && _audioSource != null) + { + _audioSource.Play(); + _isPlaying = true; + OnVoiceStarted?.Invoke(voiceData); + } + } + + public async UniTask PlayVoiceAsync(VoiceData voiceData) + { + if (voiceData == null || !voiceData.IsPlayable()) + { + Debug.LogWarning("[VoiceController] 재생할 수 있는 VoiceData가 없습니다."); + return; + } + + PrepareAudioSource(); + + await UniTask.Delay(50); + + _currentVoice = voiceData; + if (_audioSource != null) + { + _audioSource.clip = voiceData.AudioClip; + _audioSource.volume = _volume; + } + + if (_autoPlay && _audioSource != null) + { + _audioSource.Play(); + _isPlaying = true; + OnVoiceStarted?.Invoke(voiceData); + + await UniTask.WaitUntil(() => !_isPlaying); + } + } + + public void StopVoice() + { + if (_audioSource != null && _audioSource.isPlaying) + { + _audioSource.Stop(); + _isPlaying = false; + OnVoiceStopped?.Invoke(); + } + } + + public void PauseVoice() + { + if (_audioSource != null && _audioSource.isPlaying) + { + _audioSource.Pause(); + _isPlaying = false; + } + } + + public void ResumeVoice() + { + if (_audioSource != null && _audioSource.clip != null && !_audioSource.isPlaying) + { + _audioSource.UnPause(); + _isPlaying = true; + } + } + + public void SetAutoPlay(bool autoPlay) + { + _autoPlay = autoPlay; + } + + public AudioClip? GetCurrentClip() + { + return _audioSource?.clip; + } + + #endregion + + #region Private Methods + + private void PrepareAudioSource() + { + if (_audioSource == null) return; + + if (_audioSource.isPlaying) + { + _audioSource.Stop(); + } + + _audioSource.volume = _volume; + _audioSource.clip = null; + } + + #endregion + } +} \ No newline at end of file diff --git a/Assets/Core/Audio/VoiceController.cs.meta b/Assets/Core/Audio/VoiceController.cs.meta new file mode 100644 index 0000000..b294186 --- /dev/null +++ b/Assets/Core/Audio/VoiceController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3032c507d42ef0740bf28ca870b8fb21 \ No newline at end of file diff --git a/Assets/Core/Audio/VoiceManager.cs b/Assets/Core/Audio/VoiceManager.cs deleted file mode 100644 index 087c8ae..0000000 --- a/Assets/Core/Audio/VoiceManager.cs +++ /dev/null @@ -1,180 +0,0 @@ -#nullable enable -using System; -using UnityEngine; -using ProjectVG.Domain.Chat.Model; -using Cysharp.Threading.Tasks; - -namespace ProjectVG.Core.Audio -{ - public class VoiceManager : Singleton - { - [Header("Voice Audio Source")] - [SerializeField] private AudioSource _voiceSource; - - [Header("Voice Settings")] - [SerializeField] private float _volume = 1.0f; - [SerializeField] private bool _autoPlay = true; - - private VoiceData? _currentVoice; - private bool _isPlaying = false; - - public bool IsPlaying => _isPlaying; - public float Volume => _volume; - public VoiceData? CurrentVoice => _currentVoice; - - public event Action? OnVoiceFinished; - public event Action? OnVoiceStarted; - public event Action? OnVoiceStopped; - - #region Unity Lifecycle - - protected override void Awake() - { - base.Awake(); - Initialize(); - } - - private void Update() - { - if (_isPlaying && !_voiceSource.isPlaying && _voiceSource.clip != null) - { - _isPlaying = false; - OnVoiceFinished?.Invoke(); - } - } - - private void OnDestroy() - { - StopVoice(); - } - - #endregion - - #region Public Methods - - public async void PlayVoice(VoiceData voiceData) - { - if (voiceData == null || !voiceData.IsPlayable()) - { - Debug.LogWarning("[VoiceManager] 재생할 수 있는 VoiceData가 없습니다."); - return; - } - - PrepareAudioSource(); - - await UniTask.Delay(50); - - _currentVoice = voiceData; - _voiceSource.clip = voiceData.AudioClip; - _voiceSource.volume = _volume; - - if (_autoPlay) - { - _voiceSource.Play(); - _isPlaying = true; - OnVoiceStarted?.Invoke(voiceData); - } - } - - public async UniTask PlayVoiceAsync(VoiceData voiceData) - { - if (voiceData == null || !voiceData.IsPlayable()) - { - Debug.LogWarning("[VoiceManager] 재생할 수 있는 VoiceData가 없습니다."); - return; - } - - PrepareAudioSource(); - - await UniTask.Delay(50); - - _currentVoice = voiceData; - _voiceSource.clip = voiceData.AudioClip; - _voiceSource.volume = _volume; - - if (_autoPlay) - { - _voiceSource.Play(); - _isPlaying = true; - OnVoiceStarted?.Invoke(voiceData); - - await UniTask.WaitUntil(() => !_isPlaying); - } - } - - public void StopVoice() - { - if (_voiceSource.isPlaying) - { - _voiceSource.Stop(); - _isPlaying = false; - OnVoiceStopped?.Invoke(); - } - } - - public void PauseVoice() - { - if (_voiceSource.isPlaying) - { - _voiceSource.Pause(); - _isPlaying = false; - } - } - - public void ResumeVoice() - { - if (_voiceSource.clip != null && !_voiceSource.isPlaying) - { - _voiceSource.UnPause(); - _isPlaying = true; - } - } - - public void SetVolume(float volume) - { - _volume = Mathf.Clamp01(volume); - - if (_voiceSource != null) - { - _voiceSource.volume = _volume; - } - } - - public void SetAutoPlay(bool autoPlay) - { - _autoPlay = autoPlay; - } - - #endregion - - #region Private Methods - - private void Initialize() - { - if (_voiceSource == null) - { - _voiceSource = gameObject.AddComponent(); - _voiceSource.playOnAwake = false; - _voiceSource.loop = false; - _voiceSource.volume = _volume; - } - - SetVolume(_volume); - } - - private void PrepareAudioSource() - { - if (_voiceSource == null) return; - - if (_voiceSource.isPlaying) - { - _voiceSource.Stop(); - } - - _voiceSource.volume = _volume; - _voiceSource.clip = null; - } - - #endregion - } -} \ No newline at end of file diff --git a/Assets/Core/Audio/VoiceManager.cs.meta b/Assets/Core/Audio/VoiceManager.cs.meta deleted file mode 100644 index 759350d..0000000 --- a/Assets/Core/Audio/VoiceManager.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 6c790fc11dc6c5c4eaa90ee37fd0f53b \ No newline at end of file diff --git a/Assets/Core/DI/DIContainer.cs b/Assets/Core/DI/DIContainer.cs deleted file mode 100644 index b8f9a95..0000000 --- a/Assets/Core/DI/DIContainer.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using System.Collections.Generic; -using UnityEngine; -using ProjectVG.Core.Attributes; - -namespace ProjectVG.Core.DI -{ - public class DIContainer : Singleton - { - private readonly Dictionary _services = new Dictionary(); - - #region Unity Lifecycle - - protected override void Awake() - { - base.Awake(); - } - - #endregion - - #region Public Methods - - public void Register(T service) - { - _services[typeof(T)] = service; - } - - public void Unregister() - { - _services.Remove(typeof(T)); - } - - public T Get() - { - if (_services.TryGetValue(typeof(T), out var service)) - { - return (T)service; - } - return default(T); - } - - public void InjectDependencies(MonoBehaviour component) - { - var type = component.GetType(); - var fields = type.GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); - - Debug.Log($"[DIContainer] {component.GetType().Name}에 의존성 주입 시작"); - - foreach (var field in fields) - { - var injectAttribute = field.GetCustomAttributes(typeof(InjectAttribute), true); - if (injectAttribute.Length > 0) - { - var serviceType = field.FieldType; - var service = GetService(serviceType); - if (service != null) - { - field.SetValue(component, service); - Debug.Log($"[DIContainer] 의존성 주입 완료: {component.GetType().Name}.{field.Name} <- {serviceType.Name}"); - - // 주입 후 검증 - var injectedValue = field.GetValue(component); - if (injectedValue != null) - { - Debug.Log($"[DIContainer] 주입 검증 성공: {field.Name}에 {serviceType.Name} 인스턴스가 정상적으로 설정됨"); - } - else - { - Debug.LogError($"[DIContainer] 주입 검증 실패: {field.Name}이 여전히 null임"); - } - } - else - { - Debug.LogWarning($"[DIContainer] 의존성 주입 실패: {serviceType.Name} 서비스를 찾을 수 없습니다."); - Debug.LogWarning($"[DIContainer] 등록된 서비스 목록: {string.Join(", ", _services.Keys)}"); - } - } - } - } - - #endregion - - #region Private Methods - - private object GetService(Type serviceType) - { - if (_services.TryGetValue(serviceType, out var service)) - { - return service; - } - return null; - } - - #endregion - } -} \ No newline at end of file diff --git a/Assets/Core/DI/DIContainer.cs.meta b/Assets/Core/DI/DIContainer.cs.meta deleted file mode 100644 index 5e6e2f3..0000000 --- a/Assets/Core/DI/DIContainer.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: d45e4b5cd956cd74ead23af47fbec602 \ No newline at end of file diff --git a/Assets/Core/DebugConsole/GameDebugConsoleManager.cs b/Assets/Core/DebugConsole/GameDebugConsoleManager.cs index 23b0591..8078a99 100644 --- a/Assets/Core/DebugConsole/GameDebugConsoleManager.cs +++ b/Assets/Core/DebugConsole/GameDebugConsoleManager.cs @@ -1,3 +1,4 @@ +#nullable enable using UnityEngine; using UnityEngine.UI; using TMPro; @@ -190,7 +191,7 @@ private void CreateLogEntryObject(LogEntry entry) { if (_logEntryPrefab == null || _logContentParent == null) return; - GameObject logEntryObj; + GameObject? logEntryObj; if (_settings?.UseObjectPooling == true) { logEntryObj = GetPooledObject(); @@ -271,7 +272,7 @@ private void InitializeObjectPool() } } - private GameObject GetPooledObject() + private GameObject? GetPooledObject() { if (_objectPool.Count > 0) { diff --git a/Assets/Core/DebugConsole/LogEntryPrefab.cs b/Assets/Core/DebugConsole/LogEntryPrefab.cs index d9dd4f0..ff80ce3 100644 --- a/Assets/Core/DebugConsole/LogEntryPrefab.cs +++ b/Assets/Core/DebugConsole/LogEntryPrefab.cs @@ -1,3 +1,4 @@ +#nullable enable using UnityEngine; using TMPro; diff --git a/Assets/Core/DebugConsole/README_DebugConsole.md b/Assets/Core/DebugConsole/README_DebugConsole.md index 62816e9..4008856 100644 --- a/Assets/Core/DebugConsole/README_DebugConsole.md +++ b/Assets/Core/DebugConsole/README_DebugConsole.md @@ -1,7 +1,7 @@ -# 인게임 디버그 콘솔 사용 가이드 +# 인앱 디버그 콘솔 사용 가이드 ## 개요 -인게임에서 Debug.Log 메시지를 실시간으로 확인할 수 있는 관리자 콘솔입니다. +앱 실행 중 Debug.Log 메시지를 실시간으로 확인할 수 있는 관리자 콘솔입니다. ## 주요 기능 - **실시간 로그 표시**: 모든 Debug.Log 메시지를 실시간으로 확인 @@ -41,7 +41,7 @@ LogEntryPrefab (GameObject) ``` ### 3. 컴포넌트 설정 -1. **InGameDebugConsole** 스크립트를 ConsolePanel에 추가 +1. **InAppDebugConsole** 스크립트를 ConsolePanel에 추가 2. **LogEntryPrefab** 스크립트를 로그 엔트리 프리팹에 추가 3. **DebugConsoleSettings** ScriptableObject 생성: - Project 창에서 우클릭 → Create → ProjectVG → Debug Console Settings @@ -103,7 +103,7 @@ LogEntryPrefab (GameObject) ```csharp // 디버그 콘솔 참조 -InGameDebugConsole debugConsole = FindObjectOfType(); +InAppDebugConsole debugConsole = FindObjectOfType(); // 특정 키워드로 필터링 debugConsole.SetFilter("ChatManager"); @@ -151,5 +151,5 @@ debugConsole.ToggleConsole(); 2. 민감한 정보가 로그에 포함되지 않도록 주의 3. 모바일에서는 3개 손가락 동시 터치로 콘솔 제어 (설정에서 변경 가능) 4. 오브젝트 풀링 사용 시 Pool Size를 적절히 설정하여 메모리 사용량 조절 -5. 백그라운드 로깅 사용 시 게임 성능에 미치는 영향을 모니터링 +5. 백그라운드 로깅 사용 시 앱 성능에 미치는 영향을 모니터링 6. 성능이 중요한 경우 `InitializePoolOnStart`를 false로 설정하여 지연 초기화 사용 \ No newline at end of file diff --git a/Assets/Core/Extensions.meta b/Assets/Core/Extensions.meta deleted file mode 100644 index 69f76fe..0000000 --- a/Assets/Core/Extensions.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 69143743cfba359419e75dee89edbd2b -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Core/Input/ScreenTapManager.cs b/Assets/Core/Input/ScreenTapManager.cs index 82f8262..b50b85d 100644 --- a/Assets/Core/Input/ScreenTapManager.cs +++ b/Assets/Core/Input/ScreenTapManager.cs @@ -6,6 +6,7 @@ public interface IInputProvider { bool TryGetPosition(out Vector3 position); + bool IsUIBlockingInput(); } public interface IInputUpProvider @@ -41,8 +42,13 @@ public bool TryGetPosition(out Vector3 position) return false; } + public bool IsUIBlockingInput() + { + return IsPointerOverIgnoredUI(); + } + /// - /// "IgnoreLookAt" 태그가 달린 UI 클릭 여부 체크 + /// UI 클릭 여부 체크 /// private bool IsPointerOverIgnoredUI() { @@ -62,8 +68,14 @@ private bool IsPointerOverIgnoredUI() foreach (var result in results) { - if (result.gameObject.CompareTag("IgnoreLookAt")) + // UI 요소인지 확인 + if (result.gameObject.layer == LayerMask.NameToLayer("UI") || + result.gameObject.GetComponent() != null || + result.gameObject.GetComponent() != null || + result.gameObject.GetComponent() != null) + { return true; + } } return false; } @@ -100,22 +112,135 @@ public class ScreenTapManager : Singleton private CubismRaycaster _raycaster = null; + [Header("디버그 설정")] + [SerializeField] private bool _enableDebugLog = false; + [SerializeField] private bool _enableDebugRay = false; + [SerializeField] private Color _debugRayColor = Color.red; + [SerializeField] private float _debugRayDuration = 2f; + + private Vector3 _lastTapPosition = Vector3.zero; + private bool _hasTapped = false; + public void Initialize(Camera cam) { - if (_camera == null) - _camera = cam; + _camera = cam; if(_inputProvider == null) _inputProvider = new DefaultInputProvider(); if (_inputUpProvider == null) _inputUpProvider = new DefaultInputUpProvider(); } + private void Update() + { + UpdateTapDebug(); + } + + private void UpdateTapDebug() + { + if (_inputProvider.TryGetPosition(out var currentPosition)) + { + if (!_hasTapped) + { + _lastTapPosition = currentPosition; + _hasTapped = true; + LogTapDebug(currentPosition, "탭 시작"); + DrawDebugRay(currentPosition); + } + } + else + { + if (_hasTapped && _enableDebugLog && _inputProvider is DefaultInputProvider defaultProvider) + { + if (defaultProvider.IsUIBlockingInput()) + { + Debug.Log("[ScreenTapManager] UI가 입력을 차단했습니다."); + } + } + _hasTapped = false; + } + } + + private void LogTapDebug(Vector3 screenPosition, string action) + { + if (!_enableDebugLog) return; + + var worldPosition = _camera != null ? _camera.ScreenToWorldPoint(screenPosition) : Vector3.zero; + var viewportPosition = _camera != null ? _camera.ScreenToViewportPoint(screenPosition) : Vector3.zero; + + Debug.Log($"[ScreenTapManager] {action} - 스크린: {screenPosition}, 월드: {worldPosition}, 뷰포트: {viewportPosition}"); + } + + private void DrawDebugRay(Vector3 screenPosition) + { + if (!_enableDebugRay || _camera == null) return; + + var ray = _camera.ScreenPointToRay(screenPosition); + Debug.DrawRay(ray.origin, ray.direction * 100f, _debugRayColor, _debugRayDuration); + } + + public Vector3 GetLastTapPosition() + { + return _lastTapPosition; + } + + public bool HasTapped() + { + return _hasTapped; + } + + public void SetDebugEnabled(bool enableLog, bool enableRay) + { + _enableDebugLog = enableLog; + _enableDebugRay = enableRay; + } + + private void OnDrawGizmos() + { + if (!_enableDebugRay || _camera == null || !_hasTapped) return; + + Gizmos.color = _debugRayColor; + var worldPosition = _camera.ScreenToWorldPoint(_lastTapPosition); + Gizmos.DrawWireSphere(worldPosition, 0.1f); + + var ray = _camera.ScreenPointToRay(_lastTapPosition); + Gizmos.DrawRay(ray.origin, ray.direction * 10f); + } + + /// + /// 씬 전환 시 Camera를 업데이트한다. + /// + public void UpdateCamera(Camera newCamera) + { + _camera = newCamera; + Debug.Log($"[ScreenTapManager] Camera 업데이트: {(newCamera != null ? newCamera.name : "null")}"); + } + + /// + /// 현재 Main Camera로 Camera를 업데이트한다. + /// + public void UpdateToMainCamera() + { + var mainCamera = Camera.main; + if (mainCamera != null) + { + UpdateCamera(mainCamera); + } + else + { + Debug.LogWarning("[ScreenTapManager] Main Camera를 찾을 수 없습니다."); + } + } + #region LockAt public bool TryGetLookDirection(out Vector3 lookDir) { if (_inputProvider.TryGetPosition(out var screenPos)) { lookDir = ConvertScreenToLookDirection(screenPos); + if (_enableDebugLog) + { + Debug.Log($"[ScreenTapManager] LookAt 방향: {lookDir}"); + } return true; } lookDir = Vector3.zero; @@ -144,12 +269,34 @@ public bool TryGetTapUpPosition(out CubismRaycastHit[] hitResults) { hitResults = null; + // 필수 컴포넌트 null 체크 + if (_camera == null) + { + Debug.LogWarning("[ScreenTapManager] Camera가 null입니다. Initialize()를 호출해주세요."); + return false; + } + + if (_raycaster == null) + { + Debug.LogWarning("[ScreenTapManager] CubismRaycaster가 null입니다. SetRaycaster()를 호출해주세요."); + return false; + } + + if (_inputUpProvider == null) + { + Debug.LogWarning("[ScreenTapManager] InputUpProvider가 null입니다. Initialize()를 호출해주세요."); + return false; + } + // 손 뗀 시점이 아니면 false if (!_inputUpProvider.TryGetPosition(out var screenPosition)) { return false; } + LogTapDebug(screenPosition, "탭 종료"); + DrawDebugRay(screenPosition); + var results = new CubismRaycastHit[4]; var ray = _camera.ScreenPointToRay(screenPosition); var hitCount = _raycaster.Raycast(ray, results); @@ -157,9 +304,27 @@ public bool TryGetTapUpPosition(out CubismRaycastHit[] hitResults) if (hitCount > 0) { hitResults = results; + if (_enableDebugLog) + { + Debug.Log($"[ScreenTapManager] Raycast 히트: {hitCount}개 오브젝트"); + for (int i = 0; i < hitCount; i++) + { + var drawable = results[i].Drawable; + var worldPos = results[i].WorldPosition; + var localPos = results[i].LocalPosition; + var distance = results[i].Distance; + + Debug.Log($"[ScreenTapManager] 히트 {i}: Drawable={drawable.name}, 월드위치={worldPos}, 로컬위치={localPos}, 거리={distance:F2}"); + } + } return true; } + if (_enableDebugLog) + { + Debug.Log("[ScreenTapManager] Raycast 히트 없음"); + } + return false; } #endregion diff --git a/Assets/Core/Loading/LoadingManager.cs b/Assets/Core/Loading/LoadingManager.cs index fa78e23..cfc1ec4 100644 --- a/Assets/Core/Loading/LoadingManager.cs +++ b/Assets/Core/Loading/LoadingManager.cs @@ -26,7 +26,7 @@ public TaskInfo(string name, string description, float progressValue) /// /// 로딩 과정을 전담 관리하는 싱글톤 매니저 - /// 실제 초기화는 GameManager가 담당하고, 이 클래스는 UI와 사용자 피드백에 집중 + /// 실제 초기화는 SystemManager가 담당하고, 이 클래스는 UI와 사용자 피드백에 집중 /// public class LoadingManager : Singleton { @@ -34,14 +34,18 @@ public class LoadingManager : Singleton [SerializeField] private LoadingUI _loadingUI; private TaskInfo _currentTask; - private bool _gameStarted; + private bool _appStarted; + [SerializeField] private float _autoProgressMax = 0.9f; + [SerializeField] private float _autoProgressSpeed = 0.25f; + private Coroutine _autoProgressCoroutine; public event Action OnInitializationFailed; #region Unity Lifecycle - private void Awake() + protected override void Awake() { + base.Awake(); SetupEventListeners(); } @@ -54,15 +58,26 @@ private void OnDestroy() #region Public Methods + /// + /// 로딩 UI 시작 및 자동 진행 시작 + /// + public void BeginLoadingUI() + { + UpdateTask("INITIALIZATION", "시스템 초기화 중...", 0.05f); + StartAutoProgress(); + } + public void StartInitialization() { - if (GameManager.Instance != null) + if (SystemManager.Instance != null) { - GameManager.Instance.InitializeGame(); + UpdateTask("INITIALIZATION", "시스템 초기화 중...", 0.05f); + StartAutoProgress(); + SystemManager.Instance.Initialize(); } else { - Debug.LogError("[LoadingManager] GameManager가 없습니다."); + Debug.LogError("[LoadingManager] SystemManager가 없습니다."); } } @@ -77,24 +92,24 @@ public void UpdateTask(string taskName, string description, float progress) _loadingUI.UpdateTask(_currentTask); } - if (!_gameStarted && _currentTask.progress >= 1f) + if (!_appStarted && _currentTask.progress >= 1f) { - StartGame(); + StartApp(); } } - public async void StartGame() + public async void StartApp() { - if (_gameStarted) + if (_appStarted) return; - _gameStarted = true; + _appStarted = true; if (_loadingUI != null) { await _loadingUI.FadeOut(); } - if (GameManager.Instance != null) + if (SystemManager.Instance != null) { - await GameManager.Instance.TransitionToMainSceneAsync(); + await SystemManager.Instance.TransitionToMainSceneAsync(); } } @@ -104,37 +119,29 @@ public async void StartGame() private void SetupEventListeners() { - if (GameManager.Instance != null) + if (SystemManager.Instance != null) { - GameManager.Instance.OnGameInitialized += OnGameInitialized; - GameManager.Instance.OnInitializationError += OnInitializationError; - var initializationManager = GameManager.Instance.GetComponent(); - if (initializationManager != null) - { - initializationManager.OnProgressUpdated += OnProgressUpdated; - } + SystemManager.Instance.OnAppInitialized += OnAppInitialized; + SystemManager.Instance.OnInitializationError += OnInitializationError; } } private void RemoveEventListeners() { - if (GameManager.Instance != null) + if (SystemManager.Instance != null) { - GameManager.Instance.OnGameInitialized -= OnGameInitialized; - GameManager.Instance.OnInitializationError -= OnInitializationError; - var initializationManager = GameManager.Instance.GetComponent(); - if (initializationManager != null) - { - initializationManager.OnProgressUpdated -= OnProgressUpdated; - } + SystemManager.Instance.OnAppInitialized -= OnAppInitialized; + SystemManager.Instance.OnInitializationError -= OnInitializationError; } } - private void OnGameInitialized() + private void OnAppInitialized() { - if (!_gameStarted) + if (!_appStarted) { - StartGame(); + StopAutoProgress(); + UpdateTask("INITIALIZATION", "완료", 1f); + StartApp(); } } @@ -144,10 +151,36 @@ private void OnInitializationError(string error) OnInitializationFailed?.Invoke(error); } - private void OnProgressUpdated(string taskName, string description, float progress) - { - UpdateTask(taskName, description, progress); - } + /// + /// 자동 진행 바 업데이트 시작 + /// + private void StartAutoProgress() + { + if (_autoProgressCoroutine != null) return; + _autoProgressCoroutine = StartCoroutine(AutoProgressRoutine()); + } + + /// + /// 자동 진행 바 중지 + /// + private void StopAutoProgress() + { + if (_autoProgressCoroutine == null) return; + StopCoroutine(_autoProgressCoroutine); + _autoProgressCoroutine = null; + } + + private System.Collections.IEnumerator AutoProgressRoutine() + { + float p = Mathf.Clamp01(_currentTask.progress); + while (p < _autoProgressMax && !_appStarted) + { + p += Time.deltaTime * _autoProgressSpeed; + UpdateTask("INITIALIZATION", "시스템 초기화 중...", Mathf.Min(p, _autoProgressMax)); + yield return null; + } + _autoProgressCoroutine = null; + } #endregion } diff --git a/Assets/Core/Loading/LoadingUI.cs b/Assets/Core/Loading/LoadingUI.cs index aaf4d44..24a0feb 100644 --- a/Assets/Core/Loading/LoadingUI.cs +++ b/Assets/Core/Loading/LoadingUI.cs @@ -111,7 +111,7 @@ private void InitializeUI() } // 초기 상태 메시지 - UpdateStatus("게임 시작 준비 중..."); + UpdateStatus("앱 시작 준비 중..."); } /// diff --git a/Assets/Core/Localization.meta b/Assets/Core/Localization.meta deleted file mode 100644 index 3942246..0000000 --- a/Assets/Core/Localization.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 8c038861992a06046889d384bb65a294 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Core/Managers/DependencyManager.cs b/Assets/Core/Managers/DependencyManager.cs deleted file mode 100644 index 4d26426..0000000 --- a/Assets/Core/Managers/DependencyManager.cs +++ /dev/null @@ -1,75 +0,0 @@ -using UnityEngine; -using ProjectVG.Core.DI; -using ProjectVG.Infrastructure.Network.Services; -using ProjectVG.Infrastructure.Network.WebSocket; -using ProjectVG.Infrastructure.Network.Http; - - namespace ProjectVG.Core.Managers - { - /// - /// 의존성 주입을 전담하는 매니저 - /// - public class DependencyManager : MonoBehaviour - { - private DIContainer _container; - - private void Awake() - { - _container = DIContainer.Instance; - } - - public void SetupDependencies(ManagerRegistry managerRegistry) - { - if (managerRegistry == null) - { - Debug.LogError("[DependencyManager] ManagerRegistry가 null입니다."); - return; - } - - RegisterServices(managerRegistry); - InjectDependencies(managerRegistry); - Debug.Log("[DependencyManager] DI 완료"); - } - - public void RegisterService(T service) - { - _container.Register(service); - } - - public T GetService() - { - return _container.Get(); - } - - private void RegisterServices(ManagerRegistry managerRegistry) - { - if (managerRegistry.SessionManager != null) - { - _container.Register(managerRegistry.SessionManager); - } - - if (managerRegistry.WebSocketManager != null) - { - _container.Register(managerRegistry.WebSocketManager); - } - - if (managerRegistry.HttpApiClient != null) - { - _container.Register(managerRegistry.HttpApiClient); - } - } - - private void InjectDependencies(ManagerRegistry managerRegistry) - { - if (managerRegistry.SessionManager != null) - { - _container.InjectDependencies(managerRegistry.SessionManager); - } - - if (managerRegistry.HttpApiClient != null) - { - _container.InjectDependencies(managerRegistry.HttpApiClient); - } - } - } - } diff --git a/Assets/Core/Managers/DependencyManager.cs.meta b/Assets/Core/Managers/DependencyManager.cs.meta deleted file mode 100644 index 904a8c2..0000000 --- a/Assets/Core/Managers/DependencyManager.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 4170d4d59f89094499434d69bfe8b704 \ No newline at end of file diff --git a/Assets/Core/Managers/GameManager.cs b/Assets/Core/Managers/GameManager.cs deleted file mode 100644 index 48163d8..0000000 --- a/Assets/Core/Managers/GameManager.cs +++ /dev/null @@ -1,180 +0,0 @@ -using UnityEngine; -using System; -using ProjectVG.Infrastructure.Network.WebSocket; -using ProjectVG.Infrastructure.Network.Services; -using ProjectVG.Infrastructure.Network.Http; -using ProjectVG.Core.DI; -using Cysharp.Threading.Tasks; - -namespace ProjectVG.Core.Managers -{ - public class GameManager : Singleton - { - [Header("Core Managers")] - [SerializeField] private InitializationManager _initializationManager; - [SerializeField] private ManagerRegistry _managerRegistry; - [SerializeField] private DependencyManager _dependencyManager; - - [Header("Settings")] - [SerializeField] private bool _autoInitializeOnStart = true; - [SerializeField] private bool _createManagersIfNotExist = true; - - private bool _initializationKickoffDone = false; - - public bool IsInitialized => _initializationManager?.IsInitialized ?? false; - - public WebSocketManager WebSocketManager => _managerRegistry?.WebSocketManager; - public SessionManager SessionManager => _managerRegistry?.SessionManager; - - public event Action OnGameInitialized - { - add => _initializationManager.OnInitializationCompleted += value; - remove => _initializationManager.OnInitializationCompleted -= value; - } - public event Action OnInitializationError - { - add => _initializationManager.OnInitializationError += value; - remove => _initializationManager.OnInitializationError -= value; - } - - protected override void Awake() - { - base.Awake(); - if (this != Instance) - { - return; - } - InitializeComponents(); - - if (_autoInitializeOnStart && !_initializationKickoffDone && !IsInitialized) - { - _initializationKickoffDone = true; - InitializeGame(); - } - } - - private void OnDestroy() - { - if (this != Instance) - { - return; - } - Shutdown(); - } - - public async void InitializeGame() - { - if (_initializationKickoffDone && (IsInitialized || (_initializationManager?.IsInitializing ?? false))) - { - return; - } - _initializationKickoffDone = true; - await InitializeGameAsync(); - } - - public async UniTask InitializeGameAsync() - { - if (_initializationManager == null) - { - Debug.LogError("[GameManager] InitializationManager가 설정되지 않았습니다."); - return; - } - - if (_initializationManager.IsInitialized) - { - return; - } - - if (_initializationManager.IsInitializing) - { - while (_initializationManager.IsInitializing && !_initializationManager.IsInitialized) - { - await UniTask.Yield(); - } - return; - } - - await _initializationManager.InitializeAsync(); - if (IsInitialized) - { - Debug.Log("[GameManager] 게임 시스템 준비 완료"); - } - } - - public void Shutdown() - { - _managerRegistry?.ShutdownAllManagers(); - Debug.Log("[GameManager] 시스템 종료 완료"); - } - - [ContextMenu("Log Manager Status")] - public void LogManagerStatus() - { - Debug.Log($"[GameManager] Initialized: {IsInitialized}"); - _managerRegistry?.LogManagerStatus(); - } - - public async UniTask TransitionToMainSceneAsync() - { - try - { - await UnityEngine.SceneManagement.SceneManager.LoadSceneAsync("MainSence"); - Debug.Log("[GameManager] MainScene 전환 완료"); - } - catch (System.Exception ex) - { - Debug.LogError($"[GameManager] 씬 전환 실패: {ex.Message}"); - } - } - - private void InitializeComponents() - { - CreateManagersIfNotExist(); - SetupManagerReferences(); - } - - private void CreateManagersIfNotExist() - { - if (_createManagersIfNotExist) - { - if (_initializationManager == null) - { - var initObj = new GameObject("InitializationManager"); - initObj.transform.SetParent(transform); - _initializationManager = initObj.AddComponent(); - } - - if (_managerRegistry == null) - { - var registryObj = new GameObject("ManagerRegistry"); - registryObj.transform.SetParent(transform); - _managerRegistry = registryObj.AddComponent(); - } - - if (_dependencyManager == null) - { - var depObj = new GameObject("DependencyManager"); - depObj.transform.SetParent(transform); - _dependencyManager = depObj.AddComponent(); - } - } - } - - private void SetupManagerReferences() - { - if (_initializationManager != null && _managerRegistry != null && _dependencyManager != null) - { - _initializationManager.Initialize(_managerRegistry, _dependencyManager); - } - else - { - Debug.LogError("[GameManager] 필수 매니저가 설정되지 않았습니다."); - } - } - } - - public interface IManager - { - void Shutdown(); - } -} \ No newline at end of file diff --git a/Assets/Core/Managers/GameManager.cs.meta b/Assets/Core/Managers/GameManager.cs.meta deleted file mode 100644 index e566be2..0000000 --- a/Assets/Core/Managers/GameManager.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: f2343a97be172e748a6501089d9e3c0a \ No newline at end of file diff --git a/Assets/Core/Managers/InitializationManager.cs b/Assets/Core/Managers/InitializationManager.cs deleted file mode 100644 index 37700ca..0000000 --- a/Assets/Core/Managers/InitializationManager.cs +++ /dev/null @@ -1,155 +0,0 @@ -using UnityEngine; -using System; -using Cysharp.Threading.Tasks; - -namespace ProjectVG.Core.Managers -{ - public enum InitializationPhase - { - NotStarted, - InitializingManagers, - ConnectingToServer, - LoadingResources, - Completed - } - - /** - * 초기화 과정을 전담하는 매니저 - * 단계별 초기화, 진행률 추적, 이벤트 발생을 담당 - */ - public class InitializationManager : MonoBehaviour - { - private InitializationPhase _currentPhase = InitializationPhase.NotStarted; - private bool _isInitialized = false; - private bool _isInitializing = false; - - public InitializationPhase CurrentPhase => _currentPhase; - public bool IsInitialized => _isInitialized; - public bool IsInitializing => _isInitializing; - - public event Action OnInitializationCompleted; - public event Action OnInitializationError; - public event Action OnProgressUpdated; - - private ManagerRegistry _managerRegistry; - private DependencyManager _dependencyManager; - - #region Public Methods - - public void Initialize(ManagerRegistry managerRegistry, DependencyManager dependencyManager) - { - _managerRegistry = managerRegistry; - _dependencyManager = dependencyManager; - } - - public async UniTask InitializeAsync() - { - if (_isInitialized) - { - return; - } - - if (_isInitializing) - { - while (_isInitializing && !_isInitialized) - { - await UniTask.Yield(); - } - return; - } - - _isInitializing = true; - - try - { - _currentPhase = InitializationPhase.InitializingManagers; - await InitializeManagersAsync(); - - _currentPhase = InitializationPhase.ConnectingToServer; - await ConnectToServerAsync(); - - _currentPhase = InitializationPhase.LoadingResources; - await LoadResourcesAsync(); - - _currentPhase = InitializationPhase.Completed; - UpdateLoadingProgress("INITIALIZATION", "게임 준비 완료", 1.0f); - - _isInitialized = true; - Debug.Log("[InitializationManager] 초기화 완료"); - OnInitializationCompleted?.Invoke(); - } - catch (Exception ex) - { - string error = $"[InitializationManager] 초기화 실패: {ex.Message}"; - Debug.LogError(error); - OnInitializationError?.Invoke(error); - } - finally - { - _isInitializing = false; - } - } - - #endregion - - #region Private Methods - - private async UniTask InitializeManagersAsync() - { - if (_managerRegistry == null) - throw new InvalidOperationException("ManagerRegistry가 설정되지 않았습니다."); - - UpdateLoadingProgress("INITIALIZATION", "시스템 매니저 초기화", 0.05f); - - _managerRegistry.InitializeAllManagers(); - - UpdateLoadingProgress("INITIALIZATION", "매니저 등록", 0.12f); - - _dependencyManager?.SetupDependencies(_managerRegistry); - - UpdateLoadingProgress("INITIALIZATION", "의존성 주입", 0.20f); - - if (_managerRegistry.SessionManager != null) - { - _managerRegistry.SessionManager.Initialize(); - } - } - - private async UniTask ConnectToServerAsync() - { - if (_managerRegistry == null) - throw new InvalidOperationException("ManagerRegistry가 설정되지 않았습니다."); - - var sessionManager = _managerRegistry.SessionManager; - if (sessionManager == null) - throw new InvalidOperationException("SessionManager가 초기화되지 않았습니다."); - - UpdateLoadingProgress("INITIALIZATION", "네트워크 연결", 0.30f); - UpdateLoadingProgress("INITIALIZATION", "세션 생성", 0.45f); - - bool connected = await sessionManager.EnsureConnectionAsync(); - if (!connected) - { - throw new InvalidOperationException("세션 연결에 실패했습니다."); - } - - UpdateLoadingProgress("INITIALIZATION", "세션 완료", 0.60f); - } - - private async UniTask LoadResourcesAsync() - { - UpdateLoadingProgress("INITIALIZATION", "리소스 스캔", 0.65f); - await UniTask.Delay(100); - UpdateLoadingProgress("INITIALIZATION", "필수 에셋 로딩", 0.75f); - await UniTask.Delay(100); - UpdateLoadingProgress("INITIALIZATION", "리소스 로딩 완료", 0.90f); - } - - private void UpdateLoadingProgress(string taskName, string description, float progress) - { - OnProgressUpdated?.Invoke(taskName, description, progress); - } - - #endregion - } -} diff --git a/Assets/Core/Managers/InitializationManager.cs.meta b/Assets/Core/Managers/InitializationManager.cs.meta deleted file mode 100644 index dfba111..0000000 --- a/Assets/Core/Managers/InitializationManager.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 64867cef2ba0d4849a0a93a3253d7c5d \ No newline at end of file diff --git a/Assets/Core/Managers/ManagerRegistry.cs b/Assets/Core/Managers/ManagerRegistry.cs deleted file mode 100644 index 5b807ee..0000000 --- a/Assets/Core/Managers/ManagerRegistry.cs +++ /dev/null @@ -1,125 +0,0 @@ -using UnityEngine; -using System; -using System.Collections.Generic; -using ProjectVG.Infrastructure.Network.WebSocket; -using ProjectVG.Infrastructure.Network.Services; -using ProjectVG.Infrastructure.Network.Http; - -namespace ProjectVG.Core.Managers -{ - /** - * 매니저들의 등록, 생성, 생명주기를 관리하는 매니저 - */ - public class ManagerRegistry : MonoBehaviour - { - [Header("Manager References")] - [SerializeField] private WebSocketManager _webSocketManager; - [SerializeField] private SessionManager _sessionManager; - [SerializeField] private HttpApiClient _httpApiClient; - [SerializeField] private AudioManager _audioManager; - - [Header("Settings")] - [SerializeField] private bool _createManagersIfNotExist = true; - - private readonly List _managers = new List(); - - public WebSocketManager WebSocketManager => _webSocketManager; - public SessionManager SessionManager => _sessionManager; - public HttpApiClient HttpApiClient => _httpApiClient; - public AudioManager AudioManager => _audioManager; - - #region Public Methods - - public void InitializeAllManagers() - { - InitializeWebSocketManager(); - InitializeHttpApiClient(); - InitializeSessionManager(); - } - - public bool AreManagersReady() - { - return _webSocketManager != null && - _sessionManager != null && - _httpApiClient != null; - } - - public bool IsSessionConnected() - { - return _sessionManager != null && _sessionManager.IsSessionConnected; - } - - public void ShutdownAllManagers() - { - for (int i = _managers.Count - 1; i >= 0; i--) - { - try - { - _managers[i]?.Shutdown(); - } - catch (Exception ex) - { - Debug.LogError($"[ManagerRegistry] 매니저 종료 오류: {ex.Message}"); - } - } - _managers.Clear(); - Debug.Log("[ManagerRegistry] 모든 매니저 종료 완료"); - } - - public void LogManagerStatus() - { - Debug.Log($"Managers Ready: {(AreManagersReady() ? "Yes" : "No")}, Session: {(IsSessionConnected() ? "Connected" : "Disconnected")}"); - } - - #endregion - - #region Private Methods - - private void InitializeWebSocketManager() - { - if (_webSocketManager == null && _createManagersIfNotExist) - { - var webSocketObj = new GameObject("WebSocketManager"); - webSocketObj.transform.SetParent(transform); - _webSocketManager = webSocketObj.AddComponent(); - } - if (_webSocketManager == null) - { - throw new InvalidOperationException("WebSocketManager를 초기화할 수 없습니다."); - } - _managers.Add(_webSocketManager); - } - - private void InitializeSessionManager() - { - if (_sessionManager == null && _createManagersIfNotExist) - { - var sessionObj = new GameObject("SessionManager"); - sessionObj.transform.SetParent(transform); - _sessionManager = sessionObj.AddComponent(); - } - if (_sessionManager == null) - { - throw new InvalidOperationException("SessionManager를 초기화할 수 없습니다."); - } - _managers.Add(_sessionManager); - } - - private void InitializeHttpApiClient() - { - if (_httpApiClient == null && _createManagersIfNotExist) - { - var httpObj = new GameObject("HttpApiClient"); - httpObj.transform.SetParent(transform); - _httpApiClient = httpObj.AddComponent(); - } - if (_httpApiClient == null) - { - throw new InvalidOperationException("HttpApiClient를 초기화할 수 없습니다."); - } - _managers.Add(_httpApiClient); - } - - #endregion - } -} diff --git a/Assets/Core/Managers/ManagerRegistry.cs.meta b/Assets/Core/Managers/ManagerRegistry.cs.meta deleted file mode 100644 index c5cdea9..0000000 --- a/Assets/Core/Managers/ManagerRegistry.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: e9adb6dc961927c489cf26eefd75c62c \ No newline at end of file diff --git a/Assets/Core/Managers/README_GameManager.md b/Assets/Core/Managers/README_GameManager.md deleted file mode 100644 index e478f73..0000000 --- a/Assets/Core/Managers/README_GameManager.md +++ /dev/null @@ -1,129 +0,0 @@ -# GameManager 패턴 사용법 - -## 개요 -GameManager는 Unity에서 가장 권장되는 매니저 초기화 패턴입니다. 게임의 핵심 매니저들(WebSocketManager, SessionManager, HttpApiClient)의 생명주기를 관리합니다. - -## Unity에서 가장 권장되는 이유 - -### 1. **명확한 책임 분리** -- GameManager: 매니저들의 생명주기 관리 -- 각 매니저: 자신의 도메인 로직만 담당 - -### 2. **의존성 관리** -- 매니저들 간의 의존성을 명확하게 관리 -- 초기화 순서 보장 (WebSocketManager → SessionManager → HttpApiClient) - -### 3. **확장성** -- 새로운 매니저 추가가 용이 -- 설정 변경이 간단 - -### 4. **디버깅 용이성** -- Inspector에서 매니저 상태 확인 가능 -- ContextMenu를 통한 상태 로그 출력 - -## 설정 방법 - -### 1. GameManager 프리팹 생성 -1. 빈 GameObject 생성 -2. GameManager 컴포넌트 추가 -3. 프리팹으로 저장 - -### 2. 씬에 배치 -- 첫 번째 씬에 GameManager 프리팹 배치 -- DontDestroyOnLoad로 설정되어 씬 전환 시에도 유지 - -### 3. 설정 옵션 -GameManager Inspector에서 설정 가능: -- **Auto Initialize On Start**: Start() 시 자동 초기화 여부 -- **Create Managers If Not Exist**: 매니저가 없을 때 자동 생성 여부 -- **Manager References**: 각 매니저의 참조 (선택사항) - -## 사용법 - -### 자동 초기화 (기본) -```csharp -// GameManager가 Start()에서 자동으로 초기화 -// 별도 코드 작성 불필요 -``` - -### 수동 초기화 -```csharp -// GameManager 참조 -var gameManager = GameManager.Instance; - -// 수동 초기화 -gameManager.InitializeGame(); - -// 초기화 완료 이벤트 구독 -gameManager.OnGameInitialized += () => { - Debug.Log("게임 초기화 완료!"); -}; - -// 초기화 에러 이벤트 구독 -gameManager.OnInitializationError += (error) => { - Debug.LogError($"초기화 실패: {error}"); -}; -``` - -### 매니저 접근 -```csharp -// GameManager를 통한 접근 -var webSocket = GameManager.Instance.WebSocketManager; -var session = GameManager.Instance.SessionManager; -var httpClient = GameManager.Instance.HttpApiClient; - -// 또는 직접 접근 (싱글톤) -var webSocket = WebSocketManager.Instance; -var session = SessionManager.Instance; -var httpClient = HttpApiClient.Instance; -``` - -### 상태 확인 -```csharp -// 에디터에서 -// GameManager 우클릭 → Log Manager Status - -// 코드에서 -if (GameManager.Instance.AreManagersReady()) -{ - // 모든 매니저가 준비됨 -} -``` - -## 장점 - -### 1. **Unity 커뮤니티 표준** -- 대부분의 Unity 프로젝트에서 사용 -- 개발자들이 익숙한 패턴 - -### 2. **Inspector 지원** -- 매니저 참조를 Inspector에서 설정 가능 -- 실시간 상태 확인 가능 - -### 3. **명확한 생명주기** -- 초기화 순서 보장 -- 종료 시 역순으로 정리 - -### 4. **에러 처리** -- 초기화 실패 시 명확한 에러 메시지 -- 이벤트를 통한 에러 처리 - -## 다른 방법들과의 비교 - -| 방법 | 장점 | 단점 | 권장도 | -|------|------|------|--------| -| **GameManager 패턴** | 표준, 명확, 확장성 | 약간의 보일러플레이트 | ⭐⭐⭐⭐⭐ | -| ScriptableObject 초기화 | 자동화, 설정 관리 | 복잡, Resources 의존 | ⭐⭐⭐ | -| 별도 컴포넌트 | 간단, 직관적 | 실수 가능성, 관리 어려움 | ⭐⭐ | - -## 결론 - -**GameManager 패턴**이 Unity에서 가장 권장되는 방식입니다. 이유: - -1. **Unity 커뮤니티 표준**: 대부분의 프로덕션 프로젝트에서 사용 -2. **명확한 책임 분리**: 각 매니저의 역할이 명확 -3. **확장성**: 새로운 매니저 추가가 용이 -4. **디버깅 용이성**: Inspector에서 상태 확인 가능 -5. **에러 처리**: 명확한 에러 메시지와 처리 - -이 패턴을 사용하면 프로젝트의 유지보수성과 확장성이 크게 향상됩니다. \ No newline at end of file diff --git a/Assets/Core/Managers/SystemManager.cs b/Assets/Core/Managers/SystemManager.cs new file mode 100644 index 0000000..2d76b8c --- /dev/null +++ b/Assets/Core/Managers/SystemManager.cs @@ -0,0 +1,261 @@ +#nullable enable +using UnityEngine; +using System; +using ProjectVG.Infrastructure.Network.WebSocket; +using ProjectVG.Infrastructure.Network.Services; +using ProjectVG.Infrastructure.Network.Http; +using Cysharp.Threading.Tasks; +using ProjectVG.Core.Loading; +using ProjectVG.Core.Audio; +using ProjectVG.Domain.Chat.Service; +using ProjectVG.Core.Utils; + +namespace ProjectVG.Core.Managers +{ + public class SystemManager : Singleton + { + [Header("Core Managers")] + [SerializeField] private WebSocketManager? _webSocketManager; + [SerializeField] private SessionManager? _sessionManager; + [SerializeField] private HttpApiClient? _httpApiClient; + [SerializeField] private AudioManager? _audioManager; + [SerializeField] private LoadingManager? _loadingManager; + [SerializeField] private ChatManager? _chatManager; + + + [Header("Settings")] + [SerializeField] private bool _autoInitializeOnStart = true; + [SerializeField] private bool _createManagersIfNotExist = true; + [SerializeField] private bool _autoUpdateCameraOnSceneChange = true; + + [Header("Camera Settings")] + [SerializeField] private Camera? _camera; + + private bool _initializationKickoffDone = false; + public bool IsInitialized { get; private set; } + + public WebSocketManager? WebSocketManager => _webSocketManager; + public SessionManager? SessionManager => _sessionManager; + public AudioManager? AudioManager => _audioManager; + public LoadingManager? LoadingManager => _loadingManager; + + public event Action? OnAppInitialized; + public event Action? OnInitializationError; + + protected override void Awake() + { + base.Awake(); + if (this != Instance) + { + return; + } + InitializeComponents(); + + if (_autoInitializeOnStart && !_initializationKickoffDone && !IsInitialized) + { + _initializationKickoffDone = true; + Initialize(); + } + } + + private void Start() + { + if (_autoUpdateCameraOnSceneChange) + { + UnityEngine.SceneManagement.SceneManager.sceneLoaded += OnSceneLoaded; + } + } + + private void OnDestroy() + { + if (this != Instance) + { + return; + } + + if (_autoUpdateCameraOnSceneChange) + { + UnityEngine.SceneManagement.SceneManager.sceneLoaded -= OnSceneLoaded; + } + + Shutdown(); + } + + /// + /// 씬이 로드될 때 호출된다. + /// + private void OnSceneLoaded(UnityEngine.SceneManagement.Scene scene, UnityEngine.SceneManagement.LoadSceneMode mode) + { + Debug.Log($"[SystemManager] 씬 로드됨: {scene.name}"); + UpdateCamera(); + } + + /// + /// 현재 씬의 Main Camera로 Camera를 업데이트한다. + /// + public void UpdateCamera() + { + var mainCamera = Camera.main; + if (mainCamera != null) + { + _camera = mainCamera; + Debug.Log($"[SystemManager] Camera 업데이트: {mainCamera.name}"); + + // ScreenTapManager에 Camera 주입 + if (ScreenTapManager.Instance != null) + { + ScreenTapManager.Instance.UpdateCamera(_camera); + } + } + else + { + Debug.LogWarning("[SystemManager] Main Camera를 찾을 수 없습니다."); + } + } + + /// + /// 수동으로 Camera를 설정한다. + /// + public void SetCamera(Camera camera) + { + _camera = camera; + Debug.Log($"[SystemManager] Camera 수동 설정: {(camera != null ? camera.name : "null")}"); + + // ScreenTapManager에 Camera 주입 + if (ScreenTapManager.Instance != null) + { + ScreenTapManager.Instance.UpdateCamera(_camera); + } + } + + public async void Initialize() + { + try + { + if (_initializationKickoffDone && IsInitialized) + { + return; + } + _initializationKickoffDone = true; + + // Camera 업데이트 및 ScreenTapManager 초기화 + UpdateCamera(); + if (_camera != null) + { + ScreenTapManager.Instance.Initialize(_camera); + } + + await InitializeAppAsync(); + } + catch (Exception ex) + { + Debug.LogError($"[SystemManager] Initialize 중 오류 발생: {ex.Message}"); + OnInitializationError?.Invoke(ex.Message); + } + } + + public async UniTask InitializeAppAsync() + { + try + { + await InitializeManagersAsync(); + IsInitialized = true; + Debug.Log("[SystemManager] 앱 시스템 준비 완료"); + OnAppInitialized?.Invoke(); + } + catch (Exception ex) + { + IsInitialized = false; + Debug.LogError($"[SystemManager] 초기화 실패: {ex.Message}"); + OnInitializationError?.Invoke(ex.Message); + } + } + + public void Shutdown() + { + try { _httpApiClient?.Shutdown(); } catch {} + try { _sessionManager?.Shutdown(); } catch {} + try { _webSocketManager?.Shutdown(); } catch {} + Debug.Log("[SystemManager] 시스템 종료 완료"); + } + + [ContextMenu("Log Manager Status")] + public void LogManagerStatus() + { + Debug.Log($"[SystemManager] Initialized: {IsInitialized}"); + Debug.Log($"[SystemManager] Current Camera: {(_camera != null ? _camera.name : "null")}"); + Debug.Log($"[SystemManager] WS: {(WebSocketManager != null ? "OK" : "null")}, Session: {(SessionManager != null ? "OK" : "null")}, Audio: {(AudioManager != null ? "OK" : "null")}, Loading: {(LoadingManager != null ? "OK" : "null")}"); + } + + [ContextMenu("Update Camera")] + public void UpdateCameraFromContextMenu() + { + UpdateCamera(); + } + + [ContextMenu("Set Main Camera")] + public void SetMainCameraFromContextMenu() + { + var mainCamera = Camera.main; + if (mainCamera != null) + { + SetCamera(mainCamera); + } + else + { + Debug.LogWarning("[SystemManager] Main Camera를 찾을 수 없습니다."); + } + } + + public async UniTask TransitionToMainSceneAsync() + { + try + { + await UnityEngine.SceneManagement.SceneManager.LoadSceneAsync("MainSence"); + Debug.Log("[SystemManager] MainScene 전환 완료"); + } + catch (System.Exception ex) + { + Debug.LogError($"[SystemManager] 씬 전환 실패: {ex.Message}"); + } + } + + private void InitializeComponents() + { + CreateManagersIfNotExist(); + } + + private void CreateManagersIfNotExist() + { + if (_createManagersIfNotExist) + { + _webSocketManager = WebSocketManager.Instance; + _sessionManager = SessionManager.Instance; + _httpApiClient = HttpApiClient.Instance; + _audioManager = AudioManager.Instance; + _loadingManager = LoadingManager.Instance; + } + } + + private async UniTask InitializeManagersAsync() + { + if (_webSocketManager == null || _sessionManager == null || _httpApiClient == null) + { + throw new InvalidOperationException("필수 매니저 인스턴스를 찾을 수 없습니다."); + } + _loadingManager?.BeginLoadingUI(); + _audioManager?.Initialize(); + _webSocketManager.Initialize(); + _sessionManager.Initialize(_webSocketManager); + _httpApiClient.Initialize(_sessionManager); + + bool connected = await _sessionManager.EnsureConnectionAsync(); + if (!connected) + { + throw new InvalidOperationException("세션 연결 실패"); + } + } + } + + +} \ No newline at end of file diff --git a/Assets/Core/Managers/SystemManager.cs.meta b/Assets/Core/Managers/SystemManager.cs.meta new file mode 100644 index 0000000..14acbab --- /dev/null +++ b/Assets/Core/Managers/SystemManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c31e1b9082df088489ede1181874cde4 \ No newline at end of file diff --git a/Assets/Core/Chat.meta b/Assets/Docs/Design.meta similarity index 77% rename from Assets/Core/Chat.meta rename to Assets/Docs/Design.meta index 1c75036..995e092 100644 --- a/Assets/Core/Chat.meta +++ b/Assets/Docs/Design.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: a51c1117b42a1cb449577dbfe0758bd5 +guid: fe2e2a2211f3e084a82545a6c3395cb6 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/Docs/Chat_System_Design_v2.md b/Assets/Docs/Design/Chat_System_Design_v2.md similarity index 100% rename from Assets/Docs/Chat_System_Design_v2.md rename to Assets/Docs/Design/Chat_System_Design_v2.md diff --git a/Assets/Docs/Chat_System_Design_v2.md.meta b/Assets/Docs/Design/Chat_System_Design_v2.md.meta similarity index 100% rename from Assets/Docs/Chat_System_Design_v2.md.meta rename to Assets/Docs/Design/Chat_System_Design_v2.md.meta diff --git a/Assets/Docs/Design/Component_Analysis_Document.md b/Assets/Docs/Design/Component_Analysis_Document.md new file mode 100644 index 0000000..482042e --- /dev/null +++ b/Assets/Docs/Design/Component_Analysis_Document.md @@ -0,0 +1,329 @@ +# 컴포넌트 분석 문서 + +## 개요 +이 문서는 ProjectVG-Client의 핵심 컴포넌트들을 분석하고 정리한 문서입니다. Live2D 캐릭터 시스템의 입력 처리, 시선 추적, 터치 인터랙션, 그리고 시스템 관리에 대한 구조를 설명합니다. + +## 1. ScreenTapManager - 입력 처리 시스템 + +### 1.1 개요 +`ScreenTapManager`는 플랫폼별 입력(터치/마우스)을 통합 관리하는 싱글톤 클래스입니다. + +### 1.2 주요 인터페이스 + +#### IInputProvider +```csharp +public interface IInputProvider +{ + bool TryGetPosition(out Vector3 position); +} +``` +- 현재 입력 위치를 반환하는 인터페이스 +- 터치/마우스 입력을 통합 처리 + +#### IInputUpProvider +```csharp +public interface IInputUpProvider +{ + bool TryGetPosition(out Vector3 position); +} +``` +- 터치 종료 시점의 위치만 반환하는 인터페이스 +- Raycast 이벤트 처리에 사용 + +### 1.3 구현 클래스 + +#### DefaultInputProvider +- **플랫폼별 입력 처리**: iOS/Android는 터치, 데스크톱은 마우스 +- **UI 무시 기능**: "IgnoreLookAt" 태그가 달린 UI 클릭 시 입력 무시 +- **EventSystem 통합**: Unity UI 시스템과 연동하여 UI 오버레이 감지 + +#### DefaultInputUpProvider +- **터치 종료 감지**: 터치/마우스 버튼 해제 시점만 감지 +- **Raycast 이벤트**: 터치 종료 시점에만 이벤트 발생 + +### 1.4 주요 기능 + +#### LookAt 기능 +```csharp +public bool TryGetLookDirection(out Vector3 lookDir) +``` +- 스크린 좌표를 Live2D 모델이 사용할 방향 벡터로 변환 +- 뷰포트 좌표계로 정규화하여 [-1,1] 범위로 변환 + +#### Raycast 기능 +```csharp +public bool TryGetTapUpPosition(out CubismRaycastHit[] hitResults) +``` +- 터치 종료 시점에만 Raycast 수행 +- Live2D 모델의 특정 영역 터치 감지 + +## 2. SystemManager - 시스템 통합 관리 + +### 2.1 개요 +Live2D 캐릭터 시스템의 전체적인 초기화와 관리를 담당하는 싱글톤 클래스입니다. + +### 2.2 주요 구성 요소 +- **CubismLookTarget**: 시선 추적 타겟 +- **Camera**: 메인 카메라 참조 +- **ModelConfig**: 모델 설정 데이터 +- **AudioSource**: 음성 입력 소스 +- **Button**: 표정 변경 버튼 + +### 2.3 초기화 프로세스 + +#### Start() 메서드 +1. `ScreenTapManager` 초기화 +2. `AudioManager` 초기화 +3. 모델 초기화 (`ModelInit`) + +#### ModelInit() 메서드 +1. 기존 모델 제거 +2. 새 모델 인스턴스 생성 +3. LookAt 설정 +4. LipSync 설정 +5. Raycast 설정 + +### 2.4 설정 메서드들 + +#### SetLockAt() +```csharp +private void SetLockAt(ModelConfig modelConfig) +{ + var lookController = _currentModel.GetComponent(); + lookController.Target = cubismLookTarget.gameObject; + lookController.Damping = modelConfig.LockAtDamping; + cubismLookTarget.Initialize(modelConfig); +} +``` + +#### SetLipSync() +```csharp +private void SetLipSync(ModelConfig modelConfig) +{ + var mouthController = _currentModel.GetComponent(); + mouthController.AudioInput = voiceSource; + mouthController.Gain = modelConfig.Gain; + mouthController.Smoothing = modelConfig.Smoothing; +} +``` + +#### SetRayCast() +```csharp +private void SetRayCast(ModelConfig modelConfig) +{ + var hitHandler = _currentModel.GetComponent(); + hitHandler.Initialize(); + expressionChangeBtn.onClick.AddListener(hitHandler.ExpressionChange_Btn); +} +``` + +## 3. CubismLookTarget - 시선 추적 타겟 + +### 3.1 개요 +Live2D 모델의 시선 추적을 위한 타겟 클래스로, `ICubismLookTarget` 인터페이스를 구현합니다. + +### 3.2 주요 기능 + +#### Initialize() +```csharp +public void Initialize(ModelConfig modelConfig) +{ + _modelConfig = modelConfig; +} +``` +- 모델 설정 데이터를 저장 + +#### GetPosition() +```csharp +public Vector3 GetPosition() +{ + if (!ScreenTapManager.Instance.TryGetLookDirection(out var lookDir)) + return Vector3.zero; + return lookDir * _modelConfig.LookSensitivity; +} +``` +- `ScreenTapManager`에서 입력 방향을 받아서 민감도 적용 +- 시선 추적 활성화 여부 확인 + +#### IsActive() +```csharp +public bool IsActive() +{ + return _modelConfig.IsLockAtActive; +} +``` +- 모델 설정에 따른 시선 추적 활성화 상태 반환 + +## 4. CubismHitHandler - 터치 인터랙션 처리 + +### 4.1 개요 +Live2D 모델의 특정 영역 터치를 감지하고 반응을 처리하는 클래스입니다. + +### 4.2 주요 구성 요소 +- **CubismRaycaster**: 터치 감지를 위한 레이캐스터 +- **CubismExpressionController**: 표정 변경을 위한 컨트롤러 + +### 4.3 초기화 +```csharp +public void Initialize() +{ + _raycaster = GetComponent(); + _expressionController = GetComponent(); + ScreenTapManager.Instance.SetRaycaster(_raycaster); +} +``` + +### 4.4 터치 처리 + +#### Update() 메서드 +```csharp +private void Update() +{ + if (ScreenTapManager.Instance.TryGetTapUpPosition(out var hits)) + { + foreach (var hit in hits) + { + if(hit.Drawable is null) continue; + HandleHit(hit.Drawable.name); + } + } +} +``` + +#### HandleHit() 메서드 +```csharp +private void HandleHit(string drawableName) +{ + switch (drawableName) + { + case "HitAreaHead": + Debug.Log("머리 터치 → 표정 변경 or 모션 재생"); + ExpressionChange(); + break; + case "HitAreaBody": + Debug.Log("몸통 터치 → 다른 반응"); + break; + } +} +``` + +### 4.5 표정 변경 기능 + +#### ExpressionChange() +```csharp +private void ExpressionChange() +{ + _expressionController.CurrentExpressionIndex = + GetNextExpressionIndex(_expressionController.CurrentExpressionIndex, 0, + _expressionController.ExpressionsList.CubismExpressionObjects.Length); +} +``` + +#### GetNextExpressionIndex() +```csharp +private int GetNextExpressionIndex(int current, int min, int max) +{ + return ((current - min + 1) % (max - min + 1)) + min; +} +``` + +## 5. ModelConfig - 모델 설정 데이터 + +### 5.1 개요 +ScriptableObject 기반의 모델 설정 데이터 클래스로, Live2D 모델의 다양한 설정을 관리합니다. + +### 5.2 주요 설정 카테고리 + +#### 모델 정보 +- **modelName**: 모델 식별용 이름 +- **modelDescription**: 모델 설명 +- **thumbnail**: 썸네일 이미지 + +#### 시선 설정 +- **lookSensitivity**: 시선 추적 민감도 (0-30) +- **lockAtDamping**: 시선 추적 반응 속도 (0-5) +- **isLockAtActive**: 시선 추적 활성화 여부 + +#### 립싱크 설정 +- **gain**: 음량 증폭 배율 (1-10) +- **smoothing**: 입 움직임 부드러움 (0-1) + +#### 모델 프리팹 +- **modelPrefab**: 실제 모델 프리팹 + +### 5.3 사용 예시 +```csharp +// natoriConfig.asset 예시 +modelName: Natori +modelDescription: "이지적인 집사" +lookSensitivity: 5 +lockAtDamping: 0.15 +isLockAtActive: true +gain: 10 +smoothing: 1 +``` + +## 6. 연관 클래스들 + +### 6.1 SystemManager +- 앱 전체의 초기화와 관리 +- WebSocket, Session, HTTP API 클라이언트 관리 +- 시스템 간 의존성 설정 + +### 6.2 AudioManager +- 음성, BGM, SFX 소스 관리 +- 현재는 기본 구조만 구현된 상태 + +### 6.3 Live2D 프레임워크 클래스들 +- **CubismLookController**: 시선 추적 컨트롤러 +- **CubismAudioMouthInput**: 립싱크 입력 처리 +- **CubismRaycaster**: 터치 감지 레이캐스터 +- **CubismExpressionController**: 표정 변경 컨트롤러 + +## 7. 시스템 아키텍처 + +### 7.1 데이터 흐름 +``` +사용자 입력 → ScreenTapManager → CubismLookTarget → CubismLookController → Live2D 모델 +터치 종료 → ScreenTapManager → CubismHitHandler → CubismExpressionController → 표정 변경 +``` + +### 7.2 의존성 구조 +``` +SystemManager +├── ScreenTapManager (싱글톤) +├── AudioManager (싱글톤) +├── CubismLookTarget +├── ModelConfig (ScriptableObject) +└── CubismHitHandler +``` + +### 7.3 초기화 순서 +1. `SystemManager.Start()` 호출 +2. `ScreenTapManager.Initialize()` - 카메라 설정 +3. `AudioManager.Initialize()` - 오디오 시스템 초기화 +4. `ModelInit()` - 모델 생성 및 설정 +5. 각 컴포넌트별 초기화 메서드 호출 + +## 8. 확장성 및 개선 사항 + +### 8.1 현재 구조의 장점 +- **모듈화**: 각 기능이 독립적인 클래스로 분리 +- **설정 기반**: ScriptableObject를 통한 데이터 관리 +- **플랫폼 호환성**: 터치/마우스 입력 통합 처리 +- **확장성**: 인터페이스 기반 설계로 새로운 기능 추가 용이 + +### 8.2 개선 가능한 부분 +- **에러 처리**: 예외 상황에 대한 처리 부족 +- **성능 최적화**: Update() 메서드의 최적화 필요 +- **설정 검증**: ModelConfig의 유효성 검사 추가 +- **로깅 시스템**: 디버깅을 위한 로깅 시스템 구축 + +## 9. 결론 + +이 시스템은 Live2D 캐릭터와의 상호작용을 위한 잘 구조화된 아키텍처를 제공합니다. 입력 처리, 시선 추적, 터치 인터랙션, 그리고 설정 관리가 체계적으로 분리되어 있어 유지보수성과 확장성이 우수합니다. 특히 ScriptableObject를 활용한 설정 관리와 인터페이스 기반 설계는 코드의 재사용성과 테스트 용이성을 높여줍니다. + + + + + diff --git a/Assets/Core/Managers/README_GameManager.md.meta b/Assets/Docs/Design/Component_Analysis_Document.md.meta similarity index 75% rename from Assets/Core/Managers/README_GameManager.md.meta rename to Assets/Docs/Design/Component_Analysis_Document.md.meta index c24bedb..7ca46b0 100644 --- a/Assets/Core/Managers/README_GameManager.md.meta +++ b/Assets/Docs/Design/Component_Analysis_Document.md.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: eb3280819ad3dc247b57af8efe6c9ef6 +guid: 310a102fb70ae1348bc89929d0f94431 TextScriptImporter: externalObjects: {} userData: diff --git a/Assets/Docs/DialogueBubble_Design.md b/Assets/Docs/Design/DialogueBubble_Design.md similarity index 100% rename from Assets/Docs/DialogueBubble_Design.md rename to Assets/Docs/Design/DialogueBubble_Design.md diff --git a/Assets/Docs/DialogueBubble_Design.md.meta b/Assets/Docs/Design/DialogueBubble_Design.md.meta similarity index 100% rename from Assets/Docs/DialogueBubble_Design.md.meta rename to Assets/Docs/Design/DialogueBubble_Design.md.meta diff --git a/Assets/Docs/Initial_Loading_System_Design.md b/Assets/Docs/Design/Initial_Loading_System_Design.md similarity index 80% rename from Assets/Docs/Initial_Loading_System_Design.md rename to Assets/Docs/Design/Initial_Loading_System_Design.md index b5b9fb8..eb54480 100644 --- a/Assets/Docs/Initial_Loading_System_Design.md +++ b/Assets/Docs/Design/Initial_Loading_System_Design.md @@ -26,29 +26,29 @@ - 로딩 중 상태 표시 - 각 단계별 진행률 표시 -- 준비 완료 시 "게임 시작" 버튼 활성화 +- 준비 완료 시 "앱 시작" 버튼 활성화 - 페이드 인/아웃 효과로 씬 전환 ## 아키텍처 설계 -### 선택된 접근 방식: 이벤트 기반 시스템 (GameManager 중심) +### 선택된 접근 방식: 이벤트 기반 시스템 (SystemManager 중심) **선택 이유:** -- GameManager가 모든 씬에서 지속되는 싱글톤 +- SystemManager가 모든 씬에서 지속되는 싱글톤 - StartupManager는 Start 씬에서만 존재 - 이벤트 구독을 통한 효율적인 상태 관리 -- 기존 GameManager 시스템과의 완벽한 통합 +- 기존 SystemManager 시스템과의 완벽한 통합 ### 시스템 구성요소 ``` -GameManager (DontDestroyOnLoad) +SystemManager (DontDestroyOnLoad) ├── InitializationPhase 이벤트 발생 ├── Progress 이벤트 발생 └── 완료/에러 이벤트 발생 ↓ (이벤트 구독) LoadingManager (Start 씬 전용) -├── GameManager 이벤트 구독 +├── SystemManager 이벤트 구독 ├── LoadingUI 제어 └── SceneTransitionManager 호출 ↓ @@ -58,14 +58,14 @@ SceneTransitionManager (싱글톤) ### 이벤트 기반 동작 흐름 -1. **GameManager**: 초기화 진행하며 이벤트 발생 -2. **LoadingManager**: GameManager 이벤트 구독하여 UI 업데이트 +1. **SystemManager**: 초기화 진행하며 이벤트 발생 +2. **LoadingManager**: SystemManager 이벤트 구독하여 UI 업데이트 3. **LoadingUI**: 진행 상황 표시 및 사용자 상호작용 4. **SceneTransitionManager**: 씬 전환 관리 ## 구현된 클래스 -### 1. GameManager (확장됨) +### 1. SystemManager (확장됨) ```csharp public enum InitializationPhase @@ -77,16 +77,16 @@ public enum InitializationPhase Completed } -public class GameManager : Singleton +public class SystemManager : Singleton { // 이벤트 public event Action OnPhaseChanged; public event Action OnProgressChanged; - public event Action OnGameInitialized; + public event Action OnAppInitialized; public event Action OnInitializationError; // 비동기 초기화 - public async UniTask InitializeGameAsync(); + public async UniTask InitializeAppAsync(); // 상태 조회 public InitializationStatus GetInitializationStatus(); @@ -104,24 +104,24 @@ public class GameManager : Singleton ```csharp public class LoadingManager : MonoBehaviour { - // GameManager 이벤트 구독 - private void SubscribeToGameManager(); + // SystemManager 이벤트 구독 + private void SubscribeToSystemManager(); // 초기화 시작 public async void StartInitialization(); - // 게임 시작 (씬 전환) - public async void StartGame(); + // 앱 시작 (씬 전환) + public async void StartApp(); // 이벤트 핸들러 private void OnPhaseChanged(InitializationPhase phase); private void OnProgressChanged(float progress); - private void OnGameInitialized(); + private void OnAppInitialized(); } ``` **주요 기능:** -- GameManager 이벤트 구독 관리 +- SystemManager 이벤트 구독 관리 - LoadingUI와 연동 - 초기화 완료 시 씬 전환 처리 @@ -176,9 +176,9 @@ public class SceneTransitionManager : Singleton ``` 1. StartScene 로드 -2. LoadingManager 생성 및 GameManager 이벤트 구독 +2. LoadingManager 생성 및 SystemManager 이벤트 구독 3. LoadingManager.StartInitialization() 호출 -4. GameManager.InitializeGameAsync() 실행 +4. SystemManager.InitializeAppAsync() 실행 ├── Phase: InitializingManagers (0% → 40%) ├── Phase: ConnectingToServer (40% → 80%) └── Phase: LoadingResources (80% → 100%) @@ -189,7 +189,7 @@ public class SceneTransitionManager : Singleton ### 2. 이벤트 흐름 ``` -GameManager LoadingManager LoadingUI +SystemManager LoadingManager LoadingUI | | | |──OnPhaseChanged──────────────▶| | | |──UpdatePhase──────────▶| @@ -197,14 +197,14 @@ GameManager LoadingManager LoadingUI |──OnProgressChanged───────────▶| | | |──UpdateProgress───────▶| | | | - |──OnGameInitialized───────────▶| | + |──OnAppInitialized────────────▶| | | |──ShowStartButton──────▶| ``` ## 주요 개선사항 ### 1. 이벤트 기반 아키텍처 -- **분리된 관심사**: GameManager는 초기화, LoadingManager는 UI 관리 +- **분리된 관심사**: SystemManager는 초기화, LoadingManager는 UI 관리 - **유연한 확장**: 새로운 구독자 추가 용이 - **생명주기 독립성**: Start 씬 전용 컴포넌트와 전역 싱글톤 분리 @@ -224,7 +224,7 @@ GameManager LoadingManager LoadingUI Assets/ ├── Core/ │ ├── Managers/ -│ │ └── GameManager.cs (확장됨) +│ │ └── SystemManager.cs (확장됨) │ └── Loading/ │ ├── LoadingManager.cs │ ├── LoadingUI.cs @@ -245,9 +245,9 @@ Assets/ 3. UI 요소들 (ProgressBar, StatusText, StartButton 등) 연결 4. 다음 씬 이름 설정 -### 2. GameManager 설정 +### 2. SystemManager 설정 -- 기존 GameManager 설정 그대로 사용 +- 기존 SystemManager 설정 그대로 사용 - 자동 초기화 옵션 유지 또는 LoadingManager에서 수동 호출 ### 3. 씬 전환 설정 @@ -258,7 +258,7 @@ Assets/ ## 고려사항 ### 1. 성능 최적화 -- GameManager 이벤트는 Start 씬에서만 구독 +- SystemManager 이벤트는 Start 씬에서만 구독 - 메모리 누수 방지를 위한 적절한 구독 해제 - 불필요한 업데이트 최소화 @@ -274,4 +274,4 @@ Assets/ ## 결론 -이벤트 기반 아키텍처를 통해 GameManager의 전역적 특성과 LoadingManager의 씬 전용 특성을 효과적으로 분리했습니다. 이를 통해 유지보수성과 확장성을 높이면서도 사용자에게 명확한 초기화 진행 상황을 제공할 수 있습니다. +이벤트 기반 아키텍처를 통해 SystemManager의 전역적 특성과 LoadingManager의 씬 전용 특성을 효과적으로 분리했습니다. 이를 통해 유지보수성과 확장성을 높이면서도 사용자에게 명확한 초기화 진행 상황을 제공할 수 있습니다. diff --git a/Assets/Docs/Initial_Loading_System_Design.md.meta b/Assets/Docs/Design/Initial_Loading_System_Design.md.meta similarity index 100% rename from Assets/Docs/Initial_Loading_System_Design.md.meta rename to Assets/Docs/Design/Initial_Loading_System_Design.md.meta diff --git a/Assets/Docs/Initial_Startup_System_Design.md b/Assets/Docs/Design/Initial_Startup_System_Design.md similarity index 80% rename from Assets/Docs/Initial_Startup_System_Design.md rename to Assets/Docs/Design/Initial_Startup_System_Design.md index b5b9fb8..eb54480 100644 --- a/Assets/Docs/Initial_Startup_System_Design.md +++ b/Assets/Docs/Design/Initial_Startup_System_Design.md @@ -26,29 +26,29 @@ - 로딩 중 상태 표시 - 각 단계별 진행률 표시 -- 준비 완료 시 "게임 시작" 버튼 활성화 +- 준비 완료 시 "앱 시작" 버튼 활성화 - 페이드 인/아웃 효과로 씬 전환 ## 아키텍처 설계 -### 선택된 접근 방식: 이벤트 기반 시스템 (GameManager 중심) +### 선택된 접근 방식: 이벤트 기반 시스템 (SystemManager 중심) **선택 이유:** -- GameManager가 모든 씬에서 지속되는 싱글톤 +- SystemManager가 모든 씬에서 지속되는 싱글톤 - StartupManager는 Start 씬에서만 존재 - 이벤트 구독을 통한 효율적인 상태 관리 -- 기존 GameManager 시스템과의 완벽한 통합 +- 기존 SystemManager 시스템과의 완벽한 통합 ### 시스템 구성요소 ``` -GameManager (DontDestroyOnLoad) +SystemManager (DontDestroyOnLoad) ├── InitializationPhase 이벤트 발생 ├── Progress 이벤트 발생 └── 완료/에러 이벤트 발생 ↓ (이벤트 구독) LoadingManager (Start 씬 전용) -├── GameManager 이벤트 구독 +├── SystemManager 이벤트 구독 ├── LoadingUI 제어 └── SceneTransitionManager 호출 ↓ @@ -58,14 +58,14 @@ SceneTransitionManager (싱글톤) ### 이벤트 기반 동작 흐름 -1. **GameManager**: 초기화 진행하며 이벤트 발생 -2. **LoadingManager**: GameManager 이벤트 구독하여 UI 업데이트 +1. **SystemManager**: 초기화 진행하며 이벤트 발생 +2. **LoadingManager**: SystemManager 이벤트 구독하여 UI 업데이트 3. **LoadingUI**: 진행 상황 표시 및 사용자 상호작용 4. **SceneTransitionManager**: 씬 전환 관리 ## 구현된 클래스 -### 1. GameManager (확장됨) +### 1. SystemManager (확장됨) ```csharp public enum InitializationPhase @@ -77,16 +77,16 @@ public enum InitializationPhase Completed } -public class GameManager : Singleton +public class SystemManager : Singleton { // 이벤트 public event Action OnPhaseChanged; public event Action OnProgressChanged; - public event Action OnGameInitialized; + public event Action OnAppInitialized; public event Action OnInitializationError; // 비동기 초기화 - public async UniTask InitializeGameAsync(); + public async UniTask InitializeAppAsync(); // 상태 조회 public InitializationStatus GetInitializationStatus(); @@ -104,24 +104,24 @@ public class GameManager : Singleton ```csharp public class LoadingManager : MonoBehaviour { - // GameManager 이벤트 구독 - private void SubscribeToGameManager(); + // SystemManager 이벤트 구독 + private void SubscribeToSystemManager(); // 초기화 시작 public async void StartInitialization(); - // 게임 시작 (씬 전환) - public async void StartGame(); + // 앱 시작 (씬 전환) + public async void StartApp(); // 이벤트 핸들러 private void OnPhaseChanged(InitializationPhase phase); private void OnProgressChanged(float progress); - private void OnGameInitialized(); + private void OnAppInitialized(); } ``` **주요 기능:** -- GameManager 이벤트 구독 관리 +- SystemManager 이벤트 구독 관리 - LoadingUI와 연동 - 초기화 완료 시 씬 전환 처리 @@ -176,9 +176,9 @@ public class SceneTransitionManager : Singleton ``` 1. StartScene 로드 -2. LoadingManager 생성 및 GameManager 이벤트 구독 +2. LoadingManager 생성 및 SystemManager 이벤트 구독 3. LoadingManager.StartInitialization() 호출 -4. GameManager.InitializeGameAsync() 실행 +4. SystemManager.InitializeAppAsync() 실행 ├── Phase: InitializingManagers (0% → 40%) ├── Phase: ConnectingToServer (40% → 80%) └── Phase: LoadingResources (80% → 100%) @@ -189,7 +189,7 @@ public class SceneTransitionManager : Singleton ### 2. 이벤트 흐름 ``` -GameManager LoadingManager LoadingUI +SystemManager LoadingManager LoadingUI | | | |──OnPhaseChanged──────────────▶| | | |──UpdatePhase──────────▶| @@ -197,14 +197,14 @@ GameManager LoadingManager LoadingUI |──OnProgressChanged───────────▶| | | |──UpdateProgress───────▶| | | | - |──OnGameInitialized───────────▶| | + |──OnAppInitialized────────────▶| | | |──ShowStartButton──────▶| ``` ## 주요 개선사항 ### 1. 이벤트 기반 아키텍처 -- **분리된 관심사**: GameManager는 초기화, LoadingManager는 UI 관리 +- **분리된 관심사**: SystemManager는 초기화, LoadingManager는 UI 관리 - **유연한 확장**: 새로운 구독자 추가 용이 - **생명주기 독립성**: Start 씬 전용 컴포넌트와 전역 싱글톤 분리 @@ -224,7 +224,7 @@ GameManager LoadingManager LoadingUI Assets/ ├── Core/ │ ├── Managers/ -│ │ └── GameManager.cs (확장됨) +│ │ └── SystemManager.cs (확장됨) │ └── Loading/ │ ├── LoadingManager.cs │ ├── LoadingUI.cs @@ -245,9 +245,9 @@ Assets/ 3. UI 요소들 (ProgressBar, StatusText, StartButton 등) 연결 4. 다음 씬 이름 설정 -### 2. GameManager 설정 +### 2. SystemManager 설정 -- 기존 GameManager 설정 그대로 사용 +- 기존 SystemManager 설정 그대로 사용 - 자동 초기화 옵션 유지 또는 LoadingManager에서 수동 호출 ### 3. 씬 전환 설정 @@ -258,7 +258,7 @@ Assets/ ## 고려사항 ### 1. 성능 최적화 -- GameManager 이벤트는 Start 씬에서만 구독 +- SystemManager 이벤트는 Start 씬에서만 구독 - 메모리 누수 방지를 위한 적절한 구독 해제 - 불필요한 업데이트 최소화 @@ -274,4 +274,4 @@ Assets/ ## 결론 -이벤트 기반 아키텍처를 통해 GameManager의 전역적 특성과 LoadingManager의 씬 전용 특성을 효과적으로 분리했습니다. 이를 통해 유지보수성과 확장성을 높이면서도 사용자에게 명확한 초기화 진행 상황을 제공할 수 있습니다. +이벤트 기반 아키텍처를 통해 SystemManager의 전역적 특성과 LoadingManager의 씬 전용 특성을 효과적으로 분리했습니다. 이를 통해 유지보수성과 확장성을 높이면서도 사용자에게 명확한 초기화 진행 상황을 제공할 수 있습니다. diff --git a/Assets/Docs/Initial_Startup_System_Design.md.meta b/Assets/Docs/Design/Initial_Startup_System_Design.md.meta similarity index 100% rename from Assets/Docs/Initial_Startup_System_Design.md.meta rename to Assets/Docs/Design/Initial_Startup_System_Design.md.meta diff --git a/Assets/Docs/Design/Live2D_Service_Design.md b/Assets/Docs/Design/Live2D_Service_Design.md new file mode 100644 index 0000000..a09f8e4 --- /dev/null +++ b/Assets/Docs/Design/Live2D_Service_Design.md @@ -0,0 +1,165 @@ +## Live2D 연동 Service 설계 (Emotion/Action 중심) + +### 목적 +- **캐릭터 반응**: 서버로부터 수신한 텍스트, 음성, 감정(Emotion), 행동(Action) 정보를 바탕으로 Live2D 캐릭터의 표정/모션/상태를 변경한다. +- **모듈화/확장성**: 감정/행동 처리를 각각 모듈로 분리하여 맵핑과 우선순위 정책을 유연하게 확장한다. +- **일관된 상태관리**: 음성 재생, 감정 지속시간, 행동 트리거 등 비동기 이벤트를 단일 상태 모델에서 일관되게 관리한다. + +### 범위 +- 포함: 감정/행동 파이프라인 설계, 상태관리, Live2D SDK 연동 포인트, 데이터 매핑 규칙, 테스트 전략, 단계별 구현 로드맵 +- 제외: 정교한 액션 모션 구현(더미 처리), 고급 물리/파라미터 튜닝, 에디터 툴링(UI) + +### 용어 및 데이터 모델 +- **Emotion**: 캐릭터의 표정/감정 상태를 표현. 예: Neutral, Happy, Sad, Angry, Surprised 등 +- **Action**: 캐릭터의 몸짓/행동 트리거. 예: Nod, ShakeHead, WaveHand 등(현재 더미) +- **ChatResponse.Metadata**: 서버가 감정/행동 정보를 담아 보내는 확장 필드. 제안 키: + - `emotion`: string (예: "happy") + - `emotion_intensity`: float 0~1 + - `emotion_duration_ms`: int + - `action`: string (예: "wave_hand") + - `action_args`: object + +### 전체 아키텍처 개요 +- **ChatManager**: 서버 왕복, 메시지 큐 및 음성/버블 표시. 수신 메시지를 감정/행동 파이프라인으로 라우팅. +- **Live2DCharacterManager**: 캐릭터 상태의 단일 진입점. Emotion/Action 컨트롤러를 보유하고 우선순위/병행 정책 적용. +- **EmotionController**: 감정 → Live2D Expression 맵핑/블렌딩/지속시간/해제 관리. +- **ActionController**: 행동 → Live2D Motion/Parameter 트리거. 현재 더미. +- **VoiceManager(기존)**: 음성 재생 및 완료 이벤트 발생. +- **ChatBubbleManager(기존)**: 대화 버블 표시. + +```mermaid +sequenceDiagram +participant U as User +participant CM as ChatManager +participant API as ApiService +participant L2D as Live2DCharacterManager +participant EMO as EmotionController +participant ACT as ActionController +participant VO as VoiceManager + +U->>CM: 사용자 입력 전송 +CM->>API: 요청(텍스트, 캐릭터/유저 ID) +API-->>CM: 응답(text, audio, metadata{emotion, action}) +CM->>CM: ChatMessage 변환 및 큐 처리 +CM->>VO: 음성 재생 +CM->>L2D: ApplyReaction(metadata) +L2D->>EMO: SetEmotion(emotion, intensity, duration) +L2D->>ACT: TriggerAction(action) +VO-->>L2D: OnVoiceFinished() +L2D->>EMO: Release/Restore 상태 갱신 +``` + +이미지 버전: `Assets/Docs/diagrams/live2d_sequence.svg` / `Assets/Docs/diagrams/live2d_sequence.png` +![Live2D Sequence](diagrams/live2d_sequence.svg) + +### 상태 관리 설계 +- **CharacterStateModel** 제안 필드 + - `currentEmotion`, `pendingEmotion`, `emotionIntensity`, `emotionExpireAt` + - `currentAction`, `actionPlaying`, `actionExpireAt` + - `isVoicePlaying` +- **우선순위(초안)** + - Action > Voice Gating > Emotion 순으로 적용. 예: 강한 액션은 진행 중 감정을 일시 덮어씀. + - Voice 재생 중 과격한 표정 전환은 완화(블렌드 타임 증가) 또는 대기열로 보관. +- **시간/블렌딩** + - 감정은 `duration` 동안 유지 후 `Neutral`로 복귀. 블렌드 인/아웃 시간 설정. + - 액션은 모션 길이 동안 `actionPlaying=true`, 종료 시 이전 감정 복원. + +### Live2D SDK 연동 포인트 +- Expression: `CubismExpressionController.CurrentExpressionIndex` 또는 이름 기반 선택. 감정 → Expression 이름/인덱스 맵 필요. +- Motion: 추후 `CubismMotionController` 또는 모션 그룹 기반 트리거. 현재는 더미 처리(로그/플래그). +- Parameter(선택): 입모양/깜박임 등은 차후 `Voice` 파형 기반 드라이브(범위 외). + +### Live2D 모델 관리자 / 파라미터 컨트롤러 설계 +- **Live2DModelManager** + - 역할: 모델 로드/언로드, 프리로드, 활성 모델 전환, 표시/비표시, 컴포넌트 접근 + - 고려: 풀링, 프리로딩, 비동기 로드, 메모리 관리, 품질/성능(텍스처 압축/샘플링, MipMap, LOD) +- **Live2DParameterController** + - 역할: 파라미터 직접 제어(Set/Blend/Reset), 프리셋 적용(감정/액션의 파라미터 세트), 업데이트 루프에서 블렌딩 처리 + - 고려: 충돌해결(우선순위/레이어), 블렌드 커브, 최대/최소 클램프, 이름→ID 매핑 캐시 +- **Live2DCharacterManager** + - 역할: 상위 조정자. 모델 관리자/파라미터 컨트롤러를 묶어 감정/행동 반응을 일관되게 적용 + - 정책: Action > Voice > Emotion 우선순위, 지속시간/블렌드 관리, 구성 자산 기반 매핑 + +```mermaid +graph TD + CM[ChatManager] --> LCM[Live2DCharacterManager] + LCM --> LMM[Live2DModelManager] + LCM --> LPC[Live2DParameterController] + LMM -->|Active Model| L2D[Live2D Components] + LPC -->|Parameters/Expressions| L2D +``` + +이미지 버전: `Assets/Docs/diagrams/live2d_architecture.svg` / `Assets/Docs/diagrams/live2d_architecture.png` +![Live2D Architecture](diagrams/live2d_architecture.svg) + +### 모듈 API 초안 (스켈레톤 중심) +```csharp +/** 모델 전반의 상태를 단일 진입점에서 관리한다. */ +public interface ILive2DModelManagerFacade +{ + void Initialize(); + void ApplyReaction(EmotionData emotionData, ActionData actionData); + void OnVoiceStarted(); + void OnVoiceFinished(); +} + +/** 감정 → Expression 맵핑과 블렌딩을 담당한다. */ +public interface IEmotionController +{ + void Initialize(); + void SetEmotion(string emotion, float intensity, int durationMs); + void ClearEmotion(); +} + +/** 행동 → 모션/파라미터 트리거를 담당한다. */ +public interface IActionController +{ + void Initialize(); + void TriggerAction(string action, object args = null); +} + +/** 감정/행동 데이터 전달을 위한 단순 DTO. */ +public struct EmotionData { public string Emotion; public float Intensity; public int DurationMs; } +public struct ActionData { public string Action; public object Args; } +``` + +### 메시지 매핑 규칙 +- ChatResponse → ChatMessage 변환 시 `metadata`에서 감정/행동 추출. +- 누락/알 수 없는 값은 안전한 기본값 적용: + - emotion 미지정: `neutral` + - intensity 누락: `0.5f` + - duration 누락: `2000ms` + - action 미지정: 처리 없음 + +### 에러 처리 및 로깅 +- 알 수 없는 emotion/action은 경고 로그와 함께 무시. 매핑 테이블에 기록하여 추후 보강. +- Live2D 컴포넌트 결여 시 초기화 단계에서 명시적 오류. + +### 테스트 전략 +- 단위 테스트 + - Emotion 맵핑: 입력 emotion → 예상 Expression 식별자 + - 상태 머신: 음성 재생 중 감정 대기/복원, 액션 우선 적용 +- 통합 테스트 + - ChatManager 이벤트 플로우에서 ApplyReaction 호출 여부 + - 음성 종료 이벤트 후 상태 정상 복원 + +### 구성/데이터 자산 +- `Live2DModelConfig`(ScriptableObject 제안) + - `Emotion → ExpressionKey` 사전 + - `Action → MotionKey` 사전(더미 가능) + - 블렌드/지속 기본값, 우선순위 정책 + +### 단계별 로드맵(점진 구현) +1) 스켈레톤 클래스/인터페이스 추가 및 DI 연결 +2) EmotionController 최소 맵핑(Neutral/Happy 등)과 블렌드 기본값 +3) ChatManager → Live2DCharacterManager 라우팅 연결, Voice 이벤트 연동 +4) ActionController 더미 트리거(로그/플래그) 및 상태 복원 +5) 구성 자산(SO) 도입, 맵 테이블 외부화 +6) 모션 컨트롤러 연동 및 액션 일부 실제 재생 + +### 오픈 이슈 +- 라이브2D 모션 자산命名/그룹 규칙 확정 필요 +- 감정 우선순위 정책의 UX 적정값(블렌드/지속) 튜닝 필요 +- 서버 메타데이터 스키마 확정 및 계약 문서화 필요 + + diff --git a/Assets/Docs/New_Manager_Checklist.md.meta b/Assets/Docs/Design/Live2D_Service_Design.md.meta similarity index 75% rename from Assets/Docs/New_Manager_Checklist.md.meta rename to Assets/Docs/Design/Live2D_Service_Design.md.meta index c297355..6f63b46 100644 --- a/Assets/Docs/New_Manager_Checklist.md.meta +++ b/Assets/Docs/Design/Live2D_Service_Design.md.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 7f33f526f09614c49839275f4953bd57 +guid: ab727606d752cc940ad0601903444ffd TextScriptImporter: externalObjects: {} userData: diff --git a/Assets/Art.meta b/Assets/Docs/Guides.meta similarity index 77% rename from Assets/Art.meta rename to Assets/Docs/Guides.meta index a0fc4a5..f6f479a 100644 --- a/Assets/Art.meta +++ b/Assets/Docs/Guides.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: d25bcdd67766fb24691112602b8b8bb8 +guid: d86ffccbebb5ad441aea196e0d5d3199 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/Docs/Guides/Manager_System_Guide.md b/Assets/Docs/Guides/Manager_System_Guide.md new file mode 100644 index 0000000..6f1be25 --- /dev/null +++ b/Assets/Docs/Guides/Manager_System_Guide.md @@ -0,0 +1,110 @@ +# 매니저 시스템 가이드 (소규모 팀용) + +간단/소규모 기준: 인스펙터 연결 + Initialize 패턴 + +## 규칙(요약) +| 항목 | 규칙 | 예시 | +|---|---|---| +| 의존성 연결 | SerializedField 인스펙터 참조 | [SerializeField] private AudioManager _audio; | +| 동적 생성 | 비활성 Instantiate → 값 세팅 → Initialize → 활성화 | go.SetActive(false); comp.Initialize(...); go.SetActive(true); | +| 수명주기 | Awake/OnEnable에서 외부 의존 사용 금지, Initialize 이후 사용 | Initialize 호출 순서 보장 | +| 구성 루트 | Bootstrapper 1개, DontDestroyOnLoad | GameBootstrapper | +| 설정 자산 | ScriptableObject 참조 | AppEnvironmentConfig | + +## 패턴 1) 정적 의존성(인스펙터 연결) +```csharp +using UnityEngine; + +public sealed class GameBootstrapper : MonoBehaviour +{ + [SerializeField] private AudioManager _audioManager; + [SerializeField] private UIManager _uiManager; + + private void Awake() + { + _audioManager.Initialize(); + _uiManager.Initialize(); + } +} + +public sealed class UIManager : MonoBehaviour +{ + [SerializeField] private AudioManager _audioManager; + + /// + /// 매니저 초기화 + /// + public void Initialize() { } +} +``` + +포인트 +- SerializedField로 동일 씬/프리팹 내 의존 연결 +- Awake에서 Initialize 순차 호출(간단, 명확) + +## 패턴 2) 동적 생성(Initialize 세팅) +```csharp +using UnityEngine; + +public sealed class EnemyFactory +{ + public Enemy Spawn(Enemy prefab, Vector3 pos, EnemyStats stats) + { + var go = Object.Instantiate(prefab.gameObject, pos, Quaternion.identity); + go.SetActive(false); + var enemy = go.GetComponent(); + enemy.Initialize(stats); + go.SetActive(true); + return enemy; + } +} + +public sealed class Enemy : MonoBehaviour +{ + /// + /// 런타임 파라미터 주입 + /// + public void Initialize(EnemyStats stats) { } +} +``` + +포인트 +- Awake/OnEnable 이전 값 주입 보장 +- 프리팹 바리언트로 기본값, Initialize로 런타임 값만 주입 + +## 패턴 3) 경량 서비스 조회(선택) +```csharp +public static class ServiceLocator +{ + private static readonly Dictionary _map = new(); + public static void Register(T svc) => _map[typeof(T)] = svc; + public static T Get() => (T)_map[typeof(T)]; +} + +public sealed class GameBootstrapper : MonoBehaviour +{ + [SerializeField] private AudioManager _audio; + private void Awake() + { + ServiceLocator.Register(_audio); + _audio.Initialize(); + } +} +``` + +포인트 +- 작은 규모에서만 사용, 과도한 DI 지양 +- 1회 조회 후 캐싱 권장 + +## 실전 체크리스트 +- [ ] 인스펙터 참조 누락 없음(씬/프리팹) +- [ ] Awake에서 Initialize 순서 정의(상위→하위) +- [ ] 동적 객체는 비활성 Instantiate → Initialize 후 활성화 +- [ ] ScriptableObject로 설정/리소스 참조 외부화 +- [ ] FindObjectOfType 매 프레임 호출 금지(초기 1회 캐싱만) + +## 안티 패턴 +- 글로벌 싱글톤 남용(하드 의존) +- Awake에서 외부 매니저 즉시 호출(주입 순서 깨짐) +- Resources 남용(필요 최소만) + diff --git a/Assets/Docs/Manager_System_Guide.md.meta b/Assets/Docs/Guides/Manager_System_Guide.md.meta similarity index 100% rename from Assets/Docs/Manager_System_Guide.md.meta rename to Assets/Docs/Guides/Manager_System_Guide.md.meta diff --git a/Assets/Docs/Guides/ProjectVG_Structure_Guide.md b/Assets/Docs/Guides/ProjectVG_Structure_Guide.md new file mode 100644 index 0000000..d1285e4 --- /dev/null +++ b/Assets/Docs/Guides/ProjectVG_Structure_Guide.md @@ -0,0 +1,169 @@ + +# ProjectVG Unity 프로젝트 구조 가이드 + +```text +Assets/ +├─ App/ - 앱 엔트리, 전역 부트스트랩 +│ └─ Scenes/ - 앱/메인/로딩 씬 +├─ Core/ - 공통 런타임 모듈 +│ ├─ Input/ - 입력 매니저/라우팅(ScreenTapManager 등) +│ ├─ Audio/ - 오디오 매니저/레코더/보이스 +│ ├─ Loading/ - 로딩/씬 전환 유틸 +│ ├─ Managers/ - 초기화/DI/레지스트리(System/Dependency) +│ └─ Utils/ - 범용 유틸/싱글톤 +├─ Domain/ - 기능(도메인)별 코드/자산 +│ ├─ Character/ +│ │ ├─ Model/ - Live2D 자산(.moc3/.json/Prefab/Motions/Expressions) +│ │ ├─ View/ - 캐릭터 프리팹/뷰 스크립트 +│ │ ├─ Script/ - 컨트롤러/매니저/컴포넌트/Config SO +│ │ └─ Animation/ - 타임라인/모션 클립 +│ └─ Chat/ +│ ├─ Script/ - 채팅 로직/서비스 연동 +│ ├─ View/ - UI 프리팹/스크립트 +│ └─ Model/ - 데이터 모델/DTO +├─ Infrastructure/ - 외부 연동/저장/네트워크 +│ ├─ Network/ - Http/WS 클라이언트, Services, DTOs, Configs +│ ├─ Data/ - 로컬 저장 래퍼(SaveData/PlayerPrefs) +│ ├─ Bridge/ - 네이티브/외부 SDK 브리지 +│ └─ Config/ - 실행환경 설정 SO(AppEnvironmentConfig 등) +├─ UI/ - 공용 UI 리소스 +│ ├─ Panels/ - 화면 단위 패널 프리팹 +│ ├─ Prefabs/ - 공용 UI 프리팹 +│ ├─ Scripts/ - UI 상호작용 스크립트 +│ ├─ Transitions/ - 전환 효과(페이드/이동 등) +│ └─ Fonts/ - 폰트/머티리얼 +├─ Resources/ - Resources.Load 대상(필요 최소) +│ ├─ App/ - 앱 공용 리소스 +│ └─ Character/ - 레지스트리/모델 등록 SO +├─ Settings/ - 프로젝트 설정 자산 +│ ├─ Rendering/ +│ │ └─ URP/ - URP 에셋/글로벌 설정/Renderer2D +│ ├─ InputSystem/ - .inputactions +│ └─ Adaptive Performance/ - 프로바이더/프로필 자산 +├─ Scenes/ - 공용/샘플 씬 +├─ Plugins/ - 외부 SDK/라이브러리 +│ ├─ Live2D/ - Cubism SDK +│ └─ TextMesh Pro/ - TMP 패키지 리소스 +├─ Editor/ - 에디터 전용 스크립트 +├─ Tests/ - 테스트 +│ ├─ Editor/ - 에디터 테스트 +│ └─ Runtime/ - 플레이모드/유닛 테스트 +├─ Docs/ - 문서 +│ ├─ Design/ - 설계/다이어그램 +│ └─ Guides/ - 가이드/규칙 +└─ Samples/ - 샘플 코드/리소스 + └─ Core/ + └─ Managers/ - 샘플 매니저 스크립트 +``` + +```mermaid +graph TD + A[Assets] --> APP[App] + A --> CORE[Core] + A --> DOMAIN[Domain] + A --> INFRA[Infrastructure] + A --> UI[UI] + A --> RES[Resources] + A --> SETT[Settings] + A --> SCENES[Scenes] + A --> PLUG[Plugins] + A --> EDITOR[Editor] + A --> TESTS[Tests] + A --> DOCS[Docs] + A --> SAMPLES[Samples] + + CORE --> CORE_Input[Input] + CORE --> CORE_Audio[Audio] + CORE --> CORE_Loading[Loading] + CORE --> CORE_Managers[Managers] + CORE --> CORE_Utils[Utils] + + DOMAIN --> D_Character[Character] + D_Character --> D_Model[Model] + D_Character --> D_View[View] + D_Character --> D_Script[Script] + D_Character --> D_Anim[Animation] + + INFRA --> Nw[Network] + INFRA --> Data[Data] + INFRA --> Bridge[Bridge] + INFRA --> Cfg[Config] + + SETT --> URP[Rendering/URP] + SETT --> InputSys[InputSystem] + SETT --> AP[AdaptivePerformance] + + DOCS --> Design[Design] + DOCS --> Guides[Guides] +``` + +## 최상위 디렉토리 +| 경로 | 용도 | 예시 | +|---|---|---| +| Assets/App | 엔트리/앱 전역 흐름 | App.cs, App/Scenes | +| Assets/Core | 공통 기능(엔진 독립) | Audio, Input, Loading, Managers, Utils | +| Assets/Domain | 기능(도메인)별 코드/자산 | Character, Chat, Popup | +| Assets/Infrastructure | 외부 연동/저장/네트워크 | Network, Data, Bridge, Config | +| Assets/UI | 공용 UI 리소스 | Panels, Prefabs, Scripts, Transitions, Fonts | +| Assets/Resources | 런타임 Resources.Load 대상 | Config assets, Registries | +| Assets/Settings | 프로젝트 설정 자산 | Rendering/URP, InputSystem, AdaptivePerformance | +| Assets/Scenes | 공용 씬(샘플/공통) | SampleScene, DevScene | +| Assets/Plugins | 외부 SDK/라이브러리 | Live2D, TextMesh Pro | +| Assets/Editor | 에디터 전용 코드 | Inspectors, MenuItems | +| Assets/Tests | 테스트 | Editor, Runtime | +| Assets/Docs | 문서 | Design, Guides, diagrams | +| Assets/Samples | 샘플 코드/리소스 | Core/Managers/SampleSystemManager.cs | + +## Core 하위 +| 경로 | 용도 | 비고 | +|---|---|---| +| Core/Input | 입력 처리 | ScreenTapManager | +| Core/Audio | 오디오 공통 | AudioManager, VoiceManager | +| Core/Loading | 로딩/씬 전환 | LoadingManager, SceneTransitionManager | +| Core/Managers | 초기화/DI/레지스트리 | InitializationManager, DependencyManager | +| Core/Utils | 범용 유틸 | Singleton 등 | + +## Domain 하위(패턴) +| 경로 | 용도 | 예시 | +|---|---|---| +| Domain//Model | 모델 자산(겉모습) | Live2D .moc3/.json, Prefab, Motions | +| Domain//View | 뷰/프리팹 | UI/3D 프리팹 | +| Domain//Script | 로직/컨트롤러 | Manager, Controller, Component, Config | +| Domain//Animation | 타임라인/클립 | Playables, AnimClips | + +## Infrastructure 하위 +| 경로 | 용도 | 예시 | +|---|---|---| +| Infrastructure/Network | API/WS/HTTP | Services, DTOs, Configs | +| Infrastructure/Data | 로컬 저장 | SaveData, PlayerPrefs 래퍼 | +| Infrastructure/Bridge | 네이티브/외부 SDK 브리지 | Android/iOS glue | +| Infrastructure/Config | 실행환경 설정 | AppEnvironmentConfig | + +## Settings vs Plugins +| 항목 | 내용 | +|---|---| +| Settings | 프로젝트 설정 자산(.asset, 프로필). 예: URP, InputSystem, Adaptive Performance | +| Plugins | 외부 기능 코드/리소스. 예: Live2D Cubism, TMP | + +## 배치 규칙 +- 스크립트: 기능 기준 배치(Domain/Core/Infrastructure/UI) +- 모델 자산: Domain//Model// +- 설정 자산: Assets/Settings/** +- Resources: 런타임 강제 로드 대상만 배치(남용 금지) +- Addressables: 도입 시 Resources 대체, 키 규칙 Domain/Category/Name +- 샘플: Assets/Samples/** + +## 씬 규칙 +- 공용/샘플 씬: Assets/Scenes/** +- 기능 전용 씬: Domain//View 또는 Tests/Runtime/** +- 네이밍: PascalCase + 역할(Main/Loading/Dev/Sample) + +## Addressables 키 규칙 +- Domain/Category/Name +- 예: UI/Panels/PanelChat, Characters/Natori/Model + +## 체크리스트 +- 폴더/네임스페이스/파일명/타입명 일치 +- Settings 자산 중앙화(URP/InputSystem/AdaptivePerformance) +- Model 자산 폴더 표준화(Character/Model/) +- 샘플/테스트 자산 분리(Samples, Tests) diff --git a/Assets/Docs/ProjectVG_Structure_Guide.md.meta b/Assets/Docs/Guides/ProjectVG_Structure_Guide.md.meta similarity index 100% rename from Assets/Docs/ProjectVG_Structure_Guide.md.meta rename to Assets/Docs/Guides/ProjectVG_Structure_Guide.md.meta diff --git a/Assets/Docs/Guides/Unity_Naming_Conventions.md b/Assets/Docs/Guides/Unity_Naming_Conventions.md new file mode 100644 index 0000000..f3f1d91 --- /dev/null +++ b/Assets/Docs/Guides/Unity_Naming_Conventions.md @@ -0,0 +1,62 @@ +# Unity/C# 네이밍 컨벤션 (ProjectVG) + +## C# 식별자 +| 대상 | 규칙 | 예시 | +|---|---|---| +| 클래스/구조체/열거형/속성/메서드 | PascalCase | ChatManager, MessageType, LoadConfig | +| 인터페이스 | IPascalCase | IChatService | +| 상수(const) | UPPER_SNAKE_CASE | DEFAULT_TIMEOUT_MS | +| 비공개 필드 | _camelCase | _sessionId | +| 직렬화 비공개 필드 | _camelCase | _gain | +| 매개변수/지역변수 | camelCase | messageText | +| 제네릭 매개변수 | TName | TItem, TResponse | + +## C# 패턴 +| 항목 | 규칙 | 예시 | +|---|---|---| +| 불리언 | Is/Has/Can/Should 접두사 | IsConnected, HasData | +| 이벤트 이름 | OnXxx / XxxChanged | OnMessageReceived, VolumeChanged | +| 비동기 메서드 | Async 접미사, ct 매개변수 | LoadAsync(CancellationToken ct) | +| 네임스페이스 | 폴더 구조 반영, 루트 ProjectVG | ProjectVG.Domain.Chat | +| 파일명 | 공개 루트 타입명과 동일 | ChatManager.cs | + +## Unity 클래스 +| 범주 | 접미/접두 | 예시 | 용도 | +|---|---|---|---| +| MonoBehaviour | Controller | PlayerController | 기능 제어 | +| | Manager | AudioManager | 전역 수명/상태 | +| | Service | ChatService | 외부/비즈 로직 | +| | View/Presenter | ChatView | UI 표시/중개 | +| | Bootstrapper/Installer | GameBootstrapper | 초기화/조립 | +| ScriptableObject | Config/Settings/Profile/Definition/Registry/Database | Live2DModelConfig | 데이터/설정 | +| 기타 | Handler/Factory/Pool | WebSocketHandler | 책임 명확화 | + +공개 API: 명령형 동사 사용(Initialize/Apply/Load 등) + +## Unity 자산 +| 타입 | 규칙 | 예시 | +|---|---|---| +| 씬 | PascalCase, 역할 접미 허용(Main/Boot/Loading/Sample) | MainScene, LoadingScene | +| 프리팹 | PascalCase, UI 루트 접두사 Panel/Dialog/HUD | PanelChat, DialogConfirm | +| SO 에셋 | 타입명 기반 + 키 | NetworkConfig_Prod | +| Addressables | Domain/Category/Name | UI/Panels/PanelChat | +| 폴더 | PascalCase 단수형 | Domain/Character/Model | + +## UI 위젯 +| 위치 | 규칙 | 예시 | +|---|---|---| +| Hierarchy 이름 | 접두사 사용 Panel/Btn/Img/Txt/Input/Scroll/Toggle/Slider/Dropdown | PanelChat, BtnSend | +| 코드 필드명 | 의미 중심 camelCase | sendButton, titleText | + +## 예시(요약) +- MonoBehaviour: ScreenTapManager, Live2DModelManagerFacade +- ScriptableObject: Live2DModelConfig, AppEnvironmentConfig +- Prefab: PanelChat, ChatBubbleUI, AudioInputView +- Scene: MainScene, Live2DScene + +## 금지/주의 +- 범용어 남용: Data/Util/Helper/Manager(무의미 사용) +- 약어 조합: Cfg/Ctrllr/Svc +- 파일명 ≠ 타입명 불일치 +- 부정형 이벤트/플래그: NotReady → 긍정형 ShouldWait/IsReady + diff --git a/Assets/Docs/Unity_Naming_Conventions.md.meta b/Assets/Docs/Guides/Unity_Naming_Conventions.md.meta similarity index 100% rename from Assets/Docs/Unity_Naming_Conventions.md.meta rename to Assets/Docs/Guides/Unity_Naming_Conventions.md.meta diff --git a/Assets/Docs/Manager_System_Guide.md b/Assets/Docs/Manager_System_Guide.md deleted file mode 100644 index deab96a..0000000 --- a/Assets/Docs/Manager_System_Guide.md +++ /dev/null @@ -1,305 +0,0 @@ -# 매니저 시스템 사용 가이드 - -## 개요 - -리팩토링된 매니저 시스템은 **단일 책임 원칙**에 따라 GameManager의 복잡성을 분산시켜 관리하기 쉽게 만들었습니다. - -## 🏗️ 시스템 구조 - -``` -GameManager (퍼사드/조율자) -├── InitializationManager (초기화 전담) -├── ManagerRegistry (매니저 생성/관리) -└── DependencyManager (의존성 주입) -``` - -### 각 매니저의 역할 - -| 매니저 | 책임 | 주요 기능 | -|--------|------|-----------| -| **GameManager** | 전체 조율, 퍼사드 | 간단한 인터페이스 제공, 이벤트 중계 | -| **InitializationManager** | 초기화 과정 관리 | 단계별 초기화, 진행률 추적, 이벤트 발생 | -| **ManagerRegistry** | 매니저 생명주기 관리 | 매니저 생성/등록/해제, 상태 조회 | -| **DependencyManager** | 의존성 주입 | 서비스 등록, 의존성 해결 | - -## 📖 기본 사용법 - -### 1. 기존 코드 - 변경 없음! - -```csharp -// 여전히 이렇게 사용 가능 -GameManager.Instance.InitializeGameAsync(); -GameManager.Instance.OnGameInitialized += OnGameReady; - -// 매니저 접근도 동일 -var webSocket = GameManager.Instance.WebSocketManager; -var session = GameManager.Instance.SessionManager; -``` - -### 2. 새로운 기능들 - -```csharp -// 상세한 초기화 상태 조회 -var status = GameManager.Instance.GetInitializationStatus(); -Debug.Log($"현재 단계: {status.CurrentPhase}"); -Debug.Log($"진행률: {status.Progress * 100}%"); - -// 매니저 준비 상태 확인 -if (GameManager.Instance.AreManagersReady()) -{ - // 모든 매니저가 준비됨 -} -``` - -## 🔧 새로운 매니저 추가하기 - -### Step 1: 매니저 클래스 생성 - -```csharp -using UnityEngine; -using ProjectVG.Core.Managers; - -public class AudioManager : MonoBehaviour, IManager -{ - [Header("Audio Settings")] - [SerializeField] private float _masterVolume = 1.0f; - - public float MasterVolume => _masterVolume; - - public void Initialize() - { - Debug.Log("[AudioManager] 초기화 완료"); - } - - public void Shutdown() - { - Debug.Log("[AudioManager] 종료 처리"); - } - - public void PlaySound(string soundName) - { - // 사운드 재생 로직 - } -} -``` - -### Step 2: ManagerRegistry에 추가 - -```csharp -// ManagerRegistry.cs에 추가 -[Header("Manager References")] -[SerializeField] private AudioManager _audioManager; // 👈 추가 - -public AudioManager AudioManager => _audioManager; // 👈 추가 - -public void InitializeAllManagers() -{ - InitializeWebSocketManager(); - InitializeSessionManager(); - InitializeHttpApiClient(); - InitializeAudioManager(); // 👈 추가 -} - -private void InitializeAudioManager() // 👈 새 메서드 -{ - if (_audioManager == null && _createManagersIfNotExist) - { - var audioObj = new GameObject("AudioManager"); - audioObj.transform.SetParent(transform); - _audioManager = audioObj.AddComponent(); - } - - if (_audioManager != null) - { - _managers.Add(_audioManager); - _audioManager.Initialize(); - Debug.Log("[ManagerRegistry] AudioManager 초기화 완료"); - } -} -``` - -### Step 3: GameManager에 접근자 추가 - -```csharp -// GameManager.cs에 추가 -public AudioManager AudioManager => _managerRegistry?.AudioManager; -``` - -### Step 4: (선택사항) 의존성 주입 설정 - -```csharp -// DependencyManager.cs에 추가 (필요한 경우) -private void RegisterServices(ManagerRegistry managerRegistry) -{ - // 기존 코드... - - if (managerRegistry.AudioManager != null) - { - _container.Register(managerRegistry.AudioManager); - Debug.Log("[DependencyManager] AudioManager 등록 완료"); - } -} -``` - -## 🎯 실제 사용 예시 - -### 새로운 AudioManager 사용 - -```csharp -public class GameController : MonoBehaviour -{ - private void Start() - { - // GameManager 초기화 완료 대기 - GameManager.Instance.OnGameInitialized += OnGameReady; - } - - private void OnGameReady() - { - // AudioManager 사용 - var audioManager = GameManager.Instance.AudioManager; - audioManager?.PlaySound("background_music"); - - Debug.Log($"Audio Volume: {audioManager.MasterVolume}"); - } -} -``` - -## 🚀 고급 사용법 - -### 1. 커스텀 초기화 단계 추가 - -새로운 매니저가 특별한 초기화 과정이 필요한 경우: - -```csharp -// InitializationPhase enum에 추가 -public enum InitializationPhase -{ - NotStarted, - InitializingManagers, - ConnectingToServer, - LoadingResources, - InitializingAudio, // 👈 새 단계 추가 - Completed -} - -// InitializationManager.cs에서 단계 추가 -private async UniTask InitializeAsync() -{ - // 기존 단계들... - - SetPhase(InitializationPhase.InitializingAudio); - await InitializeAudioAsync(); // 새 단계 - UpdateProgress(0.9f); - - SetPhase(InitializationPhase.Completed); - // ... -} -``` - -### 2. 매니저간 의존성 처리 - -```csharp -public class UIManager : MonoBehaviour, IManager -{ - [Inject] private AudioManager _audioManager; // 의존성 주입 - - public void ShowMenu() - { - _audioManager?.PlaySound("menu_open"); - // UI 표시 로직 - } -} -``` - -## 📋 체크리스트 - -새로운 매니저를 추가할 때 확인하세요: - -- [ ] `IManager` 인터페이스 구현 -- [ ] `ManagerRegistry`에 참조 추가 -- [ ] `ManagerRegistry.InitializeAllManagers()`에 초기화 메서드 추가 -- [ ] `GameManager`에 접근자 추가 -- [ ] (필요시) `DependencyManager`에 의존성 등록 -- [ ] (필요시) 새로운 초기화 단계 추가 -- [ ] 매니저 상태 로깅에 추가 - -## 🔍 디버깅 팁 - -### 1. 매니저 상태 확인 - -```csharp -// Inspector에서 또는 코드에서 호출 -GameManager.Instance.LogManagerStatus(); -``` - -### 2. 초기화 진행 상황 모니터링 - -```csharp -GameManager.Instance.OnPhaseChanged += (phase) => { - Debug.Log($"초기화 단계: {phase}"); -}; - -GameManager.Instance.OnProgressChanged += (progress) => { - Debug.Log($"진행률: {progress * 100:F1}%"); -}; -``` - -### 3. 일반적인 문제들 - -| 문제 | 원인 | 해결책 | -|------|------|--------| -| "Manager가 null" | 초기화 전에 접근 | `OnGameInitialized` 이벤트 대기 | -| "의존성 주입 실패" | 서비스 미등록 | `DependencyManager`에 등록 확인 | -| "초기화 실패" | 매니저 생성 실패 | `_createManagersIfNotExist` 설정 확인 | - -## 🎨 베스트 프랙티스 - -### 1. 매니저 설계 원칙 - -✅ **해야 할 것** -- 단일 책임 원칙 준수 -- `IManager` 인터페이스 구현 -- 명확한 Initialize/Shutdown 구현 -- 의존성 주입 활용 - -❌ **하지 말아야 할 것** -- 다른 매니저에 직접 의존 -- GameManager에 복잡한 로직 추가 -- Singleton 남용 - -### 2. 네이밍 컨벤션 - -```csharp -// 좋은 예 -AudioManager, NetworkManager, UIManager - -// 나쁜 예 -Manager, SoundSystem, AudioController -``` - -### 3. 이벤트 사용 - -```csharp -// 매니저에서 이벤트 발생 -public class AudioManager : MonoBehaviour, IManager -{ - public event Action OnSoundPlayed; - - public void PlaySound(string soundName) - { - // 재생 로직 - OnSoundPlayed?.Invoke(soundName); - } -} -``` - -## 📚 추가 자료 - -- [Unity 싱글톤 패턴 가이드](./Unity_Singleton_Guide.md) -- [의존성 주입 사용법](./Dependency_Injection_Guide.md) -- [이벤트 시스템 가이드](./Event_System_Guide.md) - ---- - -> 💡 **팁**: 새로운 매니저를 추가하기 전에 기존 매니저로 해결할 수 있는지 먼저 검토해보세요. 너무 많은 매니저는 오히려 복잡성을 증가시킬 수 있습니다. diff --git a/Assets/Docs/New_Manager_Checklist.md b/Assets/Docs/New_Manager_Checklist.md deleted file mode 100644 index 4c75460..0000000 --- a/Assets/Docs/New_Manager_Checklist.md +++ /dev/null @@ -1,198 +0,0 @@ -# 새로운 매니저 추가 체크리스트 - -## 📋 필수 체크리스트 - -새로운 매니저를 추가할 때 다음 항목들을 순서대로 확인하세요: - -### ✅ 1. 매니저 클래스 생성 - -- [ ] `IManager` 인터페이스 구현 -- [ ] `Initialize()` 메서드 구현 -- [ ] `Shutdown()` 메서드 구현 -- [ ] 필요한 이벤트 정의 -- [ ] 적절한 네임스페이스 사용 (`ProjectVG.Core.Managers`) - -```csharp -public class YourManager : MonoBehaviour, IManager -{ - public void Initialize() { /* 구현 */ } - public void Shutdown() { /* 구현 */ } -} -``` - -### ✅ 2. ManagerRegistry 수정 - -- [ ] Header에 SerializeField 변수 추가 -- [ ] Public 프로퍼티 추가 -- [ ] `InitializeAllManagers()`에 초기화 메서드 호출 추가 -- [ ] Private 초기화 메서드 구현 - -```csharp -[SerializeField] private YourManager _yourManager; -public YourManager YourManager => _yourManager; - -public void InitializeAllManagers() -{ - // 기존 초기화들... - InitializeYourManager(); // 추가 -} - -private void InitializeYourManager() { /* 구현 */ } -``` - -### ✅ 3. GameManager 수정 - -- [ ] Public 접근자 프로퍼티 추가 - -```csharp -public YourManager YourManager => _managerRegistry?.YourManager; -``` - -### ✅ 4. (선택사항) 의존성 주입 설정 - -- [ ] `DependencyManager.RegisterServices()`에 등록 추가 -- [ ] `DependencyManager.InjectDependencies()`에 주입 추가 (필요시) - -### ✅ 5. 테스트 및 검증 - -- [ ] 컴파일 에러 없음 확인 -- [ ] 런타임에서 매니저 생성 확인 -- [ ] 초기화 로그 확인 -- [ ] `GameManager.Instance.LogManagerStatus()` 실행하여 상태 확인 - -## 🚀 고급 옵션 체크리스트 - -### ✅ 커스텀 초기화 단계 (필요시) - -- [ ] `InitializationPhase` enum에 새 단계 추가 -- [ ] `InitializationManager.InitializeAsync()`에 새 단계 추가 -- [ ] 진행률 계산 업데이트 - -### ✅ 매니저간 의존성 (필요시) - -- [ ] `[Inject]` 어트리뷰트 사용 -- [ ] `DependencyManager`에서 의존성 등록 -- [ ] 초기화 순서 고려 - -### ✅ 이벤트 시스템 (추천) - -- [ ] 매니저에서 적절한 이벤트 발생 -- [ ] 이벤트 구독/해제 예시 작성 -- [ ] 메모리 누수 방지 (이벤트 해제) - -## 📝 실제 예시: AudioManager - -```csharp -// 1. 매니저 클래스 -public class AudioManager : MonoBehaviour, IManager -{ - public void Initialize() { /* 오디오 시스템 초기화 */ } - public void Shutdown() { /* 리소스 정리 */ } - public void PlaySound(string soundName) { /* 사운드 재생 */ } -} - -// 2. ManagerRegistry 추가 -[SerializeField] private AudioManager _audioManager; -public AudioManager AudioManager => _audioManager; - -// 3. GameManager 접근자 -public AudioManager AudioManager => _managerRegistry?.AudioManager; - -// 4. 사용 예시 -GameManager.Instance.AudioManager?.PlaySound("button_click"); -``` - -## 🐛 일반적인 실수들 - -### ❌ 하지 말아야 할 것들 - -- **GameManager에 복잡한 로직 추가** - ```csharp - // 나쁜 예 - public void PlaySound(string name) { /* 복잡한 로직 */ } - - // 좋은 예 - public AudioManager AudioManager => _managerRegistry?.AudioManager; - ``` - -- **다른 매니저에 직접 의존** - ```csharp - // 나쁜 예 - public class UIManager : MonoBehaviour - { - private AudioManager _audioManager; // 직접 참조 - } - - // 좋은 예 - public class UIManager : MonoBehaviour - { - [Inject] private AudioManager _audioManager; // 의존성 주입 - } - ``` - -- **초기화 순서 무시** - ```csharp - // 나쁜 예 - void Start() - { - GameManager.Instance.AudioManager.PlaySound("start"); // 초기화 전 호출 - } - - // 좋은 예 - void Start() - { - GameManager.Instance.OnGameInitialized += () => { - GameManager.Instance.AudioManager.PlaySound("start"); - }; - } - ``` - -## 🔍 디버깅 가이드 - -### 매니저가 null인 경우 - -1. **초기화 전에 접근했는지 확인** - ```csharp - GameManager.Instance.OnGameInitialized += () => { - // 여기서 매니저 사용 - }; - ``` - -2. **ManagerRegistry에 제대로 등록했는지 확인** - ```csharp - // Inspector에서 매니저 참조가 설정되었는지 확인 - ``` - -3. **초기화 메서드가 호출되는지 확인** - ```csharp - // 로그를 통해 InitializeYourManager()가 호출되는지 확인 - ``` - -### 의존성 주입이 안 되는 경우 - -1. **서비스가 등록되었는지 확인** - ```csharp - // DependencyManager.RegisterServices()에서 등록 확인 - ``` - -2. **InjectDependencies가 호출되었는지 확인** - ```csharp - // DependencyManager.InjectDependencies()에서 주입 확인 - ``` - -## 📊 성능 고려사항 - -- **매니저 수 제한**: 너무 많은 매니저는 복잡성 증가 -- **초기화 시간**: 복잡한 초기화는 LoadingResources 단계에서 -- **메모리 사용량**: 불필요한 리소스 로딩 방지 -- **이벤트 정리**: OnDestroy에서 이벤트 구독 해제 - -## 📚 참고 자료 - -- [Manager System Guide](./Manager_System_Guide.md) -- [Unity Singleton Pattern](./Unity_Singleton_Guide.md) -- [Dependency Injection Guide](./Dependency_Injection_Guide.md) - ---- - -> 💡 **꿀팁**: 새로운 매니저를 추가하기 전에 기존 매니저로 해결할 수 있는지 검토해보세요. AudioManager에 UI 사운드 기능을 추가하는 것이 UISoundManager를 새로 만드는 것보다 나을 수 있습니다. diff --git a/Assets/Docs/ProjectVG_Structure_Guide.md b/Assets/Docs/ProjectVG_Structure_Guide.md deleted file mode 100644 index a9473e4..0000000 --- a/Assets/Docs/ProjectVG_Structure_Guide.md +++ /dev/null @@ -1,100 +0,0 @@ - -# 📁 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/ # Live2D 모델 파일들 (.moc3, .json 등) -│ │ ├── View/ # 캐릭터 Prefab, UI 뷰 -│ │ ├── Script/ # 제어 스크립트 (Controller, Motion) -│ │ └── 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/Unity_Naming_Conventions.md b/Assets/Docs/Unity_Naming_Conventions.md deleted file mode 100644 index 273fb6c..0000000 --- a/Assets/Docs/Unity_Naming_Conventions.md +++ /dev/null @@ -1,236 +0,0 @@ -# Unity/C# 네이밍 컨벤션 가이드 - -이 문서는 ProjectVG Unity 클라이언트의 코드 네이밍 규칙과 컨벤션을 정의합니다. - ---- - -## 기본 C# 컨벤션 - -### 네이밍 스타일 -- **클래스명, 메서드명, 프로퍼티명**: `PascalCase` -- **변수명, 매개변수명**: `camelCase` -- **상수명**: `UPPER_SNAKE_CASE` -- **인터페이스명**: `IPascalCase` (I 접두사) - -### 접근 제한자 규칙 -- **private 필드**: `_camelCase` (언더스코어 접두사) -- **public 필드**: `camelCase` -- **프로퍼티**: `PascalCase` -- **메서드**: `PascalCase` - -### 예시 -```csharp -public class ChatManager -{ - private string _sessionId; // private 필드 - public string characterId; // public 필드 - public string SessionId { get; set; } // 프로퍼티 - - private void InitializeSession() // private 메서드 - { - // 구현 - } - - public void SendMessage(string message) // public 메서드 - { - // 구현 - } -} -``` - ---- - -## Unity 클래스 네이밍 컨벤션 - -| 역할 | 접미어 또는 접두어 | 예시 | 설명 | -|------|-------------------|------|------| -| **MonoBehaviour** | Controller, Manager, Behaviour, System 등 | PlayerController, GameManager, CameraBehaviour | 씬에 붙는 실행 스크립트 | -| **데이터 객체** (Plain C# Class) | Data, Info, Model, Config, State 등 | PlayerData, LevelInfo, GameConfig | 직렬화 또는 로직 없는 순수 데이터 | -| **싱글톤 서비스** | Service, Manager, System | AudioService, InputManager, SaveSystem | 전역 기능 담당 클래스 | -| **인터페이스** | I 접두어 | IMoveable, IDamageable | 일반 C# 인터페이스 명명 규칙 동일 | -| **UI 컴포넌트** | UI, Panel, View, Dialog | MainMenuUI, SettingsPanel, GameOverDialog | UI Prefab용 MonoBehaviour | -| **이벤트/메시지 객체** | Event, Message | GameStartEvent, PlayerDeathMessage | EventBus 또는 Observer 용 메시지 구조체 | -| **스크립터블 오브젝트** | SO, Config, Asset, Definition | WeaponConfig, LevelDefinition | ScriptableObject 파생 클래스 | -| **테스트/디버깅** | Debug, Tester, Sample, Fake | PlayerDebug, SoundTester, FakeEnemyAI | 테스트, 샘플 용도 클래스 | - ---- - -## Unity 특화 컨벤션 - -### 클래스명 접미사 규칙 - -| 역할 | 접미사 | 예시 | 설명 | -|------|--------|------|------| -| 매니저/컨트롤러 | `Manager` | `ChatManager`, `AudioManager` | 전체 시스템 관리 | -| 서비스 | `Service` | `ChatApiService`, `DataService` | 외부 서비스 연동 | -| 컨트롤러 | `Controller` | `PlayerController`, `UIController` | 특정 기능 제어 | -| 핸들러 | `Handler` | `WebSocketHandler`, `EventHandler` | 이벤트/메시지 처리 | -| 팩토리 | `Factory` | `UIFactory`, `ObjectFactory` | 객체 생성 | -| 풀 | `Pool` | `ObjectPool`, `AudioPool` | 객체 재사용 | -| 뷰 | `View` | `ChatView`, `CharacterView` | UI 표시 담당 | -| 모델 | `Model` | `ChatModel`, `UserModel` | 데이터 구조 | -| DTO | `Request`/`Response` | `ChatRequest`, `LoginResponse` | 데이터 전송 객체 | -| 인터페이스 | `I` + 기능명 | `INetworkClient`, `IAudioPlayer` | 인터페이스 | - -### 메서드명 접두사 규칙 - -| 기능 | 접두사 | 예시 | 설명 | -|------|--------|------|------| -| 초기화 | `Initialize` | `InitializeChat()` | 초기 설정 | -| 설정 | `Setup` | `SetupUI()` | 구성 설정 | -| 시작 | `Start` | `StartSession()` | 프로세스 시작 | -| 중지 | `Stop` | `StopAudio()` | 프로세스 중지 | -| 정리 | `Cleanup` | `CleanupResources()` | 리소스 정리 | -| 업데이트 | `Update` | `UpdatePosition()` | 상태 업데이트 | -| 처리 | `Process` | `ProcessMessage()` | 데이터 처리 | -| 검증 | `Validate` | `ValidateInput()` | 입력 검증 | -| 변환 | `Convert` | `ConvertToJson()` | 형식 변환 | -| 로드 | `Load` | `LoadConfig()` | 데이터 로드 | -| 저장 | `Save` | `SaveData()` | 데이터 저장 | -| 전송 | `Send` | `SendMessage()` | 네트워크 전송 | -| 수신 | `Receive` | `ReceiveResponse()` | 네트워크 수신 | - -### 변수명 접두사 규칙 - -| 타입 | 접두사 | 예시 | 설명 | -|------|--------|------|------| -| GameObject | `go` | `goPlayer` | 게임 오브젝트 | -| Transform | `tr` | `trPlayer` | 트랜스폼 | -| Component | `comp` | `compAudio` | 컴포넌트 | -| UI 요소 | `ui` | `uiButton` | UI 오브젝트 | -| Text | `txt` | `txtMessage` | 텍스트 컴포넌트 | -| Image | `img` | `imgAvatar` | 이미지 컴포넌트 | -| Button | `btn` | `btnSend` | 버튼 컴포넌트 | -| InputField | `input` | `inputMessage` | 입력 필드 | -| AudioSource | `audio` | `audioBGM` | 오디오 소스 | -| Camera | `cam` | `camMain` | 카메라 | -| Rigidbody | `rb` | `rbPlayer` | 리지드바디 | -| Collider | `col` | `colPlayer` | 콜라이더 | - -### UI 오브젝트 네이밍 - -| UI 요소 | 접두사 | 예시 | 설명 | -|---------|--------|------|------| -| Panel | `Panel` | `PanelChat`, `PanelMain` | 패널 | -| Button | `Btn` | `BtnSend`, `BtnClose` | 버튼 | -| Text | `Txt` | `TxtMessage`, `TxtTitle` | 텍스트 | -| Image | `Img` | `ImgAvatar`, `ImgBackground` | 이미지 | -| InputField | `Input` | `InputMessage`, `InputName` | 입력 필드 | -| ScrollView | `Scroll` | `ScrollChat`, `ScrollList` | 스크롤 뷰 | -| Toggle | `Toggle` | `ToggleSound`, `ToggleMusic` | 토글 | -| Slider | `Slider` | `SliderVolume`, `SliderProgress` | 슬라이더 | -| Dropdown | `Dropdown` | `DropdownLanguage` | 드롭다운 | - ---- - -## 프로젝트별 특화 규칙 - -### Domain 클래스 네이밍 -``` -Domain/Character/ -├── Script/ -│ ├── CharacterController.cs # 캐릭터 제어 -│ ├── CharacterAnimation.cs # 애니메이션 관리 -│ └── CharacterState.cs # 상태 관리 -├── View/ -│ ├── CharacterView.cs # 뷰 로직 -│ └── CharacterUI.cs # UI 관련 -└── Model/ - └── CharacterData.cs # 데이터 모델 -``` - -### Infrastructure 클래스 네이밍 -``` -Infrastructure/Network/ -├── Services/ -│ ├── ChatApiService.cs # API 서비스 -│ └── WebSocketService.cs # 웹소켓 서비스 -├── DTOs/ -│ ├── ChatRequest.cs # 요청 DTO -│ └── ChatResponse.cs # 응답 DTO -└── Handlers/ - └── MessageHandler.cs # 메시지 처리 -``` - -### Core 클래스 네이밍 -``` -Core/ -├── Audio/ -│ ├── AudioManager.cs # 오디오 매니저 -│ └── VoicePlayer.cs # 음성 재생 -├── Input/ -│ ├── InputManager.cs # 입력 매니저 -│ └── TouchHandler.cs # 터치 처리 -└── Utils/ - ├── JsonHelper.cs # JSON 유틸 - └── TimeHelper.cs # 시간 유틸 -``` - ---- - -## 코딩 스타일 가이드 - -### 메서드 구조 -```csharp -public class ChatManager : MonoBehaviour -{ - // 1. SerializeField (Inspector 노출) - [SerializeField] private ChatUI _chatUI; - [SerializeField] private AudioManager _audioManager; - - // 2. private 필드 - private string _sessionId; - private bool _isInitialized; - - // 3. public 프로퍼티 - public bool IsConnected { get; private set; } - - // 4. Unity 생명주기 메서드 - private void Awake() - { - InitializeComponents(); - } - - private void Start() - { - InitializeChat(); - } - - // 5. public 메서드 - public void SendMessage(string message) - { - ValidateInput(message); - ProcessMessage(message); - } - - // 6. private 메서드 - private void InitializeComponents() - { - // 구현 - } - - // 7. 이벤트 핸들러 - private void OnMessageReceived(ChatResponse response) - { - // 구현 - } -} -``` - -### 네임스페이스 규칙 -```csharp -namespace ProjectVG.Domain.Chat -{ - public class ChatManager { } -} - -namespace ProjectVG.Infrastructure.Network -{ - public class ChatApiService { } -} - -namespace ProjectVG.Core.Audio -{ - public class AudioManager { } -} -``` \ No newline at end of file diff --git a/Assets/Core/Attributes.meta b/Assets/Docs/diagrams.meta similarity index 77% rename from Assets/Core/Attributes.meta rename to Assets/Docs/diagrams.meta index d84cb55..5613773 100644 --- a/Assets/Core/Attributes.meta +++ b/Assets/Docs/diagrams.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 9c42baf40e460a24baa55ced9fdb5b7a +guid: fcfec176b97236845967e6d6d0f6282d folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/Docs/diagrams/live2d_architecture.mmd b/Assets/Docs/diagrams/live2d_architecture.mmd new file mode 100644 index 0000000..b5dddca --- /dev/null +++ b/Assets/Docs/diagrams/live2d_architecture.mmd @@ -0,0 +1,10 @@ +graph TD + CM[ChatManager] --> LCM[Live2DCharacterManager] + LCM --> LMM[Live2DModelManager] + LCM --> LPC[Live2DParameterController] + LCM --> EMO[EmotionController] + LCM --> ACT[ActionController] + LMM -->|Active Model| L2D[Live2D Components] + LPC -->|Parameters/Expressions| L2D + + diff --git a/Assets/Docs/diagrams/live2d_architecture.mmd.meta b/Assets/Docs/diagrams/live2d_architecture.mmd.meta new file mode 100644 index 0000000..df84f53 --- /dev/null +++ b/Assets/Docs/diagrams/live2d_architecture.mmd.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 42e5237b6af6d3a44b03d71fe6499a05 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Docs/diagrams/live2d_architecture.png b/Assets/Docs/diagrams/live2d_architecture.png new file mode 100644 index 0000000..1268872 Binary files /dev/null and b/Assets/Docs/diagrams/live2d_architecture.png differ diff --git a/Assets/Docs/diagrams/live2d_architecture.png.meta b/Assets/Docs/diagrams/live2d_architecture.png.meta new file mode 100644 index 0000000..45ccf78 --- /dev/null +++ b/Assets/Docs/diagrams/live2d_architecture.png.meta @@ -0,0 +1,156 @@ +fileFormatVersion: 2 +guid: 162591fdb3798c448af2334846875d92 +TextureImporter: + internalIDToNameTable: + - first: + 213: 6587900086707653923 + second: live2d_architecture_0 + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 2 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: + - serializedVersion: 2 + name: live2d_architecture_0 + rect: + serializedVersion: 2 + x: 0 + y: 0 + width: 784 + height: 319 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: 32d89f3893cec6b50800000000000000 + internalID: 6587900086707653923 + vertices: [] + indices: + edges: [] + weights: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: + live2d_architecture_0: 6587900086707653923 + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Docs/diagrams/live2d_architecture.svg b/Assets/Docs/diagrams/live2d_architecture.svg new file mode 100644 index 0000000..7e83756 --- /dev/null +++ b/Assets/Docs/diagrams/live2d_architecture.svg @@ -0,0 +1 @@ +

Active Model

Parameters/Expressions

ChatManager

Live2DCharacterManager

Live2DModelManager

Live2DParameterController

EmotionController

ActionController

Live2D Components

\ No newline at end of file diff --git a/Assets/Docs/diagrams/live2d_architecture.svg.meta b/Assets/Docs/diagrams/live2d_architecture.svg.meta new file mode 100644 index 0000000..4e5d73a --- /dev/null +++ b/Assets/Docs/diagrams/live2d_architecture.svg.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7c9a7be09b8901c419630f98fa918250 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Docs/diagrams/live2d_sequence.mmd b/Assets/Docs/diagrams/live2d_sequence.mmd new file mode 100644 index 0000000..b98fa18 --- /dev/null +++ b/Assets/Docs/diagrams/live2d_sequence.mmd @@ -0,0 +1,21 @@ +sequenceDiagram +participant U as User +participant CM as ChatManager +participant API as ApiService +participant L2D as Live2DCharacterManager +participant EMO as EmotionController +participant ACT as ActionController +participant VO as VoiceManager + +U->>CM: 사용자 입력 전송 +CM->>API: 요청(텍스트, 캐릭터/유저 ID) +API-->>CM: 응답(text, audio, metadata{emotion, action}) +CM->>CM: ChatMessage 변환 및 큐 처리 +CM->>VO: 음성 재생 +CM->>L2D: ApplyReaction(metadata) +L2D->>EMO: SetEmotion(emotion, intensity, duration) +L2D->>ACT: TriggerAction(action) +VO-->>L2D: OnVoiceFinished() +L2D->>EMO: Release/Restore 상태 갱신 + + diff --git a/Assets/Docs/diagrams/live2d_sequence.mmd.meta b/Assets/Docs/diagrams/live2d_sequence.mmd.meta new file mode 100644 index 0000000..559fe93 --- /dev/null +++ b/Assets/Docs/diagrams/live2d_sequence.mmd.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f0cb5cd19b9e0b84a81fb773e73e4f55 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Docs/diagrams/live2d_sequence.png b/Assets/Docs/diagrams/live2d_sequence.png new file mode 100644 index 0000000..970dd95 Binary files /dev/null and b/Assets/Docs/diagrams/live2d_sequence.png differ diff --git a/Assets/TutorialInfo/Icons/Mobile 2D.png.meta b/Assets/Docs/diagrams/live2d_sequence.png.meta similarity index 66% rename from Assets/TutorialInfo/Icons/Mobile 2D.png.meta rename to Assets/Docs/diagrams/live2d_sequence.png.meta index 89301f3..ddb3569 100644 --- a/Assets/TutorialInfo/Icons/Mobile 2D.png.meta +++ b/Assets/Docs/diagrams/live2d_sequence.png.meta @@ -1,12 +1,15 @@ fileFormatVersion: 2 -guid: eda43ba821d75d046a45209bde150047 +guid: 210fd052dfa6bd3489caf78a21bf6905 TextureImporter: - internalIDToNameTable: [] + internalIDToNameTable: + - first: + 213: 4898076768880400070 + second: live2d_sequence_0 externalObjects: {} - serializedVersion: 11 + serializedVersion: 13 mipmaps: mipMapMode: 0 - enableMipMap: 1 + enableMipMap: 0 sRGBTexture: 1 linearTexture: 0 fadeOut: 0 @@ -20,11 +23,12 @@ TextureImporter: externalNormalMap: 0 heightScale: 0.25 normalMapFilter: 0 + flipGreenChannel: 0 isReadable: 0 streamingMipmaps: 0 streamingMipmapsPriority: 0 vTOnly: 0 - ignoreMasterTextureLimit: 0 + ignoreMipmapLimit: 0 grayScaleToAlpha: 0 generateCubemap: 6 cubemapConvolution: 0 @@ -42,7 +46,7 @@ TextureImporter: nPOTScale: 0 lightmap: 0 compressionQuality: 50 - spriteMode: 1 + spriteMode: 2 spriteExtrude: 1 spriteMeshType: 1 alignment: 0 @@ -51,9 +55,9 @@ TextureImporter: spriteBorder: {x: 0, y: 0, z: 0, w: 0} spriteGenerateFallbackPhysicsShape: 1 alphaUsage: 1 - alphaIsTransparency: 0 + alphaIsTransparency: 1 spriteTessellationDetail: -1 - textureType: 0 + textureType: 8 textureShape: 1 singleChannelComponent: 0 flipbookRows: 1 @@ -63,8 +67,10 @@ TextureImporter: textureFormatSet: 0 ignorePngGamma: 0 applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 platformSettings: - - serializedVersion: 3 + - serializedVersion: 4 buildTarget: DefaultTexturePlatform maxTextureSize: 2048 resizeAlgorithm: 0 @@ -74,9 +80,10 @@ TextureImporter: crunchedCompression: 0 allowsAlphaSplitting: 0 overridden: 0 + ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 - - serializedVersion: 3 + - serializedVersion: 4 buildTarget: Standalone maxTextureSize: 2048 resizeAlgorithm: 0 @@ -86,10 +93,11 @@ TextureImporter: crunchedCompression: 0 allowsAlphaSplitting: 0 overridden: 0 + ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 - - serializedVersion: 3 - buildTarget: Server + - serializedVersion: 4 + buildTarget: Android maxTextureSize: 2048 resizeAlgorithm: 0 textureFormat: -1 @@ -98,25 +106,51 @@ TextureImporter: crunchedCompression: 0 allowsAlphaSplitting: 0 overridden: 0 + ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 spriteSheet: serializedVersion: 2 - sprites: [] + sprites: + - serializedVersion: 2 + name: live2d_sequence_0 + rect: + serializedVersion: 2 + x: 0 + y: 0 + width: 784 + height: 286 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: 6c695b3ced679f340800000000000000 + internalID: 4898076768880400070 + vertices: [] + indices: + edges: [] + weights: [] outline: [] + customData: physicsShape: [] bones: [] - spriteID: 5e97eb03825dee720800000000000000 + spriteID: internalID: 0 vertices: [] indices: edges: [] weights: [] secondaryTextures: [] - nameFileIdTable: {} - spritePackingTag: + spriteCustomMetadata: + entries: [] + nameFileIdTable: + live2d_sequence_0: 4898076768880400070 + mipmapLimitGroupName: pSDRemoveMatte: 0 - pSDShowRemoveMatteOption: 0 userData: assetBundleName: assetBundleVariant: diff --git a/Assets/Docs/diagrams/live2d_sequence.svg b/Assets/Docs/diagrams/live2d_sequence.svg new file mode 100644 index 0000000..e0aef9e --- /dev/null +++ b/Assets/Docs/diagrams/live2d_sequence.svg @@ -0,0 +1 @@ +VoiceManagerActionControllerEmotionControllerLive2DCharacterManagerApiServiceChatManagerUserVoiceManagerActionControllerEmotionControllerLive2DCharacterManagerApiServiceChatManagerUser사용자 입력 전송요청(텍스트, 캐릭터/유저 ID)응답(text, audio, metadata{emotion, action})ChatMessage 변환 및 큐 처리음성 재생ApplyReaction(metadata)SetEmotion(emotion, intensity, duration)TriggerAction(action)OnVoiceFinished()Release/Restore 상태 갱신 \ No newline at end of file diff --git a/Assets/Docs/diagrams/live2d_sequence.svg.meta b/Assets/Docs/diagrams/live2d_sequence.svg.meta new file mode 100644 index 0000000..3f840c0 --- /dev/null +++ b/Assets/Docs/diagrams/live2d_sequence.svg.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1c2e02197b6cd3547a0ecf2732bda685 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Docs/diagrams/live2d_sequence@2x.png b/Assets/Docs/diagrams/live2d_sequence@2x.png new file mode 100644 index 0000000..aff2e7c Binary files /dev/null and b/Assets/Docs/diagrams/live2d_sequence@2x.png differ diff --git a/Assets/TutorialInfo/Icons/Help_Icon.png.meta b/Assets/Docs/diagrams/live2d_sequence@2x.png.meta similarity index 56% rename from Assets/TutorialInfo/Icons/Help_Icon.png.meta rename to Assets/Docs/diagrams/live2d_sequence@2x.png.meta index 86cc13a..85fa2c4 100644 --- a/Assets/TutorialInfo/Icons/Help_Icon.png.meta +++ b/Assets/Docs/diagrams/live2d_sequence@2x.png.meta @@ -1,9 +1,12 @@ fileFormatVersion: 2 -guid: 9266273b8f123004195741f969177dda +guid: 0d997b361bec8304ea565d0b926c882e TextureImporter: - fileIDToRecycleName: {} + internalIDToNameTable: + - first: + 213: 5675336534635650432 + second: live2d_sequence@2x_0 externalObjects: {} - serializedVersion: 5 + serializedVersion: 13 mipmaps: mipMapMode: 0 enableMipMap: 0 @@ -20,7 +23,12 @@ TextureImporter: externalNormalMap: 0 heightScale: 0.25 normalMapFilter: 0 + flipGreenChannel: 0 isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 grayScaleToAlpha: 0 generateCubemap: 6 cubemapConvolution: 0 @@ -29,16 +37,16 @@ TextureImporter: maxTextureSize: 2048 textureSettings: serializedVersion: 2 - filterMode: -1 + filterMode: 1 aniso: 1 - mipBias: -1 + mipBias: 0 wrapU: 1 wrapV: 1 - wrapW: -1 + wrapW: 1 nPOTScale: 0 lightmap: 0 compressionQuality: 50 - spriteMode: 0 + spriteMode: 2 spriteExtrude: 1 spriteMeshType: 1 alignment: 0 @@ -49,16 +57,22 @@ TextureImporter: alphaUsage: 1 alphaIsTransparency: 1 spriteTessellationDetail: -1 - textureType: 2 + textureType: 8 textureShape: 1 singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 maxTextureSizeSet: 0 compressionQualitySet: 0 textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 platformSettings: - - serializedVersion: 2 + - serializedVersion: 4 buildTarget: DefaultTexturePlatform - maxTextureSize: 8192 + maxTextureSize: 2048 resizeAlgorithm: 0 textureFormat: -1 textureCompression: 1 @@ -66,10 +80,12 @@ TextureImporter: crunchedCompression: 0 allowsAlphaSplitting: 0 overridden: 0 + ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 - - serializedVersion: 2 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 buildTarget: Standalone - maxTextureSize: 8192 + maxTextureSize: 2048 resizeAlgorithm: 0 textureFormat: -1 textureCompression: 1 @@ -77,32 +93,12 @@ TextureImporter: crunchedCompression: 0 allowsAlphaSplitting: 0 overridden: 0 + ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 - - serializedVersion: 2 - buildTarget: iPhone - maxTextureSize: 8192 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - androidETC2FallbackOverride: 0 - - serializedVersion: 2 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 buildTarget: Android - maxTextureSize: 8192 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - androidETC2FallbackOverride: 0 - - serializedVersion: 2 - buildTarget: Windows Store Apps - maxTextureSize: 8192 + maxTextureSize: 2048 resizeAlgorithm: 0 textureFormat: -1 textureCompression: 1 @@ -110,19 +106,51 @@ TextureImporter: crunchedCompression: 0 allowsAlphaSplitting: 0 overridden: 0 + ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 spriteSheet: serializedVersion: 2 - sprites: [] + sprites: + - serializedVersion: 2 + name: live2d_sequence@2x_0 + rect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1568 + height: 572 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: 08d11cf4288d2ce40800000000000000 + internalID: 5675336534635650432 + vertices: [] + indices: + edges: [] + weights: [] outline: [] + customData: physicsShape: [] bones: [] spriteID: + internalID: 0 vertices: [] indices: edges: [] weights: [] - spritePackingTag: + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: + live2d_sequence@2x_0: 5675336534635650432 + mipmapLimitGroupName: + pSDRemoveMatte: 0 userData: assetBundleName: assetBundleVariant: diff --git a/Assets/Core/DI.meta b/Assets/Domain/Character/Script/Component.meta similarity index 77% rename from Assets/Core/DI.meta rename to Assets/Domain/Character/Script/Component.meta index 6686f9d..06915c6 100644 --- a/Assets/Core/DI.meta +++ b/Assets/Domain/Character/Script/Component.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: a528030241c95284cb4188eeb31d5685 +guid: 7fb0bddb7904cff4d85d4afa552339be folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/Domain/Character/Script/CubismHitHandler.cs b/Assets/Domain/Character/Script/Component/CubismHitHandler.cs similarity index 81% rename from Assets/Domain/Character/Script/CubismHitHandler.cs rename to Assets/Domain/Character/Script/Component/CubismHitHandler.cs index fe9ece9..8b066ba 100644 --- a/Assets/Domain/Character/Script/CubismHitHandler.cs +++ b/Assets/Domain/Character/Script/Component/CubismHitHandler.cs @@ -13,11 +13,24 @@ public void Initialize() { _raycaster = GetComponent(); _expressionController = GetComponent(); - ScreenTapManager.Instance.SetRaycaster(_raycaster); + + 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) diff --git a/Assets/Domain/Character/Script/CubismHitHandler.cs.meta b/Assets/Domain/Character/Script/Component/CubismHitHandler.cs.meta similarity index 100% rename from Assets/Domain/Character/Script/CubismHitHandler.cs.meta rename to Assets/Domain/Character/Script/Component/CubismHitHandler.cs.meta diff --git a/Assets/Domain/Character/Script/CubismLookTarget.cs b/Assets/Domain/Character/Script/Component/CubismLookTarget.cs similarity index 100% rename from Assets/Domain/Character/Script/CubismLookTarget.cs rename to Assets/Domain/Character/Script/Component/CubismLookTarget.cs diff --git a/Assets/Domain/Character/Script/CubismLookTarget.cs.meta b/Assets/Domain/Character/Script/Component/CubismLookTarget.cs.meta similarity index 100% rename from Assets/Domain/Character/Script/CubismLookTarget.cs.meta rename to Assets/Domain/Character/Script/Component/CubismLookTarget.cs.meta diff --git a/Assets/Domain/Character/Script/Config.meta b/Assets/Domain/Character/Script/Config.meta new file mode 100644 index 0000000..ea4a79f --- /dev/null +++ b/Assets/Domain/Character/Script/Config.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9b920a8a8e1a9c146bd2843d87eb15be +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Domain/Character/Script/Config/Live2DModelConfig.cs b/Assets/Domain/Character/Script/Config/Live2DModelConfig.cs new file mode 100644 index 0000000..4c1c54b --- /dev/null +++ b/Assets/Domain/Character/Script/Config/Live2DModelConfig.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace ProjectVG.Domain.Character.Live2D.Model +{ + [CreateAssetMenu(fileName = "Live2DModelConfig", menuName = "ProjectVG/Live2D/ModelConfig", order = 100)] + public class Live2DModelConfig : ScriptableObject + { + [Serializable] + public class EmotionMapping + { + [Header("감정 설정")] + [Tooltip("감정 키입니다. 서버에서 전송되는 감정 값과 일치해야 합니다.")] + public string emotionKey; + + [Tooltip("Live2D Expression 이름입니다. 모델의 표정 파일명과 일치해야 합니다.")] + public string expressionName; + + [Header("기본값")] + [Tooltip("감정의 기본 강도입니다. (0.0 ~ 1.0)")] + [Range(0f, 1f)] + public float defaultIntensity = 0.5f; + + [Tooltip("감정의 기본 지속시간입니다. (밀리초)")] + [Range(500, 10000)] + public int defaultDurationMs = 2000; + } + + [Serializable] + public class ActionMapping + { + [Header("행동 설정")] + [Tooltip("행동 키입니다. 서버에서 전송되는 행동 값과 일치해야 합니다.")] + public string actionKey; + + [Tooltip("Live2D 모션 그룹 이름입니다.")] + public string motionGroup; + + [Tooltip("Live2D 모션 파일 이름입니다.")] + public string motionName; + } + + [Header("[ 캐릭터 기본정보 ]")] + [Space(5)] + [Tooltip("캐릭터 고유 ID")] + [SerializeField] private string characterId; + + [Tooltip("캐릭터 이름")] + [SerializeField] private string characterName; + + [Tooltip("Live2D 캐릭터 프리팹 (Cubism 모델 포함)")] + [SerializeField] private GameObject characterPrefab; + + + [Tooltip("캐릭터 썸네일 이미지")] + [SerializeField] private Texture2D thumbnail; + + [Tooltip("캐릭터 설명")] + [SerializeField, Multiline] private string characterDescription; + + [Space(5)] + [Header("────────────────────────────────────────")] + [Header("[ 행동/표정 ]")] + [Space(2)] + [Tooltip("감정 → Live2D Expression 매핑")] + [SerializeField] private List emotionMappings = new List(); + + [Tooltip("행동 → Live2D Motion 매핑")] + [SerializeField] private List actionMappings = new List(); + + [Space(5)] + [Header("────────────────────────────────────────")] + [Header("[ 시선 설정 ]")] + [Space(2)] + + [Tooltip("시선 추적 사용 여부")] + [SerializeField] private bool isLockAtActive = true; + + [Tooltip("시선 민감도 (값이 클수록 회전이 커짐)")] + [Range(0f, 30f)] + [SerializeField] private float lookSensitivity = 1.0f; + + [Tooltip("시선 반응 속도 (값이 작을수록 빠름)")] + [Range(0f, 5f)] + [SerializeField] private float lockAtDamping = 0.0f; + + [Space(5)] + [Header("────────────────────────────────────────")] + [Header("[ 립싱크 설정 ]")] + [Space(2)] + + [Tooltip("음량 배수 (1 = 기본)")] + [Range(1f, 10f)] + [SerializeField] private float gain = 1f; + + [Tooltip("입 움직임 부드러움 (값이 클수록 부드럽지만 부하 증가)")] + [Range(0f, 1f)] + [SerializeField] private float smoothing = 1f; + + // 캐릭터 기본정보 + public string CharacterId => characterId; + public string CharacterName => characterName; + public GameObject CharacterPrefab => characterPrefab; + public Texture2D Thumbnail => thumbnail; + public string CharacterDescription => characterDescription; + + public string ModelId => characterId; + public string ModelName => characterName; + public GameObject ModelPrefab => characterPrefab; + public string ModelDescription => characterDescription; + + // 행동/표정 + public List EmotionMappings => emotionMappings; + public List ActionMappings => actionMappings; + + // 세부 설정 + public bool IsLockAtActive => isLockAtActive; + public float LookSensitivity => lookSensitivity; + public float LockAtDamping => lockAtDamping; + public float Gain => gain; + public float Smoothing => smoothing; + } +} + + diff --git a/Assets/Domain/Character/Script/Config/Live2DModelConfig.cs.meta b/Assets/Domain/Character/Script/Config/Live2DModelConfig.cs.meta new file mode 100644 index 0000000..998fcf6 --- /dev/null +++ b/Assets/Domain/Character/Script/Config/Live2DModelConfig.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f22d39f6e7c71524d911cb4b658d7fd9 \ No newline at end of file diff --git a/Assets/Domain/Character/Script/Config/Live2DModelRegistry.cs b/Assets/Domain/Character/Script/Config/Live2DModelRegistry.cs new file mode 100644 index 0000000..3569b94 --- /dev/null +++ b/Assets/Domain/Character/Script/Config/Live2DModelRegistry.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace ProjectVG.Domain.Character.Live2D.Model +{ + [CreateAssetMenu(fileName = "Live2DModelRegistry", menuName = "ProjectVG/Live2D/ModelRegistry", order = 101)] + public class Live2DModelRegistry : ScriptableObject + { + [System.Serializable] + public class Entry + { + public string characterId; + public Live2DModelConfig characterConfig; + } + + [SerializeField] private List _entries = new List(); + + public bool TryGetConfig(string characterId, out Live2DModelConfig config) + { + foreach (var e in _entries) + { + if (e != null && e.characterId == characterId) + { + config = e.characterConfig; + return config != null; + } + } + config = null; + return false; + } + } +} + + diff --git a/Assets/Domain/Character/Script/Config/Live2DModelRegistry.cs.meta b/Assets/Domain/Character/Script/Config/Live2DModelRegistry.cs.meta new file mode 100644 index 0000000..bd51e18 --- /dev/null +++ b/Assets/Domain/Character/Script/Config/Live2DModelRegistry.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a663c59d4d7aa174dbef2ec581104a3d \ No newline at end of file diff --git a/Assets/Domain/Character/Script/Controller.meta b/Assets/Domain/Character/Script/Controller.meta new file mode 100644 index 0000000..830fe1c --- /dev/null +++ b/Assets/Domain/Character/Script/Controller.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 37c695efe237c2a47888f4ce37fa681e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Domain/Character/Script/Controller/ActionController.cs b/Assets/Domain/Character/Script/Controller/ActionController.cs new file mode 100644 index 0000000..f87b4c2 --- /dev/null +++ b/Assets/Domain/Character/Script/Controller/ActionController.cs @@ -0,0 +1,20 @@ +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 new file mode 100644 index 0000000..1cc9337 --- /dev/null +++ b/Assets/Domain/Character/Script/Controller/ActionController.cs.meta @@ -0,0 +1,2 @@ +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 new file mode 100644 index 0000000..4816e2d --- /dev/null +++ b/Assets/Domain/Character/Script/Controller/DTOs.cs @@ -0,0 +1,10 @@ +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 new file mode 100644 index 0000000..266eeca --- /dev/null +++ b/Assets/Domain/Character/Script/Controller/DTOs.cs.meta @@ -0,0 +1,2 @@ +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 new file mode 100644 index 0000000..16ef56a --- /dev/null +++ b/Assets/Domain/Character/Script/Controller/EmotionController.cs @@ -0,0 +1,24 @@ +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 new file mode 100644 index 0000000..af39ad2 --- /dev/null +++ b/Assets/Domain/Character/Script/Controller/EmotionController.cs.meta @@ -0,0 +1,2 @@ +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 new file mode 100644 index 0000000..df24f71 --- /dev/null +++ b/Assets/Domain/Character/Script/Controller/IActionController.cs @@ -0,0 +1,13 @@ +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 new file mode 100644 index 0000000..a8816b8 --- /dev/null +++ b/Assets/Domain/Character/Script/Controller/IActionController.cs.meta @@ -0,0 +1,2 @@ +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 new file mode 100644 index 0000000..3232beb --- /dev/null +++ b/Assets/Domain/Character/Script/Controller/IEmotionController.cs @@ -0,0 +1,14 @@ +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 new file mode 100644 index 0000000..b4e8673 --- /dev/null +++ b/Assets/Domain/Character/Script/Controller/IEmotionController.cs.meta @@ -0,0 +1,2 @@ +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 new file mode 100644 index 0000000..6420840 --- /dev/null +++ b/Assets/Domain/Character/Script/Controller/ILive2DModelApplier.cs @@ -0,0 +1,12 @@ +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 new file mode 100644 index 0000000..29399d9 --- /dev/null +++ b/Assets/Domain/Character/Script/Controller/ILive2DModelApplier.cs.meta @@ -0,0 +1,2 @@ +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 new file mode 100644 index 0000000..43dd699 --- /dev/null +++ b/Assets/Domain/Character/Script/Controller/Live2DModelApplier.cs @@ -0,0 +1,13 @@ +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 new file mode 100644 index 0000000..132d5ba --- /dev/null +++ b/Assets/Domain/Character/Script/Controller/Live2DModelApplier.cs.meta @@ -0,0 +1,2 @@ +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 new file mode 100644 index 0000000..a0877c1 --- /dev/null +++ b/Assets/Domain/Character/Script/Controller/Live2DParameterController.cs @@ -0,0 +1,24 @@ +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 new file mode 100644 index 0000000..6e1f953 --- /dev/null +++ b/Assets/Domain/Character/Script/Controller/Live2DParameterController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2c84ef8b52117614e806503794e80e87 \ No newline at end of file diff --git a/Assets/Domain/Character/Script/Manager.meta b/Assets/Domain/Character/Script/Manager.meta new file mode 100644 index 0000000..d2debce --- /dev/null +++ b/Assets/Domain/Character/Script/Manager.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a07cad638f33a9e448551011b2f085bf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Domain/Character/Script/Manager/ILive2DCharacterManager.cs b/Assets/Domain/Character/Script/Manager/ILive2DCharacterManager.cs new file mode 100644 index 0000000..9e3f518 --- /dev/null +++ b/Assets/Domain/Character/Script/Manager/ILive2DCharacterManager.cs @@ -0,0 +1,17 @@ +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 new file mode 100644 index 0000000..3fd378f --- /dev/null +++ b/Assets/Domain/Character/Script/Manager/ILive2DCharacterManager.cs.meta @@ -0,0 +1,2 @@ +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 new file mode 100644 index 0000000..f11edc4 --- /dev/null +++ b/Assets/Domain/Character/Script/Manager/Live2DCharacterManager.cs @@ -0,0 +1,28 @@ +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 new file mode 100644 index 0000000..c06ba09 --- /dev/null +++ b/Assets/Domain/Character/Script/Manager/Live2DCharacterManager.cs.meta @@ -0,0 +1,2 @@ +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 new file mode 100644 index 0000000..8e390b0 --- /dev/null +++ b/Assets/Domain/Character/Script/Manager/Live2DModelManager.cs @@ -0,0 +1,377 @@ +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 new file mode 100644 index 0000000..58c5d74 --- /dev/null +++ b/Assets/Domain/Character/Script/Manager/Live2DModelManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: bbd7bfb36017db743b3b7cab74183970 \ No newline at end of file diff --git a/Assets/Domain/Character/Script/Test.meta b/Assets/Domain/Character/Script/Test.meta new file mode 100644 index 0000000..793641e --- /dev/null +++ b/Assets/Domain/Character/Script/Test.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c762a71a10e06714a96c237780ce3100 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Domain/Character/Script/Test/Live2DModelTest.cs b/Assets/Domain/Character/Script/Test/Live2DModelTest.cs new file mode 100644 index 0000000..94be95e --- /dev/null +++ b/Assets/Domain/Character/Script/Test/Live2DModelTest.cs @@ -0,0 +1,180 @@ +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 new file mode 100644 index 0000000..d21d899 --- /dev/null +++ b/Assets/Domain/Character/Script/Test/Live2DModelTest.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7fb1442983837c140abae42a85733fe6 \ No newline at end of file diff --git a/Assets/Domain/Character/Script/TestVoice.cs b/Assets/Domain/Character/Script/Test/TestVoice.cs similarity index 100% rename from Assets/Domain/Character/Script/TestVoice.cs rename to Assets/Domain/Character/Script/Test/TestVoice.cs diff --git a/Assets/Domain/Character/Script/TestVoice.cs.meta b/Assets/Domain/Character/Script/Test/TestVoice.cs.meta similarity index 100% rename from Assets/Domain/Character/Script/TestVoice.cs.meta rename to Assets/Domain/Character/Script/Test/TestVoice.cs.meta diff --git a/Assets/Domain/Chat/Service/ChatBubbleManager.cs b/Assets/Domain/Chat/Service/ChatBubbleManager.cs deleted file mode 100644 index a5bdded..0000000 --- a/Assets/Domain/Chat/Service/ChatBubbleManager.cs +++ /dev/null @@ -1,253 +0,0 @@ -#nullable enable -using System; -using System.Collections.Generic; -using UnityEngine; -using UnityEngine.UI; -using ProjectVG.Domain.Chat.Model; -using ProjectVG.Domain.Chat.View; - -namespace ProjectVG.Domain.Chat.Service -{ - public class ChatBubbleManager : MonoBehaviour - { - [Header("UI Components")] - [SerializeField] private ScrollRect? _scrollRect; - [SerializeField] private GridLayoutGroup? _gridLayoutGroup; - [SerializeField] private ContentSizeFitter? _contentSizeFitter; - - [Header("Bubble Settings")] - [SerializeField] private GameObject? _chatBubblePrefab; - [SerializeField] private Transform? _bubbleContainer; - - [Header("Queue Animation Settings")] - [SerializeField] private bool _enableQueueAnimation = true; - [SerializeField] private float _queueAnimationDelay = 0.1f; - - [Header("Performance Settings")] - [SerializeField] private int _maxBubbles = 20; - [SerializeField] private bool _autoCleanup = true; - [SerializeField] private int _cleanupThreshold = 15; - - private List _activeBubbles = new List(); - - public int ActiveBubbleCount => _activeBubbles.Count; - - public event Action? OnBubbleCreated; - public event Action? OnBubbleDestroyed; - public event Action? OnAllBubblesCleared; - - #region Unity Lifecycle - - private void Awake() - { - Initialize(); - } - - private void OnDestroy() - { - ClearAllBubbles(); - } - - #endregion - - #region Public Methods - - public void CreateBubble(Actor actor, string text, float displayTime = -1f) - { - if (_chatBubblePrefab == null || _bubbleContainer == null) - { - Debug.LogError("[ChatBubbleManager] ChatBubblePrefab 또는 BubbleContainer가 설정되지 않았습니다!"); - return; - } - - try - { - GameObject? bubbleObject = Instantiate(_chatBubblePrefab, _bubbleContainer); - ChatBubbleUI? bubbleUI = bubbleObject?.GetComponent(); - - if (bubbleUI == null) - { - Debug.LogError("[ChatBubbleManager] ChatBubbleUI 컴포넌트를 찾을 수 없습니다!"); - if (bubbleObject != null) - Destroy(bubbleObject); - return; - } - - SetupCanvasGroup(bubbleObject); - - if (_activeBubbles.Count >= _maxBubbles) - { - Debug.LogWarning($"[ChatBubbleManager] 최대 버블 수({_maxBubbles})에 도달했습니다. 가장 오래된 버블을 제거합니다."); - RemoveOldestBubble(); - } - - if (_autoCleanup && _activeBubbles.Count >= _cleanupThreshold) - { - CleanupOldBubbles(); - } - - if (_enableQueueAnimation && _activeBubbles.Count > 0) - { - StartQueueAnimationForExistingBubbles(); - } - - bubbleUI.Initialize(actor, text, displayTime, this); - - bubbleUI.OnBubbleDestroyed += OnBubbleDestroyed; - bubbleUI.OnToastAnimationComplete += OnBubbleToastAnimationComplete; - - _activeBubbles.Add(bubbleUI); - - OnBubbleCreated?.Invoke(bubbleUI); - } - catch (Exception ex) - { - Debug.LogError($"[ChatBubbleManager] 버블 생성 실패: {ex.Message}"); - } - } - - public void RemoveBubble(ChatBubbleUI? bubble) - { - if (bubble == null) return; - - try - { - bubble.OnBubbleDestroyed -= OnBubbleDestroyed; - bubble.OnToastAnimationComplete -= OnBubbleToastAnimationComplete; - - _activeBubbles.Remove(bubble); - - OnBubbleDestroyed?.Invoke(bubble); - } - catch (Exception ex) - { - Debug.LogError($"[ChatBubbleManager] 버블 제거 실패: {ex.Message}"); - } - } - - public void ClearAllBubbles() - { - try - { - foreach (var bubble in _activeBubbles.ToArray()) - { - if (bubble != null) - { - bubble.OnBubbleDestroyed -= OnBubbleDestroyed; - bubble.OnToastAnimationComplete -= OnBubbleToastAnimationComplete; - Destroy(bubble.gameObject); - } - } - - _activeBubbles.Clear(); - - OnAllBubblesCleared?.Invoke(); - } - catch (Exception ex) - { - Debug.LogError($"[ChatBubbleManager] 모든 버블 제거 실패: {ex.Message}"); - } - } - - #endregion - - #region Private Methods - - private void Initialize() - { - if (_bubbleContainer == null) - { - Debug.LogError("[ChatBubbleManager] BubbleContainer가 설정되지 않았습니다! 인스펙터에서 설정해주세요."); - return; - } - - if (_chatBubblePrefab == null) - { - Debug.LogError("[ChatBubbleManager] ChatBubblePrefab이 설정되지 않았습니다!"); - return; - } - - Debug.Log("[ChatBubbleManager] 초기화 완료"); - } - - private void StartQueueAnimationForExistingBubbles() - { - Canvas.ForceUpdateCanvases(); - - for (int i = 0; i < _activeBubbles.Count; i++) - { - var bubble = _activeBubbles[i]; - if (bubble != null && bubble.IsToastAnimationComplete) - { - float delay = i * _queueAnimationDelay; - StartCoroutine(QueueAnimationWithDelay(bubble, delay)); - } - } - } - - private System.Collections.IEnumerator QueueAnimationWithDelay(ChatBubbleUI? bubble, float delay) - { - yield return new WaitForSeconds(delay); - - if (bubble != null) - { - bubble.StartQueueSlideAnimation(); - } - } - - private void OnBubbleToastAnimationComplete(ChatBubbleUI? bubble) - { - if (bubble == null) return; - - - if (_scrollRect != null) - { - Canvas.ForceUpdateCanvases(); - _scrollRect.verticalNormalizedPosition = 0f; - } - } - - private void RemoveOldestBubble() - { - if (_activeBubbles.Count > 0) - { - var oldestBubble = _activeBubbles[0]; - RemoveBubble(oldestBubble); - if (oldestBubble != null) - { - Destroy(oldestBubble.gameObject); - } - } - } - - private void CleanupOldBubbles() - { - int bubblesToRemove = _activeBubbles.Count - _cleanupThreshold; - for (int i = 0; i < bubblesToRemove && i < _activeBubbles.Count; i++) - { - var bubble = _activeBubbles[0]; - RemoveBubble(bubble); - if (bubble != null) - { - Destroy(bubble.gameObject); - } - } - } - - private void SetupCanvasGroup(GameObject? bubbleObject) - { - if (bubbleObject == null) return; - - CanvasGroup? canvasGroup = bubbleObject.GetComponent(); - if (canvasGroup == null) - { - canvasGroup = bubbleObject.AddComponent(); - Debug.Log($"[ChatBubbleManager] ChatBubble에 CanvasGroup이 자동으로 추가되었습니다: {bubbleObject.name}"); - } - - canvasGroup.alpha = 0f; - } - - #endregion - } -} \ No newline at end of file diff --git a/Assets/Domain/Chat/Service/ChatBubbleManager.cs.meta b/Assets/Domain/Chat/Service/ChatBubbleManager.cs.meta deleted file mode 100644 index 0a7b58b..0000000 --- a/Assets/Domain/Chat/Service/ChatBubbleManager.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: bebe894334d166c44b4b4e1c755d03ae \ No newline at end of file diff --git a/Assets/Domain/Chat/Service/ChatManager.cs b/Assets/Domain/Chat/Service/ChatManager.cs index 172f287..a018824 100644 --- a/Assets/Domain/Chat/Service/ChatManager.cs +++ b/Assets/Domain/Chat/Service/ChatManager.cs @@ -1,5 +1,6 @@ #nullable enable using System; +using System.Collections; using System.Collections.Generic; using UnityEngine; using Cysharp.Threading.Tasks; @@ -7,17 +8,16 @@ using ProjectVG.Domain.Chat.Model; using ProjectVG.Infrastructure.Network.WebSocket; using ProjectVG.Infrastructure.Network.Services; -using ProjectVG.Infrastructure.Network.DTOs.Chat; -using ProjectVG.Domain.Chat.Service; +using ProjectVG.Domain.Chat.View; + namespace ProjectVG.Domain.Chat.Service { - public class ChatManager : Singleton + public class ChatManager : MonoBehaviour { [Header("Components")] - [SerializeField] private WebSocketManager _webSocketManager; - [SerializeField] private VoiceManager _voiceManager; - [SerializeField] private ChatBubbleManager _chatBubbleManager; + [SerializeField] private ChatBubblePanel? _chatBubblePanel; + [Header("Chat Settings")] [SerializeField] private string _characterId = "44444444-4444-4444-4444-444444444444"; @@ -31,6 +31,9 @@ public class ChatManager : Singleton 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(); @@ -43,13 +46,53 @@ public class ChatManager : Singleton #region Unity Lifecycle - protected override void Awake() + private static ChatManager? _instance; + public static ChatManager Instance { - base.Awake(); + 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(); } @@ -59,11 +102,6 @@ private void OnDestroy() { _webSocketManager.OnChatMessageReceived -= HandleChatMessageReceived; } - - if (_voiceManager != null) - { - _voiceManager.OnVoiceFinished -= OnVoiceFinished; - } } #endregion @@ -77,25 +115,15 @@ public void Initialize() try { - if (_webSocketManager == null) - _webSocketManager = WebSocketManager.Instance; - - if (_voiceManager == null) - _voiceManager = VoiceManager.Instance; - - if (_chatBubbleManager == null) - _chatBubbleManager = FindObjectOfType(); + // 기본 매니저들 초기화 + _webSocketManager = WebSocketManager.Instance; + _audioManager = AudioManager.Instance; if (_webSocketManager != null) { _webSocketManager.OnChatMessageReceived += HandleChatMessageReceived; } - if (_voiceManager != null) - { - _voiceManager.OnVoiceFinished += OnVoiceFinished; - } - _isInitialized = true; _isConnected = true; Debug.Log("[ChatManager] 초기화 완료"); @@ -114,9 +142,9 @@ public async void SendUserMessage(string message) try { - if (_chatBubbleManager != null) + if (_chatBubblePanel != null) { - _chatBubbleManager.CreateBubble(Actor.User, message); + _chatBubblePanel.CreateBubble(Actor.User, message); } var chatService = ApiServiceManager.Instance.Chat; @@ -174,6 +202,10 @@ public void ClearMessageQueue() } } + + + + #endregion #region Private Methods @@ -224,14 +256,14 @@ private void ProcessMessageImmediately(ChatMessage chatMessage) OnChatMessageReceived?.Invoke(chatMessage); // 캐릭터 메시지를 버블로 표시 - if (_chatBubbleManager != null && !string.IsNullOrEmpty(chatMessage.Text)) + if (_chatBubblePanel != null && !string.IsNullOrEmpty(chatMessage.Text)) { - _chatBubbleManager.CreateBubble(Actor.Character, chatMessage.Text); + _chatBubblePanel.CreateBubble(Actor.Character, chatMessage.Text); } - if (chatMessage.VoiceData != null && _voiceManager != null) + if (chatMessage.VoiceData != null && _audioManager != null) { - _voiceManager.PlayVoice(chatMessage.VoiceData); + _audioManager.PlayVoice(chatMessage.VoiceData); } } catch (Exception ex) @@ -247,14 +279,14 @@ private async UniTask ProcessMessageImmediatelyAsync(ChatMessage chatMessage) { OnChatMessageReceived?.Invoke(chatMessage); - if (_chatBubbleManager != null && !string.IsNullOrEmpty(chatMessage.Text)) + if (_chatBubblePanel != null && !string.IsNullOrEmpty(chatMessage.Text)) { - _chatBubbleManager.CreateBubble(Actor.Character, chatMessage.Text); + _chatBubblePanel.CreateBubble(Actor.Character, chatMessage.Text); } - if (chatMessage.VoiceData != null && _voiceManager != null) + if (chatMessage.VoiceData != null && _audioManager != null) { - await _voiceManager.PlayVoiceAsync(chatMessage.VoiceData); + await _audioManager.PlayVoiceAsync(chatMessage.VoiceData); } } catch (Exception ex) @@ -281,9 +313,7 @@ private bool ValidateUserInput(string message) return true; } - private void OnVoiceFinished() - { - } + private void HandleChatMessageReceived(ChatMessage chatMessage) { diff --git a/Assets/Domain/Chat/View/ChatBubblePanel.cs b/Assets/Domain/Chat/View/ChatBubblePanel.cs new file mode 100644 index 0000000..66fc3af --- /dev/null +++ b/Assets/Domain/Chat/View/ChatBubblePanel.cs @@ -0,0 +1,255 @@ +#nullable enable +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.UI; +using ProjectVG.Domain.Chat.Model; +using ProjectVG.Domain.Chat.View; + +namespace ProjectVG.Domain.Chat.View +{ + public class ChatBubblePanel : MonoBehaviour + { + [Header("UI Components")] + [SerializeField] private ScrollRect? _scrollRect; + [SerializeField] private GridLayoutGroup? _gridLayoutGroup; + [SerializeField] private ContentSizeFitter? _contentSizeFitter; + + [Header("Bubble Settings")] + [SerializeField] private GameObject? _chatBubblePrefab; + [SerializeField] private Transform? _bubbleContainer; + + [Header("Queue Animation Settings")] + [SerializeField] private bool _enableQueueAnimation = true; + [SerializeField] private float _queueAnimationDelay = 0.1f; + + [Header("Performance Settings")] + [SerializeField] private int _maxBubbles = 20; + [SerializeField] private bool _autoCleanup = true; + [SerializeField] private int _cleanupThreshold = 15; + + private List _activeBubbles = new List(); + + public int ActiveBubbleCount => _activeBubbles.Count; + + public event Action? OnBubbleCreated; + public event Action? OnBubbleDestroyed; + public event Action? OnAllBubblesCleared; + + #region Unity Lifecycle + + private void Awake() + { + Initialize(); + } + + private void OnDestroy() + { + ClearAllBubbles(); + } + + #endregion + + #region Public Methods + + public void CreateBubble(Actor actor, string text, float displayTime = -1f) + { + if (_chatBubblePrefab == null || _bubbleContainer == null) + { + Debug.LogError("[ChatBubblePanel] ChatBubblePrefab 또는 BubbleContainer가 설정되지 않았습니다!"); + return; + } + + try + { + GameObject? bubbleObject = Instantiate(_chatBubblePrefab, _bubbleContainer); + ChatBubbleUI? bubbleUI = bubbleObject?.GetComponent(); + + if (bubbleUI == null) + { + Debug.LogError("[ChatBubblePanel] ChatBubbleUI 컴포넌트를 찾을 수 없습니다!"); + if (bubbleObject != null) + Destroy(bubbleObject); + return; + } + + SetupCanvasGroup(bubbleObject); + + if (_activeBubbles.Count >= _maxBubbles) + { + Debug.LogWarning($"[ChatBubblePanel] 최대 버블 수({_maxBubbles})에 도달했습니다. 가장 오래된 버블을 제거합니다."); + RemoveOldestBubble(); + } + + if (_autoCleanup && _activeBubbles.Count >= _cleanupThreshold) + { + CleanupOldBubbles(); + } + + if (_enableQueueAnimation && _activeBubbles.Count > 0) + { + StartQueueAnimationForExistingBubbles(); + } + + bubbleUI.Initialize(actor, text, displayTime, this); + + bubbleUI.OnBubbleDestroyed += OnBubbleDestroyed; + bubbleUI.OnToastAnimationComplete += OnBubbleToastAnimationComplete; + + _activeBubbles.Add(bubbleUI); + + OnBubbleCreated?.Invoke(bubbleUI); + } + catch (Exception ex) + { + Debug.LogError($"[ChatBubblePanel] 버블 생성 실패: {ex.Message}"); + } + } + + public void RemoveBubble(ChatBubbleUI? bubble) + { + if (bubble == null) return; + + try + { + bubble.OnBubbleDestroyed -= OnBubbleDestroyed; + bubble.OnToastAnimationComplete -= OnBubbleToastAnimationComplete; + + _activeBubbles.Remove(bubble); + + OnBubbleDestroyed?.Invoke(bubble); + } + catch (Exception ex) + { + Debug.LogError($"[ChatBubblePanel] 버블 제거 실패: {ex.Message}"); + } + } + + public void ClearAllBubbles() + { + try + { + foreach (var bubble in _activeBubbles.ToArray()) + { + if (bubble != null) + { + bubble.OnBubbleDestroyed -= OnBubbleDestroyed; + bubble.OnToastAnimationComplete -= OnBubbleToastAnimationComplete; + Destroy(bubble.gameObject); + } + } + + _activeBubbles.Clear(); + + OnAllBubblesCleared?.Invoke(); + } + catch (Exception ex) + { + Debug.LogError($"[ChatBubblePanel] 모든 버블 제거 실패: {ex.Message}"); + } + } + + #endregion + + #region Private Methods + + private void Initialize() + { + if (_bubbleContainer == null) + { + Debug.LogError("[ChatBubblePanel] BubbleContainer가 설정되지 않았습니다! 인스펙터에서 설정해주세요."); + return; + } + + if (_chatBubblePrefab == null) + { + Debug.LogError("[ChatBubblePanel] ChatBubblePrefab이 설정되지 않았습니다!"); + return; + } + + Debug.Log("[ChatBubblePanel] 초기화 완료"); + } + + private void StartQueueAnimationForExistingBubbles() + { + Canvas.ForceUpdateCanvases(); + + for (int i = 0; i < _activeBubbles.Count; i++) + { + var bubble = _activeBubbles[i]; + if (bubble != null && bubble.IsToastAnimationComplete) + { + float delay = i * _queueAnimationDelay; + StartCoroutine(QueueAnimationWithDelay(bubble, delay)); + } + } + } + + private System.Collections.IEnumerator QueueAnimationWithDelay(ChatBubbleUI? bubble, float delay) + { + yield return new WaitForSeconds(delay); + + if (bubble != null) + { + bubble.StartQueueSlideAnimation(); + } + } + + private void OnBubbleToastAnimationComplete(ChatBubbleUI? bubble) + { + if (bubble == null) return; + + + if (_scrollRect != null) + { + Canvas.ForceUpdateCanvases(); + _scrollRect.verticalNormalizedPosition = 0f; + } + } + + private void RemoveOldestBubble() + { + if (_activeBubbles.Count > 0) + { + var oldestBubble = _activeBubbles[0]; + RemoveBubble(oldestBubble); + if (oldestBubble != null) + { + Destroy(oldestBubble.gameObject); + } + } + } + + private void CleanupOldBubbles() + { + int bubblesToRemove = _activeBubbles.Count - _cleanupThreshold; + for (int i = 0; i < bubblesToRemove && i < _activeBubbles.Count; i++) + { + var bubble = _activeBubbles[0]; + RemoveBubble(bubble); + if (bubble != null) + { + Destroy(bubble.gameObject); + } + } + } + + private void SetupCanvasGroup(GameObject? bubbleObject) + { + if (bubbleObject == null) return; + + CanvasGroup? canvasGroup = bubbleObject.GetComponent(); + if (canvasGroup == null) + { + canvasGroup = bubbleObject.AddComponent(); + Debug.Log($"[ChatBubblePanel] ChatBubble에 CanvasGroup이 자동으로 추가되었습니다: {bubbleObject.name}"); + } + + canvasGroup.alpha = 0f; + } + + #endregion + } +} + + diff --git a/Assets/Domain/Chat/View/ChatBubblePanel.cs.meta b/Assets/Domain/Chat/View/ChatBubblePanel.cs.meta new file mode 100644 index 0000000..1573451 --- /dev/null +++ b/Assets/Domain/Chat/View/ChatBubblePanel.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 30700e2214ca700469760eef5b0069ea + diff --git a/Assets/Domain/Chat/View/ChatBubbleUI.cs b/Assets/Domain/Chat/View/ChatBubbleUI.cs index bee3aff..6429084 100644 --- a/Assets/Domain/Chat/View/ChatBubbleUI.cs +++ b/Assets/Domain/Chat/View/ChatBubbleUI.cs @@ -5,7 +5,7 @@ using UnityEngine.UI; using TMPro; using ProjectVG.Domain.Chat.Model; -using ProjectVG.Domain.Chat.Service; + namespace ProjectVG.Domain.Chat.View { @@ -23,7 +23,6 @@ public class ChatBubbleUI : MonoBehaviour private CanvasGroup? _canvasGroup; [Header("Animation Settings")] - [SerializeField] private float _slideInDuration = 0.25f; [SerializeField] private float _slideOutDuration = 0.03f; [SerializeField] private float _typingSpeed = 0.025f; [SerializeField] private bool _enableAutoDestroy = true; @@ -34,7 +33,7 @@ public class ChatBubbleUI : MonoBehaviour [SerializeField] private float _toastBounceHeight = 20f; [SerializeField] private float _toastBounceScale = 1.1f; [SerializeField] private float _queueSlideDuration = 0.2f; - [SerializeField] private float _queueSlideDistance = 10f; + [SerializeField] private bool _enableBounceEffect = true; [SerializeField] private bool _enableScaleEffect = true; [SerializeField] private EasingType _bounceEasing = EasingType.Bounce; @@ -60,14 +59,13 @@ public enum EasingType private string _fullText = string.Empty; private float _displayTime; - private bool _isInitialized = false; private bool _isAnimating = false; private bool _isTyping = false; private float _typingProgress = 0f; private Coroutine? _typingCoroutine; private Coroutine? _animationCoroutine; - private ChatBubbleManager? _manager; + private ChatBubblePanel? _manager; // 애니메이션 관련 변수들 private Vector3 _originalPosition; @@ -110,7 +108,7 @@ private void OnDestroy() #region Public Methods - public void Initialize(Actor actor, string text, float displayTime, ChatBubbleManager? manager = null) + public void Initialize(Actor actor, string text, float displayTime, ChatBubblePanel? manager = null) { _actor = actor; _fullText = text; @@ -135,7 +133,6 @@ public void Initialize(Actor actor, string text, float displayTime, ChatBubbleMa } } - _isInitialized = true; OnBubbleCreated?.Invoke(this); } diff --git a/Assets/Domain/Chat/View/VoiceInputView.cs b/Assets/Domain/Chat/View/VoiceInputView.cs index 9a4baba..1bbe81b 100644 --- a/Assets/Domain/Chat/View/VoiceInputView.cs +++ b/Assets/Domain/Chat/View/VoiceInputView.cs @@ -17,15 +17,7 @@ public class VoiceInputView : MonoBehaviour [Header("UI Components")] [SerializeField] private Button? _btnVoice; [SerializeField] private Button? _btnVoiceStop; - [SerializeField] private TextMeshProUGUI? _txtVoiceStatus; - [SerializeField] private Slider? _progressBar; // 녹음 진행률 표시 - [Header("Voice Settings")] - [SerializeField] private float _maxRecordingTime = 30f; - [SerializeField] private string _voiceStatusRecording = "Recording..."; // "녹음 중..."에서 변경 - [SerializeField] private string _voiceStatusProcessing = "Converting speech to text..."; // "음성을 텍스트로 변환 중..."에서 변경 - - private ChatManager? _chatManager; private AudioRecorder? _audioRecorder; @@ -64,7 +56,6 @@ private void OnDestroy() _audioRecorder.OnRecordingStarted -= OnRecordingStarted; _audioRecorder.OnRecordingStopped -= OnRecordingStopped; _audioRecorder.OnRecordingCompleted -= OnRecordingCompleted; - _audioRecorder.OnRecordingProgress -= OnRecordingProgress; _audioRecorder.OnError -= OnRecordingError; } } @@ -96,8 +87,6 @@ public async void SendVoiceMessage(byte[] audioData) try { - UpdateVoiceStatus(_voiceStatusProcessing); - string transcribedText = await ConvertSpeechToText(audioData); if (!string.IsNullOrWhiteSpace(transcribedText)) @@ -117,7 +106,6 @@ public async void SendVoiceMessage(byte[] audioData) } finally { - UpdateVoiceStatus(string.Empty); } } @@ -137,8 +125,6 @@ public void StartVoiceRecording() _isRecording = true; _recordingStartTime = Time.time; UpdateVoiceButtonState(true); - UpdateVoiceStatus(_voiceStatusRecording); - UpdateProgressBar(0f); bool success = _audioRecorder.StartRecording(); if (!success) @@ -169,8 +155,6 @@ public void StopVoiceRecording() { _isRecording = false; UpdateVoiceButtonState(false); - UpdateVoiceStatus(string.Empty); - UpdateProgressBar(0f); AudioClip? recordedClip = _audioRecorder.StopRecording(); if (recordedClip != null) @@ -213,23 +197,7 @@ private void SetupComponents() } } - if (_txtVoiceStatus == null) - { - _txtVoiceStatus = transform.Find("TxtVoiceStatus")?.GetComponent(); - if (_txtVoiceStatus == null) - { - Debug.LogWarning("[VoiceInputView] TxtVoiceStatus 텍스트를 찾을 수 없습니다."); - } - } - - if (_progressBar == null) - { - _progressBar = transform.Find("ProgressBar")?.GetComponent(); - if (_progressBar == null) - { - Debug.LogWarning("[VoiceInputView] ProgressBar 슬라이더를 찾을 수 없습니다."); - } - } + if (_audioRecorder == null) { @@ -263,7 +231,6 @@ private void SetupEventHandlers() _audioRecorder.OnRecordingStarted += OnRecordingStarted; _audioRecorder.OnRecordingStopped += OnRecordingStopped; _audioRecorder.OnRecordingCompleted += OnRecordingCompleted; - _audioRecorder.OnRecordingProgress += OnRecordingProgress; _audioRecorder.OnError += OnRecordingError; } } @@ -289,23 +256,7 @@ private void UpdateVoiceButtonState(bool isRecording) _btnVoiceStop.gameObject.SetActive(isRecording); } - private void UpdateVoiceStatus(string status) - { - if (_txtVoiceStatus != null) - { - _txtVoiceStatus.text = status; - _txtVoiceStatus.gameObject.SetActive(!string.IsNullOrEmpty(status)); - } - } - - private void UpdateProgressBar(float progress) - { - if (_progressBar != null) - { - _progressBar.value = progress; - _progressBar.gameObject.SetActive(progress > 0f); - } - } + private async System.Threading.Tasks.Task ConvertSpeechToText(byte[] audioData) { @@ -376,10 +327,7 @@ private void OnRecordingCompleted(AudioClip audioClip) // AudioRecorder에서 로그 출력 } - private void OnRecordingProgress(float progress) - { - UpdateProgressBar(progress); - } + private void OnRecordingError(string error) { diff --git a/Assets/Editor.meta b/Assets/Editor.meta new file mode 100644 index 0000000..0baaa73 --- /dev/null +++ b/Assets/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c880b556fd897fc4b8ff8fec8f532d2f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Editor/OpenDocsMenu.cs b/Assets/Editor/OpenDocsMenu.cs new file mode 100644 index 0000000..e067144 --- /dev/null +++ b/Assets/Editor/OpenDocsMenu.cs @@ -0,0 +1,14 @@ +using UnityEditor; +using UnityEngine; + +public static class OpenDocsMenu +{ + /** + * 프로젝트 문서(루트 Docs/)를 파일 탐색기로 여는 메뉴 항목을 추가합니다. + */ + [MenuItem("Help/Open Project Docs")] + public static void OpenProjectDocs() + { + + } +} diff --git a/Assets/Editor/OpenDocsMenu.cs.meta b/Assets/Editor/OpenDocsMenu.cs.meta new file mode 100644 index 0000000..30ad15e --- /dev/null +++ b/Assets/Editor/OpenDocsMenu.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 51cc0cffee369434a853777240686338 \ No newline at end of file diff --git a/Assets/Infrastructure/Config/AppEnvironmentConfig.cs b/Assets/Infrastructure/Config/AppEnvironmentConfig.cs index 8b2dca2..73ef763 100644 --- a/Assets/Infrastructure/Config/AppEnvironmentConfig.cs +++ b/Assets/Infrastructure/Config/AppEnvironmentConfig.cs @@ -8,6 +8,7 @@ namespace ProjectVG.Infrastructure.Config */ public class AppEnvironmentConfig : ScriptableObject { + #pragma warning disable CS0414 [Header("Override")] [SerializeField] private bool overrideEnabled = false; [SerializeField] private NetworkConfig.EnvironmentType overrideEnvironment = NetworkConfig.EnvironmentType.Development; @@ -15,13 +16,20 @@ public class AppEnvironmentConfig : ScriptableObject [Header("Per-Platform Default Environments")] [SerializeField] private NetworkConfig.EnvironmentType editorEnvironment = NetworkConfig.EnvironmentType.Development; [SerializeField] private NetworkConfig.EnvironmentType androidEnvironment = NetworkConfig.EnvironmentType.Production; + [SerializeField] private NetworkConfig.EnvironmentType iosEnvironment = NetworkConfig.EnvironmentType.Production; + [SerializeField] private NetworkConfig.EnvironmentType standaloneEnvironment = NetworkConfig.EnvironmentType.Development; [SerializeField] private NetworkConfig.EnvironmentType webglEnvironment = NetworkConfig.EnvironmentType.Development; [Header("Build Flags Mapping")] [SerializeField] private bool mapDevelopmentBuildToDevelopment = true; + #pragma warning restore CS0414 + + // 프로퍼티로 필드 사용을 명시적으로 표시 + public NetworkConfig.EnvironmentType IOSEnvironment => iosEnvironment; + private static AppEnvironmentConfig _instance; public static AppEnvironmentConfig Instance { @@ -91,3 +99,4 @@ private static AppEnvironmentConfig CreateDefaultAsset() } } } + diff --git a/Assets/Infrastructure/Network/Http/HttpApiClient.cs b/Assets/Infrastructure/Network/Http/HttpApiClient.cs index f7af97d..d9028e9 100644 --- a/Assets/Infrastructure/Network/Http/HttpApiClient.cs +++ b/Assets/Infrastructure/Network/Http/HttpApiClient.cs @@ -9,12 +9,10 @@ using ProjectVG.Infrastructure.Network.DTOs.Chat; using ProjectVG.Infrastructure.Network.Services; using Newtonsoft.Json; -using ProjectVG.Core.Managers; -using ProjectVG.Core.Attributes; namespace ProjectVG.Infrastructure.Network.Http { - public class HttpApiClient : Singleton, IManager + public class HttpApiClient : Singleton { [Header("API Configuration")] @@ -24,14 +22,14 @@ public class HttpApiClient : Singleton, IManager private readonly Dictionary defaultHeaders = new Dictionary(); private CancellationTokenSource cancellationTokenSource; - [Inject] private SessionManager _sessionManager; + private SessionManager _sessionManager; + public bool IsInitialized { get; private set; } #region Unity Lifecycle protected override void Awake() { base.Awake(); - Initialize(); } private void OnDestroy() @@ -43,11 +41,31 @@ private void OnDestroy() #region Public Methods + /// + /// 초기화 실행 + /// + public void Initialize(SessionManager sessionManager) + { + _sessionManager = sessionManager; + cancellationTokenSource?.Cancel(); + cancellationTokenSource?.Dispose(); + cancellationTokenSource = new CancellationTokenSource(); + ApplyNetworkConfig(); + SetupDefaultHeaders(); + IsInitialized = true; + } + + /// + /// 기본 헤더 추가 + /// public void AddDefaultHeader(string key, string value) { defaultHeaders[key] = value; } + /// + /// 인증 토큰 설정 + /// public void SetAuthToken(string token) { AddDefaultHeader(AUTHORIZATION_HEADER, $"{BEARER_PREFIX}{token}"); @@ -82,7 +100,15 @@ public async UniTask DeleteAsync(string endpoint, Dictionary UploadFileAsync(string endpoint, byte[] fileData, string fileName, string fieldName = "file", Dictionary headers = null, CancellationToken cancellationToken = default) { var url = GetFullUrl(endpoint); - return await SendFileRequestAsync(url, fileData, fileName, fieldName, headers, cancellationToken); + var formData = new Dictionary + { + { fieldName, fileData } + }; + var fileNames = new Dictionary + { + { fieldName, fileName } + }; + return await SendFormDataRequestAsync(url, formData, fileNames, headers, cancellationToken); } public async UniTask PostFormDataAsync(string endpoint, Dictionary formData, Dictionary headers = null, CancellationToken cancellationToken = default) @@ -115,22 +141,19 @@ public async UniTask PostFormDataAsync(string endpoint, Dictionary(url, formData, fileNames, headers, cancellationToken); } + /// + /// 종료 처리 + /// public void Shutdown() { cancellationTokenSource?.Cancel(); cancellationTokenSource?.Dispose(); + IsInitialized = false; } #endregion #region Private Methods - - private void Initialize() - { - cancellationTokenSource = new CancellationTokenSource(); - ApplyNetworkConfig(); - SetupDefaultHeaders(); - } private void ApplyNetworkConfig() { @@ -178,9 +201,7 @@ private string SerializeData(object data, bool requiresSession = false) return jsonData; } - private void LogRequestDetails(string method, string url, string jsonData) - { - } + private async UniTask SendJsonRequestAsync(string url, string method, string jsonData, Dictionary headers, CancellationToken cancellationToken) { @@ -217,80 +238,9 @@ private async UniTask SendJsonRequestAsync(string url, string method, stri throw new ApiException($"{NetworkConfig.MaxRetryCount + 1}번 시도 후 요청 실패", 0, "최대 재시도 횟수 초과"); } - private async UniTask SendRequestAsync(string url, string method, string jsonData, Dictionary headers, CancellationToken cancellationToken) - { - var combinedCancellationToken = CreateCombinedCancellationToken(cancellationToken); - - for (int attempt = 0; attempt <= NetworkConfig.MaxRetryCount; attempt++) - { - try - { - using var request = CreateRequest(url, method, jsonData, headers); - - var operation = request.SendWebRequest(); - await operation.WithCancellation(combinedCancellationToken); - - if (request.result == UnityWebRequest.Result.Success) - { - return ParseResponse(request); - } - else - { - await HandleRequestFailure(request, attempt, combinedCancellationToken); - } - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) when (ex is not ApiException) - { - await HandleRequestException(ex, attempt, combinedCancellationToken); - } - } - - throw new ApiException($"{NetworkConfig.MaxRetryCount + 1}번 시도 후 요청 실패", 0, "최대 재시도 횟수 초과"); - } - - private async UniTask SendFileRequestAsync(string url, byte[] fileData, string fileName, string fieldName, Dictionary headers, CancellationToken cancellationToken) - { - var combinedCancellationToken = CreateCombinedCancellationToken(cancellationToken); - - for (int attempt = 0; attempt <= NetworkConfig.MaxRetryCount; attempt++) - { - try - { - var form = new WWWForm(); - form.AddBinaryData(fieldName, fileData, fileName); - - using var request = UnityWebRequest.Post(url, form); - SetupRequest(request, headers); - request.timeout = (int)NetworkConfig.HttpTimeout; - - var operation = request.SendWebRequest(); - await operation.WithCancellation(combinedCancellationToken); + - if (request.result == UnityWebRequest.Result.Success) - { - return ParseResponse(request); - } - else - { - await HandleFileUploadFailure(request, attempt, combinedCancellationToken); - } - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) when (ex is not ApiException) - { - await HandleFileUploadException(ex, attempt, combinedCancellationToken); - } - } - - throw new ApiException($"{NetworkConfig.MaxRetryCount + 1}번 시도 후 파일 업로드 실패", 0, "최대 재시도 횟수 초과"); - } + @@ -407,22 +357,7 @@ private async UniTask HandleFileUploadException(Exception ex, int attempt, Cance throw new ApiException($"{NetworkConfig.MaxRetryCount + 1}번 시도 후 파일 업로드 실패", 0, ex.Message); } - private UnityWebRequest CreateRequest(string url, string method, string jsonData, Dictionary headers) - { - var request = new UnityWebRequest(url, method); - - if (!string.IsNullOrEmpty(jsonData)) - { - var bodyRaw = Encoding.UTF8.GetBytes(jsonData); - request.uploadHandler = new UploadHandlerRaw(bodyRaw); - } - - request.downloadHandler = new DownloadHandlerBuffer(); - SetupRequest(request, headers); - request.timeout = (int)NetworkConfig.HttpTimeout; - - return request; - } + private UnityWebRequest CreateJsonRequest(string url, string method, string jsonData, Dictionary headers) { diff --git a/Assets/Infrastructure/Network/Services/STTService.cs b/Assets/Infrastructure/Network/Services/STTService.cs index cca1769..3284d77 100644 --- a/Assets/Infrastructure/Network/Services/STTService.cs +++ b/Assets/Infrastructure/Network/Services/STTService.cs @@ -17,9 +17,9 @@ namespace ProjectVG.Infrastructure.Network.Services ///
public class STTService { - private readonly HttpApiClient _httpClient; + private readonly HttpApiClient? _httpClient; - public bool IsConnected => true; // 항상 연결 가능하다고 가정 + public bool IsConnected => true; public bool IsAvailable => _httpClient != null; public STTService() @@ -67,18 +67,10 @@ public async UniTask ConvertSpeechToTextAsync(byte[] audioData, string a string forcedLanguage = "ko"; string endpoint = $"stt/transcribe?language={forcedLanguage}"; - Debug.Log($"[STTService] STT 변환 요청 시작 - 엔드포인트: {endpoint}, 파일 크기: {audioData.Length / 1024}KB, 강제 언어: {forcedLanguage}"); - Debug.Log($"[STTService] URL 확인: {endpoint}"); - var response = await _httpClient.PostFormDataAsync(endpoint, formData, fileNames, cancellationToken: cancellationToken); - Debug.Log($"[STTService] 응답 객체 - Text: '{response?.Text}', Language: '{response?.Language}'"); - Debug.Log($"[STTService] 응답 객체 - LanguageProbability: {response?.LanguageProbability}, SegmentsCount: {response?.SegmentsCount}"); - Debug.Log($"[STTService] 응답 객체 - ProcessingTime: {response?.ProcessingTime}"); - if (response != null && !string.IsNullOrEmpty(response.Text)) { - Debug.Log($"[STTService] STT 변환 성공 - 텍스트: '{response.Text}'"); return response.Text; } else diff --git a/Assets/Infrastructure/Network/Services/SessionManager.cs b/Assets/Infrastructure/Network/Services/SessionManager.cs index 3d3f598..7d4b339 100644 --- a/Assets/Infrastructure/Network/Services/SessionManager.cs +++ b/Assets/Infrastructure/Network/Services/SessionManager.cs @@ -2,8 +2,6 @@ using System; using Cysharp.Threading.Tasks; using ProjectVG.Infrastructure.Network.WebSocket; -using ProjectVG.Core.Managers; -using ProjectVG.Core.Attributes; namespace ProjectVG.Infrastructure.Network.Services { @@ -11,13 +9,13 @@ namespace ProjectVG.Infrastructure.Network.Services /// 새로운 이벤트 기반 SessionManager /// WebSocketManager의 연결/해제 상태를 모니터링하고 세션 ID를 관리 /// - public class SessionManager : Singleton, IManager + public class SessionManager : Singleton { [Header("Session Info")] [SerializeField] private string _currentSessionId = ""; [SerializeField] private bool _isInitialized = false; - [Inject] private WebSocketManager _webSocketManager; + private WebSocketManager _webSocketManager; // 공개 속성 public string SessionId => _currentSessionId; @@ -38,11 +36,6 @@ protected override void Awake() base.Awake(); } - private void Start() - { - // DI 완료 후 ManagerRegistry에서 Initialize 호출됨 - } - private void OnDestroy() { Shutdown(); @@ -53,7 +46,7 @@ private void OnDestroy() #region Public Methods /// - /// 세션 ID를 요청합니다. 연결되지 않았다면 연결을 시도하고 기다립니다. + /// 세션 ID 요청 /// public async UniTask GetSessionIdAsync() { @@ -78,7 +71,7 @@ public async UniTask GetSessionIdAsync() } /// - /// 세션 연결을 보장합니다. 이미 연결되어 있으면 즉시 반환하고, 그렇지 않으면 연결을 시도합니다. + /// 세션 연결 보장 /// public async UniTask EnsureConnectionAsync() { @@ -94,6 +87,7 @@ public async UniTask EnsureConnectionAsync() if (_webSocketManager == null) { Debug.LogError("[SessionManager] WebSocketManager가 DI로 주입되지 않았습니다. DependencyManager 설정을 확인하세요."); + OnSessionError?.Invoke("WebSocketManager가 null입니다."); return false; } @@ -101,7 +95,7 @@ public async UniTask EnsureConnectionAsync() } /// - /// 새로운 연결 요청 로직 - 폴링 방식 + /// 연결 요청 /// private async UniTask RequestConnectionAsync() { @@ -111,6 +105,7 @@ private async UniTask RequestConnectionAsync() if (_webSocketManager == null) { Debug.LogError("[SessionManager] WebSocketManager가 DI로 주입되지 않았습니다."); + OnSessionError?.Invoke("WebSocketManager가 null입니다."); return false; } @@ -146,7 +141,7 @@ private async UniTask RequestConnectionAsync() } /// - /// 세션 연결 완료를 폴링으로 대기 + /// 세션 연결 완료 대기 /// private async UniTask WaitForSessionConnection() { @@ -199,21 +194,25 @@ public void EndSession() #region Private Methods - 초기화 및 이벤트 핸들링 - public void Initialize() + /// + /// 초기화 실행 + /// + public void Initialize(WebSocketManager webSocketManager) { try { + _webSocketManager = webSocketManager; Debug.Log("[SessionManager] 초기화 시작"); - + if (_webSocketManager == null) { - Debug.LogError("[SessionManager] WebSocketManager가 DI로 주입되지 않았습니다."); + Debug.LogError("[SessionManager] WebSocketManager가 null입니다."); + OnSessionError?.Invoke("WebSocketManager가 null입니다."); return; } - - // 이벤트 구독 + SubscribeToWebSocketEvents(); - + _isInitialized = true; Debug.Log("[SessionManager] 초기화 완료"); } @@ -221,6 +220,7 @@ public void Initialize() { Debug.LogError($"[SessionManager] 초기화 실패: {ex.Message}"); Debug.LogError($"[SessionManager] 스택 트레이스: {ex.StackTrace}"); + OnSessionError?.Invoke($"초기화 실패: {ex.Message}"); } } diff --git a/Assets/Infrastructure/Network/WebSocket/WebSocketManager.cs b/Assets/Infrastructure/Network/WebSocket/WebSocketManager.cs index 372dddc..68bb660 100644 --- a/Assets/Infrastructure/Network/WebSocket/WebSocketManager.cs +++ b/Assets/Infrastructure/Network/WebSocket/WebSocketManager.cs @@ -5,16 +5,13 @@ using Cysharp.Threading.Tasks; using ProjectVG.Infrastructure.Network.Configs; using ProjectVG.Infrastructure.Network.DTOs.Chat; -using ProjectVG.Infrastructure.Network.Services; using ProjectVG.Domain.Chat.Model; -using ProjectVG.Core.Managers; -using ProjectVG.Core.Attributes; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace ProjectVG.Infrastructure.Network.WebSocket { - public class WebSocketManager : Singleton, IManager + public class WebSocketManager : Singleton { private INativeWebSocket _nativeWebSocket; private CancellationTokenSource _cancellationTokenSource; @@ -56,7 +53,6 @@ public class WebSocketManager : Singleton, IManager protected override void Awake() { base.Awake(); - Initialize(); } private void OnDestroy() @@ -67,7 +63,26 @@ private void OnDestroy() #endregion #region Public Methods + + /// + /// 웹소켓 매니저 초기화 + /// + public void Initialize() + { + if (_cancellationTokenSource != null) + { + return; + } + _cancellationTokenSource = new CancellationTokenSource(); + InitializeNativeWebSocket(); +#pragma warning disable CS4014 + StartConnectionMonitoring(); +#pragma warning restore CS4014 + } + /// + /// 서버와 웹소켓 연결 시도 + /// public async UniTask ConnectAsync(string sessionId = null, CancellationToken cancellationToken = default) { if (_isConnected || _isConnecting) @@ -122,6 +137,9 @@ public async UniTask ConnectAsync(string sessionId = null, CancellationTok } } + /// + /// 웹소켓 연결 해제 + /// public async UniTask DisconnectAsync() { if (!_isConnected) @@ -140,29 +158,25 @@ public async UniTask DisconnectAsync() OnDisconnected?.Invoke(); } - public void SetAutoReconnect(bool enabled) - { - _autoReconnect = enabled; - } - - public void SetReconnectSettings(int maxAttempts, float delay, float maxDelay = 60f, bool useExponentialBackoff = true) - { - _maxReconnectAttempts = maxAttempts; - _reconnectDelay = delay; - _maxReconnectDelay = maxDelay; - _useExponentialBackoff = useExponentialBackoff; - } - - public async UniTask SendMessageAsync(string type, string data) + /// + /// 웹소켓 메시지 전송 + /// + public UniTask SendMessageAsync(string type, string data) { throw new NotImplementedException(); } + /// + /// 연결 상태 로깅 + /// public void LogConnectionStatus() { Debug.Log($"[WebSocket] 연결 상태: {(_isConnected ? "연결됨" : "연결안됨")}, 연결 중: {(_isConnecting ? "예" : "아니오")}, 재연결 시도: {_reconnectAttempts}/{_maxReconnectAttempts}"); } + /// + /// 매니저 종료 및 리소스 정리 + /// public void Shutdown() { if (_isShutdown) @@ -187,13 +201,6 @@ public void Shutdown() #region Private Methods - private void Initialize() - { - _cancellationTokenSource = new CancellationTokenSource(); - InitializeNativeWebSocket(); - StartConnectionMonitoring(); - } - private void InitializeNativeWebSocket() { _nativeWebSocket = WebSocketFactory.CreateWebSocket(); diff --git a/Assets/Readme.asset b/Assets/Readme.asset deleted file mode 100644 index c5d79e4..0000000 --- a/Assets/Readme.asset +++ /dev/null @@ -1,40 +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: fcf7219bab7fe46a1ad266029b2fee19, type: 3} - m_Name: Readme - m_EditorClassIdentifier: - icon: {fileID: 2800000, guid: eda43ba821d75d046a45209bde150047, type: 3} - title: Universal Mobile 2D Template - sections: - - heading: Welcome to the Universal Mobile 2D Template - text: This template sets up the right Project settings for developing a 2D game - on mobile. Also it includes some of the recommended packages for developing - on mobile. - linkText: - url: - - heading: Forums iOS - text: - linkText: Get answers and support - url: https://discussions.unity.com/tag/iOS - - heading: Forums Android - text: - linkText: Get answers and support - url: https://discussions.unity.com/tag/Android - - heading: Bugs - text: - linkText: Report any bugs - url: https://unity3d.com/unity/qa/bug-reporting - - heading: Template feedback - text: - linkText: Share your feedback on this template with us - url: https://unitysoftware.co1.qualtrics.com/jfe/form/SV_b8GWOIYxi4l6PDE?templatename=mobile2d - loadedLayout: 1 diff --git a/Assets/Resources/Character.meta b/Assets/Resources/Character.meta new file mode 100644 index 0000000..892336f --- /dev/null +++ b/Assets/Resources/Character.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 33c3ee053842bbb4a8f0c00ad6fbf92a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Resources/Character/Live2DModelRegistry.asset b/Assets/Resources/Character/Live2DModelRegistry.asset new file mode 100644 index 0000000..72bdee3 --- /dev/null +++ b/Assets/Resources/Character/Live2DModelRegistry.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: a663c59d4d7aa174dbef2ec581104a3d, type: 3} + m_Name: Live2DModelRegistry + m_EditorClassIdentifier: + _entries: + - characterId: zero + characterConfig: {fileID: 11400000, guid: 4c6d1f5cb9556f24c843f3e9fe14d49e, type: 2} diff --git a/Assets/Resources/Character/Live2DModelRegistry.asset.meta b/Assets/Resources/Character/Live2DModelRegistry.asset.meta new file mode 100644 index 0000000..516a966 --- /dev/null +++ b/Assets/Resources/Character/Live2DModelRegistry.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: cf68d9632ab0d2c42a02de12beecadff +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Resources/Character/Model.meta b/Assets/Resources/Character/Model.meta new file mode 100644 index 0000000..cea515c --- /dev/null +++ b/Assets/Resources/Character/Model.meta @@ -0,0 +1,8 @@ +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 new file mode 100644 index 0000000..09aed52 --- /dev/null +++ b/Assets/Resources/Character/Model/CharacterZero.asset @@ -0,0 +1,33 @@ +%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/Model/CharacterZero.asset.meta b/Assets/Resources/Character/Model/CharacterZero.asset.meta new file mode 100644 index 0000000..9d69fe5 --- /dev/null +++ b/Assets/Resources/Character/Model/CharacterZero.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4c6d1f5cb9556f24c843f3e9fe14d49e +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Samples.meta b/Assets/Samples.meta new file mode 100644 index 0000000..1790a84 --- /dev/null +++ b/Assets/Samples.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7e93090ffb9d8aa47ab8da6804c4f7ca +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Samples/Core.meta b/Assets/Samples/Core.meta new file mode 100644 index 0000000..9210e88 --- /dev/null +++ b/Assets/Samples/Core.meta @@ -0,0 +1,8 @@ +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 new file mode 100644 index 0000000..ce6405e --- /dev/null +++ b/Assets/Samples/Core/Managers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bd11cb6c95a02a94faa52c02ded0b378 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Core/SystemManager.cs b/Assets/Samples/Core/Managers/SampleSystemManager.cs similarity index 95% rename from Assets/Core/SystemManager.cs rename to Assets/Samples/Core/Managers/SampleSystemManager.cs index 2236490..3a49e09 100644 --- a/Assets/Core/SystemManager.cs +++ b/Assets/Samples/Core/Managers/SampleSystemManager.cs @@ -2,8 +2,9 @@ using Live2D.Cubism.Framework.MouthMovement; using UnityEngine; using UnityEngine.UI; +using ProjectVG.Core.Audio; -public class SystemManager : Singleton +public class SampleSystemManager : Singleton { [SerializeField] private CubismLookTarget cubismLookTarget = null; [SerializeField] private Camera mCamera = null; diff --git a/Assets/Core/SystemManager.cs.meta b/Assets/Samples/Core/Managers/SampleSystemManager.cs.meta similarity index 100% rename from Assets/Core/SystemManager.cs.meta rename to Assets/Samples/Core/Managers/SampleSystemManager.cs.meta diff --git a/Assets/Adaptive Performance.meta b/Assets/Settings/Adaptive Performance.meta similarity index 100% rename from Assets/Adaptive Performance.meta rename to Assets/Settings/Adaptive Performance.meta diff --git a/Assets/Adaptive Performance/AdaptivePerformanceGeneralSettings.asset b/Assets/Settings/Adaptive Performance/AdaptivePerformanceGeneralSettings.asset similarity index 100% rename from Assets/Adaptive Performance/AdaptivePerformanceGeneralSettings.asset rename to Assets/Settings/Adaptive Performance/AdaptivePerformanceGeneralSettings.asset diff --git a/Assets/Adaptive Performance/AdaptivePerformanceGeneralSettings.asset.meta b/Assets/Settings/Adaptive Performance/AdaptivePerformanceGeneralSettings.asset.meta similarity index 100% rename from Assets/Adaptive Performance/AdaptivePerformanceGeneralSettings.asset.meta rename to Assets/Settings/Adaptive Performance/AdaptivePerformanceGeneralSettings.asset.meta diff --git a/Assets/Adaptive Performance/Provider.meta b/Assets/Settings/Adaptive Performance/Provider.meta similarity index 100% rename from Assets/Adaptive Performance/Provider.meta rename to Assets/Settings/Adaptive Performance/Provider.meta diff --git a/Assets/Adaptive Performance/Provider/Google Android Provider Loader.asset b/Assets/Settings/Adaptive Performance/Provider/Google Android Provider Loader.asset similarity index 100% rename from Assets/Adaptive Performance/Provider/Google Android Provider Loader.asset rename to Assets/Settings/Adaptive Performance/Provider/Google Android Provider Loader.asset diff --git a/Assets/Adaptive Performance/Provider/Google Android Provider Loader.asset.meta b/Assets/Settings/Adaptive Performance/Provider/Google Android Provider Loader.asset.meta similarity index 100% rename from Assets/Adaptive Performance/Provider/Google Android Provider Loader.asset.meta rename to Assets/Settings/Adaptive Performance/Provider/Google Android Provider Loader.asset.meta diff --git a/Assets/Adaptive Performance/Provider/Samsung Android Provider Loader.asset b/Assets/Settings/Adaptive Performance/Provider/Samsung Android Provider Loader.asset similarity index 100% rename from Assets/Adaptive Performance/Provider/Samsung Android Provider Loader.asset rename to Assets/Settings/Adaptive Performance/Provider/Samsung Android Provider Loader.asset diff --git a/Assets/Adaptive Performance/Provider/Samsung Android Provider Loader.asset.meta b/Assets/Settings/Adaptive Performance/Provider/Samsung Android Provider Loader.asset.meta similarity index 100% rename from Assets/Adaptive Performance/Provider/Samsung Android Provider Loader.asset.meta rename to Assets/Settings/Adaptive Performance/Provider/Samsung Android Provider Loader.asset.meta diff --git a/Assets/Adaptive Performance/Settings.meta b/Assets/Settings/Adaptive Performance/Settings.meta similarity index 100% rename from Assets/Adaptive Performance/Settings.meta rename to Assets/Settings/Adaptive Performance/Settings.meta diff --git a/Assets/Adaptive Performance/Settings/Google Android Provider Settings.asset b/Assets/Settings/Adaptive Performance/Settings/Google Android Provider Settings.asset similarity index 100% rename from Assets/Adaptive Performance/Settings/Google Android Provider Settings.asset rename to Assets/Settings/Adaptive Performance/Settings/Google Android Provider Settings.asset diff --git a/Assets/Adaptive Performance/Settings/Google Android Provider Settings.asset.meta b/Assets/Settings/Adaptive Performance/Settings/Google Android Provider Settings.asset.meta similarity index 100% rename from Assets/Adaptive Performance/Settings/Google Android Provider Settings.asset.meta rename to Assets/Settings/Adaptive Performance/Settings/Google Android Provider Settings.asset.meta diff --git a/Assets/Adaptive Performance/Settings/Samsung Android Provider Settings.asset b/Assets/Settings/Adaptive Performance/Settings/Samsung Android Provider Settings.asset similarity index 100% rename from Assets/Adaptive Performance/Settings/Samsung Android Provider Settings.asset rename to Assets/Settings/Adaptive Performance/Settings/Samsung Android Provider Settings.asset diff --git a/Assets/Adaptive Performance/Settings/Samsung Android Provider Settings.asset.meta b/Assets/Settings/Adaptive Performance/Settings/Samsung Android Provider Settings.asset.meta similarity index 100% rename from Assets/Adaptive Performance/Settings/Samsung Android Provider Settings.asset.meta rename to Assets/Settings/Adaptive Performance/Settings/Samsung Android Provider Settings.asset.meta diff --git a/Assets/Adaptive Performance/Settings/Simulator Provider Settings.asset b/Assets/Settings/Adaptive Performance/Settings/Simulator Provider Settings.asset similarity index 100% rename from Assets/Adaptive Performance/Settings/Simulator Provider Settings.asset rename to Assets/Settings/Adaptive Performance/Settings/Simulator Provider Settings.asset diff --git a/Assets/Adaptive Performance/Settings/Simulator Provider Settings.asset.meta b/Assets/Settings/Adaptive Performance/Settings/Simulator Provider Settings.asset.meta similarity index 100% rename from Assets/Adaptive Performance/Settings/Simulator Provider Settings.asset.meta rename to Assets/Settings/Adaptive Performance/Settings/Simulator Provider Settings.asset.meta diff --git a/Assets/Settings/InputSystem.meta b/Assets/Settings/InputSystem.meta new file mode 100644 index 0000000..4f37ba1 --- /dev/null +++ b/Assets/Settings/InputSystem.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4be52ae04c666284f931a36a6c925982 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/InputSystem_Actions.inputactions b/Assets/Settings/InputSystem/InputSystem_Actions.inputactions similarity index 100% rename from Assets/InputSystem_Actions.inputactions rename to Assets/Settings/InputSystem/InputSystem_Actions.inputactions diff --git a/Assets/InputSystem_Actions.inputactions.meta b/Assets/Settings/InputSystem/InputSystem_Actions.inputactions.meta similarity index 100% rename from Assets/InputSystem_Actions.inputactions.meta rename to Assets/Settings/InputSystem/InputSystem_Actions.inputactions.meta diff --git a/Assets/Settings/Rendering.meta b/Assets/Settings/Rendering.meta new file mode 100644 index 0000000..f7f8b65 --- /dev/null +++ b/Assets/Settings/Rendering.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0c372d94848e2054d95e3b1d15f36322 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/Rendering/URP.meta b/Assets/Settings/Rendering/URP.meta new file mode 100644 index 0000000..3babd86 --- /dev/null +++ b/Assets/Settings/Rendering/URP.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: efbc1660010436348b8f5427f8190913 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/DefaultVolumeProfile.asset b/Assets/Settings/Rendering/URP/DefaultVolumeProfile.asset similarity index 100% rename from Assets/DefaultVolumeProfile.asset rename to Assets/Settings/Rendering/URP/DefaultVolumeProfile.asset diff --git a/Assets/DefaultVolumeProfile.asset.meta b/Assets/Settings/Rendering/URP/DefaultVolumeProfile.asset.meta similarity index 100% rename from Assets/DefaultVolumeProfile.asset.meta rename to Assets/Settings/Rendering/URP/DefaultVolumeProfile.asset.meta diff --git a/Assets/UniversalRenderPipelineGlobalSettings.asset b/Assets/Settings/Rendering/URP/UniversalRenderPipelineGlobalSettings.asset similarity index 100% rename from Assets/UniversalRenderPipelineGlobalSettings.asset rename to Assets/Settings/Rendering/URP/UniversalRenderPipelineGlobalSettings.asset diff --git a/Assets/UniversalRenderPipelineGlobalSettings.asset.meta b/Assets/Settings/Rendering/URP/UniversalRenderPipelineGlobalSettings.asset.meta similarity index 100% rename from Assets/UniversalRenderPipelineGlobalSettings.asset.meta rename to Assets/Settings/Rendering/URP/UniversalRenderPipelineGlobalSettings.asset.meta diff --git a/Assets/Tests/Sences/Live2D.unity b/Assets/Tests/Sences/Live2D.unity new file mode 100644 index 0000000..7e8b34f --- /dev/null +++ b/Assets/Tests/Sences/Live2D.unity @@ -0,0 +1,218 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 10 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 3 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 0 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 1 + m_PVRFilteringGaussRadiusAO: 1 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &1964079831 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1964079834} + - component: {fileID: 1964079833} + - component: {fileID: 1964079832} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &1964079832 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1964079831} + m_Enabled: 1 +--- !u!20 &1964079833 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1964079831} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 1 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &1964079834 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1964079831} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: -10} + 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!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 1964079834} diff --git a/Assets/Tests/Sences/Live2D.unity.meta b/Assets/Tests/Sences/Live2D.unity.meta new file mode 100644 index 0000000..ee62832 --- /dev/null +++ b/Assets/Tests/Sences/Live2D.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c86cf31ad2619804ca85739af4aeec56 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TutorialInfo.meta b/Assets/TutorialInfo.meta deleted file mode 100644 index 539a977..0000000 --- a/Assets/TutorialInfo.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 37844c66aae9c48458b0dbb739dc59f0 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/TutorialInfo/Icons.meta b/Assets/TutorialInfo/Icons.meta deleted file mode 100644 index 1d19fb9..0000000 --- a/Assets/TutorialInfo/Icons.meta +++ /dev/null @@ -1,9 +0,0 @@ -fileFormatVersion: 2 -guid: 8a0c9218a650547d98138cd835033977 -folderAsset: yes -timeCreated: 1484670163 -licenseType: Store -DefaultImporter: - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/TutorialInfo/Icons/Help_Icon.png b/Assets/TutorialInfo/Icons/Help_Icon.png deleted file mode 100644 index 91fa215..0000000 Binary files a/Assets/TutorialInfo/Icons/Help_Icon.png and /dev/null differ diff --git a/Assets/TutorialInfo/Icons/Mobile 2D.png b/Assets/TutorialInfo/Icons/Mobile 2D.png deleted file mode 100644 index 8d8c67a..0000000 Binary files a/Assets/TutorialInfo/Icons/Mobile 2D.png and /dev/null differ diff --git a/Assets/TutorialInfo/Layout.wlt b/Assets/TutorialInfo/Layout.wlt deleted file mode 100644 index 7b50a25..0000000 --- a/Assets/TutorialInfo/Layout.wlt +++ /dev/null @@ -1,654 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!114 &1 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12004, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_PixelRect: - serializedVersion: 2 - x: 0 - y: 45 - width: 1666 - height: 958 - m_ShowMode: 4 - m_Title: - m_RootView: {fileID: 6} - m_MinSize: {x: 950, y: 542} - m_MaxSize: {x: 10000, y: 10000} ---- !u!114 &2 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: [] - m_Position: - serializedVersion: 2 - x: 0 - y: 466 - width: 290 - height: 442 - m_MinSize: {x: 234, y: 271} - m_MaxSize: {x: 10004, y: 10021} - m_ActualView: {fileID: 14} - m_Panes: - - {fileID: 14} - m_Selected: 0 - m_LastSelected: 0 ---- !u!114 &3 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: - - {fileID: 4} - - {fileID: 2} - m_Position: - serializedVersion: 2 - x: 973 - y: 0 - width: 290 - height: 908 - m_MinSize: {x: 234, y: 492} - m_MaxSize: {x: 10004, y: 14042} - vertical: 1 - controlID: 226 ---- !u!114 &4 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: [] - m_Position: - serializedVersion: 2 - x: 0 - y: 0 - width: 290 - height: 466 - m_MinSize: {x: 204, y: 221} - m_MaxSize: {x: 4004, y: 4021} - m_ActualView: {fileID: 17} - m_Panes: - - {fileID: 17} - m_Selected: 0 - m_LastSelected: 0 ---- !u!114 &5 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: [] - m_Position: - serializedVersion: 2 - x: 0 - y: 466 - width: 973 - height: 442 - m_MinSize: {x: 202, y: 221} - m_MaxSize: {x: 4002, y: 4021} - m_ActualView: {fileID: 15} - m_Panes: - - {fileID: 15} - m_Selected: 0 - m_LastSelected: 0 ---- !u!114 &6 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12008, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: - - {fileID: 7} - - {fileID: 8} - - {fileID: 9} - m_Position: - serializedVersion: 2 - x: 0 - y: 0 - width: 1666 - height: 958 - m_MinSize: {x: 950, y: 542} - m_MaxSize: {x: 10000, y: 10000} ---- !u!114 &7 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12011, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: [] - m_Position: - serializedVersion: 2 - x: 0 - y: 0 - width: 1666 - height: 30 - m_MinSize: {x: 0, y: 0} - m_MaxSize: {x: 0, y: 0} - m_LastLoadedLayoutName: Tutorial ---- !u!114 &8 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: - - {fileID: 10} - - {fileID: 3} - - {fileID: 11} - m_Position: - serializedVersion: 2 - x: 0 - y: 30 - width: 1666 - height: 908 - m_MinSize: {x: 713, y: 492} - m_MaxSize: {x: 18008, y: 14042} - vertical: 0 - controlID: 74 ---- !u!114 &9 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12042, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: [] - m_Position: - serializedVersion: 2 - x: 0 - y: 938 - width: 1666 - height: 20 - m_MinSize: {x: 0, y: 0} - m_MaxSize: {x: 0, y: 0} ---- !u!114 &10 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: - - {fileID: 12} - - {fileID: 5} - m_Position: - serializedVersion: 2 - x: 0 - y: 0 - width: 973 - height: 908 - m_MinSize: {x: 202, y: 442} - m_MaxSize: {x: 4002, y: 8042} - vertical: 1 - controlID: 75 ---- !u!114 &11 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: [] - m_Position: - serializedVersion: 2 - x: 1263 - y: 0 - width: 403 - height: 908 - m_MinSize: {x: 277, y: 71} - m_MaxSize: {x: 4002, y: 4021} - m_ActualView: {fileID: 13} - m_Panes: - - {fileID: 13} - m_Selected: 0 - m_LastSelected: 0 ---- !u!114 &12 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: [] - m_Position: - serializedVersion: 2 - x: 0 - y: 0 - width: 973 - height: 466 - m_MinSize: {x: 202, y: 221} - m_MaxSize: {x: 4002, y: 4021} - m_ActualView: {fileID: 16} - m_Panes: - - {fileID: 16} - m_Selected: 0 - m_LastSelected: 0 ---- !u!114 &13 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12019, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_AutoRepaintOnSceneChange: 0 - m_MinSize: {x: 275, y: 50} - m_MaxSize: {x: 4000, y: 4000} - m_TitleContent: - m_Text: Inspector - m_Image: {fileID: -6905738622615590433, guid: 0000000000000000d000000000000000, - type: 0} - m_Tooltip: - m_DepthBufferBits: 0 - m_Pos: - serializedVersion: 2 - x: 2 - y: 19 - width: 401 - height: 887 - m_ScrollPosition: {x: 0, y: 0} - m_InspectorMode: 0 - m_PreviewResizer: - m_CachedPref: -160 - m_ControlHash: -371814159 - m_PrefName: Preview_InspectorPreview - m_PreviewWindow: {fileID: 0} ---- !u!114 &14 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12014, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_AutoRepaintOnSceneChange: 0 - m_MinSize: {x: 230, y: 250} - m_MaxSize: {x: 10000, y: 10000} - m_TitleContent: - m_Text: Project - m_Image: {fileID: -7501376956915960154, guid: 0000000000000000d000000000000000, - type: 0} - m_Tooltip: - m_DepthBufferBits: 0 - m_Pos: - serializedVersion: 2 - x: 2 - y: 19 - width: 286 - height: 421 - m_SearchFilter: - m_NameFilter: - m_ClassNames: [] - m_AssetLabels: [] - m_AssetBundleNames: [] - m_VersionControlStates: [] - m_ReferencingInstanceIDs: - m_ScenePaths: [] - m_ShowAllHits: 0 - m_SearchArea: 0 - m_Folders: - - Assets - m_ViewMode: 0 - m_StartGridSize: 64 - m_LastFolders: - - Assets - m_LastFoldersGridSize: -1 - m_LastProjectPath: /Users/danielbrauer/Unity Projects/New Unity Project 47 - m_IsLocked: 0 - m_FolderTreeState: - scrollPos: {x: 0, y: 0} - m_SelectedIDs: ee240000 - m_LastClickedID: 9454 - m_ExpandedIDs: ee24000000ca9a3bffffff7f - m_RenameOverlay: - m_UserAcceptedRename: 0 - m_Name: - m_OriginalName: - m_EditFieldRect: - serializedVersion: 2 - x: 0 - y: 0 - width: 0 - height: 0 - m_UserData: 0 - m_IsWaitingForDelay: 0 - m_IsRenaming: 0 - m_OriginalEventType: 11 - m_IsRenamingFilename: 1 - m_ClientGUIView: {fileID: 0} - m_SearchString: - m_CreateAssetUtility: - m_EndAction: {fileID: 0} - m_InstanceID: 0 - m_Path: - m_Icon: {fileID: 0} - m_ResourceFile: - m_AssetTreeState: - scrollPos: {x: 0, y: 0} - m_SelectedIDs: 68fbffff - m_LastClickedID: 0 - m_ExpandedIDs: ee240000 - m_RenameOverlay: - m_UserAcceptedRename: 0 - m_Name: - m_OriginalName: - m_EditFieldRect: - serializedVersion: 2 - x: 0 - y: 0 - width: 0 - height: 0 - m_UserData: 0 - m_IsWaitingForDelay: 0 - m_IsRenaming: 0 - m_OriginalEventType: 11 - m_IsRenamingFilename: 1 - m_ClientGUIView: {fileID: 0} - m_SearchString: - m_CreateAssetUtility: - m_EndAction: {fileID: 0} - m_InstanceID: 0 - m_Path: - m_Icon: {fileID: 0} - m_ResourceFile: - m_ListAreaState: - m_SelectedInstanceIDs: 68fbffff - m_LastClickedInstanceID: -1176 - m_HadKeyboardFocusLastEvent: 0 - m_ExpandedInstanceIDs: c6230000 - m_RenameOverlay: - m_UserAcceptedRename: 0 - m_Name: - m_OriginalName: - m_EditFieldRect: - serializedVersion: 2 - x: 0 - y: 0 - width: 0 - height: 0 - m_UserData: 0 - m_IsWaitingForDelay: 0 - m_IsRenaming: 0 - m_OriginalEventType: 11 - m_IsRenamingFilename: 1 - m_ClientGUIView: {fileID: 0} - m_CreateAssetUtility: - m_EndAction: {fileID: 0} - m_InstanceID: 0 - m_Path: - m_Icon: {fileID: 0} - m_ResourceFile: - m_NewAssetIndexInList: -1 - m_ScrollPosition: {x: 0, y: 0} - m_GridSize: 64 - m_DirectoriesAreaWidth: 110 ---- !u!114 &15 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12015, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_AutoRepaintOnSceneChange: 1 - m_MinSize: {x: 200, y: 200} - m_MaxSize: {x: 4000, y: 4000} - m_TitleContent: - m_Text: Game - m_Image: {fileID: -2087823869225018852, guid: 0000000000000000d000000000000000, - type: 0} - m_Tooltip: - m_DepthBufferBits: 32 - m_Pos: - serializedVersion: 2 - x: 0 - y: 19 - width: 971 - height: 421 - m_MaximizeOnPlay: 0 - m_Gizmos: 0 - m_Stats: 0 - m_SelectedSizes: 00000000000000000000000000000000000000000000000000000000000000000000000000000000 - m_TargetDisplay: 0 - m_ZoomArea: - m_HRangeLocked: 0 - m_VRangeLocked: 0 - m_HBaseRangeMin: -242.75 - m_HBaseRangeMax: 242.75 - m_VBaseRangeMin: -101 - m_VBaseRangeMax: 101 - m_HAllowExceedBaseRangeMin: 1 - m_HAllowExceedBaseRangeMax: 1 - m_VAllowExceedBaseRangeMin: 1 - m_VAllowExceedBaseRangeMax: 1 - m_ScaleWithWindow: 0 - m_HSlider: 0 - m_VSlider: 0 - m_IgnoreScrollWheelUntilClicked: 0 - m_EnableMouseInput: 1 - m_EnableSliderZoom: 0 - m_UniformScale: 1 - m_UpDirection: 1 - m_DrawArea: - serializedVersion: 2 - x: 0 - y: 17 - width: 971 - height: 404 - m_Scale: {x: 2, y: 2} - m_Translation: {x: 485.5, y: 202} - m_MarginLeft: 0 - m_MarginRight: 0 - m_MarginTop: 0 - m_MarginBottom: 0 - m_LastShownAreaInsideMargins: - serializedVersion: 2 - x: -242.75 - y: -101 - width: 485.5 - height: 202 - m_MinimalGUI: 1 - m_defaultScale: 2 - m_TargetTexture: {fileID: 0} - m_CurrentColorSpace: 0 - m_LastWindowPixelSize: {x: 1942, y: 842} - m_ClearInEditMode: 1 - m_NoCameraWarning: 1 - m_LowResolutionForAspectRatios: 01000000000100000100 ---- !u!114 &16 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12013, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_AutoRepaintOnSceneChange: 1 - m_MinSize: {x: 200, y: 200} - m_MaxSize: {x: 4000, y: 4000} - m_TitleContent: - m_Text: Scene - m_Image: {fileID: 2318424515335265636, guid: 0000000000000000d000000000000000, - type: 0} - m_Tooltip: - m_DepthBufferBits: 32 - m_Pos: - serializedVersion: 2 - x: 0 - y: 19 - width: 971 - height: 445 - m_SceneLighting: 1 - lastFramingTime: 0 - m_2DMode: 0 - m_isRotationLocked: 0 - m_AudioPlay: 0 - m_Position: - m_Target: {x: 0, y: 0, z: 0} - speed: 2 - m_Value: {x: 0, y: 0, z: 0} - m_RenderMode: 0 - m_ValidateTrueMetals: 0 - m_SceneViewState: - showFog: 1 - showMaterialUpdate: 0 - showSkybox: 1 - showFlares: 1 - showImageEffects: 1 - grid: - xGrid: - m_Target: 0 - speed: 2 - m_Value: 0 - yGrid: - m_Target: 1 - speed: 2 - m_Value: 1 - zGrid: - m_Target: 0 - speed: 2 - m_Value: 0 - m_Rotation: - m_Target: {x: -0.08717229, y: 0.89959055, z: -0.21045254, w: -0.3726226} - speed: 2 - m_Value: {x: -0.08717229, y: 0.89959055, z: -0.21045254, w: -0.3726226} - m_Size: - m_Target: 10 - speed: 2 - m_Value: 10 - m_Ortho: - m_Target: 0 - speed: 2 - m_Value: 0 - m_LastSceneViewRotation: {x: 0, y: 0, z: 0, w: 0} - m_LastSceneViewOrtho: 0 - m_ReplacementShader: {fileID: 0} - m_ReplacementString: - m_LastLockedObject: {fileID: 0} - m_ViewIsLockedToObject: 0 ---- !u!114 &17 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12061, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_AutoRepaintOnSceneChange: 0 - m_MinSize: {x: 200, y: 200} - m_MaxSize: {x: 4000, y: 4000} - m_TitleContent: - m_Text: Hierarchy - m_Image: {fileID: -590624980919486359, guid: 0000000000000000d000000000000000, - type: 0} - m_Tooltip: - m_DepthBufferBits: 0 - m_Pos: - serializedVersion: 2 - x: 2 - y: 19 - width: 286 - height: 445 - m_TreeViewState: - scrollPos: {x: 0, y: 0} - m_SelectedIDs: 68fbffff - m_LastClickedID: -1176 - m_ExpandedIDs: 7efbffff00000000 - m_RenameOverlay: - m_UserAcceptedRename: 0 - m_Name: - m_OriginalName: - m_EditFieldRect: - serializedVersion: 2 - x: 0 - y: 0 - width: 0 - height: 0 - m_UserData: 0 - m_IsWaitingForDelay: 0 - m_IsRenaming: 0 - m_OriginalEventType: 11 - m_IsRenamingFilename: 0 - m_ClientGUIView: {fileID: 0} - m_SearchString: - m_ExpandedScenes: - - - m_CurrenRootInstanceID: 0 - m_Locked: 0 - m_CurrentSortingName: TransformSorting diff --git a/Assets/TutorialInfo/Layout.wlt.meta b/Assets/TutorialInfo/Layout.wlt.meta deleted file mode 100644 index 92800c6..0000000 --- a/Assets/TutorialInfo/Layout.wlt.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 96e98bffd05413f489cd9851fc862d1f -timeCreated: 1487337779 -licenseType: Store -DefaultImporter: - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/TutorialInfo/Scripts.meta b/Assets/TutorialInfo/Scripts.meta deleted file mode 100644 index 02da605..0000000 --- a/Assets/TutorialInfo/Scripts.meta +++ /dev/null @@ -1,9 +0,0 @@ -fileFormatVersion: 2 -guid: 5a9bcd70e6a4b4b05badaa72e827d8e0 -folderAsset: yes -timeCreated: 1475835190 -licenseType: Store -DefaultImporter: - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/TutorialInfo/Scripts/Editor.meta b/Assets/TutorialInfo/Scripts/Editor.meta deleted file mode 100644 index f59f099..0000000 --- a/Assets/TutorialInfo/Scripts/Editor.meta +++ /dev/null @@ -1,9 +0,0 @@ -fileFormatVersion: 2 -guid: 3ad9b87dffba344c89909c6d1b1c17e1 -folderAsset: yes -timeCreated: 1475593892 -licenseType: Store -DefaultImporter: - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/TutorialInfo/Scripts/Editor/ReadmeEditor.cs b/Assets/TutorialInfo/Scripts/Editor/ReadmeEditor.cs deleted file mode 100644 index ad55eca..0000000 --- a/Assets/TutorialInfo/Scripts/Editor/ReadmeEditor.cs +++ /dev/null @@ -1,242 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEngine; -using UnityEditor; -using System; -using System.IO; -using System.Reflection; - -[CustomEditor(typeof(Readme))] -[InitializeOnLoad] -public class ReadmeEditor : Editor -{ - static string s_ShowedReadmeSessionStateName = "ReadmeEditor.showedReadme"; - - static string s_ReadmeSourceDirectory = "Assets/TutorialInfo"; - - const float k_Space = 16f; - - static ReadmeEditor() - { - EditorApplication.delayCall += SelectReadmeAutomatically; - } - - static void RemoveTutorial() - { - if (EditorUtility.DisplayDialog("Remove Readme Assets", - - $"All contents under {s_ReadmeSourceDirectory} will be removed, are you sure you want to proceed?", - "Proceed", - "Cancel")) - { - if (Directory.Exists(s_ReadmeSourceDirectory)) - { - FileUtil.DeleteFileOrDirectory(s_ReadmeSourceDirectory); - FileUtil.DeleteFileOrDirectory(s_ReadmeSourceDirectory + ".meta"); - } - else - { - Debug.Log($"Could not find the Readme folder at {s_ReadmeSourceDirectory}"); - } - - var readmeAsset = SelectReadme(); - if (readmeAsset != null) - { - var path = AssetDatabase.GetAssetPath(readmeAsset); - FileUtil.DeleteFileOrDirectory(path + ".meta"); - FileUtil.DeleteFileOrDirectory(path); - } - - AssetDatabase.Refresh(); - } - } - - static void SelectReadmeAutomatically() - { - if (!SessionState.GetBool(s_ShowedReadmeSessionStateName, false)) - { - var readme = SelectReadme(); - SessionState.SetBool(s_ShowedReadmeSessionStateName, true); - - if (readme && !readme.loadedLayout) - { - LoadLayout(); - readme.loadedLayout = true; - } - } - } - - static void LoadLayout() - { - var assembly = typeof(EditorApplication).Assembly; - var windowLayoutType = assembly.GetType("UnityEditor.WindowLayout", true); - var method = windowLayoutType.GetMethod("LoadWindowLayout", BindingFlags.Public | BindingFlags.Static); - method.Invoke(null, new object[] { Path.Combine(Application.dataPath, "TutorialInfo/Layout.wlt"), false }); - } - - static Readme SelectReadme() - { - var ids = AssetDatabase.FindAssets("Readme t:Readme"); - if (ids.Length == 1) - { - var readmeObject = AssetDatabase.LoadMainAssetAtPath(AssetDatabase.GUIDToAssetPath(ids[0])); - - Selection.objects = new UnityEngine.Object[] { readmeObject }; - - return (Readme)readmeObject; - } - else - { - Debug.Log("Couldn't find a readme"); - return null; - } - } - - protected override void OnHeaderGUI() - { - var readme = (Readme)target; - Init(); - - var iconWidth = Mathf.Min(EditorGUIUtility.currentViewWidth / 3f - 20f, 128f); - - GUILayout.BeginHorizontal("In BigTitle"); - { - if (readme.icon != null) - { - GUILayout.Space(k_Space); - GUILayout.Label(readme.icon, GUILayout.Width(iconWidth), GUILayout.Height(iconWidth)); - } - GUILayout.Space(k_Space); - GUILayout.BeginVertical(); - { - - GUILayout.FlexibleSpace(); - GUILayout.Label(readme.title, TitleStyle); - GUILayout.FlexibleSpace(); - } - GUILayout.EndVertical(); - GUILayout.FlexibleSpace(); - } - GUILayout.EndHorizontal(); - } - - public override void OnInspectorGUI() - { - var readme = (Readme)target; - Init(); - - foreach (var section in readme.sections) - { - if (!string.IsNullOrEmpty(section.heading)) - { - GUILayout.Label(section.heading, HeadingStyle); - } - - if (!string.IsNullOrEmpty(section.text)) - { - GUILayout.Label(section.text, BodyStyle); - } - - if (!string.IsNullOrEmpty(section.linkText)) - { - if (LinkLabel(new GUIContent(section.linkText))) - { - Application.OpenURL(section.url); - } - } - - GUILayout.Space(k_Space); - } - - if (GUILayout.Button("Remove Readme Assets", ButtonStyle)) - { - RemoveTutorial(); - } - } - - bool m_Initialized; - - GUIStyle LinkStyle - { - get { return m_LinkStyle; } - } - - [SerializeField] - GUIStyle m_LinkStyle; - - GUIStyle TitleStyle - { - get { return m_TitleStyle; } - } - - [SerializeField] - GUIStyle m_TitleStyle; - - GUIStyle HeadingStyle - { - get { return m_HeadingStyle; } - } - - [SerializeField] - GUIStyle m_HeadingStyle; - - GUIStyle BodyStyle - { - get { return m_BodyStyle; } - } - - [SerializeField] - GUIStyle m_BodyStyle; - - GUIStyle ButtonStyle - { - get { return m_ButtonStyle; } - } - - [SerializeField] - GUIStyle m_ButtonStyle; - - void Init() - { - if (m_Initialized) - return; - m_BodyStyle = new GUIStyle(EditorStyles.label); - m_BodyStyle.wordWrap = true; - m_BodyStyle.fontSize = 14; - m_BodyStyle.richText = true; - - m_TitleStyle = new GUIStyle(m_BodyStyle); - m_TitleStyle.fontSize = 26; - - m_HeadingStyle = new GUIStyle(m_BodyStyle); - m_HeadingStyle.fontStyle = FontStyle.Bold; - m_HeadingStyle.fontSize = 18; - - m_LinkStyle = new GUIStyle(m_BodyStyle); - m_LinkStyle.wordWrap = false; - - // Match selection color which works nicely for both light and dark skins - m_LinkStyle.normal.textColor = new Color(0x00 / 255f, 0x78 / 255f, 0xDA / 255f, 1f); - m_LinkStyle.stretchWidth = false; - - m_ButtonStyle = new GUIStyle(EditorStyles.miniButton); - m_ButtonStyle.fontStyle = FontStyle.Bold; - - m_Initialized = true; - } - - bool LinkLabel(GUIContent label, params GUILayoutOption[] options) - { - var position = GUILayoutUtility.GetRect(label, LinkStyle, options); - - Handles.BeginGUI(); - Handles.color = LinkStyle.normal.textColor; - Handles.DrawLine(new Vector3(position.xMin, position.yMax), new Vector3(position.xMax, position.yMax)); - Handles.color = Color.white; - Handles.EndGUI(); - - EditorGUIUtility.AddCursorRect(position, MouseCursor.Link); - - return GUI.Button(position, label, LinkStyle); - } -} diff --git a/Assets/TutorialInfo/Scripts/Editor/ReadmeEditor.cs.meta b/Assets/TutorialInfo/Scripts/Editor/ReadmeEditor.cs.meta deleted file mode 100644 index f038618..0000000 --- a/Assets/TutorialInfo/Scripts/Editor/ReadmeEditor.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: 476cc7d7cd9874016adc216baab94a0a -timeCreated: 1484146680 -licenseType: Store -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/TutorialInfo/Scripts/Readme.cs b/Assets/TutorialInfo/Scripts/Readme.cs deleted file mode 100644 index 95f6269..0000000 --- a/Assets/TutorialInfo/Scripts/Readme.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using UnityEngine; - -public class Readme : ScriptableObject -{ - public Texture2D icon; - public string title; - public Section[] sections; - public bool loadedLayout; - - [Serializable] - public class Section - { - public string heading, text, linkText, url; - } -} diff --git a/Assets/TutorialInfo/Scripts/Readme.cs.meta b/Assets/TutorialInfo/Scripts/Readme.cs.meta deleted file mode 100644 index 935153f..0000000 --- a/Assets/TutorialInfo/Scripts/Readme.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: fcf7219bab7fe46a1ad266029b2fee19 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: - - icon: {instanceID: 0} - executionOrder: 0 - icon: {fileID: 2800000, guid: a186f8a87ca4f4d3aa864638ad5dfb65, type: 3} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/UI/Prefabs/ChatView.prefab b/Assets/UI/Prefabs/ChatView.prefab index 70a9993..042972b 100644 --- a/Assets/UI/Prefabs/ChatView.prefab +++ b/Assets/UI/Prefabs/ChatView.prefab @@ -11,6 +11,7 @@ GameObject: - component: {fileID: 1193639241046649271} - component: {fileID: 5443803679294381057} - component: {fileID: 900044648925520396} + - component: {fileID: 7731201516897828228} m_Layer: 5 m_Name: ChatView m_TagString: Untagged @@ -76,6 +77,28 @@ MonoBehaviour: m_OnValueChanged: m_PersistentCalls: m_Calls: [] +--- !u!114 &7731201516897828228 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1198648032041697651} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 30700e2214ca700469760eef5b0069ea, type: 3} + m_Name: + m_EditorClassIdentifier: + _scrollRect: {fileID: 900044648925520396} + _gridLayoutGroup: {fileID: 6992329501726019127} + _contentSizeFitter: {fileID: 2327760123492423530} + _chatBubblePrefab: {fileID: 3575337311992857127, guid: 7151716ef12e2424d866e4db41b1f6f9, type: 3} + _bubbleContainer: {fileID: 5196526590166986169} + _enableQueueAnimation: 1 + _queueAnimationDelay: 0.1 + _maxBubbles: 20 + _autoCleanup: 1 + _cleanupThreshold: 15 --- !u!1 &1644047271142599422 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Voice.mixer b/Assets/Voice.mixer new file mode 100644 index 0000000..84960c3 --- /dev/null +++ b/Assets/Voice.mixer @@ -0,0 +1,69 @@ +%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/Assets/Voice.mixer.meta b/Assets/Voice.mixer.meta new file mode 100644 index 0000000..3f7bc74 --- /dev/null +++ b/Assets/Voice.mixer.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3bf46f7daa3260847aa1b964ad2b01a7 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 24100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Docs/Conventions/Branching.md b/Docs/Conventions/Branching.md new file mode 100644 index 0000000..c3df9c5 --- /dev/null +++ b/Docs/Conventions/Branching.md @@ -0,0 +1,9 @@ +# 브랜치 전략 + +## 기본 원칙 +- `develop` 브랜치를 기준으로 작업합니다. +- 기능 단위 브랜치를 생성합니다. 예: `feature/chat-ui`, `fix/network-error`. + +## 머지 정책 +- PR 머지 전 테스트 통과가 필수입니다. +- 문서 및 설명은 `Docs/` 폴더에 저장합니다. diff --git a/Docs/Conventions/CodeStyle_CSharp.md b/Docs/Conventions/CodeStyle_CSharp.md new file mode 100644 index 0000000..bc11cc4 --- /dev/null +++ b/Docs/Conventions/CodeStyle_CSharp.md @@ -0,0 +1,23 @@ +# C# 코드 스타일 + +## C# 식별자 +| 대상 | 규칙 | 예시 | +|---|---|---| +| 클래스/구조체/열거형/속성/메서드 | PascalCase | ChatManager, MessageType, LoadConfig | +| 인터페이스 | IPascalCase | IChatService | +| 상수(const) | UPPER_SNAKE_CASE | DEFAULT_TIMEOUT_MS | +| 비공개 필드 | _camelCase | _sessionId | +| 직렬화 비공개 필드 | _camelCase | _gain | +| 매개변수/지역변수 | camelCase | messageText | +| 제네릭 매개변수 | TName | TItem, TResponse | + +## C# 패턴 +| 항목 | 규칙 | 예시 | +|---|---|---| +| 불리언 | Is/Has/Can/Should 접두사 | IsConnected, HasData | +| 이벤트 이름 | OnXxx / XxxChanged | OnMessageReceived, VolumeChanged | +| 비동기 메서드 | Async 접미사, ct 매개변수 | LoadAsync(CancellationToken ct) | +| 네임스페이스 | 폴더 구조 반영, 루트 ProjectVG | ProjectVG.Domain.Chat | +| 파일명 | 공개 루트 타입명과 동일 | ChatManager.cs | + +공개 API는 명령형 동사를 사용합니다(Initialize/Apply/Load 등). diff --git a/Docs/Conventions/Commit_Message.md b/Docs/Conventions/Commit_Message.md new file mode 100644 index 0000000..c8b236e --- /dev/null +++ b/Docs/Conventions/Commit_Message.md @@ -0,0 +1,41 @@ +# 커밋 메시지 규칙 + +## 구성 요소 +| 항목 | 필수 | 설명 | 예시 | +|---|---|---|---| +| Type | 예 | Commit의 종류(소문자). 이모지 사용 금지 | `feat`, `fix`, `refactor` | +| Scope | 선택 | Commit의 범위(기능/함수/페이지/API 등) | `login`, `signup`, `network` | +| Subject | 예 | 제목은 간결하게, 명사형 어미로 종료 | `회원가입 기능 추가` | +| Body | 선택 | 왜/어떻게 변경했는지 요약 | 변경 배경, 접근, 영향 범위 | +| Footer | 선택 | 이슈 트래킹/참고 사항 | `Closes #123` | + +## 헤더 예시 +``` +(optional scope): +``` + +- 주의: 이모지 사용 금지, type은 전부 소문자 (예: `feat:`, `fix(login):`) + +## 예시 +| type | 예시 메시지 | +|---|---| +| feat | `feat(login/signup): 회원가입 기능 추가` | +| fix | `fix(login): 로그인 기능 수정` | +| style | `style: 코드 포맷 변경` | +| refactor | `refactor(signup): 회원 가입 로직 개선` | +| file | `file: 이미지 파일 추가` | +| test | `test: 테스트 코드 추가` | +| docs | `docs: README.md 업데이트` | +| remove | `remove: 사용하지 않는 파일 제거` | +| ci | `ci: 자동 배포 스크립트 변동` | +| release | `release: 릴리즈 버전 1.0.3` | +| chore | `chore: 설정파일 수정` | + +## 메세지 구조 +``` +(optional scope): + +[optional body] + +[optional footer(s)] +``` diff --git a/Docs/Conventions/PR_Notes.md b/Docs/Conventions/PR_Notes.md new file mode 100644 index 0000000..8dba869 --- /dev/null +++ b/Docs/Conventions/PR_Notes.md @@ -0,0 +1,17 @@ +# PR 유의 사항 + +## 작성 팁 +- 작은 단위로 제출: 한 PR = 한 목적 +- 제목은 `type(scope): subject` 규칙 사용 +- 설명은 왜/어떻게/영향/검증 순으로 간결히 +- 큰 리네이밍/포맷 변경은 선행 PR로 분리 + +## 피해야 할 것 +- 스타일/리팩터/기능을 한 PR에 혼합 +- 불필요한 파일 추가/삭제 +- 테스트/빌드 실패 상태로 제출 +- 모호한 설명("수정", "변경")만 있는 경우 + +## 참고 +- 브레이킹 체인지는 명시하고 마이그레이션 절차 포함 +- 관련 문서/이슈 링크를 본문 하단에 추가 diff --git a/Docs/Conventions/PR_Review.md b/Docs/Conventions/PR_Review.md new file mode 100644 index 0000000..23fcda2 --- /dev/null +++ b/Docs/Conventions/PR_Review.md @@ -0,0 +1,30 @@ +# PR 가이드 + +## 목적 +- 변경 의도와 영향 범위를 빠르게 이해하고 안전하게 병합하기 위함 + +## 제출 기준 +- 테스트가 존재하는 경우 모두 통과해야 합니다 +- 린트/빌드 오류가 없어야 합니다 +- 불필요한 파일/포맷 변경을 포함하지 않습니다(필요 시 선행 PR로 분리) + +## 제목 규칙 +- 형식(권장): `type(scope): subject` + - 예: `feat(chat): 메시지 전송 애니메이션 추가` + - 소문자 type 사용, 명사형 subject + +## 설명 구성(간결) +- 배경/문제: 왜 이 변경이 필요한가 +- 해결: 어떻게 해결했는가(핵심 1~3줄) +- 영향 범위: 시스템/모듈/성능/보안 +- 검증: 재현 및 확인 방법 +- 롤백: 실패 시 되돌리는 방법(선택) + +## 링크 +- 관련 이슈(선택): `Closes #123`, `Relates-to #456` +- 관련 문서: `Docs/` 내 가이드/설계 링크 + +## 머지 기준 +- CI 전 단계 통과 필수(빌드/테스트/린트) +- CodeRabbit 자동 리뷰 코멘트 해결(있을 경우) +- 브레이킹 체인지 포함 시 주의 문구와 마이그레이션 절차 명시 diff --git a/Docs/Conventions/README.md b/Docs/Conventions/README.md new file mode 100644 index 0000000..778d9be --- /dev/null +++ b/Docs/Conventions/README.md @@ -0,0 +1,16 @@ +# Conventions + +- 목적: 팀 공통 규칙의 단일 참조 지점 +- 적용 범위: C# 코드 스타일, Unity 에셋 네이밍, 브랜치/커밋/PR 규칙 + +## 문서 목록 +- [C# 코드 스타일](./CodeStyle_CSharp.md) +- [Unity 에셋 네이밍](./Unity_Asset_Naming.md) +- [브랜치 전략](./Branching.md) +- [커밋 메시지 규칙](./Commit_Message.md) +- [PR 리뷰 가이드](./PR_Review.md) + +## 관련 가이드 +- `Assets/Docs/Guides/Unity_Naming_Conventions.md` +- `Assets/Docs/Guides/ProjectVG_Structure_Guide.md` +- `Assets/Docs/Guides/Manager_System_Guide.md` diff --git a/Docs/Conventions/Unity_Asset_Naming.md b/Docs/Conventions/Unity_Asset_Naming.md new file mode 100644 index 0000000..a762fd3 --- /dev/null +++ b/Docs/Conventions/Unity_Asset_Naming.md @@ -0,0 +1,28 @@ +# Unity 에셋 네이밍 + +## Unity 자산 +| 타입 | 규칙 | 예시 | +|---|---|---| +| 씬 | PascalCase, 역할 접미 허용(Main/Boot/Loading/Sample) | MainScene, LoadingScene | +| 프리팹 | PascalCase, UI 루트 접두사 Panel/Dialog/HUD | PanelChat, DialogConfirm | +| SO 에셋 | 타입명 기반 + 키 | NetworkConfig_Prod | +| Addressables | Domain/Category/Name | UI/Panels/PanelChat | +| 폴더 | PascalCase 단수형 | Domain/Character/Model | + +## UI 위젯 +| 위치 | 규칙 | 예시 | +|---|---|---| +| Hierarchy 이름 | 접두사 사용 Panel/Btn/Img/Txt/Input/Scroll/Toggle/Slider/Dropdown | PanelChat, BtnSend | +| 코드 필드명 | 의미 중심 camelCase | sendButton, titleText | + +## 예시(요약) +- MonoBehaviour: ScreenTapManager, Live2DModelManagerFacade +- ScriptableObject: Live2DModelConfig, AppEnvironmentConfig +- Prefab: PanelChat, ChatBubbleUI, AudioInputView +- Scene: MainScene, Live2DScene + +## 금지/주의 +- 범용어 남용: Data/Util/Helper/Manager(무의미 사용) +- 약어 조합: Cfg/Ctrllr/Svc +- 파일명 ≠ 타입명 불일치 +- 부정형 이벤트/플래그: NotReady → 긍정형 ShouldWait/IsReady diff --git a/ProjectVG_Live2D_Assignment.md b/ProjectVG_Live2D_Assignment.md deleted file mode 100644 index fb8f29e..0000000 --- a/ProjectVG_Live2D_Assignment.md +++ /dev/null @@ -1,75 +0,0 @@ -from pathlib import Path - -# ProjectVG 기술과제: Live2D 모바일 빌드 - - -## 🎯 목표 -Unity Live2D SDK를 사용하여 **Android 또는 iOS 애플리케이션**으로 빌드하여 실행하는 것이 최종 요구사항입니다. - ---- - -## 📌 제약 사항 - -1. 반드시 `feature/live2d-build-assignment` 브랜치에서 작업해야 합니다. -2. 기본 제공 캐릭터가 마음에 들지 않으면 **원하는 캐릭터로 교체 가능**합니다. - - 기본 제공해드리는 모델은 `/Assts/Domain/Chracter/Model`에 있습니다. - - 단, Live2D SDK를 지원하는 모델이어야 합니다. - - [Live2D 지원 모델 참고 사이트](https://booth.pm/ko/search/live2d?tags%5B%5D=Live2D) -3. **커밋 컨벤션 및 프로젝트 구조 가이드는 반드시 준수**해야 합니다. - - 프로젝트 시작 전 Readme을 필독해주세요 - - 변경 시 사전 공유 필수입니다. -4. **OOP 및 SOLID 원칙을 반드시 준수**해야 합니다. - - Input 이벤트는 InputManager 등 별도의 컴포넌트를 만들어 외부에서 처리해야합니다(캐릭터 오브젝트가 직접 Input을 처리하는 구조는 금지입니다) (필수사항) - - 이벤트 및 액션 전달은 인터페이스 기반(`IInputHandler`, `IMotionTrigger` 등)의 느슨한 결합 구조로 설계되어야 합니다. (선택사항) - ---- - -## ✅ 필수 요구사항 - -### 1. 캐릭터 모션 팔로우 -- 클릭한 위치를 기준으로 캐릭터의 시선이 자연스럽게 따라가야 합니다. -- SDK에서 제공하는 `LookAt` 또는 `Parameter` API를 사용하여 구현합니다. -- 단순한 위치 이동이 아닌 얼굴 방향 회전으로 표현되어야 하며, 최대 회전 각도 제한이 적용되어야 합니다. - -### 2. 이벤트 기반 캐릭터 반응 -- 특정 Input 이벤트 발생 시, Live2D 캐릭터의 **표정 또는 모션**을 변경해야 합니다. -- 예시: - - 특정 부위를 터치 → 표정 변화 - - 버튼 클릭 → 특정 행동 실행 -- 터치 이벤트는 모델의 `HitArea`를 통해 특정 부위를 식별 후 처리되어야 합니다. -- 표정은 Expression, 모션은 Motion으로 분리하여 구성해야 합니다. - - -### 🔊 오디오 액션 -- 특정 Input 이벤트 발생 시 **소리를 출력**해야 합니다. - - 음성, 효과음 모두 허용되며, **3MB 이하의 WAV 파일**만 사용 가능합니다. -- AudioManager 등을 통한 재생 시스템 설계 권장 (싱글톤 or 이벤트 기반) -- AudioClip은 Resources 또는 Addressable 방식으로 로드합니다. - - -## 📱 모바일 빌드 및 최적화 - -- 모든 요구사항을 만족하는 **APK 생성** 후 테스트 -- 모바일 환경에서 다음 조건을 만족해야 합니다: - - **프레임 드랍 없음 (60fps 이상 유지)** - - **성능 최적화 완료** -- 빌드 타겟 최소 요구: - - Android: API Level 29 (Android 10) 이상 - - iOS: iOS 14 이상 -- Unity Profiler, Frame Debugger 등을 활용하여 성능 측정 - ---- - -## ✨ 추가 요구사항 (선택사항) - -### 💫 상호작용 효과 추가 -- 캐릭터 액션에 **파티클, 빛 효과 등**의 시각 효과를 추가합니다. -- **Unity URP(Universal Render Pipeline)** 기반에서 구현합니다. -- **5MB 이하의 에셋**만 사용 가능합니다. - -### 🎨 그래픽 품질 개선 -- 모바일 및 PC 해상도 대응 필수 (최소 1080x1920 기준, 가능하다면 1440 x 3040까지) -- **안티앨리어싱(MSAA 4x 이상)** 적용을 통해 계단 현상을 줄입니다. - - ---- \ No newline at end of file diff --git a/ignore.conf b/ignore.conf index 301d24b..fcd5697 100644 --- a/ignore.conf +++ b/ignore.conf @@ -1,3 +1,4 @@ +# Unity 기본 디렉토리 Library library Temp @@ -14,18 +15,29 @@ MemoryCaptures memorycaptures Logs logs + +# Asset Store 도구 **/Assets/AssetStoreTools **/assets/assetstoretools + +# Plastic SCM /Assets/Plugins/PlasticSCM* /assets/plugins/PlasticSCM* + +# Unity 임시 파일 *.private *.private.meta ^*.private.[0-9]+$ ^*.private.[0-9]+.meta$ +~UnityDirMonSyncFile~* + +# IDE 디렉토리 .vs .vscode .idea .gradle + +# IDE 생성 파일 ExportedObj .consulo *.csproj @@ -42,27 +54,80 @@ ExportedObj *.mdb *.opendb *.VC.db +*.vsconfig *.pidb.meta *.pdb.meta *.mdb.meta + +# Unity 시스템 파일 sysinfo.txt crashlytics-build.properties + +# 빌드 파일 *.apk *.aab *.app *.unitypackage -~UnityDirMonSyncFile~* + +# Addressables **/Assets/AddressableAssetsData/*/*.bin* **/assets/addressableassetsdata/*/*.bin* **/Assets/StreamingAssets/aa.meta **/assets/streamingassets/*/aa/* + +# 시스템 파일 .DS_Store* Thumbs.db Desktop.ini +# Git 관련 .git/ .gitignore .gitattributes *.iml .idea/ .git + +# 추가된 항목들 +*.blend1 +*.blend1.meta +/[Rr]ecordings/ +*.DotSettings.user +mono_crash.* +*.unitypackage.meta +InitTestScene*.unity* +/ServerData +/[Aa]ssets/AddressableAssetsData/link.xml* +/[Aa]ssets/Addressables_Temp* +*.stacktrace +*.crash +*.xcworkspace +*.xcuserdata +*.pbxuser +*.mode1v3 +*.mode2v3 +*.ipa +*.xcodeproj +*.cmo3 +*.psd +*.log +*.bak +*.meta~ + +# 대용량 미디어 파일 +*.mp4 +*.avi +*.mov +*.wmv +*.flv +*.mkv +*.webm +*.m4a +*.m4v +*.m4b +*.m4r +*.m4p + +# 특정 플러그인 +/Assets/Plugins/FiveMinuteChat +/Assets/Plugins/WebGLTemplates diff --git a/.vsconfig b/vsconfig similarity index 100% rename from .vsconfig rename to vsconfig