Skip to content

Conversation

@ImGdevel
Copy link
Member

@ImGdevel ImGdevel commented Aug 13, 2025

Summary by CodeRabbit

  • 신기능
    • 오디오 시스템 전면 개편: 채널별(BGM/보이스/SFX/UI) 제어, 믹서 연동, 볼륨 저장·이벤트, 비동기 보이스 재생 지원
    • 앱 중앙 관리자(SystemManager) 도입: 초기화·카메라 연동·매니저 상태 공개
    • Live2D 기능 추가: 모델 레지스트리·모델 설정 에셋, 모델 매니저, 테스트 UI 및 감정/동작 DTO·컨트롤러
    • 채팅 UI 개선: ChatBubblePanel로 말풍선 생성·정리·스크롤 향상
  • 개선
    • 입력 처리 강화(광범위 UI 차단 감지, 디버그/레이 시각화, 카메라 업데이트 API)
    • 로딩 UI 자동 진행 및 "앱 시작" 플로우 정렬
    • 문서·다이어그램·가이드 대규모 추가/정리
  • 버그 수정
    • 터치/히트 처리 널 가드 추가로 안정성 향상
  • 스타일/기타
    • 음성 입력 UI 간소화(녹음 진행 표시 제거)

@coderabbitai
Copy link

coderabbitai bot commented Aug 13, 2025

Walkthrough

오디오 시스템을 컨트롤러·믹서 패턴으로 재구성하고 SystemManager로 초기화 흐름을 전환했으며 DI 및 기존 매니저/레지스트리 관련 파일들을 삭제하고 Live2D 도메인, 채팅 패널, 네트워크 및 다수 문서·메타를 추가/수정/삭제했습니다.

Changes

Cohort / File(s) Summary
Audio System Overhaul
Assets/Core/Audio/AudioManager.cs, Assets/Core/Audio/AudioControllerCore.cs, Assets/Core/Audio/IAudioController.cs, Assets/Core/Audio/BGMController.cs, Assets/Core/Audio/VoiceController.cs, Assets/Core/Audio/SFXController.cs, Assets/Core/Audio/UIController.cs, Assets/Core/Audio/NewAudioMixer.mixer, Assets/Core/Audio/*.meta
AudioManager가 컨트롤러·AudioMixer 기반으로 리팩터링됨. AudioControllerCore·IAudioController 및 BGM/Voice/SFX/UI 컨트롤러 추가, 볼륨 퍼시스트·이벤트·플레이 API 확장, NewAudioMixer 자산과 관련 메타 추가.
Manager System Refactor
Assets/Core/Managers/SystemManager.cs, Assets/Core/Managers/SystemManager.cs.meta, Assets/Core/Loading/LoadingManager.cs, Assets/Core/Loading/LoadingUI.cs, (removed: Assets/Core/Managers/GameManager.cs, Assets/Core/Managers/InitializationManager.cs, Assets/Core/Managers/ManagerRegistry.cs, Assets/Core/Managers/DependencyManager.cs)
GameManager/초기화 파이프라인 삭제. SystemManager 신설(싱글턴·카메라 관리·Initialize/InitializeAppAsync·OnAppInitialized 등), LoadingManager 명칭·흐름(StartApp) 및 자동 진행 기능 추가. 여러 기존 매니저 파일 삭제.
Dependency Injection Removal
Assets/Core/Attributes/InjectAttribute.cs(-), Assets/Core/DI/DIContainer.cs(-), 관련 .meta 삭제
리플렉션 기반 InjectAttribute와 DIContainer 제거로 런타임 DI 코드 삭제.
Input & Interaction
Assets/Core/Input/ScreenTapManager.cs, Assets/Domain/Character/Script/Component/CubismHitHandler.cs
IInputProvider에 IsUIBlockingInput 추가, UI 차단 판정 확장, 카메라/디버그 API 및 탭 상태 API 추가. CubismHitHandler에 널 가드와 경고 로깅 추가.
Live2D Domain Additions
Assets/Domain/Character/Script/Config/Live2DModelConfig.cs, .../Live2DModelRegistry.cs, .../Controller/*, .../Manager/Live2DModelManager.cs, .../Manager/ILive2DCharacterManager.cs, .../Test/Live2DModelTest.cs, Assets/Resources/Character.meta
Live2D용 ScriptableObject 구성(모델·감정·동작 매핑), 레지스트리, 인터페이스·컨트롤러·Applier·Parameter·Facade·테스트 및 Live2D 모델 로드/액티베이트/언로드/가시성 제어 추가.
Chat UI Refactor
Assets/Domain/Chat/Service/ChatManager.cs, Assets/Domain/Chat/View/ChatBubblePanel.cs, Assets/Domain/Chat/View/ChatBubbleUI.cs, (removed: Assets/Domain/Chat/Service/ChatBubbleManager.cs)
ChatBubbleManager 제거 후 ChatBubblePanel 추가로 대체. ChatManager가 ChatBubblePanel 및 AudioManager 사용하도록 변경, ChatBubbleUI 시그니처 갱신.
Networking & Services
Assets/Infrastructure/Network/WebSocket/WebSocketManager.cs, Assets/Infrastructure/Network/Services/SessionManager.cs, Assets/Infrastructure/Network/Http/HttpApiClient.cs
IManager 의존 제거, 수동 Initialize 계약 도입. WebSocket 메시지 버퍼링 및 세션/채팅 이벤트 확장, SessionManager에 웹소켓 기반 상태·이벤트 노출, HttpApiClient에 Initialize/Shutdown 및 multipart form-data 업로드 재구성.
Docs & Diagrams
Assets/Docs/Design/*, Assets/Docs/Guides/*, Assets/Docs/diagrams/*, Docs/Conventions/*
SystemManager 전환 관련 문서 갱신, Live2D 서비스 설계 문서·다이어그램 및 여러 가이드(네이밍/브랜치/PR/커밋 등) 추가, 일부 기존 문서 삭제/이동.
Asset/Meta / Misc
여러 .meta 추가/갱신/삭제 (예: Assets/App/Scenes/Live2d Sence.unity.meta, Assets/Core/**/*.meta, Assets/Docs/**/*.meta), .gitignore, .plasticignore, 기타 에디터 스크립트 및 설정 파일
다수 Unity .meta 파일 GUID 변경·신규 생성·삭제, .gitignore/.plasticignore 한국어화 및 확장, 이미지/다이어그램 임포터 메타 업데이트, 에디터 메뉴 스크립트 추가 및 기타 설정 파일 변경.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant SystemManager
  participant Loading as LoadingManager
  participant WS as WebSocketManager
  participant Session as SessionManager
  participant Audio as AudioManager
  participant Input as ScreenTapManager

  User->>SystemManager: Initialize()
  SystemManager->>Loading: BeginLoadingUI()
  SystemManager->>SystemManager: UpdateCamera()/SetCamera(Camera.main)
  SystemManager->>Input: Initialize(Camera)
  SystemManager->>WS: Initialize()
  SystemManager->>Session: Initialize(WS)
  SystemManager->>Audio: Initialize() / Mixer 구성
  SystemManager-->>Loading: OnAppInitialized 이벤트 발행
Loading
sequenceDiagram
  participant Server
  participant WS as WebSocketManager
  participant Chat as ChatManager
  participant Panel as ChatBubblePanel
  participant Audio as AudioManager

  Server-->>WS: 채팅/세션 메시지 수신
  WS-->>Chat: OnChatMessageReceived
  Chat->>Panel: CreateBubble(actor, text)
  alt 음성 포함
    Chat->>Audio: PlayVoiceAsync(voiceData)
    Audio-->>Chat: 재생 완료 이벤트
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90 minutes

Possibly related PRs

  • Feature: recode voice #5Assets/Core/Audio/AudioRecorder.cs 관련 변경을 포함한 PR로, 본 PR의 AudioRecorder 수정(널 체크·필드 제거)과 충돌·동작 연관성이 높습니다.

Poem

껑충 뛴 토끼가 코드 밭을 살피네,
소리와 모델이 손잡고 새 길을 걷네.
DI 굴을 닫고 매니저는 새 집으로,
말풍선은 바뀌고 리듬은 다시 맞춰져요.
🥕✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactory/project-structure

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@ImGdevel ImGdevel force-pushed the refactory/project-structure branch from cbbf5cb to be91caa Compare August 13, 2025 14:22
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 58

🔭 Outside diff range comments (7)
Assets/Domain/Chat/View/ChatBubblePanel.cs.meta (1)

1-4: 파일 포맷 오류: 불필요한 라인(“4”) 포함 및 MonoImporter 누락

  • Line 4의 단독 숫자 “4”는 형식 오류입니다. Unity가 메타 파싱/재생성하며 GUID가 변경될 수 있습니다.
  • C# 스크립트 메타에 필요한 MonoImporter 블록이 없습니다.

아래 패치로 잘못된 라인을 제거하고 표준 블록을 추가해 주세요.

-fileFormatVersion: 2
-guid: 30700e2214ca700469760eef5b0069ea
-
-4
+fileFormatVersion: 2
+guid: 30700e2214ca700469760eef5b0069ea
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {fileID: 0}
+  userData:
+  assetBundleName:
+  assetBundleVariant:
Assets/Docs/Design/Initial_Startup_System_Design.md (3)

44-58: 코드 블록에 언어 명시 필요

Line 44의 코드 블록에 언어가 지정되지 않았습니다.

-```
+```text
 SystemManager (DontDestroyOnLoad)
 ├── InitializationPhase 이벤트 발생
 ├── Progress 이벤트 발생
 └── 완료/에러 이벤트 발생
     ↓ (이벤트 구독)
 LoadingManager (Start 씬 전용)
 ├── SystemManager 이벤트 구독
 ├── LoadingUI 제어
 └── SceneTransitionManager 호출
     ↓
 LoadingUI ← LoadingManager
 SceneTransitionManager (싱글톤)

177-187: 시퀀스 다이어그램 언어 명시 필요

Line 177의 코드 블록에도 언어가 지정되지 않았습니다.

-```
+```text
 1. StartScene 로드
 2. LoadingManager 생성 및 SystemManager 이벤트 구독
 3. LoadingManager.StartInitialization() 호출
 4. SystemManager.InitializeAppAsync() 실행
    ├── Phase: InitializingManagers (0% → 40%)
    ├── Phase: ConnectingToServer (40% → 80%)
    └── Phase: LoadingResources (80% → 100%)
 5. 완료 이벤트 발생 → StartButton 활성화
 6. 사용자 버튼 클릭 → SceneTransition 실행

223-237: 파일 구조 다이어그램 언어 명시 필요

Line 223의 코드 블록에도 언어가 지정되지 않았습니다.

-```
+```text
 Assets/
 ├── Core/
 │   ├── Managers/
 │   │   └── SystemManager.cs (확장됨)
 │   └── Loading/
 │       ├── LoadingManager.cs
 │       ├── LoadingUI.cs
 │       └── SceneTransitionManager.cs
 ├── App/
 │   └── Scenes/
 │       └── StartScene.unity
 └── Docs/
     └── Initial_Startup_System_Design.md
Assets/Infrastructure/Network/Services/SessionManager.cs (1)

86-91: DI 관련 로그 메시지 수정 필요

DI를 더 이상 사용하지 않는데 로그 메시지에서 여전히 "DI로 주입"이라는 표현을 사용하고 있습니다. 새로운 초기화 방식을 반영하도록 메시지를 수정해야 합니다.

-            // DI로 주입받은 WebSocketManager 확인
+            // 초기화 시 전달받은 WebSocketManager 확인
             if (_webSocketManager == null)
             {
-                Debug.LogError("[SessionManager] WebSocketManager가 DI로 주입되지 않았습니다. DependencyManager 설정을 확인하세요.");
+                Debug.LogError("[SessionManager] WebSocketManager가 초기화되지 않았습니다. Initialize 메서드를 먼저 호출하세요.");
                 return false;
             }

Line 103-107에도 동일한 수정이 필요합니다:

-                // DI로 주입받은 WebSocketManager 사용
+                // 초기화 시 전달받은 WebSocketManager 사용
                 if (_webSocketManager == null)
                 {
-                    Debug.LogError("[SessionManager] WebSocketManager가 DI로 주입되지 않았습니다.");
+                    Debug.LogError("[SessionManager] WebSocketManager가 초기화되지 않았습니다.");
                     return false;
                 }
Assets/Infrastructure/Network/Http/HttpApiClient.cs (1)

186-199: 세션 ID 주입 로직의 타입 체크 문제

requiresSession이 true일 때 dataChatRequest 타입인지 확인하지만, 실제로는 다른 타입일 수도 있습니다. 이 경우 세션 ID가 주입되지 않습니다.

-            if (requiresSession && data is ChatRequest chatRequest && string.IsNullOrEmpty(chatRequest.sessionId))
+            if (requiresSession)
             {
                 var sessionId = _sessionManager?.SessionId ?? "";
                 if (!string.IsNullOrEmpty(sessionId))
                 {
                     var jsonObject = JsonConvert.DeserializeObject<Dictionary<string, object>>(jsonData);
                     jsonObject["session_id"] = sessionId;
                     jsonData = JsonConvert.SerializeObject(jsonObject);
                 }
                 else
                 {
                     Debug.LogWarning("[HttpApiClient] 세션 연결이 필요한 요청이지만 세션 ID를 획득할 수 없습니다.");
                 }
             }
Assets/Core/Audio/VoiceController.cs (1)

28-35: Update 메서드의 음성 종료 감지 로직 개선 필요

Line 30의 조건문이 AudioSource의 재생 상태와 내부 플래그를 동시에 체크하는데, 이는 타이밍 이슈를 일으킬 수 있습니다.

더 안정적인 음성 종료 감지 로직으로 개선하세요:

 private void Update()
 {
-    if (_isPlaying && !_audioSource.isPlaying && _audioSource.clip != null)
+    if (_isPlaying && _audioSource != null && !_audioSource.isPlaying)
     {
+        // 실제로 재생이 완료된 경우에만 (일시정지가 아닌)
+        if (_audioSource.time >= _audioSource.clip?.length - 0.01f || _audioSource.time == 0)
+        {
             _isPlaying = false;
             OnVoiceFinished?.Invoke();
+        }
     }
 }
🧹 Nitpick comments (51)
Assets/Domain/Character/Script/Manager/Live2DModelManager.cs.meta (1)

1-2: Live2D 스크립트 분리 컴파일(asmdef) 고려

Domain/Character와 Core를 asmdef로 구분하면 컴파일 범위 축소와 의존성 방향(상->하) 관리가 수월합니다. 신규 Live2D 매니저 계층에 assembly definition 도입을 검토해 주세요.

Assets/Resources/Character.meta (1)

1-8: Resources 사용에 대한 아키텍처 재검토 제안

Resources 폴더는 메모리/로딩 제어가 어렵고 대규모 프로젝트에서 스케일링 이슈가 잦습니다. Addressables 전환을 권장합니다. 불가피하게 Resources를 쓴다면:

  • 로드 경로 규칙을 일관되게 정의(예: "Character/{id}/{variant}").
  • 미사용 에셋 UnloadUnusedAssets 호출 지점 관리.
  • 런타임 캐싱/풀링 전략 수립.

현재 폴더 메타 자체는 정상입니다.

Assets/Core/DebugConsole/README_DebugConsole.md (1)

17-30: 마크다운 린트(MD040): 코드펜스 언어 지정 누락

두 개의 코드 블록에 언어 지정이 없어 markdownlint 경고가 발생합니다. 단순 텍스트 블록이면 text(또는 plaintext) 지정 권장.

아래처럼 코드 펜스 시작부에 언어를 추가해 주세요.

-```
+```text
 Canvas (Screen Space - Overlay)
 └── ConsolePanel (GameObject)
 ...
-```
+```
-```
+```text
 LogEntryPrefab (GameObject)
 └── LogText (TextMeshProUGUI)
 ...
-```
+```

Also applies to: 32-41

Assets/Domain/Character/Script/Controller/IEmotionController.cs (3)

1-2: 네임스페이스-디렉터리 불일치

파일 경로는 Controller인데 네임스페이스는 Service입니다. 추후 탐색성과 일관성을 위해 다음 중 하나로 맞추는 것을 권장합니다.

  • 네임스페이스를 ProjectVG.Domain.Character.Controller로 변경
  • 또는 파일을 Service 디렉터리로 이동
-namespace ProjectVG.Domain.Character.Service
+namespace ProjectVG.Domain.Character.Controller

8-10: API 시그니처 개선 제안: 문자열/밀리초 대신 타입 안정성과 일관된 시간 단위를 사용

  • emotion: string 대신 enum(예: EmotionId)으로 교체하면 오타/매직스트링 방지 및 자동완성 이점이 큽니다.
  • durationMs: Unity 관례에 맞춰 초 단위(float seconds) 사용을 권장합니다. 밀리초 int는 변환/정밀도 이슈와 혼선을 부릅니다.

예시:

public enum EmotionId { Neutral, Happy, Sad, Angry /* ... */ }

public interface IEmotionController
{
    void Initialize();
    void SetEmotion(EmotionId emotion, float intensity, float durationSeconds);
    void ClearEmotion();
}

1-12: nullable 컨텍스트 활성화 권장

IAudioController처럼 #nullable enable을 활성화하면 string 등의 null 가능성을 컴파일 타임에 더 잘 관리할 수 있습니다.

+#nullable enable
 namespace ProjectVG.Domain.Character.Service
 {
     /// <summary>
     /// 감정 → Expression 맵핑과 블렌딩을 담당한다.
     /// </summary>
     public interface IEmotionController
     {
         void Initialize();
         void SetEmotion(string emotion, float intensity, int durationMs);
         void ClearEmotion();
     }
 }
Assets/Docs/Guides/ProjectVG_Structure_Guide.md (1)

100-116: 표 앞뒤에 빈 줄을 추가해 markdownlint(MD058) 경고를 해결하세요.

현재 여러 표 앞뒤에 공백 줄이 없어 CI에서 경고가 납니다. 각 표 위/아래에 빈 줄 1줄씩 추가해 주세요.

예시 수정(diff):

-## 최상위 디렉토리
-| 경로 | 용도 | 예시 |
+## 최상위 디렉토리
+
+| 경로 | 용도 | 예시 |
 |---|---|---|
 ...
-| Assets/Samples | 샘플 코드/리소스 | Core/Managers/SampleSystemManager.cs |
+| Assets/Samples | 샘플 코드/리소스 | Core/Managers/SampleSystemManager.cs |
+

Also applies to: 117-125, 126-133, 134-141, 142-147

Assets/Docs/diagrams/live2d_architecture.png.meta (2)

78-80: 다이어그램 가독성 향상: 텍스처 압축 해제 및 스프라이트 피벗 통일 권장

문서용 PNG는 텍스트/선이 많아 압축/필터링에 취약합니다. 현재 TextureCompression=1(압축)과 스프라이트 피벗(0,0)이 설정되어 있어 UI/문서에서 흐릿하거나 어긋날 수 있습니다. Default/Standalone/Android 모두 압축 해제, 스프라이트 피벗을 0.5/0.5로 통일하는 것을 권장합니다.

제안 수정(diff):

-    textureCompression: 1
+    textureCompression: 0
-      pivot: {x: 0, y: 0}
+      pivot: {x: 0.5, y: 0.5}

적용 후 에디터에서 미리보기로 선명도와 정렬이 개선되는지 확인 바랍니다.

Also applies to: 91-93, 105-106, 124-124


49-51: spriteMode=Multiple 설정 검토 — 단일 스프라이트만 사용 시 Single 권장

현재 spriteMode=2(Multiple)지만 실제로는 스프라이트 1개만 정의되어 있습니다. 문서 표시에만 사용된다면 Single 모드가 관리 단순화에 유리합니다. 만약 향후 슬라이싱 계획이 없다면 Single로 전환해 주세요.

전환 전 확인:

  • 이 에셋을 SpriteAtlas/슬라이싱 기능과 연계하지 않는지
  • 런타임에서 서브스프라이트 인덱싱을 사용하지 않는지

Also applies to: 112-151

Assets/Docs/Guides/Manager_System_Guide.md (1)

5-13: 표 앞뒤에 빈 줄 추가로 markdownlint(MD058) 해결

“## 규칙(요약)” 표 상하에 공백 줄을 추가해 주세요.

예시(diff):

-## 규칙(요약)
-| 항목 | 규칙 | 예시 |
+## 규칙(요약)
+
+| 항목 | 규칙 | 예시 |
 |---|---|---|
 ...
-| 설정 자산 | ScriptableObject 참조 | AppEnvironmentConfig |
+| 설정 자산 | ScriptableObject 참조 | AppEnvironmentConfig |
+
Assets/Docs/Design/Component_Analysis_Document.md (2)

285-289: 코드 블록 언어 지정 누락(MD040) — “text” 명시

데이터 흐름/의존성 다이어그램 코드 블록에 언어가 없습니다. “```text”로 명시해 lint 경고를 제거하세요.

예시(diff):

-```
+```text
 사용자 입력 → ScreenTapManager → CubismLookTarget → CubismLookController → Live2D 모델
 터치 종료 → ScreenTapManager → CubismHitHandler → CubismExpressionController → 표정 변경

```diff
-```
+```text
 SystemManager
 ├── ScreenTapManager (싱글톤)
 ├── AudioManager (싱글톤)
 ├── CubismLookTarget
 ├── ModelConfig (ScriptableObject)
 └── CubismHitHandler


Also applies to: 291-299

---

`112-118`: **이벤트 핸들러 명명 컨벤션 불일치 — 메서드명 개선**

“ExpressionChange_Btn”는 컨벤션에 맞지 않습니다. OnXxx 또는 동사형 메서드명으로 교체하세요.


예시(diff):

```diff
-    expressionChangeBtn.onClick.AddListener(hitHandler.ExpressionChange_Btn);
+    expressionChangeBtn.onClick.AddListener(hitHandler.OnExpressionChangeButtonClicked);

HitHandler 내 메서드도 동일하게 변경:

-private void ExpressionChange_Btn() { ... }
+private void OnExpressionChangeButtonClicked() { ... }
Assets/Docs/Guides/Unity_Naming_Conventions.md (2)

4-13: 표 앞뒤에 빈 줄 추가로 markdownlint(MD058) 해결

모든 표 상하에 공백 줄을 추가해 주세요.

예시(diff):

-## C# 식별자
-| 대상 | 규칙 | 예시 |
+## C# 식별자
+
+| 대상 | 규칙 | 예시 |
 |---|---|---|
 ...
-| 제네릭 매개변수 | TName | TItem, TResponse |
+| 제네릭 매개변수 | TName | TItem, TResponse |
+

Also applies to: 15-22, 24-33, 37-44, 46-50


19-19: 비동기 메서드 규칙 구체화 제안: CancellationToken 기본값 및 파라미터 위치

Async 메서드 시그니처에 ct는 마지막 매개변수로 두고 기본값을 허용하는 규칙을 명시하면 일관성이 올라갑니다.

예시:

  • 권장 시그니처: DoWorkAsync(Foo foo, Bar bar, CancellationToken ct = default)
  • ConfigureAwait 사용 여부, 예외 흐름(Throw vs Try)도 가이드에 추가 검토 바랍니다.
Assets/Core/Input/ScreenTapManager.cs (2)

71-79: UI 감지 로직 개선됨

UI 요소 감지를 위해 여러 컴포넌트 타입을 체크하는 것은 좋은 접근입니다. 하지만 성능 최적화를 위해 레이어 체크를 먼저 수행하는 것이 더 효율적일 수 있습니다.

성능 최적화를 위해 가장 빠른 체크(레이어)를 먼저 수행하도록 순서를 조정하는 것을 고려해보세요:

-            // UI 요소인지 확인
-            if (result.gameObject.layer == LayerMask.NameToLayer("UI") ||
-                result.gameObject.GetComponent<Canvas>() != null ||
-                result.gameObject.GetComponent<UnityEngine.UI.Graphic>() != null ||
-                result.gameObject.GetComponent<UnityEngine.UI.Selectable>() != null)
-            {
+            // UI 요소인지 확인 (빠른 체크부터)
+            if (result.gameObject.layer == LayerMask.NameToLayer("UI"))
+            {
                 return true;
-            }
+            }
+            
+            // 추가 UI 컴포넌트 체크
+            if (result.gameObject.GetComponent<Canvas>() != null ||
+                result.gameObject.GetComponent<UnityEngine.UI.Graphic>() != null ||
+                result.gameObject.GetComponent<UnityEngine.UI.Selectable>() != null)
+            {
+                return true;
+            }

133-161: Update 루프의 디버그 처리 오버헤드 검토 필요

매 프레임 UpdateTapDebug()를 호출하는 것은 디버그가 비활성화되어 있을 때도 오버헤드가 발생할 수 있습니다.

디버그가 활성화된 경우에만 Update 처리를 하도록 최적화를 고려해보세요:

 private void Update()
 {
-    UpdateTapDebug();
+    if (_enableDebugLog || _enableDebugRay)
+    {
+        UpdateTapDebug();
+    }
 }
Assets/Infrastructure/Network/WebSocket/WebSocketManager.cs (2)

72-75: 중복 초기화 방지 로직 검토 필요

_cancellationTokenSource의 null 체크만으로 중복 초기화를 방지하고 있는데, 더 명확한 초기화 상태 플래그를 사용하는 것이 좋을 수 있습니다.

명시적인 초기화 상태 플래그 추가를 고려해보세요:

+    private bool _isInitialized = false;
+
     public void Initialize()
     {
-        if (_cancellationTokenSource != null)
+        if (_isInitialized)
         {
             return;
         }
+        _isInitialized = true;
         _cancellationTokenSource = new CancellationTokenSource();
         InitializeNativeWebSocket();
         StartConnectionMonitoring();
     }

162-165: SendMessageAsync 구현 누락

SendMessageAsync 메서드가 NotImplementedException을 던지고 있습니다. 이는 TODO 항목으로 보입니다.

SendMessageAsync 구현이 필요합니다. 구현을 도와드릴까요? 이를 추적하기 위한 이슈를 생성하시겠습니까?

Assets/Docs/Design/Live2D_Service_Design.md.meta (1)

2-2: 문서 자산 GUID 변경 사항 확인

문서 자산은 보통 다른 에셋이 참조하지 않지만, Addressables나 에디터 확장 등에서 직접 참조하는 경우 GUID 변경은 참조 끊김을 유발합니다. 해당 문서를 참조하는 에셋이 없는지 한 번 확인해 주세요.

Assets/Domain/Character/Script/Controller/IActionController.cs (1)

6-10: TriggerAction 인자 타입을 강하게 만들 것을 제안 (API 안정성/가독성 향상)

현재 string action, object args = null는 호출부 캐스팅/박싱 오류를 런타임에 노출시킵니다. DTO(예: ActionData) 또는 제네릭 인자로 강타입화를 고려해 주세요.

가능한 대안:

  • DTO 사용: void TriggerAction(ActionData data);
  • 제네릭: void TriggerAction<TArgs>(string action, TArgs args);
  • 딕셔너리: void TriggerAction(string action, IReadOnlyDictionary<string, object> args = null);

예시(현 파일 내 대체 제안):

-        void TriggerAction(string action, object args = null);
+        // 강타입화로 런타임 캐스팅 오류 방지
+        void TriggerAction(ActionData data);

추가로, ActionController 구현체도 동일 시그니처로 반영 부탁드립니다.

Assets/Docs/diagrams/live2d_architecture.mmd (1)

5-8: EMO/ACT에서 L2D로의 실제 적용 경로도 도식화하면 이해도가 올라갑니다

현재 LPC만 L2D로 향하고 있어, Emotion/Action이 파라미터/모션으로 L2D에 반영되는 흐름이 빠진 듯합니다. 아래 간선 추가를 제안합니다.

   LCM --> EMO[EmotionController]
   LCM --> ACT[ActionController]
   LMM -->|Active Model| L2D[Live2D Components]
   LPC -->|Parameters/Expressions| L2D
+  EMO -->|Emotions/Motions| L2D
+  ACT -->|Actions/Animations| L2D
Assets/Core/Audio/NewAudioMixer.mixer (1)

121-124: 스냅샷 기본값 보강 제안 (초기 볼륨 일관성/UX 개선)

현재 Snapshot에는 Master만 존재하며 0.1355 dB로 약간 Boost된 상태입니다. 일반적으로 초기값은 0.0 dB(정규화)를 사용하고, 서브그룹(BGM/SFX/Voice/UI)도 함께 정의하면 초기화 로직 단순화에 유리합니다.

예시:

   m_FloatValues:
-    d683a197e89cb63419aa87d5e9ffd201: 0.13552584
+    d683a197e89cb63419aa87d5e9ffd201: 0.0          # Master
+    e16781d3daa6c0e45adbff52097c39f1: 0.0          # BGM
+    a14a87239cbc80d4ea3cc25c732ce286: 0.0          # SFX
+    ebec21cb4c0d05b4bae9b518144b691f: 0.0          # Voice
+    5b2b28ee812512d479114f20e49b7c3e: 0.0          # UI

주의: 에디터에서 스냅샷 활성화 후 그룹 볼륨을 0 dB로 세팅/저장하면 YAML이 자동 갱신됩니다.

Assets/Domain/Character/Script/Controller/ActionController.cs (1)

8-8: 중복 부착 방지 및 에디터 노출 용도 지정 Attribute 추가 제안

ActionController는 프로젝트 전역에서 재사용되는 컴포넌트로 보입니다. 실수로 중복 부착되는 것을 막고 메뉴에서 찾기 쉬우면 운영이 수월합니다.

-	public class ActionController : MonoBehaviour, IActionController
+	[DisallowMultipleComponent]
+	[AddComponentMenu("ProjectVG/Character/Action Controller")]
+	public class ActionController : MonoBehaviour, IActionController
Assets/Docs/diagrams/live2d_sequence.mmd (1)

15-17: 메시지 시그니처를 실제 API와 정합되게 구체화

설계/코드에서 ApplyReaction(EmotionData, ActionData)로 사용되는 것으로 보입니다. 다이어그램도 구조화된 메타데이터 전달을 반영하면 커뮤니케이션이 명확해집니다.

-CM->>L2D: ApplyReaction(metadata)
-L2D->>EMO: SetEmotion(emotion, intensity, duration)
-L2D->>ACT: TriggerAction(action)
+CM->>L2D: ApplyReaction(emotionData, actionData)
+L2D->>EMO: SetEmotion(emotionData.Emotion, emotionData.Intensity, emotionData.DurationMs)
+L2D->>ACT: TriggerAction(actionData)
Assets/Domain/Character/Script/Controller/ILive2DModelApplier.cs (2)

1-3: Fully Qualified Type 축약을 위한 using 추가

가독성을 위해 네임스페이스 using을 추가하고, 본문에서는 축약형 타입을 사용하세요.

 using UnityEngine;
+using ProjectVG.Domain.Character.Live2D.Model;

5-9: 오류 처리 계약 명확화(반환값 또는 예외 계약 정의)

Apply의 실패 시 처리 기준(예: 필수 컴포넌트 부재, 널 인수 등)이 불명확합니다. 반환값(bool/Result) 또는 예외 계약을 명시해 상위 호출부의 에러 핸들링을 일관화하세요.

원하시면 Result 패턴을 사용하는 인터페이스/구현 템플릿을 생성해 드리겠습니다.

Assets/Domain/Character/Script/Controller/Live2DParameterController.cs (2)

8-8: 중복 부착 방지 Attribute 권장

파라미터 컨트롤러는 1모델 1컴포넌트가 자연스럽습니다. 중복 부착을 막아 운용 사고를 줄이세요.

-	public class Live2DParameterController : MonoBehaviour
+	[DisallowMultipleComponent]
+	[AddComponentMenu("ProjectVG/Character/Live2D Parameter Controller")]
+	public class Live2DParameterController : MonoBehaviour

14-20: 프리셋/파라미터 키의 타입 안정성 확보 방안

문자열 키는 오타에 취약합니다. ScriptableObject 기반 프리셋, 상수/enum, 또는 GUID 기반 레지스트리 등으로 키를 타입/데이터화하면 유지보수가 수월합니다. 필요 시 간단한 Preset ScriptableObject 스캐폴드를 제공할 수 있습니다.

프리셋/파라미터 관리용 ScriptableObject와 레지스트리 예제를 만들어드릴까요?

Assets/Domain/Character/Script/Manager/ILive2DCharacterManager.cs (2)

1-1: 불필요한 using 제거

이 파일은 인터페이스만 정의하며 UnityEngine API를 사용하지 않습니다. 의존성 최소화를 위해 using UnityEngine을 제거하세요.

-using UnityEngine;

12-13: VoiceManager 이벤트 페이로드와 시그니처 불일치

VoiceManager는 OnVoiceStarted에 VoiceData 페이로드를 제공합니다. 본 인터페이스는 매개변수가 없어 음성 길이/메타데이터 등 유용한 정보를 활용할 수 없습니다. 추후 확장성을 위해 페이로드를 수용하는 오버로드 추가(또는 시그니처 변경)를 고려해 주세요.

예시:

void OnVoiceStarted(VoiceData voice);
void OnVoiceFinished();

참고: 호환성 이슈를 줄이려면 기존 무매개변수 메서드는 유지하고 오버로드를 추가하는 것이 안전합니다.

Assets/Domain/Character/Script/Controller/Live2DModelApplier.cs (1)

7-7: 미사용 직렬화 필드(_autoApplyOnEnable) 처리

_autoApplyOnEnable이 현재 코드 경로에서 사용되지 않습니다. 기능 의도가 자동 적용이라면 OnEnable 훅과 기본 대상/설정 필드가 필요합니다. 아니면 필드를 제거하세요.

자동 적용을 구현하려면 다음과 같이 보완할 수 있습니다:

 public class Live2DModelApplier : MonoBehaviour, ILive2DModelApplier
 {
     [SerializeField] private bool _autoApplyOnEnable = true;
+    [Header("Defaults (Auto-Apply 대상)")]
+    [SerializeField] private GameObject _defaultModel;
+    [SerializeField] private ProjectVG.Domain.Character.Live2D.Model.Live2DModelConfig _defaultConfig;
+
+    private void OnEnable()
+    {
+        if (_autoApplyOnEnable && _defaultModel != null && _defaultConfig != null)
+        {
+            Apply(_defaultModel, _defaultConfig);
+        }
+    }
Assets/Domain/Character/Script/Config/Live2DModelRegistry.cs (1)

1-2: StringComparison 사용을 위한 using 추가

TryGetConfig에서 문자열 비교를 엄격하게(Ordinal) 처리하려면 System 네임스페이스가 필요합니다.

-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
Assets/Domain/Chat/View/ChatBubbleUI.cs (2)

70-70: 구체 타입 의존 최소화(인터페이스 도입 제안)

UI가 ChatBubblePanel 구체 타입에 의존하면 교체/테스트가 어려워집니다. 필요한 기능만 노출하는 인터페이스(IChatBubbleHost 등)를 정의하고 그 인터페이스에 의존하도록 변경을 권장합니다.

예시:

public interface IChatBubbleHost
{
    // 필요 시, 버블 제거/슬라이드 트리거 등 최소한의 계약만 정의
    void RemoveBubble(ChatBubbleUI bubble);
}

private IChatBubbleHost? _manager;

ChatBubblePanel은 IChatBubbleHost를 구현하고, Initialize의 manager 파라미터 타입도 IChatBubbleHost로 변경합니다.


113-119: 오버로드된 Initialize와 내부 Initialize 명칭 충돌로 인한 가독성 저하

Awake에서 호출하는 내부 초기화 메서드 이름(Initialize)과 공개 API(Initialize(…))가 충돌합니다. 의도와 호출자를 명확히 하기 위해 내부 메서드를 InitializeComponents 등으로 변경하는 것을 권장합니다.

아래는 파일 외 범위 변경안을 포함한 예시입니다:

- private void Awake()
+ private void Awake()
  {
-     Initialize();
+     InitializeComponents();
  }

- private void Initialize()
+ private void InitializeComponents()
  {
     // 기존 본문 동일
  }
Assets/Domain/Character/Script/Controller/EmotionController.cs (1)

8-8: 중복 부착 방지 어트리뷰트 권장

감정 컨트롤러는 보통 1개만 존재해야 합니다. 안전을 위해 DisallowMultipleComponent를 추가하세요.

-public class EmotionController : MonoBehaviour, IEmotionController
+[DisallowMultipleComponent]
+public class EmotionController : MonoBehaviour, IEmotionController
Assets/Domain/Character/Script/Manager/Live2DCharacterManager.cs (2)

10-24: TODO 구현체 완성 필요

현재 모든 메서드가 빈 구현체로 되어 있습니다. 이는 facade 패턴의 초기 구조이지만, 실제 기능이 구현되지 않은 상태입니다.

TODO 구현을 완료하는 코드를 생성해드릴까요? Live2DModelManager와 파라미터 컨트롤러를 통합하는 구현체를 작성할 수 있습니다.


28-29: 불필요한 빈 줄 제거

파일 끝에 불필요한 빈 줄이 있습니다.

-}
-
-
+}
Assets/Core/Audio/BGMController.cs (1)

21-40: 널 참조 경고 개선 필요

nullable 참조 타입을 활성화했지만 _audioSource가 여전히 nullable로 처리되고 있습니다. base.Initialize()에서 AudioSource가 생성되므로 이후에는 null이 아님이 보장됩니다.

AudioControllerCore의 _audioSource를 null-forgiving operator를 사용하거나 초기화 이후 non-nullable로 처리하는 것을 고려해보세요:

 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}");
 }
Assets/Core/Audio/AudioControllerCore.cs (1)

52-53: 볼륨 0일 때 -80dB 매직 넘버 사용

-80dB는 Unity AudioMixer의 최소값을 나타내는 매직 넘버입니다. 이를 상수로 정의하면 코드의 가독성과 유지보수성이 향상됩니다.

상수로 정의하는 것을 제안합니다:

+private const float MinDecibelValue = -80f;
+
 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;
+        float dbValue = _volume > 0 ? 20f * Mathf.Log10(_volume) : MinDecibelValue;
         _audioMixerGroup.audioMixer.SetFloat(_volumeParameterName, dbValue);
     }
 }
Assets/Domain/Character/Script/Test/Live2DModelTest.cs (1)

34-41: null 체크 후 즉시 사용 패턴 개선 가능

null 체크 후 즉시 사용하는 패턴을 null 병합 연산자나 조건부 연산자로 간결하게 표현할 수 있습니다.

더 간결한 코드를 위해 다음과 같이 개선할 수 있습니다:

-// Live2DModelManager 초기화 확인
-if (Live2DModelManager.Instance != null)
-{
-    UpdateStatus("Live2DModelManager 준비 완료");
-}
-else
-{
-    UpdateStatus("Live2DModelManager 초기화 실패");
-}
+// Live2DModelManager 초기화 확인
+UpdateStatus(Live2DModelManager.Instance != null 
+    ? "Live2DModelManager 준비 완료" 
+    : "Live2DModelManager 초기화 실패");
Assets/Core/Audio/UIController.cs (2)

90-100: 풀이 비었을 때 동적 확장 고려

오디오 소스 풀이 모두 사용 중일 때 null을 반환하는 대신, 동적으로 풀을 확장하는 것을 고려해볼 수 있습니다.

풀을 동적으로 확장하는 로직을 추가하시겠습니까? 이렇게 하면 UI 사운드가 누락되는 것을 방지할 수 있습니다.


30-40: ToArray() 호출로 인한 불필요한 할당

_activeSources.ToArray()는 매번 새로운 배열을 할당합니다. 이는 GC 압박을 증가시킬 수 있습니다.

역순으로 순회하거나 별도의 리스트를 사용하여 할당을 피하세요:

 public void Stop()
 {
-    foreach (var source in _activeSources.ToArray())
+    for (int i = _activeSources.Count - 1; i >= 0; i--)
     {
+        var source = _activeSources[i];
         if (source != null)
         {
             source.Stop();
             ReturnToPool(source);
         }
     }
 }
Assets/Domain/Character/Script/Manager/Live2DModelManager.cs (2)

19-22: Awake 메서드가 불필요합니다

base 클래스의 Awake만 호출하고 있으므로 이 오버라이드는 제거할 수 있습니다.

-private void Awake()
-{
-    base.Awake();
-}

323-324: TODO 주석을 처리해주세요

시선 타겟과 오디오 소스 설정이 미완성 상태입니다. 이러한 기능이 필수적이라면 구현을 완료해야 합니다.

Line 323과 Line 339에 TODO 주석이 있습니다. 시선 추적 타겟과 립싱크용 오디오 소스 설정 로직을 구현하시겠습니까? 새로운 이슈를 생성해 드릴까요?

Assets/Infrastructure/Network/Services/SessionManager.cs (1)

288-301: IManager 구현 주석이 남아있습니다

클래스가 더 이상 IManager를 구현하지 않는데 주석이 남아있습니다. 이는 혼란을 일으킬 수 있습니다.

-        #region IManager 구현
+        #region Cleanup Methods
Assets/Domain/Chat/View/ChatBubblePanel.cs (1)

9-9: 중복된 네임스페이스 선언

ProjectVG.Domain.Chat.View 네임스페이스가 using 문과 namespace 선언에서 중복됩니다.

-using ProjectVG.Domain.Chat.View;

 namespace ProjectVG.Domain.Chat.View
Assets/Infrastructure/Network/Http/HttpApiClient.cs (1)

35-38: OnDestroy에서 불필요한 Shutdown 호출

OnDestroy에서 Shutdown을 호출하지만 메서드 본문이 비어있습니다. base 클래스의 singleton 패턴에서 이미 정리를 처리하므로 이 호출은 불필요할 수 있습니다.

 private void OnDestroy()
 {
-    Shutdown();
+    // Shutdown은 명시적으로 호출되어야 함
 }
Assets/Domain/Character/Script/Config/Live2DModelConfig.cs (1)

10-28: EmotionMapping 클래스에 유효성 검증 메서드 추가 필요

EmotionMapping의 기본값들이 Range 어트리뷰트로 제한되어 있지만, 런타임에서 값을 변경할 때 검증이 없습니다.

유효성 검증 메서드를 추가하세요:

 [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;
+    
+    public bool IsValid()
+    {
+        return !string.IsNullOrEmpty(emotionKey) && 
+               !string.IsNullOrEmpty(expressionName) &&
+               defaultIntensity >= 0f && defaultIntensity <= 1f &&
+               defaultDurationMs >= 500 && defaultDurationMs <= 10000;
+    }
 }
Assets/Core/Audio/VoiceController.cs (1)

196-207: PrepareAudioSource 메서드의 null 체크 후 즉시 리턴 처리

Line 198에서 _audioSource가 null일 때 조용히 리턴하는데, 이는 호출자가 문제를 인지하지 못하게 합니다.

null 체크 시 경고 로그를 추가하세요:

 private void PrepareAudioSource()
 {
-    if (_audioSource == null) return;
+    if (_audioSource == null)
+    {
+        Debug.LogWarning("[VoiceController] PrepareAudioSource 호출 시 AudioSource가 null입니다.");
+        return;
+    }
     
     if (_audioSource.isPlaying)
     {
         _audioSource.Stop();
     }
     
     _audioSource.volume = _volume;
     _audioSource.clip = null;
 }
Assets/Core/Audio/AudioManager.cs (2)

232-242: TODO 구현 필요: GetActiveSFXCount와 GetActiveUICount

Line 234와 240에 TODO 주석이 있는 미구현 메서드들이 있습니다. 이들은 항상 0을 반환하고 있어 실제 활성 오디오 개수를 반영하지 못합니다.

SFXController와 UIController에 활성 소스 개수를 반환하는 메서드를 추가하는 구현이 필요합니다. 제가 이 기능을 구현하는 코드를 생성해드릴까요? 아니면 이를 추적하기 위한 새 이슈를 생성할까요?


285-300: CreateOrFindGroup 메서드의 에러 처리 개선 필요

Line 298에서 그룹을 찾지 못했을 때 경고만 출력하고 null을 반환하는데, 이는 후속 작업에서 NullReferenceException을 일으킬 수 있습니다.

더 명확한 에러 메시지와 폴백 처리를 추가하세요:

 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에서 수동으로 생성해주세요.");
+    Debug.LogError($"[AudioManager] 필수 AudioMixer 그룹 '{groupName}'을 찾을 수 없습니다. " +
+                   $"NewAudioMixer.mixer 에셋에서 '{groupName}' 그룹을 생성해주세요.");
     return null;
 }

Comment on lines +1 to +7
fileFormatVersion: 2
guid: 834aa5ca0d5c48149b8c17792ca7ae9a
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

자산 이름 오타 가능성: 'Live2d Sence' → 'Live2D Scene'로 정정 권장

  • Scene 자산 이름에 오타('Sence' 대신 'Scene')가 있어 보입니다.
  • Live2D 표기 일관성도 'Live2d' → 'Live2D'로 맞추는 것이 좋습니다.
  • 반드시 Unity 에디터 내에서 리네임하세요(프로젝트 뷰에서 이름 변경). 이렇게 해야 GUID가 유지되어 참조가 끊기지 않습니다.

필요 시, 저장소 내 유사 오타를 일괄 점검하세요.

다음 스크립트로 경로상 오타 및 표기 불일치를 찾아 확인할 수 있습니다.


🏁 Script executed:

#!/bin/bash
# 'Sence' 오타 및 'Live2d' 표기 사용 현황 점검
fd -HI 'Sence' | sed 's/^/PATH: /'
fd -HI -g '*Live2d*' | sed 's/^/PATH: /'

# 파일 내용 내에 포함된 경우(메타/에셋 참조 문자열 등)도 탐색
rg -n --no-heading -S 'Sence|Live2d' -A 1 -B 1

Length of output: 1308


자산 및 코드 내 'Sence' 오타 및 'Live2d' 표기 불일치 수정 필요

아래 파일들에서 철자 오류(’Sence’ → ’Scene’) 및 표기 일관성(’Live2d’ → ’Live2D’)을 Unity 에디터(Project 뷰) 내에서 리네임하여 GUID가 유지되도록 수정하세요.

– Assets/App/Scenes/Live2d Sence.unity
– Assets/App/Scenes/Live2d Sence.unity.meta
• ‘Live2d Sence’ → ‘Live2D Scene’
– Assets/App/Scenes/MainSence.unity
– Assets/App/Scenes/MainSence.unity.meta
• ‘MainSence’ → ‘MainScene’
– Assets/App/Scenes/StartSence.unity
– Assets/App/Scenes/StartSence.unity.meta
• ‘StartSence’ → ‘StartScene’
– Assets/Tests/Sences/
– Assets/Tests/Sences.meta
• 폴더명 ‘Sences’ → ‘Scenes’

아울러 코드 및 빌드 설정 참조도 함께 업데이트해야 합니다.
– ProjectSettings/EditorBuildSettings.asset (9, 12행)
path: Assets/App/Scenes/MainSence.unity → MainScene.unity
path: Assets/App/Scenes/StartSence.unity → StartScene.unity
– Assets/Core/Managers/SystemManager.cs (205행)
SceneManager.LoadSceneAsync("MainSence") → LoadSceneAsync("MainScene")

수정 후, 에디터 내 Build Settings 및 런타임 참조가 모두 올바르게 연결되는지 꼭 확인하세요.

🤖 Prompt for AI Agents
In Assets/App/Scenes/Live2d Sence.unity.meta (lines 1-7) and the other listed
files, there are spelling and casing errors: rename assets and folders in the
Unity Project view to preserve GUIDs — "Live2d Sence" → "Live2D Scene",
"MainSence" → "MainScene", "StartSence" → "StartScene", and folder
"Assets/Tests/Sences" → "Assets/Tests/Scenes"; then update references: open
ProjectSettings/EditorBuildSettings.asset and change the scene paths on lines 9
and 12 to point to Assets/App/Scenes/MainScene.unity and
Assets/App/Scenes/StartScene.unity (or re-add them via Build Settings), and
change the string in Assets/Core/Managers/SystemManager.cs at line 205 from
SceneManager.LoadSceneAsync("MainSence") to LoadSceneAsync("MainScene"); finally
run a project-wide search for remaining "Sence"/"Live2d" occurrences, fix them,
verify Build Settings and runtime scene links in the Editor, and commit the
renamed assets ensuring their .meta GUIDs remain unchanged.

Comment on lines +1 to +2
fileFormatVersion: 2
guid: 8b7ab8e5a4fd02b448fdc3f34d05ea54 No newline at end of file
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

마지막 개행 누락(Nit)

파일 말미에 개행이 없는 것으로 보입니다. 일부 도구/리뷰 훅에서 경고가 발생할 수 있으니 개행 추가를 권장합니다. 기능에는 영향 없습니다.

다음 스크립트로 개행 누락 메타 파일을 찾을 수 있습니다.


🏁 Script executed:

#!/bin/bash
# 말미 개행 없는 .meta 파일 탐지
fd -e meta -X sh -c 'for f do [ -s "$f" ] && tail -c1 "$f" | od -An -t u1 | grep -q 10 || echo "NO_EOF_NEWLINE: $f"; done' sh

Length of output: 92782


🏁 Script executed:

#!/bin/bash
# Check if last character of the .meta file is a newline (1 if present, 0 if missing)
tail -c1 Assets/Core/Audio/AudioControllerCore.cs.meta | wc -l

Length of output: 66


개행 누락: Assets/Core/Audio/AudioControllerCore.cs.meta
Assets/Core/Audio/AudioControllerCore.cs.meta 파일 말미에 개행이 없어 일부 도구/리뷰 훅에서 경고가 발생할 수 있습니다.

  • Assets/Core/Audio/AudioControllerCore.cs.meta 파일 마지막에 개행(뉴라인) 한 줄을 추가해주세요.
🤖 Prompt for AI Agents
In Assets/Core/Audio/AudioControllerCore.cs.meta around lines 1 to 2, the file
is missing a trailing newline at EOF which can trigger warnings in some tools;
open the file and add a single newline character at the end of the file (ensure
the last line ends with a newline), then save and commit the change.

Comment on lines +10 to +12
void Stop();
bool IsPlaying();
float GetVolume();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

UI/SFX 컨트롤러에서 base 메서드 숨김 위험(CS0114) — override 누락 확인 및 수정 필요

IAudioController가 Stop/IsPlaying을 요구하고, AudioControllerCore에 동일 시그니처의 virtual 메서드가 있습니다. 현재 UIController/SFXController는 override 키워드 없이 동일 시그니처를 선언하고 있어 base 메서드를 “가립니다”. 이 경우 AudioControllerCore 타입으로 다룰 때 기대와 다른 동작(기본 구현 호출) 가능성이 있습니다. VoiceController는 override로 올바르게 구현되어 있어 일관성 측면에서도 불일치입니다.

아래와 같이 UIController/SFXController에서 override를 명시해 주세요. (외부 파일 수정 예시)

// UIController.cs
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;
}
// SFXController.cs
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;
}
🤖 Prompt for AI Agents
Assets/Core/Audio/IAudioController.cs around lines 10-12: UIController.cs and
SFXController.cs currently declare Stop() and IsPlaying() with the same
signatures as the virtual methods on AudioControllerCore but without the
override keyword, causing base-method hiding (CS0114) — update the method
declarations in UIController.cs and SFXController.cs to include the override
modifier for Stop() and IsPlaying(), keeping the existing implementations (i.e.,
iterate _activeSources to Stop and ReturnToPool, and return _activeSources.Count
> 0 for IsPlaying()), ensuring signatures exactly match the base virtual methods
so polymorphism works correctly.

Comment on lines +1 to +2
fileFormatVersion: 2
guid: f638fbd296a39e04ab1e48c5432c5c7c No newline at end of file
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

IAudioController.cs.meta의 MonoImporter 블록 추가 권장

동일 사유로 MonoImporter 섹션을 추가해 불필요한 메타 재생성을 방지하세요.

 fileFormatVersion: 2
 guid: f638fbd296a39e04ab1e48c5432c5c7c
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

다음 스크립트로 다른 C# 스크립트 .meta 파일에 MonoImporter 블록이 포함되어 있는지 확인해 주세요.


🏁 Script executed:

#!/bin/bash
# 다른 .cs.meta 파일에서 MonoImporter 섹션 존재 여부 점검
rg -l "MonoImporter:" -g "*.cs.meta"

Length of output: 14045


IAudioController.cs.meta에 MonoImporter 블록 추가 필요

다른 C# 스크립트 메타 파일에서는 MonoImporter 섹션이 있어 불필요한 메타 재생성을 방지하고 있으므로, 아래 파일에도 동일하게 추가해주세요.

  • 파일: Assets/Core/Audio/IAudioController.cs.meta
    위치: guid: f638fbd296a39e04ab1e48c5432c5c7c 아래
 fileFormatVersion: 2
 guid: f638fbd296a39e04ab1e48c5432c5c7c
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fileFormatVersion: 2
guid: f638fbd296a39e04ab1e48c5432c5c7c
fileFormatVersion: 2
guid: f638fbd296a39e04ab1e48c5432c5c7c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
🤖 Prompt for AI Agents
In Assets/Core/Audio/IAudioController.cs.meta around lines 1-2 (guid:
f638fbd296a39e04ab1e48c5432c5c7c), the file is missing the MonoImporter block
that other C# script .meta files include; add a MonoImporter section immediately
below the guid entry matching the shape and keys used by other C# .meta files in
the repo (e.g., serializedVersion, externalObjects, executionOrder, icon,
userData, defaultReferences, executionOrder, isScript, etc.) so Unity will not
regenerate the meta file—ensure field names and types match the existing C# meta
files in the project.

Comment on lines +62 to +64
m_SuspendThreshold: -80
m_EnableSuspend: 1
m_UpdateMode: 0
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

DSP Suspend 설정 의도 확인

m_EnableSuspend: 1, m_SuspendThreshold: -80는 무음 시 DSP를 정지시켜 전력소모를 줄일 수 있지만, 재개 시 클릭/페이드 이슈가 발생할 수 있습니다. 모바일/저사양 타깃에서만 켜는 전략도 고려해 주세요.

아래 검색으로 런타임 볼륨 제어 코드가 존재하는지, 스냅샷/SetFloat 사용 여부를 확인해 주세요. Expose 미설정 상태라면 SetFloat 호출이 실패합니다.


🏁 Script executed:

#!/bin/bash
# AudioMixer 볼륨 제어 사용 흔적 확인
rg -n 'AudioMixer.*SetFloat\(' -S
rg -n 'AudioMixerSnapshot' -S

Length of output: 189


추가 확인: NewAudioMixer.mixer의 파라미터 노출 여부 및 C# 코드 내 SetFloat 사용 여부를 확인합니다.


🏁 Script executed:

#!/bin/bash
# NewAudioMixer.mixer의 스냅샷·파라미터 노출 섹션 확인
sed -n '100,150p' Assets/Core/Audio/NewAudioMixer.mixer

# 노출(exposed) 파라미터 검색
rg -n 'exposed' Assets/Core/Audio/NewAudioMixer.mixer

# 모든 C# 파일 내 SetFloat 호출 검색
rg -n 'SetFloat\(' -t cs

Length of output: 2528


DSP Suspend 파라미터 런타임 제어를 위해 Expose 필요 및 설정 의도 확인
현재 m_EnableSuspend, m_SuspendThreshold가 Mixer에 노출되지 않아 Runtime SetFloat나 Snapshot 전환으로 제어할 수 없습니다. 모바일·저사양 기기에서만 DSP Suspend를 적용하려면 다음을 반영해 주세요.

• Assets/Core/Audio/NewAudioMixer.mixer
– AudioMixer Inspector의 “Expose Parameter”에서 EnableSuspendSuspendThreshold 노출
• C# 코드 (AudioControllerCore.cs, AudioManager.cs)
– 플랫폼 조건에 따라 audioMixer.SetFloat("EnableSuspend", 1) 혹은 SuspendThreshold 값 설정 추가
• 클릭/페이드 문제 방지
– Suspend 재개 시 발생할 수 있는 클릭·페이드를 완화하기 위해 페이드인·아웃 로직이나 짧은 딜레이 삽입 고려

🤖 Prompt for AI Agents
In Assets/Core/Audio/NewAudioMixer.mixer around lines 62-64, the DSP suspend
parameters m_EnableSuspend and m_SuspendThreshold are not exposed for runtime
control; expose the two parameters in the AudioMixer Inspector as
"EnableSuspend" and "SuspendThreshold" so they can be driven by
SetFloat/Snapshots, then in C# (AudioControllerCore.cs and AudioManager.cs) add
platform-conditional calls to audioMixer.SetFloat("EnableSuspend", 1 or 0) and
audioMixer.SetFloat("SuspendThreshold", <value>) when enabling/disabling DSP
suspend, and when resuming DSP apply a short fade-in/out or a small delay before
fully restoring levels to mitigate clicks/fades.

Comment on lines +1 to +2
fileFormatVersion: 2
guid: ea75b23f7c651a24c9e0055ae07e8234 No newline at end of file
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

.cs.meta 포맷 불완전: MonoImporter 누락

C# 스크립트 메타에는 MonoImporter가 필요합니다. 누락 시 에디터가 메타를 재생성하거나 설정이 초기화될 수 있습니다. 동일 리포지토리의 다른 .cs.meta들과 형식을 일치시키세요.

다음 패치를 적용해 주세요.

 fileFormatVersion: 2
 guid: ea75b23f7c651a24c9e0055ae07e8234
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

위 파일도 이전 코멘트의 스크립트로 MonoImporter 존재 여부/중복 GUID를 함께 확인해 주세요.


MonoImporter 존재 여부 및 GUID 중복 확인을 위해 아래 스크립트를 실행해 주세요.


🏁 Script executed:

#!/bin/bash
# 1. .cs.meta 파일들에서 MonoImporter 항목이 있는지 검색
echo "== MonoImporter 존재 여부 확인 =="
rg -n "MonoImporter" --glob "*.cs.meta"

# 2. 해당 GUID가 중복으로 사용된 .cs.meta 파일이 있는지 검색
GUID="ea75b23f7c651a24c9e0055ae07e8234"
echo "== GUID 중복 사용 확인: $GUID =="
rg -n "$GUID" --glob "*.cs.meta"

Length of output: 17418


.cs.meta 포맷 수정: MonoImporter 항목 추가 필요

위 파일에는 MonoImporter 섹션이 누락되어 있어 에디터가 메타를 재생성하거나 설정을 초기화할 수 있습니다. 다른 C# 스크립트 .meta 파일과 형식을 일치시켜 주세요.

확인 결과:

  • MonoImporter 항목이 Assets/Domain/Character/Script/Manager/ILive2DCharacterManager.cs.meta에 존재하지 않음
  • GUID ea75b23f7c651a24c9e0055ae07e8234는 중복 사용되지 않음

수정 대상:

  • Assets/Domain/Character/Script/Manager/ILive2DCharacterManager.cs.meta

적용할 패치:

 fileFormatVersion: 2
 guid: ea75b23f7c651a24c9e0055ae07e8234
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fileFormatVersion: 2
guid: ea75b23f7c651a24c9e0055ae07e8234
fileFormatVersion: 2
guid: ea75b23f7c651a24c9e0055ae07e8234
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
🤖 Prompt for AI Agents
In Assets/Domain/Character/Script/Manager/ILive2DCharacterManager.cs.meta around
lines 1-2, the file is missing the MonoImporter section required for Unity C#
script metadata; add a MonoImporter block matching the format used by other
.cs.meta files (including serializedVersion, iconInstanceID, executionOrder,
defaultReferences, externalObjects, userData, assetBundleName and
assetBundleVariant or equivalent fields) and ensure the existing
fileFormatVersion and guid remain unchanged; copy the typical MonoImporter
structure from a nearby C# .meta and adjust only GUID-preserving entries so the
editor will not regenerate/reset import settings.

Comment on lines +1 to +2
fileFormatVersion: 2
guid: aeee24d07701ad747a92d82afe08caa6 No newline at end of file
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Live2DCharacterManager.cs.meta에도 MonoImporter 섹션이 없습니다

스크립트 메타 포맷을 표준 형태로 맞춰 두세요. 특히 실행 순서(executionOrder) 값을 메타에서 관리하는 경우 누락 시 의도치 않은 순서가 적용될 수 있습니다.

 fileFormatVersion: 2
 guid: aeee24d07701ad747a92d82afe08caa6
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

🏁 Script executed:

#!/bin/bash
# 1. Find some C# script meta files
echo "샘플 .cs.meta 파일 목록:"
find Assets -type f -name "*.cs.meta" | head -n 5

# 2. 출력 예시로 첫 번째 파일 헤더 부분 확인
file=$(find Assets -type f -name "*.cs.meta" | head -n1)
echo -e "\n==> $file 내용 (첫 30줄):"
head -n 30 "$file"

Length of output: 642


MonoImporter 섹션 추가 필요: Live2DCharacterManager.cs.meta
Live2DCharacterManager.cs.meta에 스크립트 임포터 설정(특히 executionOrder)이 누락되어 있습니다. Unity에서 의도한 실행 순서를 보장하려면 아래와 같이 MonoImporter 블록을 추가해주세요.

대상 파일

  • Assets/Domain/Character/Script/Manager/Live2DCharacterManager.cs.meta

적용할 diff

 fileFormatVersion: 2
 guid: aeee24d07701ad747a92d82afe08caa6
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fileFormatVersion: 2
guid: aeee24d07701ad747a92d82afe08caa6
fileFormatVersion: 2
guid: aeee24d07701ad747a92d82afe08caa6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
🤖 Prompt for AI Agents
In Assets/Domain/Character/Script/Manager/Live2DCharacterManager.cs.meta around
lines 1-2, the file is missing a MonoImporter section that defines the script
import settings (notably executionOrder); add a MonoImporter block to the .meta
containing at minimum serializedVersion, iconInstanceID (keep as 0 if none) and
executionOrder set to the integer your project expects for
Live2DCharacterManager (replace with the correct order number), so Unity will
preserve the intended script execution order on import.

Comment on lines +1 to +8
fileFormatVersion: 2
guid: c762a71a10e06714a96c237780ce3100
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

테스트 폴더는 Editor 전용으로 분리/컴파일 범위 제한 권장

실행용 빌드에 테스트 하네스가 포함되지 않도록 아래를 권장합니다:

  • Tests용 asmdef 추가(Include Platforms: Editor) 또는 폴더명을 Editor로 분리
  • 가능하면 Unity Test Framework 표준 구조(Assets/Tests)로 이동
  • 도메인 런타임 코드와 테스트 의존성 분리(참조 방향 단방향 유지)

폴더 내 asmdef 존재 여부를 빠르게 확인하려면 아래를 실행해 주세요.


🏁 Script executed:

#!/bin/bash
fd -a -g "*.asmdef" "Assets/Domain/Character/Script/Test" || echo "No asmdef found under Test/"

Length of output: 57


추가로 아래 스크립트를 실행해 ‘Assets/Domain/Character/Script/Test’ 폴더 내에 실제로 .asmdef 파일이 존재하는지 확인해 주세요.


🏁 Script executed:

#!/bin/bash
files=$(find Assets/Domain/Character/Script/Test -type f -name '*.asmdef')
if [ -z "$files" ]; then
  echo "No asmdef found under Test/"
else
  echo "$files"
fi

Length of output: 158


Editor 전용 테스트 코드 분리 필요

Assets/Domain/Character/Script/Test 폴더 내에 .asmdef 파일이 없어 테스트 코드가 런타임 빌드에 포함될 가능성이 확인되었습니다. 아래 중 하나를 적용해 주세요:

  • Tests 전용 *.asmdef 생성
    • Include Platforms: Editor 로 설정
    • 도메인 런타임 어셈블리와 단방향 참조 유지
  • 테스트 코드를 Editor 폴더로 분리
    • 예: Assets/Domain/Character/Editor/Test
  • Unity Test Framework 권장 폴더 구조로 이동
    • 예: Assets/Tests/Domain/Character
🤖 Prompt for AI Agents
Assets/Domain/Character/Script/Test.meta around lines 1-8: the Test folder lacks
an asmdef so editor-only tests may be included in runtime builds; fix by one of
the following—preferred: add a Tests asmdef (e.g.,
Domain.Character.Tests.asmdef) inside Assets/Domain/Character/Script/Test and
set "Include Platforms" to only "Editor", and set a one-way reference to the
domain runtime assembly; alternative: move test scripts into an Editor folder
(Assets/Domain/Character/Editor/Test) or into the Unity Test Framework structure
(e.g., Assets/Tests/Domain/Character) so they are excluded from runtime builds.

Comment on lines +50 to +61
private static ChatManager? _instance;
public static ChatManager Instance
{
base.Awake();
get
{
if (_instance == null)
{
_instance = FindAnyObjectByType<ChatManager>();
}
return _instance;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

싱글톤 패턴 스레드 안전성 문제

현재 싱글톤 구현이 스레드 안전하지 않습니다. 여러 스레드에서 동시에 Instance에 접근하면 문제가 발생할 수 있습니다.

 private static ChatManager? _instance;
+private static readonly object _instanceLock = new object();
+
 public static ChatManager Instance
 {
     get
     {
-        if (_instance == null)
-        {
-            _instance = FindAnyObjectByType<ChatManager>();
-        }
-        return _instance;
+        lock (_instanceLock)
+        {
+            if (_instance == null)
+            {
+                _instance = FindAnyObjectByType<ChatManager>();
+            }
+            return _instance;
+        }
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static ChatManager? _instance;
public static ChatManager Instance
{
base.Awake();
get
{
if (_instance == null)
{
_instance = FindAnyObjectByType<ChatManager>();
}
return _instance;
}
}
private static ChatManager? _instance;
private static readonly object _instanceLock = new object();
public static ChatManager Instance
{
get
{
lock (_instanceLock)
{
if (_instance == null)
{
_instance = FindAnyObjectByType<ChatManager>();
}
return _instance;
}
}
}
🤖 Prompt for AI Agents
In Assets/Domain/Chat/Service/ChatManager.cs around lines 50 to 61, the current
singleton accessor is not thread-safe; change it to a thread-safe implementation
by either using a private static readonly object lock and applying
double-checked locking inside the Instance getter (check for null, lock,
re-check null, assign via FindAnyObjectByType) or, preferably, replace the
pattern with a static readonly Lazy<ChatManager> (initialized to call
FindAnyObjectByType) and expose its Value as the Instance; ensure null checks
and minimal locking to avoid race conditions.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review continued from previous batch...

Comment on lines 15 to 38
public virtual void Initialize(string volumeParameterName)
{
_volumeParameterName = volumeParameterName;

// AudioSource가 없으면 자동 생성
if (_audioSource == null)
{
_audioSource = gameObject.AddComponent<AudioSource>();
_audioSource.playOnAwake = false;
_audioSource.loop = false;
_audioSource.volume = _volume;
}

// AudioMixerGroup이 설정되지 않은 경우 AudioManager에서 자동 할당
if (_audioMixerGroup == null)
{
_audioMixerGroup = GetAudioMixerGroupFromManager();
}

if (_audioSource != null && _audioMixerGroup != null)
{
_audioSource.outputAudioMixerGroup = _audioMixerGroup;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Initialize 메서드의 책임이 과중합니다

Initialize 메서드가 너무 많은 책임을 가지고 있습니다. AudioSource 생성, 설정, AudioMixerGroup 할당 등이 한 메서드에 모두 포함되어 있어 단일 책임 원칙(SRP)을 위반합니다.

각 책임을 별도의 메서드로 분리하는 것을 제안합니다:

 public virtual void Initialize(string volumeParameterName)
 {
     _volumeParameterName = volumeParameterName;
     
-    // AudioSource가 없으면 자동 생성
-    if (_audioSource == null)
-    {
-        _audioSource = gameObject.AddComponent<AudioSource>();
-        _audioSource.playOnAwake = false;
-        _audioSource.loop = false;
-        _audioSource.volume = _volume;
-    }
-    
-    // AudioMixerGroup이 설정되지 않은 경우 AudioManager에서 자동 할당
-    if (_audioMixerGroup == null)
-    {
-        _audioMixerGroup = GetAudioMixerGroupFromManager();
-    }
-    
-    if (_audioSource != null && _audioMixerGroup != null)
-    {
-        _audioSource.outputAudioMixerGroup = _audioMixerGroup;
-    }
+    EnsureAudioSource();
+    EnsureAudioMixerGroup();
+    ConfigureAudioOutput();
 }
+
+private void EnsureAudioSource()
+{
+    if (_audioSource == null)
+    {
+        _audioSource = gameObject.AddComponent<AudioSource>();
+        _audioSource.playOnAwake = false;
+        _audioSource.loop = false;
+        _audioSource.volume = _volume;
+    }
+}
+
+private void EnsureAudioMixerGroup()
+{
+    if (_audioMixerGroup == null)
+    {
+        _audioMixerGroup = GetAudioMixerGroupFromManager();
+    }
+}
+
+private void ConfigureAudioOutput()
+{
+    if (_audioSource != null && _audioMixerGroup != null)
+    {
+        _audioSource.outputAudioMixerGroup = _audioMixerGroup;
+    }
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public virtual void Initialize(string volumeParameterName)
{
_volumeParameterName = volumeParameterName;
// AudioSource가 없으면 자동 생성
if (_audioSource == null)
{
_audioSource = gameObject.AddComponent<AudioSource>();
_audioSource.playOnAwake = false;
_audioSource.loop = false;
_audioSource.volume = _volume;
}
// AudioMixerGroup이 설정되지 않은 경우 AudioManager에서 자동 할당
if (_audioMixerGroup == null)
{
_audioMixerGroup = GetAudioMixerGroupFromManager();
}
if (_audioSource != null && _audioMixerGroup != null)
{
_audioSource.outputAudioMixerGroup = _audioMixerGroup;
}
}
public virtual void Initialize(string volumeParameterName)
{
_volumeParameterName = volumeParameterName;
EnsureAudioSource();
EnsureAudioMixerGroup();
ConfigureAudioOutput();
}
private void EnsureAudioSource()
{
if (_audioSource == null)
{
_audioSource = gameObject.AddComponent<AudioSource>();
_audioSource.playOnAwake = false;
_audioSource.loop = false;
_audioSource.volume = _volume;
}
}
private void EnsureAudioMixerGroup()
{
if (_audioMixerGroup == null)
{
_audioMixerGroup = GetAudioMixerGroupFromManager();
}
}
private void ConfigureAudioOutput()
{
if (_audioSource != null && _audioMixerGroup != null)
{
_audioSource.outputAudioMixerGroup = _audioMixerGroup;
}
}
🤖 Prompt for AI Agents
In Assets/Core/Audio/AudioControllerCore.cs around lines 15 to 38, Initialize
currently does too much (creating/configuring AudioSource and resolving
AudioMixerGroup); split responsibilities into small methods: set the
_volumeParameterName, move AudioSource creation/configuration into a new
EnsureAudioSource method, move AudioMixerGroup lookup into
EnsureAudioMixerGroup, and move assignment of outputAudioMixerGroup into
ApplyMixerToSource; have Initialize call these helper methods in order, mark
them protected (or protected virtual) so subclasses can override, and keep
existing behavior (playOnAwake=false, loop=false, volume=_volume) when creating
the AudioSource.

Comment on lines 69 to 81
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가 없습니다.");
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

중복 코드: BeginLoadingUI와 StartInitialization

BeginLoadingUIStartInitialization 메서드에서 동일한 초기화 로직이 중복되어 있습니다.

중복을 제거하고 하나의 메서드로 통합하거나, 한 메서드가 다른 메서드를 호출하도록 변경하세요:

 public void BeginLoadingUI()
 {
     UpdateTask("INITIALIZATION", "시스템 초기화 중...", 0.05f);
     StartAutoProgress();
 }
 
 public void StartInitialization()
 {
     if (SystemManager.Instance != null)
     {
-        UpdateTask("INITIALIZATION", "시스템 초기화 중...", 0.05f);
-        StartAutoProgress();
+        BeginLoadingUI();
         SystemManager.Instance.Initialize();
     }
     else
     {
         Debug.LogError("[LoadingManager] SystemManager가 없습니다.");
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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가 없습니다.");
}
}
public void BeginLoadingUI()
{
UpdateTask("INITIALIZATION", "시스템 초기화 중...", 0.05f);
StartAutoProgress();
}
public void StartInitialization()
{
if (SystemManager.Instance != null)
{
BeginLoadingUI();
SystemManager.Instance.Initialize();
}
else
{
Debug.LogError("[LoadingManager] SystemManager가 없습니다.");
}
}
🤖 Prompt for AI Agents
In Assets/Core/Loading/LoadingManager.cs around lines 69-81, BeginLoadingUI and
StartInitialization duplicate the same initialization steps (UpdateTask,
StartAutoProgress, SystemManager.Initialize and null-check/log). Consolidate by
extracting the shared logic into a single private helper (e.g.,
PerformInitialization or InitializeLoading) that does the null check, calls
UpdateTask with the tag/message/progress, starts auto progress, and calls
SystemManager.Instance.Initialize(); then have both BeginLoadingUI and
StartInitialization call that helper (or make one call the other) and remove the
duplicated lines so the null-check and logging remain handled in one place.

Comment on lines 130 to 146
public async void Initialize()
{
if (_initializationKickoffDone && IsInitialized)
{
return;
}
_initializationKickoffDone = true;

// Camera 업데이트 및 ScreenTapManager 초기화
UpdateCamera();
if (_camera != null)
{
ScreenTapManager.Instance.Initialize(_camera);
}

await InitializeAppAsync();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Initialize 메서드의 중복 호출 방지 로직 개선 필요

Line 132-134의 조건문이 AND 연산자를 사용하고 있어, _initializationKickoffDone이 true이고 IsInitialized가 false인 경우 재초기화 시도가 가능합니다. 이는 의도하지 않은 동작일 수 있습니다.

초기화 상태 체크 로직을 개선하세요:

 public async void Initialize()
 {
-    if (_initializationKickoffDone && IsInitialized)
+    if (_initializationKickoffDone || IsInitialized)
     {
+        Debug.Log($"[SystemManager] 초기화 스킵 - kickoff: {_initializationKickoffDone}, initialized: {IsInitialized}");
         return;
     }
     _initializationKickoffDone = true;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public async void Initialize()
{
if (_initializationKickoffDone && IsInitialized)
{
return;
}
_initializationKickoffDone = true;
// Camera 업데이트 및 ScreenTapManager 초기화
UpdateCamera();
if (_camera != null)
{
ScreenTapManager.Instance.Initialize(_camera);
}
await InitializeAppAsync();
}
public async void Initialize()
{
if (_initializationKickoffDone || IsInitialized)
{
Debug.Log($"[SystemManager] 초기화 스킵 - kickoff: {_initializationKickoffDone}, initialized: {IsInitialized}");
return;
}
_initializationKickoffDone = true;
// Camera 업데이트 및 ScreenTapManager 초기화
UpdateCamera();
if (_camera != null)
{
ScreenTapManager.Instance.Initialize(_camera);
}
await InitializeAppAsync();
}
🤖 Prompt for AI Agents
In Assets/Core/Managers/SystemManager.cs around lines 130 to 146, the
early-return check uses && which allows re-entry when _initializationKickoffDone
is true but IsInitialized is false; change the condition to use || so the method
returns if either kickoff already ran or the system is already initialized (i.e.
if (_initializationKickoffDone || IsInitialized) { return; }), keeping the
subsequent _initializationKickoffDone assignment and rest of the flow intact.

Comment on lines +108 to +111
public string ModelId => characterId;
public string ModelName => characterName;
public GameObject ModelPrefab => characterPrefab;
public string ModelDescription => characterDescription;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

중복된 프로퍼티 제거 필요

Line 108-111에서 Model 관련 프로퍼티들이 Character 프로퍼티들과 동일한 값을 반환하는 중복 코드입니다. 이는 혼란을 야기할 수 있습니다.

중복 프로퍼티를 제거하거나 명확한 용도 구분이 필요합니다:

-    public string ModelId => characterId;
-    public string ModelName => characterName;
-    public GameObject ModelPrefab => characterPrefab;
-    public string ModelDescription => characterDescription;
+    // Model 관련 프로퍼티는 Character 프로퍼티 사용을 권장
+    [Obsolete("Use CharacterId instead")]
+    public string ModelId => characterId;
+    [Obsolete("Use CharacterName instead")]
+    public string ModelName => characterName;
+    [Obsolete("Use CharacterPrefab instead")]
+    public GameObject ModelPrefab => characterPrefab;
+    [Obsolete("Use CharacterDescription instead")]
+    public string ModelDescription => characterDescription;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public string ModelId => characterId;
public string ModelName => characterName;
public GameObject ModelPrefab => characterPrefab;
public string ModelDescription => characterDescription;
// Model 관련 프로퍼티는 Character 프로퍼티 사용을 권장
[Obsolete("Use CharacterId instead")]
public string ModelId => characterId;
[Obsolete("Use CharacterName instead")]
public string ModelName => characterName;
[Obsolete("Use CharacterPrefab instead")]
public GameObject ModelPrefab => characterPrefab;
[Obsolete("Use CharacterDescription instead")]
public string ModelDescription => characterDescription;
🤖 Prompt for AI Agents
In Assets/Domain/Character/Script/Config/Live2DModelConfig.cs around lines 108
to 111, four Model* properties simply return the identical Character* fields
causing duplicate, confusing API surface; either remove these duplicate
ModelId/ModelName/ModelPrefab/ModelDescription properties and update all callers
to use the Character* properties, or if separate model-facing names are needed,
change them to use distinct backing fields (e.g., modelId/modelName/etc.),
initialize those fields appropriately, or mark the duplicates Obsolete and
forward to Character* with a clear comment; then run a solution-wide
search/replace to update usages accordingly.


namespace ProjectVG.Domain.Character.Service
{
public class Live2DModelManager : Singleton<Live2DModelManager>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Singleton 클래스를 찾을 수 없습니다

Singleton 기반 클래스가 import되지 않았습니다. 제공된 코드 스니펫에 따르면 ProjectVG.Core.Utils 네임스페이스에 있습니다.

다음과 같이 import를 추가하세요:

 using Live2D.Cubism.Framework.LookAt;
 using Live2D.Cubism.Framework.MouthMovement;
+using ProjectVG.Core.Utils;
 
 namespace ProjectVG.Domain.Character.Service
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public class Live2DModelManager : Singleton<Live2DModelManager>
using Live2D.Cubism.Framework.LookAt;
using Live2D.Cubism.Framework.MouthMovement;
using ProjectVG.Core.Utils;
namespace ProjectVG.Domain.Character.Service
🤖 Prompt for AI Agents
In Assets/Domain/Character/Script/Manager/Live2DModelManager.cs around line 10,
the base class Singleton is not found because its namespace isn't imported; add
the using for ProjectVG.Core.Utils at the top of the file so the Singleton<T>
base class resolves (i.e., import the ProjectVG.Core.Utils namespace).

Comment on lines +91 to +104
private async void OnLoadCharacterClicked()
{
UpdateStatus($"캐릭터 로드 시작: {_testCharacterId}");

var character = await Live2DModelManager.Instance.LoadCharacterAsync(_testCharacterId, activateImmediately: true);
if (character != null)
{
UpdateStatus($"캐릭터 로드 및 활성화 완료: {_testCharacterId}");
}
else
{
UpdateStatus($"캐릭터 로드 실패: {_testCharacterId}");
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

async void 사용으로 인한 예외 처리 누락

async void 메서드는 예외를 제대로 처리하지 못하며, 예외 발생 시 애플리케이션이 충돌할 수 있습니다. UI 이벤트 핸들러에서도 적절한 예외 처리가 필요합니다.

try-catch 블록을 추가하여 예외를 처리하세요:

 private async void OnLoadCharacterClicked()
 {
-    UpdateStatus($"캐릭터 로드 시작: {_testCharacterId}");
-    
-    var character = await Live2DModelManager.Instance.LoadCharacterAsync(_testCharacterId, activateImmediately: true);
-    if (character != null)
-    {
-        UpdateStatus($"캐릭터 로드 및 활성화 완료: {_testCharacterId}");
-    }
-    else
-    {
-        UpdateStatus($"캐릭터 로드 실패: {_testCharacterId}");
-    }
+    try
+    {
+        UpdateStatus($"캐릭터 로드 시작: {_testCharacterId}");
+        
+        var character = await Live2DModelManager.Instance.LoadCharacterAsync(_testCharacterId, activateImmediately: true);
+        if (character != null)
+        {
+            UpdateStatus($"캐릭터 로드 및 활성화 완료: {_testCharacterId}");
+        }
+        else
+        {
+            UpdateStatus($"캐릭터 로드 실패: {_testCharacterId}");
+        }
+    }
+    catch (Exception ex)
+    {
+        Debug.LogError($"[Live2DModelTest] 캐릭터 로드 중 오류 발생: {ex.Message}");
+        UpdateStatus($"캐릭터 로드 오류: {ex.Message}");
+    }
 }
🤖 Prompt for AI Agents
In Assets/Domain/Character/Script/Test/Live2DModelTest.cs around lines 91-104,
the method OnLoadCharacterClicked is declared async void which loses exceptions;
change it to return Task (async Task OnLoadCharacterClicked) and wrap the await
call in a try-catch that catches Exception, logs the error via UpdateStatus (or
a logger) with the exception message/stack, and rethrows only if truly
unrecoverable; if the event system requires a void handler, keep async void but
add the try-catch inside the method to handle and log exceptions instead of
letting them crash the app.

### 주요 수정사항

#### AudioManager 개선
- LoadVolumeSettings에서 무한 재귀 호출 위험 방지
  - _isLoadingSettings 플래그 추가로 로딩 중 저장 방지
  - SaveVolumeSettings에서 로딩 중일 때 early return 처리

#### AudioController 코루틴 안정성 개선
- UIController 및 SFXController의 ReturnToPoolWhenFinished 코루틴에 null 체크 추가
  - AudioSource 파괴 시 NullReferenceException 방지
  - while 루프와 ReturnToPool 호출 시 null 검증

#### SessionManager 이벤트 처리 개선
- WebSocketManager null 처리 시 OnSessionError 이벤트 발생 추가
  - Initialize, EnsureConnectionAsync, RequestConnectionAsync 메서드에서 이벤트 발생
  - 예외 발생 시에도 OnSessionError 이벤트 발생

#### ChatManager 초기화 안정성 개선
- InitializeWhenReady 코루틴에 타임아웃 추가 (5초)
  - ChatBubblePanel null 상태에서 무한 대기 방지
  - 타임아웃 시 에러 로그 출력 후 yield break 처리
- System.Collections 네임스페이스 추가로 IEnumerator 타입 오류 해결

#### SystemManager 예외 처리 개선
- Initialize async void 메서드에 try-catch 블록 추가
- 예외 발생 시 OnInitializationError 이벤트 발생

#### Singleton import 누락 수정
- AudioManager, SystemManager에 ProjectVG.Core.Utils 네임스페이스 추가
- Singleton<T> 클래스 참조 오류 해결

### 기술적 개선사항
- async void 메서드의 예외 처리 강화
- 코루틴에서 null 체크 및 타임아웃 처리
- 이벤트 기반 에러 처리 일관성 확보
- 메모리 누수 및 무한 루프 방지
## 주요 변경사항

### 1. 오디오 시스템 아키텍처 개선
- **AudioManager 리팩토링**: 단일 AudioManager에서 계층적 구조로 변경
  - BGMController, SFXController, UIController, VoiceController로 분리
  - AudioControllerCore 베이스 클래스와 IAudioController 인터페이스 도입
  - 각 컨트롤러별 전용 AudioMixerGroup 지원

- **오브젝트 풀링 시스템**: SFX 및 UI 사운드용 AudioSource 풀링 구현
  - 동적 AudioSource 생성 및 관리
  - 동시 재생 지원으로 성능 최적화

- **VoiceController 통합**: 기존 VoiceController 기능을 새로운 아키텍처에 통합
  - VoiceData, UniTask, 이벤트 시스템 유지
  - PauseVoice/ResumeVoice 기능 보존

### 2. ChatManager 개선
- **Singleton 패턴 최적화**: DontDestroyOnLoad 제거로 초기화 순서 문제 해결
- **UI 참조 안정화**: _chatBubblePanel null 참조 문제 해결
  - InitializeWhenReady 코루틴에 타임아웃 추가
  - 초기화 순서 안정화

### 3. 파일 구조 정리
- **ChatBubblePanel 이동**: Service → View 디렉토리로 이동
- **임시 파일 제거**: UIAudioPlayer, UIAudioEventSystem, UISoundButton 삭제

### 4. 컴파일 경고 및 오류 해결
- **Nullable Reference Types**: 모든 파일에 #nullable enable 적용
- **CS8600/CS8601/CS8602/CS8603 경고**: null 체크 및 nullable 타입 선언
- **CS8618 경고**: 필드 초기화 및 nullable 선언
- **CS0114 경고**: override 키워드 추가 (Awake 메서드)
- **CS0414 경고**: 사용하지 않는 필드 제거
- **CS4014/CS1998 경고**: async 메서드 최적화
- **CS0305/CS1622 오류**: 코루틴 문법 수정

### 5. VoiceInputView 간소화
- **UI 요소 제거**: 진행률 표시 및 상태 텍스트 제거
- **불필요한 이벤트 제거**: OnRecordingProgress 이벤트 구독 해제

### 6. 시스템 안정성 개선
- **예외 처리 강화**: async void 메서드에 try-catch 추가
- **이벤트 일관성**: SessionManager에서 WebSocketManager null 처리 개선
- **무한 루프 방지**: 코루틴에 타임아웃 및 안전장치 추가

## 기술적 세부사항
- Unity AudioMixer 기반 볼륨 제어 시스템
- PlayerPrefs를 통한 설정 저장/로드
- Cysharp.Threading.Tasks (UniTask) 활용
- 메모리 누수 방지를 위한 AudioClip 정리 로직

## 영향받는 파일
- Core/Audio/: AudioManager, AudioControllerCore, BGMController, SFXController, UIController, VoiceController
- Domain/Chat/: ChatManager, ChatBubblePanel, VoiceInputView, ChatBubbleUI
- Core/Managers/: SystemManager
- Infrastructure/: SessionManager, WebSocketManager, STTService
- 기타: AudioRecorder, LoadingManager, Live2DModelManager 등
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

🔭 Outside diff range comments (3)
Assets/Core/Audio/AudioRecorder.cs (1)

317-321: 런타임에서 DestroyImmediate 사용은 위험 — Destroy로 교체 권장

DestroyImmediate는 에디터 전용 시나리오에 적합하며, 플레이 모드/런타임에서 사용 시 프레임 안전성 문제와 예기치 않은 부작용이 발생할 수 있습니다. 일반적으로 Destroy를 사용해야 합니다. 에디터 전용 처리라면 전처리기 지시문으로 분기하세요.

권장 수정:

-                DestroyImmediate(_recordingClip);
+                Destroy(_recordingClip);

또는 에디터 전용 분기:

#if UNITY_EDITOR
    DestroyImmediate(_recordingClip);
#else
    Destroy(_recordingClip);
#endif
Assets/Infrastructure/Network/Services/STTService.cs (1)

67-68: 언어 파라미터 하드코딩에 대한 개선 제안

현재 forcedLanguage를 "ko"로 하드코딩하고 있어 메서드 파라미터 language가 무시됩니다. 이는 API 사용자에게 혼란을 줄 수 있습니다.

다음 중 하나의 방식으로 개선을 권장합니다:

옵션 1: 파라미터 사용

- string forcedLanguage = "ko";
- string endpoint = $"stt/transcribe?language={forcedLanguage}";
+ string endpoint = $"stt/transcribe?language={language}";

옵션 2: 메서드 시그니처에서 파라미터 제거

- public async UniTask<string> ConvertSpeechToTextAsync(byte[] audioData, string audioFormat = "wav", string language = "ko", CancellationToken cancellationToken = default)
+ public async UniTask<string> ConvertSpeechToTextAsync(byte[] audioData, string audioFormat = "wav", CancellationToken cancellationToken = default)

옵션 3: 주석으로 명확히 설명

/// <param name="language">언어 코드 (현재 서버는 한국어 "ko"만 지원)</param>
Assets/Infrastructure/Network/WebSocket/WebSocketManager.cs (1)

345-373: JSON 완전성 검사 로직의 잠재적 문제

IsCompleteJsonMessage 메서드에서 이스케이프된 따옴표를 처리할 때 \\"가 아닌 연속된 백슬래시(\\\\)를 고려하지 않고 있습니다. 예를 들어 "test\\\"" 같은 경우 잘못 파싱될 수 있습니다.

 private bool IsCompleteJsonMessage(string message)
 {
     if (string.IsNullOrEmpty(message))
         return false;
         
     int openBraces = 0;
     int closeBraces = 0;
     bool inString = false;
-    char escapeChar = '\\';
     
     for (int i = 0; i < message.Length; i++)
     {
         char c = message[i];
         
-        if (c == '"' && (i == 0 || message[i - 1] != escapeChar))
+        // 이스케이프 시퀀스를 적절히 처리
+        bool isEscaped = false;
+        if (i > 0)
+        {
+            int backslashCount = 0;
+            for (int j = i - 1; j >= 0 && message[j] == '\\'; j--)
+            {
+                backslashCount++;
+            }
+            isEscaped = (backslashCount % 2) == 1;
+        }
+        
+        if (c == '"' && !isEscaped)
         {
             inString = !inString;
         }
         else if (!inString)
         {
             if (c == '{')
                 openBraces++;
             else if (c == '}')
                 closeBraces++;
         }
     }
     
     return openBraces > 0 && openBraces == closeBraces;
 }
♻️ Duplicate comments (5)
Assets/Domain/Character/Script/Controller/Live2DModelApplier.cs (1)

7-9: Apply 본문 비어 있음 → 최소 검증/로그/예외 처리 추가 제안

런타임 경로에서 NRE/침묵 실패를 방지하기 위해 입력 검증과 예외 처리를 최소한으로 넣는 것이 좋습니다. 과거 코멘트와 동일한 지적입니다.

적용 제안(diff):

-        public void Apply(GameObject activeModel, ProjectVG.Domain.Character.Live2D.Model.Live2DModelConfig characterConfig)
-        {
-        }
+        public void Apply(GameObject activeModel, ProjectVG.Domain.Character.Live2D.Model.Live2DModelConfig characterConfig)
+        {
+            if (activeModel == null)
+            {
+                Debug.LogError("[Live2DModelApplier] activeModel is null.");
+                return;
+            }
+            if (characterConfig == null)
+            {
+                Debug.LogError("[Live2DModelApplier] characterConfig is null.");
+                return;
+            }
+
+            try
+            {
+                // TODO: 모델에 Config 적용(색/표정/모션/시선/립싱크 등 세부 파라미터 바인딩)
+                Debug.Log($"[Live2DModelApplier] Config '{characterConfig}'을(를) '{activeModel.name}'에 적용했습니다.");
+            }
+            catch (Exception ex)
+            {
+                Debug.LogError($"[Live2DModelApplier] 적용 중 예외 발생. model='{activeModel?.name}', config='{characterConfig}'.");
+                Debug.LogException(ex);
+            }
+        }
Assets/Domain/Chat/Service/ChatManager.cs (1)

49-60: 싱글톤 패턴 스레드 안전성 문제

현재 싱글톤 구현이 스레드 안전하지 않습니다. 여러 스레드에서 동시에 Instance에 접근하면 문제가 발생할 수 있습니다.

 private static ChatManager? _instance;
+private static readonly object _instanceLock = new object();
+
 public static ChatManager Instance
 {
     get
     {
-        if (_instance == null)
-        {
-            _instance = FindAnyObjectByType<ChatManager>();
-        }
-        return _instance;
+        lock (_instanceLock)
+        {
+            if (_instance == null)
+            {
+                _instance = FindAnyObjectByType<ChatManager>();
+            }
+            return _instance;
+        }
     }
 }
Assets/Core/Managers/SystemManager.cs (3)

133-139: Initialize 메서드의 중복 호출 방지 로직 개선 필요

Line 135의 조건문이 AND 연산자를 사용하고 있어, _initializationKickoffDone이 true이고 IsInitialized가 false인 경우 재초기화 시도가 가능합니다. 이는 의도하지 않은 동작일 수 있습니다.

 public async void Initialize()
 {
     try
     {
-        if (_initializationKickoffDone && IsInitialized)
+        if (_initializationKickoffDone || IsInitialized)
         {
+            Debug.Log($"[SystemManager] 초기화 스킵 - kickoff: {_initializationKickoffDone}, initialized: {IsInitialized}");
             return;
         }
         _initializationKickoffDone = true;

210-221: 씬 이름 오타: "MainSence" → "MainScene"

Line 214에서 씬 이름이 "MainSence"로 잘못 입력되어 있습니다. 이는 런타임 에러를 발생시킬 수 있습니다.

 public async UniTask TransitionToMainSceneAsync()
 {
     try
     {
-        await UnityEngine.SceneManagement.SceneManager.LoadSceneAsync("MainSence");
+        await UnityEngine.SceneManagement.SceneManager.LoadSceneAsync("MainScene");
         Debug.Log("[SystemManager] MainScene 전환 완료");
     }

240-257: InitializeManagersAsync에서 에러 처리 개선 필요

매니저 초기화 실패 시 어떤 매니저가 문제인지 구체적인 정보가 없어 디버깅이 어려울 수 있습니다.

 private async UniTask InitializeManagersAsync()
 {
     if (_webSocketManager == null || _sessionManager == null || _httpApiClient == null)
     {
-        throw new InvalidOperationException("필수 매니저 인스턴스를 찾을 수 없습니다.");
+        var missingManagers = new System.Collections.Generic.List<string>();
+        if (_webSocketManager == null) missingManagers.Add("WebSocketManager");
+        if (_sessionManager == null) missingManagers.Add("SessionManager");
+        if (_httpApiClient == null) missingManagers.Add("HttpApiClient");
+        throw new InvalidOperationException($"필수 매니저 인스턴스를 찾을 수 없습니다: {string.Join(", ", missingManagers)}");
     }
+    
+    Debug.Log("[SystemManager] LoadingManager 초기화 중...");
     _loadingManager?.BeginLoadingUI();
+    
+    Debug.Log("[SystemManager] AudioManager 초기화 중...");
     _audioManager?.Initialize();
+    
+    Debug.Log("[SystemManager] WebSocketManager 초기화 중...");
     _webSocketManager.Initialize();
+    
+    Debug.Log("[SystemManager] SessionManager 초기화 중...");
     _sessionManager.Initialize(_webSocketManager);
+    
+    Debug.Log("[SystemManager] HttpApiClient 초기화 중...");
     _httpApiClient.Initialize(_sessionManager);

+    Debug.Log("[SystemManager] 세션 연결 시도 중...");
     bool connected = await _sessionManager.EnsureConnectionAsync();
     if (!connected)
     {
         throw new InvalidOperationException("세션 연결 실패");
     }
+    Debug.Log("[SystemManager] 모든 매니저 초기화 완료");
 }
🧹 Nitpick comments (38)
Assets/Core/Audio/AudioRecorder.cs (3)

101-101: 명시적 null 체크는 OK. 공백 문자열까지 커버하도록 보강 제안

현재 삼항 연산자는 null만 처리합니다. 장치명이 공백(또는 공백만 포함)인 경우까지 기본 장치로 안전하게 폴백하려면 string.IsNullOrWhiteSpace가 더 견고합니다. 기능은 동일하며, 예상치 못한 공백 입력에도 방어적으로 동작합니다.

적용 예:

-                _recordingClip = Microphone.Start(_currentDevice != null ? _currentDevice : string.Empty, false, _maxRecordingLength, _sampleRate);
+                _recordingClip = Microphone.Start(string.IsNullOrWhiteSpace(_currentDevice) ? string.Empty : _currentDevice, false, _maxRecordingLength, _sampleRate);
-                Microphone.End(_currentDevice != null ? _currentDevice : string.Empty);
+                Microphone.End(string.IsNullOrWhiteSpace(_currentDevice) ? string.Empty : _currentDevice);

Also applies to: 135-135


96-104: 녹음 시작 시 선택된 마이크 유효성 재검증 및 기본값 폴백 권장

런타임 중 장치 플러그/언플러그로 _currentDevice가 더 이상 존재하지 않을 수 있습니다. Microphone.Start가 실패/예외가 될 수 있으므로 시작 직전에 장치 존재 여부를 재검증하고 기본 마이크로 폴백하는 방어로직을 권장합니다.

예시 diff:

             try
             {
                 _isRecording = true;
                 _recordingStartTime = Time.time;
                 
-                _recordingClip = Microphone.Start(_currentDevice != null ? _currentDevice : string.Empty, false, _maxRecordingLength, _sampleRate);
+                // 디바이스 유효성 재검증 및 폴백
+                if (Array.IndexOf(Microphone.devices, _currentDevice) < 0)
+                {
+                    _currentDevice = GetDefaultMicrophone();
+                    Debug.LogWarning("[AudioRecorder] 선택된 마이크가 없어 기본 마이크로 폴백합니다.");
+                }
+                _recordingClip = Microphone.Start(string.IsNullOrWhiteSpace(_currentDevice) ? string.Empty : _currentDevice, false, _maxRecordingLength, _sampleRate);

131-139: 정확한 길이 산정을 위해 GetPosition 기반 계산을 1차 사용하고, 시간 기반은 폴백으로

현재 실제 길이를 Time.time 차이로 계산합니다. Microphone.GetPosition(device)를 Stop 직전에 우선 사용하면 드라이버/버퍼 지연을 더 정확히 반영할 수 있습니다. 회로망/플랫폼 이슈로 position이 유효하지 않은 경우에만 기존 시간 기반으로 폴백하면 안정적입니다.

예시 diff:

-                _recordingEndTime = Time.time;
-                float actualRecordingDuration = _recordingEndTime - _recordingStartTime;
-                
-                Microphone.End(_currentDevice != null ? _currentDevice : string.Empty);
+                // Stop 시점의 실제 샘플 위치를 우선 사용
+                string deviceName = string.IsNullOrWhiteSpace(_currentDevice) ? string.Empty : _currentDevice;
+                int position = Microphone.GetPosition(deviceName);
+                Microphone.End(deviceName);
+                // position이 유효하지 않을 경우 시간 기반으로 폴백
+                float actualRecordingDuration =
+                    (_recordingClip != null && _recordingClip.frequency > 0 && position >= 0)
+                        ? Mathf.Clamp(position, 0, _recordingClip.samples) / (float)_recordingClip.frequency
+                        : (Time.time - _recordingStartTime);
Assets/Infrastructure/Config/AppEnvironmentConfig.cs (3)

11-11: CS0414 전역 비활성화 범위가 과도합니다. 제거 또는 범위 축소를 권장합니다.

현재 모든 직렬화 필드가 실제로 참조되고 있어 CS0414(할당되었지만 사용되지 않음) 억제가 불필요합니다. 광범위한 비활성화는 향후 실제 문제를 숨길 수 있습니다.

아래와 같이 지시어 제거를 제안합니다(라인 28의 restore도 함께 제거해 주세요, 다음 코멘트 참조).

-        #pragma warning disable CS0414

28-28: 불필요한 CS0414 복원 지시어 제거

상단의 disable을 제거한다면, 해당 restore 또한 제거되어야 합니다.

-        #pragma warning restore CS0414

30-32: 공개 프로퍼티 명명 및 일관성 개선 제안 (IOSEnvironment → IosEnvironment, 내부 사용 일원화 권장)

  • 명명: .NET 가이드라인에 따르면 2글자 약어는 PascalCase(예: Ios) 사용을 권장합니다. 팀 규칙에 따라 다를 수 있으니 합의된 컨벤션으로 정비해 주세요.
  • 일관성: 현재 프로퍼티는 내부에서 사용되지 않습니다. 가독성과 단일 접근 경로를 위해 GetCurrentEnvironment의 iOS 분기에서 필드 대신 프로퍼티를 반환하는 것을 권장합니다.
  • 선택 사항: iOS만 공개 프로퍼티가 있는 것은 불균형합니다. 동일한 패턴으로 다른 플랫폼도 노출하면 API 사용성이 좋아집니다.

프로퍼티 명명 및 추가(선택)의 예시:

-        public NetworkConfig.EnvironmentType IOSEnvironment => iosEnvironment;
+        public NetworkConfig.EnvironmentType IosEnvironment => iosEnvironment;
+        // (선택) 다른 플랫폼도 동일하게 노출
+        public NetworkConfig.EnvironmentType AndroidEnvironment => androidEnvironment;
+        public NetworkConfig.EnvironmentType EditorEnvironment => editorEnvironment;
+        public NetworkConfig.EnvironmentType WebGLEnvironment => webglEnvironment;
+        public NetworkConfig.EnvironmentType StandaloneEnvironment => standaloneEnvironment;
+        public bool OverrideEnabled => overrideEnabled;
+        public NetworkConfig.EnvironmentType OverrideEnvironment => overrideEnvironment;
+        public bool MapDevelopmentBuildToDevelopment => mapDevelopmentBuildToDevelopment;

GetCurrentEnvironment 내부 사용 일원화(파일 내 다른 위치의 변경이며, 참고용):

// 변경 전
case RuntimePlatform.IPhonePlayer:
    return iosEnvironment;

// 변경 후
case RuntimePlatform.IPhonePlayer:
    return IosEnvironment;
Assets/Domain/Character/Script/Controller/Live2DModelApplier.cs (4)

1-2: Exception 사용을 위해 using System 추가 필요

위 제안처럼 try/catch를 쓸 경우 System 네임스페이스가 필요합니다.

+using System;
 using UnityEngine;

3-3: 폴더(Controller)와 네임스페이스(Service) 불일치

파일 경로가 Controller인데 네임스페이스는 Service입니다. 팀 컨벤션에 맞춰 정렬하는 것을 권장합니다. 선택지:

  • 네임스페이스를 Controller로 변경, 또는
  • 파일을 Service 폴더로 이동

변경 시 ILive2DModelApplier의 네임스페이스와 참조(using)가 맞는지 확인 필요합니다.

-namespace ProjectVG.Domain.Character.Service
+namespace ProjectVG.Domain.Character.Controller

5-5: 중복 부착 방지를 위한 DisallowMultipleComponent 권장

동일 오브젝트에 다중 부착될 필요가 없는 유틸/어댑터 성격의 컴포넌트이므로 방지 어트리뷰트를 붙이는 것이 안전합니다.

-    public class Live2DModelApplier : MonoBehaviour, ILive2DModelApplier
+    [DisallowMultipleComponent]
+    public class Live2DModelApplier : MonoBehaviour, ILive2DModelApplier

5-5: MonoBehaviour 채택이 필요한지 확인: 주입/할당 경로 점검

이 구현이 MonoBehaviour여야 하는 명확한 이유(씬에 붙여 인스펙터로 참조 연결 등)가 없다면 POCO로 두는 편이 테스트성과 의존성 관리가 유리합니다. 반대로 MonoBehaviour로 유지한다면, Unity 기본 직렬화는 interface 필드(ILive2DModelApplier)의 인스펙터 할당을 지원하지 않습니다. Manager에서의 참조 획득 방식을 확인해주세요:

  • GetComponent()로 런타임 바인딩, 또는
  • [SerializeReference]를 사용해 인터페이스 필드를 직렬화(에디터 UX 고려 필요), 또는
  • Manager가 구상 타입(Live2DModelApplier)을 직접 의존
Assets/Editor/OpenDocsMenu.cs (1)

6-8: 문서화 스타일을 일관되게 유지하세요.

C# XML 문서 주석 형식(///)을 사용하는 것이 더 표준적입니다.

다음 차이점을 적용하여 문서화 스타일을 개선하세요:

-	/**
-	 * 프로젝트 문서(루트 Docs/)를 파일 탐색기로 여는 메뉴 항목을 추가합니다.
-	 */
+	/// <summary>
+	/// 프로젝트 문서(루트 Docs/)를 파일 탐색기로 여는 메뉴 항목을 추가합니다.
+	/// </summary>
ignore.conf (2)

2-17: 대소문자 이중 패턴을 단일 패턴으로 단순화하세요

동일 의미의 대/소문자 중복 라인을 bracket 패턴으로 축약하면 가독성과 유지보수성이 좋아집니다.

-Library
-library
-Temp
-temp
-Obj
-obj
-Build
-build
-Builds
-builds
-UserSettings
-usersettings
-MemoryCaptures
-memorycaptures
-Logs
-logs
+/[Ll]ibrary
+/[Tt]emp
+/[Oo]bj
+/[Bb]uild
+/[Bb]uilds
+/[Uu]ser[Ss]ettings
+/[Mm]emory[Cc]aptures
+/[Ll]ogs

1-1: ignore.conf의 사용 주체/의미를 확인해 주세요

Repo 루트의 표준 파일은 .gitignore입니다. ignore.conf가 어떤 시스템(예: 사내 툴, CI, 에디터 플러그인 등)에 의해 소비되는지 명확치 않습니다. 중복 규칙이 .gitignore와 분기되면 드리프트 위험이 큽니다. 가능하면 .gitignore로 단일화하거나, ignore.conf의 소비 주체를 파일 상단에 주석으로 명시해 주세요.

원하시면 .gitignore 기준으로 규칙을 정규화하고, ignore.conf는 제거/자동 생성하도록 스크립트를 제안드리겠습니다.

.gitignore (3)

25-27: Asset Store Tools 무시 규칙의 불일치

.gitignore에서는 AssetStoreTools를 주석 처리했고, ignore.conf에서는 무시 중입니다. 규칙이 분기되면 혼란을 야기합니다. 하나로 통일해 주세요.

통일 방안 예시:

  • 무시하려면 .gitignore의 주석을 해제:
-# /[Aa]ssets/AssetStoreTools*
+/[Aa]ssets/AssetStoreTools*
  • 무시하지 않으려면 ignore.conf에서 해당 라인을 제거.

32-36: Gradle/IDE 캐시 무시 범위 재검토 제안

.gradle/는 루트에 존재할 수 있지만, Android 플러그인이 생성하는 Assets/Plugins/Android/gradle* 등과 혼동될 수 있습니다. 필요 최소 범위로 유지하거나 경로를 명확히 하는 것을 권장합니다.

예: 루트 전용으로 제한

-.gradle/
+/.gradle/

105-118: 대용량 미디어 글로벌 무시 검토 결과

  • 현재 레포에 추적 중인 대용량 미디어 파일(mp4, avi, mov, wmv, flv, mkv, webm, m4a, m4v, m4b, m4r, m4p)이 없습니다.

  • 하지만 향후 데모나 리소스에 미디어를 커밋할 경우 전역 ignore 설정으로 인해 필요한 파일이 누락될 수 있으니, 팀 정책에 맞게 아래 옵션 중 하나를 적용하세요.

    • 옵션 A: 미디어가 필요한 디렉터리만 whitelist(!) 처리
    • 옵션 B: Git LFS로 관리하고 .gitattributes에 해당 확장자 매핑

.plasticignore (3)

25-25: .vsconfig 무시는 팀 온보딩/개발 환경 재현성에 불리합니다

.vsconfig는 VS 워크로드/컴포넌트 구성을 공유하기 위해 보통 저장소에 커밋하는 것이 권장됩니다. 무시 대상에서 제외하는 것을 고려해 주세요.

적용 제안(diff):

-*.vsconfig

71-84: 대용량 미디어 확장자 전역 무시는 과격합니다 — 범위를 디렉터리로 한정하거나 LFS/패키징을 고려하세요

프로젝트가 실제로 비디오/오디오 파일을 자산으로 사용한다면(예: 컷신, 튜토리얼, UI 배경), 전역 무시는 의도치 않은 자산 누락을 유발할 수 있습니다. 보통은 캡처/임시 산출물 디렉터리로 범위를 한정하거나, 대용량 자산 관리 전략(Plastic의 대용량 파일 저장, 패키지/UPM, 원격 CDN/Addressables)을 사용합니다.

예시로, Recordings 폴더로 범위를 축소하는 변경안(diff):

-*.mp4
-*.avi
-*.mov
-*.wmv
-*.flv
-*.mkv
-*.webm
-*.m4a
-*.m4v
-*.m4b
-*.m4r
-*.m4p
+/[Rr]ecordings/*.mp4
+/[Rr]ecordings/*.avi
+/[Rr]ecordings/*.mov
+/[Rr]ecordings/*.wmv
+/[Rr]ecordings/*.flv
+/[Rr]ecordings/*.mkv
+/[Rr]ecordings/*.webm
+/[Rr]ecordings/*.m4a
+/[Rr]ecordings/*.m4v
+/[Rr]ecordings/*.m4b
+/[Rr]ecordings/*.m4r
+/[Rr]ecordings/*.m4p

팀에서 대용량 자산을 어떻게 관리할지(저장소 추적 vs 원격 배포/어드레서블 vs 전용 스토리지) 정책 확인을 부탁드립니다.


85-87: 플러그인 디렉터리 전체 무시는 빌드 의존성 손실 위험이 있습니다

/Assets/Plugins/FiveMinuteChat, /Assets/Plugins/WebGLTemplates를 무시하면 해당 플러그인이 패키지(UPM)나 외부 소스에서 자동 복원되지 않는 한, 새 클론/CI 빌드가 실패할 수 있습니다. 패키지로 관리 중인지, 재생성 가능한 산출물인지 확인해 주세요. 필요 의존성이라면 무시 대상에서 제외하거나 패키지화/서브모듈화가 안전합니다.

Assets/Domain/Chat/View/ChatBubbleUI.cs (2)

8-8: 불필요한 빈 줄 정리

using 문 섹션에 빈 줄이 하나 더 추가되어 있습니다. 일관성을 위해 제거하는 것이 좋겠습니다.

 using ProjectVG.Domain.Chat.Model;
-
 

36-36: 제거된 필드와 관련된 빈 줄 정리

애니메이션 관련 필드들이 제거되면서 생긴 불필요한 빈 줄입니다. 코드 정리를 위해 제거하는 것을 권장합니다.

 [SerializeField] private float _queueSlideDuration = 0.2f;
-
 [SerializeField] private bool _enableBounceEffect = true;
Assets/Infrastructure/Network/Services/STTService.cs (1)

78-79: 응답 검증 로직의 개선이 필요합니다.

현재 response?.Text가 null이거나 빈 문자열일 때의 처리가 있지만, response 자체가 null일 경우에 대한 더 명확한 처리가 필요합니다.

응답 검증 로직을 더 명확하게 개선할 수 있습니다:

- if (response != null && !string.IsNullOrEmpty(response.Text))
- {
-     return response.Text;
- }
- else
- {
-     Debug.LogError($"[STTService] STT 변환 실패: 응답이 비어있습니다. Text: '{response?.Text}'");
-     throw new Exception("음성 변환 실패: 응답이 비어있습니다.");
- }
+ if (response == null)
+ {
+     Debug.LogError("[STTService] STT 변환 실패: 응답이 null입니다.");
+     throw new Exception("음성 변환 실패: 서버 응답이 없습니다.");
+ }
+ 
+ if (string.IsNullOrEmpty(response.Text))
+ {
+     Debug.LogError($"[STTService] STT 변환 실패: 변환된 텍스트가 비어있습니다.");
+     throw new Exception("음성 변환 실패: 변환된 텍스트가 비어있습니다.");
+ }
+ 
+ return response.Text;
Docs/Conventions/PR_Review.md (2)

7-10: 체크리스트를 실무 기준으로 확장하세요 (CI/리뷰 승인/브레이킹체인지/롤백 포함).

머지 게이트 강화를 위해 최소 승인 수, CI 상태, 브레이킹 체인지와 롤백 계획 항목을 추가하는 것을 권장합니다.

적용 예시:

 ## 체크리스트
 - [ ] 테스트 통과
+- [ ] 모든 CI 상태 통과 (빌드/정적분석/포맷터/링터)
+- [ ] 최소 1명 이상의 리뷰 승인
 - [ ] 변경 영향 범위 요약
 - [ ] 관련 문서 링크 또는 업데이트
+- [ ] 브레이킹 체인지 여부 및 마이그레이션 가이드
+- [ ] 롤백/백아웃 계획(선택)

3-6: 문서 업데이트 원칙을 조금 더 구체화하세요.

Docs/ 경로 내 업데이트 범위를 명시(다이어그램, API 문서, 운영 가이드 등)하면 누락을 줄일 수 있습니다.

추가 제안:

  • 변경 사항 관련 문서: 설계 다이어그램(PlantUML), API/프로토콜 스펙, 운영/런북, 마이그레이션 가이드까지 포함한다고 명시
  • PR 본문에 “영향 범위/리스크/테스트 범주(단위/통합/E2E/플레이모드)” 섹션 고정 템플릿 사용 권장
Docs/Conventions/Commit_Message.md (2)

34-34: 오탈자: ‘메세지’ → ‘메시지’.

표준 표기 사용을 권장합니다.

-## 메세지 구조
+## 메시지 구조

17-32: 컨벤션 보강: BREAKING CHANGE/Refs/Co-authored-by 지침 추가 제안.

Conventional Commits 호환성을 높이려면 Footer 섹션에 표준 키워드 사용법을 명시하세요.

추가 제안:

  • Footer 키워드: BREAKING CHANGE: <설명>, Closes #123, Refs #456, Co-authored-by: Name
  • Subject 가이드: 50자 이내, 마침표 생략, 영문일 경우 동사 원형. 본문은 72자 래핑.
  • 다중 스코프 표기: feat(ui,network): ...
    원하시면 섹션 초안도 드리겠습니다.
Docs/Conventions/Unity_Asset_Naming.md (2)

24-29: 약어 사용 금지 규칙과 예시 간 불일치(SO/UI). 허용 약어를 명시하세요.

본문 예시에 SO(=ScriptableObject), UI 약어를 사용하고 있는데, ‘약어 조합 금지’ 규칙과 충돌 소지가 있습니다. 프로젝트에서 허용되는 표준 약어 목록을 명시하는 것이 좋습니다.

 ## 금지/주의
 - 범용어 남용: Data/Util/Helper/Manager(무의미 사용)
 - 약어 조합: Cfg/Ctrllr/Svc
+- (허용 약어 예외) SO(=ScriptableObject), UI, ID 등 프로젝트 표준 약어는 사용 가능
 - 파일명 ≠ 타입명 불일치
 - 부정형 이벤트/플래그: NotReady → 긍정형 ShouldWait/IsReady

15-17: UI 위젯 접두사 목록의 범위를 명확히 하세요.

TMP_Text, RawImage, ScrollRect 등 자주 쓰는 타입 명칭을 예시로 보강하면 해석 차이를 줄일 수 있습니다.

보강 예시:

  • Txt → TitleTxt, BodyTxt (TMP_Text)
  • Img/RawImg → IconImg, PortraitRawImg
  • Scroll/ScrollRect → MessagesScroll
  • Input(InputField/TMP_InputField) → MessageInput
Docs/Conventions/CodeStyle_CSharp.md (1)

23-24: 문장 위치/형식 통일 제안.

“공개 API는 명령형 동사를 사용합니다” 규칙을 표 항목으로 포함시키면 문서 구조가 더 일관됩니다.

-| 공개 API는 명령형 동사를 사용합니다(Initialize/Apply/Load 등).
+| 공개 API 동사 | 명령형 사용 (Initialize/Apply/Load 등) | Initialize, Apply, Load |

(위 항목을 “C# 패턴” 표 하단에 행으로 추가하는 방식을 권장)

Assets/Domain/Chat/View/VoiceInputView.cs (6)

200-200: 빈 줄 정리가 필요합니다.

Line 200에 불필요한 빈 줄이 있습니다. 이는 UI 구성 요소 제거 후 남은 것으로 보입니다.

다음과 같이 빈 줄을 제거하세요:

-            }
-                
-
-                
+            }
+                
             if (_audioRecorder == null)

259-259: 빈 줄 정리가 필요합니다.

Line 259에 불필요한 빈 줄이 있습니다. 이는 제거된 UI 업데이트 메서드들의 흔적으로 보입니다.

다음과 같이 빈 줄을 제거하세요:

             if (_btnVoiceStop != null)
                 _btnVoiceStop.gameObject.SetActive(isRecording);
         }
-        
-
+        
         private async System.Threading.Tasks.Task<string> ConvertSpeechToText(byte[] audioData)

330-330: 빈 줄 정리가 필요합니다.

Line 330에 불필요한 빈 줄이 있습니다. 이는 제거된 이벤트 핸들러들의 흔적으로 보입니다.

다음과 같이 빈 줄을 제거하세요:

         private void OnRecordingCompleted(AudioClip audioClip)
         {
             // AudioRecorder에서 로그 출력
         }
-        
-
+        
         private void OnRecordingError(string error)

40-45: 주석 처리된 코드를 완전히 제거하세요.

더 이상 사용하지 않는 최대 녹음 시간 체크 로직이 주석으로 남아있습니다. AudioRecorder가 자체적으로 관리한다면 이 코드는 완전히 제거하는 것이 좋습니다.

다음과 같이 주석 처리된 코드를 제거하세요:

         private void Update()
         {
-            // 새로운 AudioRecorder는 자체적으로 최대 시간을 관리하므로 제거
-            // if (_isRecording && Time.time - _recordingStartTime > _maxRecordingTime)
-            // {
-            //     StopVoiceRecording();
-            // }
         }

38-45: Update 메서드가 비어있다면 제거를 고려하세요.

Update 메서드에서 주석 처리된 코드를 제거하면 메서드가 완전히 비어있게 됩니다. 사용하지 않는 Update 메서드는 제거하는 것이 성능상 좋습니다.

Update 메서드를 완전히 제거하세요:

-        private void Update()
-        {
-            // 새로운 AudioRecorder는 자체적으로 최대 시간을 관리하므로 제거
-            // if (_isRecording && Time.time - _recordingStartTime > _maxRecordingTime)
-            // {
-            //     StopVoiceRecording();
-            // }
-        }

315-323: 빈 이벤트 핸들러들의 용도를 명확히 하세요.

OnRecordingStarted, OnRecordingStopped, OnRecordingCompleted 핸들러들이 모두 비어있습니다. 이들이 향후 구현될 예정이거나 현재 불필요하다면 제거를 고려해보세요.

불필요한 빈 핸들러들을 제거하거나 TODO 주석을 추가하세요:

         private void OnRecordingStarted()
         {
-            // AudioRecorder에서 로그 출력
+            // TODO: UI 상태 업데이트 필요시 구현
         }
         
         private void OnRecordingStopped()
         {
-            // AudioRecorder에서 로그 출력
+            // TODO: UI 상태 업데이트 필요시 구현
         }
         
         private void OnRecordingCompleted(AudioClip audioClip)
         {
-            // AudioRecorder에서 로그 출력
+            // TODO: 녹음 완료 후 추가 처리 필요시 구현
         }

Also applies to: 325-328

Assets/Infrastructure/Network/WebSocket/WebSocketManager.cs (1)

72-81: Initialize 메서드 재진입 방지 로직 개선 필요

현재 _cancellationTokenSource가 null이 아닌 경우 바로 return하고 있어, 이미 초기화되었다는 로그나 디버그 메시지 없이 조용히 종료됩니다. 이는 디버깅 시 혼란을 야기할 수 있습니다.

 public void Initialize()
 {
     if (_cancellationTokenSource != null)
     {
+        Debug.Log("[WebSocket] 이미 초기화되었습니다.");
         return;
     }
     _cancellationTokenSource = new CancellationTokenSource();
     InitializeNativeWebSocket();
 #pragma warning disable CS4014
     StartConnectionMonitoring();
 #pragma warning restore CS4014
 }
Assets/Infrastructure/Network/Services/SessionManager.cs (2)

86-92: DI 관련 오래된 로그 메시지

로그 메시지에서 여전히 "DI로 주입받은", "DependencyManager" 등의 용어를 사용하고 있습니다. 현재 PR에서는 DI 시스템이 제거되고 명시적 초기화 방식으로 변경되었습니다.

 // DI로 주입받은 WebSocketManager 확인
 if (_webSocketManager == null)
 {
-    Debug.LogError("[SessionManager] WebSocketManager가 DI로 주입되지 않았습니다. DependencyManager 설정을 확인하세요.");
+    Debug.LogError("[SessionManager] WebSocketManager가 초기화되지 않았습니다. Initialize() 호출을 확인하세요.");
     OnSessionError?.Invoke("WebSocketManager가 null입니다.");
     return false;
 }

104-110: 중복된 WebSocketManager null 체크

RequestConnectionAsync 메서드에서 WebSocketManager null 체크가 중복되어 있습니다. 이미 EnsureConnectionAsync에서 체크하고 있습니다.

 private async UniTask<bool> RequestConnectionAsync()
 {
     try
     {
-        // DI로 주입받은 WebSocketManager 사용
-        if (_webSocketManager == null)
-        {
-            Debug.LogError("[SessionManager] WebSocketManager가 DI로 주입되지 않았습니다.");
-            OnSessionError?.Invoke("WebSocketManager가 null입니다.");
-            return false;
-        }
-        
         // 1. WebSocket 연결 상태 확인 및 연결 요청
         if (!IsWebSocketConnected)
         {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be91caa and bdaea7f.

📒 Files selected for processing (31)
  • .coderabbit.yaml (0 hunks)
  • .gitignore (4 hunks)
  • .plasticignore (2 hunks)
  • Assets/Core/Audio/AudioControllerCore.cs (1 hunks)
  • Assets/Core/Audio/AudioManager.cs (1 hunks)
  • Assets/Core/Audio/AudioRecorder.cs (2 hunks)
  • Assets/Core/Audio/SFXController.cs (1 hunks)
  • Assets/Core/Audio/UIController.cs (1 hunks)
  • Assets/Core/Audio/VoiceController.cs (1 hunks)
  • Assets/Core/DebugConsole/GameDebugConsoleManager.cs (2 hunks)
  • Assets/Core/DebugConsole/LogEntryPrefab.cs (1 hunks)
  • Assets/Core/Loading/LoadingManager.cs (5 hunks)
  • Assets/Core/Managers/SystemManager.cs (1 hunks)
  • Assets/Domain/Character/Script/Controller/Live2DModelApplier.cs (1 hunks)
  • Assets/Domain/Character/Script/Manager/Live2DModelManager.cs (1 hunks)
  • Assets/Domain/Chat/Service/ChatManager.cs (9 hunks)
  • Assets/Domain/Chat/View/ChatBubbleUI.cs (4 hunks)
  • Assets/Domain/Chat/View/VoiceInputView.cs (3 hunks)
  • Assets/Editor/OpenDocsMenu.cs (1 hunks)
  • Assets/Infrastructure/Config/AppEnvironmentConfig.cs (2 hunks)
  • Assets/Infrastructure/Network/Services/STTService.cs (1 hunks)
  • Assets/Infrastructure/Network/Services/SessionManager.cs (7 hunks)
  • Assets/Infrastructure/Network/WebSocket/WebSocketManager.cs (4 hunks)
  • Docs/Conventions/Branching.md (1 hunks)
  • Docs/Conventions/CodeStyle_CSharp.md (1 hunks)
  • Docs/Conventions/Commit_Message.md (1 hunks)
  • Docs/Conventions/PR_Review.md (1 hunks)
  • Docs/Conventions/README.md (1 hunks)
  • Docs/Conventions/Unity_Asset_Naming.md (1 hunks)
  • ProjectVG_Live2D_Assignment.md (0 hunks)
  • ignore.conf (3 hunks)
💤 Files with no reviewable changes (2)
  • .coderabbit.yaml
  • ProjectVG_Live2D_Assignment.md
✅ Files skipped from review due to trivial changes (2)
  • Docs/Conventions/Branching.md
  • Docs/Conventions/README.md
🚧 Files skipped from review as they are similar to previous changes (7)
  • Assets/Core/Audio/AudioControllerCore.cs
  • Assets/Core/Audio/UIController.cs
  • Assets/Domain/Character/Script/Manager/Live2DModelManager.cs
  • Assets/Core/Audio/SFXController.cs
  • Assets/Core/Loading/LoadingManager.cs
  • Assets/Core/Audio/VoiceController.cs
  • Assets/Core/Audio/AudioManager.cs
🧰 Additional context used
🧬 Code Graph Analysis (9)
Docs/Conventions/Unity_Asset_Naming.md (2)
Assets/TutorialInfo/Scripts/Readme.cs (1)
  • Readme (4-16)
Assets/TutorialInfo/Scripts/Editor/ReadmeEditor.cs (1)
  • CustomEditor (9-242)
Assets/Domain/Character/Script/Controller/Live2DModelApplier.cs (5)
Assets/Domain/Character/Script/Controller/ILive2DModelApplier.cs (1)
  • Apply (8-8)
Assets/Domain/Character/Script/Manager/Live2DModelManager.cs (4)
  • GameObject (29-77)
  • GameObject (156-165)
  • GameObject (258-272)
  • Live2DModelConfig (242-253)
Assets/Core/SystemManager.cs (2)
  • SystemManager (6-70)
  • ModelInit (28-45)
Assets/Domain/Character/Script/CubismLookTarget.cs (1)
  • CubismLookTarget (4-25)
Assets/Infrastructure/Data/ModelConfig.cs (1)
  • CreateAssetMenu (3-52)
Assets/Infrastructure/Config/AppEnvironmentConfig.cs (1)
Assets/Infrastructure/Network/Configs/NetworkConfig.cs (4)
  • NetworkConfig (221-254)
  • ApplyRuntimeGuard (91-100)
  • EnvironmentType (52-57)
  • ProjectVG (4-258)
Assets/Infrastructure/Network/Services/STTService.cs (2)
Assets/Infrastructure/Network/Http/HttpApiClient.cs (1)
  • HttpApiClient (15-452)
Assets/Infrastructure/Network/Services/ChatApiService.cs (1)
  • ChatApiService (14-130)
Assets/Domain/Chat/View/ChatBubbleUI.cs (2)
Assets/Domain/Chat/View/ChatBubblePanel.cs (2)
  • ChatBubblePanel (11-252)
  • Initialize (156-171)
Assets/Domain/Chat/Service/ChatBubbleManager.cs (2)
  • ChatBubbleManager (11-252)
  • CreateBubble (55-107)
Assets/Domain/Chat/Service/ChatManager.cs (3)
Assets/Domain/Chat/View/ChatBubblePanel.cs (3)
  • ChatBubblePanel (11-252)
  • Awake (41-44)
  • CreateBubble (55-107)
Assets/Infrastructure/Network/WebSocket/WebSocketManager.cs (2)
  • WebSocketManager (14-472)
  • Awake (53-56)
Assets/Core/Audio/AudioManager.cs (4)
  • AudioManager (11-334)
  • Awake (50-53)
  • PlayVoice (97-100)
  • PlayVoice (102-105)
Assets/Infrastructure/Network/Services/SessionManager.cs (4)
Assets/Core/Utils/Singleton.cs (1)
  • Singleton (3-36)
Assets/Infrastructure/Network/WebSocket/WebSocketManager.cs (2)
  • WebSocketManager (14-472)
  • Initialize (70-81)
Assets/Infrastructure/Network/Http/HttpApiClient.cs (1)
  • Initialize (47-56)
Assets/Core/Managers/ManagerRegistry.cs (1)
  • ManagerRegistry (13-124)
Assets/Infrastructure/Network/WebSocket/WebSocketManager.cs (5)
Assets/Core/Utils/Singleton.cs (1)
  • Singleton (3-36)
Assets/Domain/Chat/Service/ChatManager.cs (1)
  • Initialize (112-137)
Assets/Infrastructure/Network/WebSocket/INativeWebSocket.cs (1)
  • INativeWebSocket (10-28)
Assets/Core/Managers/ManagerRegistry.cs (1)
  • ManagerRegistry (13-124)
Assets/Infrastructure/Network/WebSocket/Platforms/WebGLWebSocket.cs (1)
  • WebGLWebSocket (13-169)
Docs/Conventions/CodeStyle_CSharp.md (1)
Assets/Infrastructure/Network/Services/ChatApiService.cs (1)
  • ProjectVG (9-131)
🪛 markdownlint-cli2 (0.17.2)
Docs/Conventions/Unity_Asset_Naming.md

4-4: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


13-13: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)

Docs/Conventions/Commit_Message.md

4-4: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


13-13: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


20-20: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


35-35: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

Docs/Conventions/CodeStyle_CSharp.md

4-4: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


15-15: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)

🔇 Additional comments (21)
Assets/Infrastructure/Config/AppEnvironmentConfig.cs (1)

20-20: iOS 전용 기본 환경 분리 추가는 방향성 좋습니다. 디버그 빌드 매핑과의 상호작용만 확인해 주세요.

iosEnvironment 기본값을 Production으로 둔 것은 운영 안전성 측면에서 합리적입니다. 다만 GetCurrentEnvironment의 mapDevelopmentBuildToDevelopment && Debug.isDebugBuild 분기가 우선 적용되므로, 디바이스 디버그 빌드에서는 iosEnvironment 값(Production)을 무시하고 Development로 강제됩니다. QA/테스트 플로우에서 의도한 동작인지 확인 부탁드립니다. 또한 NetworkConfig.CurrentEnvironment의 오케스트레이션(ApplyRuntimeGuard 포함)과 충돌이 없는지 최종 경로를 점검해 주세요.

.plasticignore (4)

47-52: LGTM: 에디터/크래시/백업 산출물 무시 추가가 적절합니다

Blender 백업(.blend1), Rider 개인 설정(.DotSettings.user), mono crash 덤프 등은 버전 관리에서 제외하는 것이 맞습니다.


53-53: 특정 씬 이름 패턴 무시는 데이터 누락 위험이 있습니다

InitTestScene*.unity*를 전역으로 무시하면 동일 접두어를 가진 실제 테스트/데모 씬이 커밋되지 않는 사고가 발생할 수 있습니다. 전용 디렉터리(예: Assets/DevOnly/ 또는 Assets/Samples/)로 이동해 그 폴더를 무시하는 방식이 더 안전합니다. 유지 의도라면 팀 합의/문서화가 필요합니다.


54-54: LGTM: Addressables/빌드 산출물 디렉터리 무시가 적절합니다

/ServerData/[Aa]ssets/Addressables_Temp*는 생성물이라 무시가 적절합니다.

Also applies to: 56-56


55-55: Addressables link.xml 무시 패턴 검토 요청

  • .plasticignore 파일 55행에 /[Aa]ssets/AddressableAssetsData/link.xml* 패턴이 존재합니다.
  • 현재 저장소에는 link.xml 파일이 없습니다.

이 패턴이 남아 있으면, 향후 link.xml을 추가하더라도 버전 관리에서 제외되어 iOS/Android(특히 IL2CPP) 빌드 시 코드 스트리핑으로 인한 런타임 오류나 기능 누락이 발생할 수 있습니다.
팀 정책상 Addressables 워크플로우에서 link.xml 커밋을 의도적으로 제외하고 있는지 확인해 주시고, 그렇지 않다면 아래 제안을 반영해 이 라인을 제거하시길 권장합니다.

적용 제안:

-/[Aa]ssets/AddressableAssetsData/link.xml*

• Addressables 워크플로우가 link.xml 커밋 제외를 공식 정책으로 삼고 있는지 확인 부탁드립니다.

Assets/Domain/Chat/View/ChatBubbleUI.cs (2)

68-68: 타입 변경 확인 완료

ChatBubbleManager에서 ChatBubblePanel로 정상적으로 타입이 변경되었습니다. 새로운 패널 기반 아키텍처와 잘 일치합니다.


111-111: 메서드 시그니처 변경 확인 완료

Initialize 메서드의 매개변수가 ChatBubbleManager에서 ChatBubblePanel로 정상적으로 변경되었습니다. 관련 코드 스니펫을 확인한 결과, ChatBubblePanel.CreateBubble에서 bubbleUI.Initialize(actor, text, displayTime, this)로 호출하는 패턴과 일치합니다.

Assets/Infrastructure/Network/Services/STTService.cs (6)

20-20: HttpApiClient 필드의 nullable 타입 변경이 적절합니다.

HttpApiClient가 초기화되지 않을 수 있는 상황을 명시적으로 처리하기 위해 nullable 타입으로 변경한 것이 좋은 개선입니다. 이는 런타임 오류를 방지하고 명시적인 null 체크를 가능하게 합니다.


22-23: IsAvailable 속성의 개선된 로직이 좋습니다.

IsAvailable 속성이 실제 HttpApiClient 인스턴스의 존재 여부를 체크하도록 변경된 것이 적절합니다. IsConnected는 여전히 true를 반환하여 호환성을 유지하면서, IsAvailable은 실제 서비스 가용성을 정확히 반영합니다.


43-47: 런타임 null 체크 및 예외 처리가 우수합니다.

메서드 실행 전에 HttpApiClient의 null 상태를 체크하고 명확한 예외 메시지와 함께 InvalidOperationException을 던지는 것이 좋은 방어적 프로그래밍 패턴입니다. 로깅과 예외 처리가 모두 적절합니다.


92-120: 테스트용 오디오 데이터 생성 메서드가 잘 구현되었습니다.

440Hz 사인파를 생성하고 WAV 형식으로 변환하는 로직이 정확하며, 테스트 목적으로 적절합니다. PCM16 변환과 WAV 헤더 추가 과정이 올바르게 구현되어 있습니다.


123-204: 데이터 모델 클래스들의 구조가 우수합니다.

STTRequest, STTResponse, STTFileInfo, STTHealthResponse 클래스들이 모두 적절한 JsonProperty 속성과 nullable 타입을 사용하여 잘 정의되어 있습니다. 서버 API와의 직렬화/역직렬화가 원활하게 처리될 것으로 보입니다.


27-32: STTService 생성 시점과 HttpApiClient 초기화 순서 확인 필요
생성자에서의 null 체크와 에러 로깅은 적절합니다. 다만, 코드베이스에서 HttpApiClient.Initialize() 호출 위치를 찾을 수 없어, STTService가 호출 시점에 Instance가 아직 초기화되지 않았을 가능성이 있습니다.

Assets/Domain/Chat/View/VoiceInputView.cs에서 _sttService = new STTService();가 호출되기 전에
– 반드시 HttpApiClient.Initialize()가 실행되고
HttpApiClient.Instance가 유효한 상태인지 확인해 주세요.

앱 시작 초기화(예: AppDelegate, Bootstrapper 등)에서 HttpApiClient.Initialize()가 먼저 호출되는지 검증 후, 필요 시 순서를 조정해 주시기 바랍니다.

Assets/Domain/Chat/View/VoiceInputView.cs (3)

112-141: 음성 녹음 시작/중지 로직이 잘 구현되었습니다.

UI 진행률 표시 제거 후에도 핵심 기능이 잘 유지되었습니다. 에러 처리와 상태 관리가 적절하게 구현되어 있으며, AudioRecorder의 새로운 아키텍처와 잘 통합되어 있습니다.

Also applies to: 143-174


80-110: 음성 메시지 전송 로직이 견고합니다.

SendVoiceMessage 메서드의 에러 처리가 잘 되어 있고, finally 블록이 비어있지만 향후 정리 로직을 위해 남겨둔 것으로 보입니다. STT 변환과 채팅 매니저 연동이 적절합니다.


67-73: VoiceInputView.Initialize()는 현재 외부에서 호출되지 않습니다
Assets/Domain/Chat/View/VoiceInputView.cs 내에서 Start()에서만 호출되고 있어, 외부 참조가 전혀 없습니다.
따라서 현 시점에서는 private 접근 제한자를 유지하는 것이 적합하며, 실제로 외부에서 재초기화가 필요해질 경우 그때 public(또는 internal)으로 변경을 검토하세요.

Assets/Core/DebugConsole/LogEntryPrefab.cs (4)

1-1: nullable reference types 활성화가 적절합니다.

코드베이스 전반에 걸친 nullable 참조 타입 도입이 일관성 있게 적용되고 있습니다.


10-10: nullable 필드 선언이 적절합니다.

SerializeField로 선언된 Unity 컴포넌트를 nullable로 처리하는 것이 적절합니다. Unity Inspector에서 할당되지 않을 수 있는 상황을 고려한 안전한 접근입니다.


12-18: null 검사 로직이 적절합니다.

_logText가 null인 경우 안전하게 처리하는 방어적 프로그래밍 패턴이 잘 적용되었습니다.


20-26: null 검사 로직이 일관성 있게 적용되었습니다.

SetLogMessage와 동일한 패턴으로 null 안전성을 보장하고 있습니다.

Assets/Core/DebugConsole/GameDebugConsoleManager.cs (1)

1-1: nullable reference types 활성화가 일관성 있게 적용되었습니다.

LogEntryPrefab.cs와 함께 nullable 컨텍스트를 활성화하여 프로젝트 전반의 타입 안전성을 향상시키고 있습니다.

Comment on lines 79 to 84
/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*

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

❓ Verification inconclusive

Addressables의 link.xml 무시는 위험할 수 있습니다

/[Aa]ssets/AddressableAssetsData/link.xml*를 무시하면 IL2CPP 보존 규칙 손실로 런타임 스트리핑 문제가 발생할 수 있습니다. 프로젝트마다 정책이 다르지만, 일반적으로 *.bin*만 무시하고 link.xml은 커밋하는 경우가 많습니다.

의도적으로 무시하는 것인지 확인 부탁드립니다. 무시 의도가 아니라면 제거하세요.

-/[Aa]ssets/AddressableAssetsData/link.xml*

검증 스크립트(레포 내에 link.xml이 이미 추적 중인지 확인):


🏁 Script executed:

#!/bin/bash
# AddressableAssetsData 아래 link.xml 추적 여부 및 위치 확인
git ls-files | rg -n $'/[Aa]ssets/AddressableAssetsData/.*/link\\.xml$' || true

Length of output: 78


Addressables용 link.xml 무시 재검토 요청

검증 결과, 현재 /Assets/AddressableAssetsData 경로 내에 link.xml 파일이 Git에 추적되고 있지 않습니다. 이 패턴을 무시하면 향후 생성되는 link.xml 파일도 자동으로 커밋에서 제외되어 IL2CPP 보존 규칙 손실 및 런타임 스트리핑 문제가 발생할 수 있습니다. 대부분의 Unity 프로젝트에서는 link.xml만큼은 커밋하는 것이 일반적이므로, 의도적인 무시인지 확인하거나 .gitignore에서 해당 라인을 제거해주세요.

  • 대상 파일: .gitignore
  • 문제 라인: 81 (/[Aa]ssets/AddressableAssetsData/link.xml*)

제안 변경사항:

-/[Aa]ssets/AddressableAssetsData/link.xml*

의도적으로 무시하려는 경우, 해당 이유를 주석으로 남겨주세요.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/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*
/ServerData
/[Aa]ssets/StreamingAssets/aa*
/[Aa]ssets/Addressables_Temp*
/[Aa]ssets/AddressableAssetsData/*/*.bin*
🤖 Prompt for AI Agents
In .gitignore around lines 79 to 84 (specifically line 81 where
"/[Aa]ssets/AddressableAssetsData/link.xml*" is listed), the rule ignores
link.xml files under Assets/AddressableAssetsData which can prevent committing
IL2CPP preservation rules; either remove that ignore pattern so link.xml is
tracked, or if the ignore is intentional, add a clear comment above the line
explaining why link.xml should be excluded (e.g., generated-only and contains no
preservation rules), then commit the updated .gitignore accordingly.

Comment on lines +63 to +66
*.private
*.private.meta
^*.private.[0-9]+$
^*.private.[0-9]+.meta$
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

정규식 패턴(^, $, +)은 .plasticignore에서 동작하지 않습니다; 이미 중복 규칙이 존재합니다

.plasticignore는 glob(와일드카드) 기반입니다. ^*.private.[0-9]+$ 같은 정규식은 의도대로 매칭되지 않습니다. 게다가 이미 위에서 *.private, *.private.meta로 포괄 무시하고 있어 중복이기도 합니다. 아래 두 줄은 제거하세요.

적용 제안(diff):

-^*.private.[0-9]+$
-^*.private.[0-9]+.meta$

🌐 Web query:

Plastic SCM의 .plasticignore(또는 ignore.conf)에서 지원하는 패턴 문법은 무엇인가요? 정규식 앵커(^, $)나 `+` 수량자를 지원하나요? 공식 문서의 규칙과 예시를 알려주세요.

💡 Result:

요약 — 결론 먼저

  • .plasticignore / ignore.conf 는 와일드카드 패턴(*)와 정규식 패턴 둘 다 지원합니다. 정규식 패턴은 "정규식 모드"로 취급하려면 반드시 줄의 맨 앞에 ^, 맨 뒤에 $ 를 붙여야 합니다. 또한 + 같은 표준 정규식 수량자(+, *, ?, 그룹, 문자클래스, lookahead 등)도 사용할 수 있습니다. (docs.plasticscm.com, forum.plasticscm.com)

공식 문서(핵심 규칙)

  1. 규칙(패턴) 종류 및 우선순위(요약)
  • 절대 경로 규칙 (예: /src/lib/file.txt)
  • 캐치-올 규칙 (예: / , * , ** 등)
  • 이름 규칙 (파일/디렉터리 이름만 비교)
  • 확장자 규칙 (예: *.java)
  • 와일드카드 / 정규식 규칙 (와일드카드와 정규식은 마지막에 평가됨)
    (문서에 규칙 유형과 적용 우선순위가 정리되어 있음). (docs.plasticscm.com)
  1. 와일드카드 문법
    • : 디렉터리 구분자('/')를 제외한 임의의 문자 0개 이상.
  • ** : 디렉터리 구분자를 포함하여 임의의 문자 0개 이상(재귀적 매치).
  • ? : 디렉터리 구분자를 제외한 정확히 한 문자.
    예) /sample/temp/** 는 sample/temp 아래 모든 파일/서브폴더를 의미. (docs.plasticscm.com, blog.plasticscm.com)
  1. 정규식 패턴
  • 정규식으로 처리하려면 패턴이 반드시 ^ 로 시작하고 $ 로 끝나야 합니다.
  • 내부에 표준 정규식 문법(문자클래스 [0-9], 수량자 +, ?, *, 그룹, 역참조/이스케이프 등)을 쓸 수 있습니다. 예시:
    • ^.*/sample-[0-9]+.txt$ (sample-숫자.txt 형태의 경로 매치)
    • ^.*/dir-0x[A-F0-9]+_?/[^\/]+$ (복합 경로/이름 매치)
  • 즉, ^와 $는 “정규식 모드로 해석하라”는 신호이자 정규식의 앵커로 사용됩니다. (docs.plasticscm.com)
  1. 예외 규칙
  • 규칙 앞에 ! 를 붙이면 예외(포함) 규칙입니다. 예외 규칙은 동일 타입의 무시 규칙보다 우선합니다. (docs.plasticscm.com)
  1. 기타 중요한 동작(릴리스 정보)
  • (릴리즈 노트) 파일명/확장자 규칙에 정규식 문자가 포함되어 있으면 자동으로 매치되도록 개선된 적이 있습니다(특정 버전 이후 **/ 접두가 필요 없게 됨). 즉, 확장자 규칙에 정규식 문자(*)가 들어가도 동작하는 경우가 있습니다. (참조: 5.0.44 릴리스노트). (plasticscm.com)

공식 예시들 (문서에서 발췌한 사용 예)

  • 와일드카드
    • /sample/temp/**
    • !/sample/temp/*.log (temp 아래 모든 것 무시하되, .log 는 제외)
    • /src/**-3/main.c
    • /doc/public/*.tex
  • 정규식

실무 팁 / 주의사항

  • 패턴은 대/소문자 구분(case-sensitive)으로 적용됩니다. (blog.plasticscm.com)
  • 정규식 모드(맨앞 ^, 맨뒤 $)를 깜빡하면 그 라인은 정규식으로 해석되지 않습니다 — 따라서 정규식을 쓰려면 반드시 ^...$ 로 감싸세요. (docs.plasticscm.com)
  • 포럼 사례들에서 lookahead 같은 고급 정규식(예: (?!Library/).*)도 사용되는 것을 볼 수 있어, 비교적 완전한 정규식 기능을 사용할 수 있습니다(다만 문서에서 엔진 종류(.NET/PCRE 등)를 명시하지 않을 수 있으니, 아주 특수한 정규식 기능을 쓸 때는 테스트를 권장). (forum.plasticscm.com)

원문(공식 문서) 링크 참고

  • Plastic SCM 문서 - Filter pattern files (ignore.conf / .plasticignore 규칙 설명). (docs.plasticscm.com)
  • 플라스틱 릴리스노트(관련 변경점: 정규식/확장자 처리 개선). (plasticscm.com)

원하시면

  • 지금 사용 중인 예시(무시하고 싶은 파일/폴더 구조)를 알려주시면, 해당 패턴을 ignore.conf 형식으로 정확히 만들어 드리겠습니다.

Citations:


정규식 패턴을 정확히 수정하세요

공식 문서에 따르면 .plasticignore^…$ 앵커로 감싼 줄을 정규식 모드로 해석하며, +, 그룹, 문자클래스 등 표준 정규식 기능을 모두 지원합니다.[1]
현재 glob 패턴(*.private, *.private.meta)만으로는 .private.숫자.private.숫자.meta를 무시하지 않으므로, 63–66행에 있는 두 줄을 다음과 같이 올바른 정규식으로 교체하세요.

(파일: .plasticignore, 63-66행)

-^*.private.[0-9]+$
-^*.private.[0-9]+.meta$
+^.*\.private\.[0-9]+$
+^.*\.private\.[0-9]+\.meta$

이제 경로 전체에서 .private. 뒤에 숫자만 있는 항목과, 그 뒤에 .meta가 붙은 항목을 정확히 무시할 수 있습니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
*.private
*.private.meta
^*.private.[0-9]+$
^*.private.[0-9]+.meta$
*.private
*.private.meta
^.*\.private\.[0-9]+$
^.*\.private\.[0-9]+\.meta$
🤖 Prompt for AI Agents
In .plasticignore around lines 63–66, the current glob entries (*.private and
*.private.meta) don't match filenames like ".private.123" or
".private.123.meta"; replace those two lines with anchored regular-expression
lines that match any path ending with ".private." followed by one or more digits
(for the numeric-only case) and a second anchored line that matches the same
plus a trailing ".meta" (for the .meta case) so the ignore rules correctly skip
both ".private.<digits>" and ".private.<digits>.meta" anywhere in the path.

Comment on lines +68 to +69
**/Assets/StreamingAssets/aa.meta
**/assets/streamingassets/*/aa/*
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

StreamingAssets 경로 패턴 대소문자/일관성 정리 제안

같은 목적의 규칙이 서로 다른 대소문자와 루트/글로브 표기를 혼용하고 있습니다. 한 가지 스타일로 통일하면 유지보수가 쉬워집니다.

적용 제안(diff):

-**/Assets/StreamingAssets/aa.meta
-**/assets/streamingassets/*/aa/*
+/[Aa]ssets/[Ss]treaming[Aa]ssets/aa.meta
+/[Aa]ssets/[Ss]treaming[Aa]ssets/*/aa/*
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
**/Assets/StreamingAssets/aa.meta
**/assets/streamingassets/*/aa/*
/[Aa]ssets/[Ss]treaming[Aa]ssets/aa.meta
/[Aa]ssets/[Ss]treaming[Aa]ssets/*/aa/*
🤖 Prompt for AI Agents
In .plasticignore around lines 68-69, the two rules use inconsistent casing and
glob styles; replace them with a consistent style by using lowercase path
components ("assets/streamingassets") and a unified glob pattern set that covers
both the aa file(s) and their .meta (for example: a recursive glob under
assets/streamingassets for aa files and a corresponding pattern for aa.meta),
removing the mixed-case and root/glob duplication so all rules follow the same
lowercase, recursive-glob convention.

Comment on lines +35 to +41
```
<type>(optional scope): <subject>
[optional body]
[optional footer(s)]
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

MD040: 메시지 구조 코드 블록에도 언어를 지정하세요.

텍스트 예시는 text 언어 지정을 권장합니다.

-```
+```text
 <type>(optional scope): <subject>
 
 [optional body]
 
 [optional footer(s)]

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

35-35: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

In Docs/Conventions/Commit_Message.md around lines 35 to 41, the fenced code
block showing the commit message structure does not specify a language; update
the block to use a language tag (use "text") and change the example code fence
to ```text so the snippet is explicitly marked as plain text.


</details>

<!-- fingerprinting:phantom:triton:chinchilla -->

<!-- This is an auto-generated comment by CodeRabbit -->

Comment on lines +3 to +6
## Unity 자산
| 타입 | 규칙 | 예시 |
|---|---|---|
|| PascalCase, 역할 접미 허용(Main/Boot/Loading/Sample) | MainScene, LoadingScene |
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

MD058: 표 전후에 빈 줄을 추가하세요 (Unity 자산).

헤더와 표 사이에 빈 줄이 없습니다.

 ## Unity 자산
+
 | 타입 | 규칙 | 예시 |
 |---|---|---|
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## Unity 자산
| 타입 | 규칙 | 예시 |
|---|---|---|
|| PascalCase, 역할 접미 허용(Main/Boot/Loading/Sample) | MainScene, LoadingScene |
## Unity 자산
| 타입 | 규칙 | 예시 |
|---|---|---|
|| PascalCase, 역할 접미 허용(Main/Boot/Loading/Sample) | MainScene, LoadingScene |
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

4-4: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)

🤖 Prompt for AI Agents
In Docs/Conventions/Unity_Asset_Naming.md around lines 3 to 6, the Markdown
table is placed immediately after the header with no blank lines causing MD058;
add a single empty line after the header (between "## Unity 자산" and the table)
and ensure there is an empty line after the table as well so the table is
separated from surrounding content.

Comment on lines +12 to +16
## UI 위젯
| 위치 | 규칙 | 예시 |
|---|---|---|
| Hierarchy 이름 | 접두사 사용 Panel/Btn/Img/Txt/Input/Scroll/Toggle/Slider/Dropdown | PanelChat, BtnSend |
| 코드 필드명 | 의미 중심 camelCase | sendButton, titleText |
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

MD058: 표 전후에 빈 줄을 추가하세요 (UI 위젯).

헤더와 표 사이에 빈 줄이 없습니다.

 ## UI 위젯
+
 | 위치 | 규칙 | 예시 |
 |---|---|---|
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## UI 위젯
| 위치 | 규칙 | 예시 |
|---|---|---|
| Hierarchy 이름 | 접두사 사용 Panel/Btn/Img/Txt/Input/Scroll/Toggle/Slider/Dropdown | PanelChat, BtnSend |
| 코드 필드명 | 의미 중심 camelCase | sendButton, titleText |
## UI 위젯
| 위치 | 규칙 | 예시 |
|---|---|---|
| Hierarchy 이름 | 접두사 사용 Panel/Btn/Img/Txt/Input/Scroll/Toggle/Slider/Dropdown | PanelChat, BtnSend |
| 코드 필드명 | 의미 중심 camelCase | sendButton, titleText |
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

13-13: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)

🤖 Prompt for AI Agents
In Docs/Conventions/Unity_Asset_Naming.md around lines 12 to 16, the markdown
table directly follows the header without a blank line which violates MD058;
insert a single empty line between the "## UI 위젯" header and the table, and
ensure there is at least one blank line after the table as well so the table is
separated from surrounding content.

Comment on lines 30 to 31
^*.private.[0-9]+$
^*.private.[0-9]+.meta$
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Gitignore는 정규식(^, [0-9]+)을 지원하지 않습니다 — 현재 패턴은 전혀 동작하지 않습니다

Git의 ignore 패턴은 glob만 지원합니다. ^[0-9]+는 무시되거나 리터럴로 해석되어 기대한 파일이 제외되지 않습니다.

아래처럼 glob 패턴으로 대체하세요(숫자 접미어 제한이 필요 없다면 이 형태가 가장 안전합니다).

-^*.private.[0-9]+$
-^*.private.[0-9]+.meta$
+*.private.*
+*.private.*.meta
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
^*.private.[0-9]+$
^*.private.[0-9]+.meta$
*.private.*
*.private.*.meta
🤖 Prompt for AI Agents
In ignore.conf around lines 30 to 31, the current patterns use regex anchors and
quantifiers (^, [0-9]+) which Git ignore does not support; replace them with
glob patterns instead — remove the ^ and [0-9]+ constructs and use something
like *.private.* to match any .private suffix (and *.private.*.meta for the meta
files); if you truly need to restrict to numeric suffixes, use a glob range like
*.private.[0-9]* and *.private.[0-9]*.meta instead.

Comment on lines 84 to +90
.git/
.gitignore
.gitattributes
*.iml
.idea/
.git

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

.gitignore / .gitattributes까지 무시하고 있어 위험합니다

레포 중첩 디렉토리에서의 .gitignore/.gitattributes 추적을 방해하고, 도구에 따라 혼란을 야기합니다. .git/.git 패턴도 불필요합니다.

아래처럼 제거하는 것을 권고합니다.

-.git/
-.gitignore
-.gitattributes
-*.iml
-.idea/
-.git
+*.iml
+.idea/
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.git/
.gitignore
.gitattributes
*.iml
.idea/
.git
*.iml
.idea/
🤖 Prompt for AI Agents
ignore.conf lines 84-90: the patterns currently ignore .gitignore and
.gitattributes and redundantly ignore .git/.git directory names, which prevents
tracking nested-repo git metadata and can confuse tools; remove the lines for
".gitignore" and ".gitattributes" and the redundant ".git" and ".git/" entries
so those files/directories are not globally ignored, leaving only intentional
ignore patterns for repositories' working trees.

@ImGdevel ImGdevel force-pushed the refactory/project-structure branch from bdaea7f to e1bd36d Compare August 14, 2025 01:05
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (9)
Docs/Conventions/Unity_Asset_Naming.md (2)

3-6: MD058: 헤더 뒤에 빈 줄 추가 필요 (Unity 자산 표).

헤더와 표 사이에 빈 줄이 없어 markdownlint(MD058)가 발생합니다. 헤더 아래에 공백 줄 1개를 추가하세요.

 ## Unity 자산
+
 | 타입 | 규칙 | 예시 |
 |---|---|---|

12-16: MD058: 헤더 뒤에 빈 줄 추가 필요 (UI 위젯 표).

헤더와 표 사이에 빈 줄이 없어 markdownlint(MD058)가 발생합니다. 헤더 아래에 공백 줄 1개를 추가하세요.

 ## UI 위젯
+
 | 위치 | 규칙 | 예시 |
 |---|---|---|
Docs/Conventions/Commit_Message.md (4)

3-6: MD058: 표 전후에 빈 줄 추가 필요 (헤더 ‘구성 요소’ 아래).

헤더 다음에 빈 줄이 없어 markdownlint 경고가 발생합니다. 한 줄 추가해 주세요.

 ## 구성 요소
+
 | 항목 | 필수 | 설명 | 예시 |
 |---|---|---|---|

12-15: MD040: 펜스 코드 블록에 언어 지정 필요 (헤더 예시).

열기 펜스에 text 언어를 지정해 주세요.

-```
+```text
 <type>(optional scope): <subject>

---

`19-22`: **MD058: 표 전후에 빈 줄 추가 필요 (헤더 ‘예시’ 아래).**

헤더 다음에 빈 줄을 추가해 주세요.



```diff
 ## 예시
+
 | type | 예시 메시지 |
 |---|---|

35-41: MD040: 펜스 코드 블록에 언어 지정 필요 (메시지 구조).

열기 펜스에 text 언어를 지정해 주세요.

-```
+```text
 <type>(optional scope): <subject>
 
 [optional body]
 
 [optional footer(s)]

</blockquote></details>
<details>
<summary>Docs/Conventions/CodeStyle_CSharp.md (3)</summary><blockquote>

`3-6`: **MD058: 표 전후에 빈 줄 추가 필요 (헤더 ‘C# 식별자’ 아래).**

헤더 다음에 빈 줄이 없어 markdownlint 경고가 발생합니다. 한 줄 추가해 주세요.



```diff
 ## C# 식별자
+
 | 대상 | 규칙 | 예시 |
 |---|---|---|

14-16: MD058: 표 전후에 빈 줄 추가 필요 (헤더 ‘C# 패턴’ 아래).

헤더 다음에 빈 줄을 추가해 주세요.

 ## C# 패턴
+
 | 항목 | 규칙 | 예시 |
 |---|---|---|

19-23: CancellationToken 파라미터 네이밍을 공개 API 기준으로 정렬하세요.

문서 예시는 ct를 쓰고 있으나, 공개 API에선 가독성을 위해 cancellationToken 사용을 권장합니다. 기존 리뷰에서도 같은 제안이 있었습니다.

-| 비동기 메서드 | Async 접미사, ct 매개변수 | LoadAsync(CancellationToken ct) |
+| 비동기 메서드 | Async 접미사, CancellationToken 매개변수 | LoadAsync(CancellationToken cancellationToken) |

추가 권장(문서에 한 줄 설명 추가):

-공개 API는 명령형 동사를 사용합니다(Initialize/Apply/Load 등).
+공개 API는 명령형 동사를 사용합니다(Initialize/Apply/Load 등).
+공개 메서드 시그니처에서는 CancellationToken cancellationToken = default 형식을 권장하며,
+내부 전달/로컬 변수에서는 ct 사용을 선택적으로 허용합니다.
🧹 Nitpick comments (2)
Docs/Conventions/Commit_Message.md (1)

34-34: 오탈자: ‘메세지’ → ‘메시지’.

표기 일관성 및 맞춤법을 위해 수정 권장합니다.

-## 메세지 구조
+## 메시지 구조
Docs/Conventions/CodeStyle_CSharp.md (1)

8-9: C# 상수 명명 규칙 재검토 요청 (UPPER_SNAKE_CASE vs PascalCase).

Microsoft 권장 사항과 다르게 본 문서는 상수를 UPPER_SNAKE_CASE로 규정합니다. 팀 합의된 스타일이면 유지 가능하나, 일반적인 C# 컨벤션은 상수도 PascalCase를 사용합니다. 프로젝트 표준으로 의도한 것인지 확인 바랍니다. 필요 시 문구/예시를 PascalCase로 정정하세요.

가능한 수정안:

-| 상수(const) | UPPER_SNAKE_CASE | DEFAULT_TIMEOUT_MS |
+| 상수(const) | PascalCase | DefaultTimeoutMs |
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bdaea7f and e1bd36d.

📒 Files selected for processing (11)
  • .github/PULL_REQUEST_TEMPLATE.md (1 hunks)
  • Assets/Editor.meta (1 hunks)
  • Assets/Editor/OpenDocsMenu.cs (1 hunks)
  • Assets/Editor/OpenDocsMenu.cs.meta (1 hunks)
  • Docs/Conventions/Branching.md (1 hunks)
  • Docs/Conventions/CodeStyle_CSharp.md (1 hunks)
  • Docs/Conventions/Commit_Message.md (1 hunks)
  • Docs/Conventions/PR_Notes.md (1 hunks)
  • Docs/Conventions/PR_Review.md (1 hunks)
  • Docs/Conventions/README.md (1 hunks)
  • Docs/Conventions/Unity_Asset_Naming.md (1 hunks)
✅ Files skipped from review due to trivial changes (4)
  • Assets/Editor/OpenDocsMenu.cs.meta
  • .github/PULL_REQUEST_TEMPLATE.md
  • Assets/Editor.meta
  • Docs/Conventions/PR_Notes.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • Docs/Conventions/PR_Review.md
  • Assets/Editor/OpenDocsMenu.cs
  • Docs/Conventions/README.md
  • Docs/Conventions/Branching.md
🧰 Additional context used
🪛 markdownlint-cli2 (0.17.2)
Docs/Conventions/CodeStyle_CSharp.md

4-4: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


15-15: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)

Docs/Conventions/Commit_Message.md

4-4: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


13-13: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


20-20: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


35-35: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

Docs/Conventions/Unity_Asset_Naming.md

4-4: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


13-13: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🔭 Outside diff range comments (4)
Assets/Domain/Chat/Service/ChatManager.cs (4)

170-182: 백그라운드 스레드에서 Unity API 호출 위험

WebSocket 콜백 쓰레드가 메인스레드가 아닐 수 있습니다. _enableMessageQueue == false 경로에서 동기 메서드(ProcessMessageImmediately)가 바로 Unity API를 건드리면 크래시/예외가 납니다. 항상 비동기 경로로 보내고 메인스레드 전환을 보장하세요.

-            if (!_enableMessageQueue)
-            {
-                ProcessMessageImmediately(chatMessage);
-                return;
-            }
+            if (!_enableMessageQueue)
+            {
+                // 항상 비동기 경로로 보내 메인 스레드에서 UI 처리되도록
+                ProcessMessageImmediatelyAsync(chatMessage).Forget();
+                return;
+            }

추가로, 아래 ProcessMessageImmediatelyAsync에 메인스레드 전환을 넣으세요(아래 코멘트 참조).


279-286: UI/오디오 처리 전 메인스레드 전환 필요

버블 생성/오디오 재생은 Unity 메인스레드에서만 안전합니다. 메서드 진입 즉시 메인스레드로 전환하세요.

         private async UniTask ProcessMessageImmediatelyAsync(ChatMessage chatMessage)
         {
             try
             {
+                await UniTask.SwitchToMainThread();
                 OnChatMessageReceived?.Invoke(chatMessage);
                 
                 if (_chatBubblePanel != null && !string.IsNullOrEmpty(chatMessage.Text))
                 {
                     _chatBubblePanel.CreateBubble(Actor.Character, chatMessage.Text);
                 }

214-221: 메시지 큐 처리 동시성: _isProcessing 경쟁 조건

_isProcessing은 잠금 없이 읽고/쓰기 때문에 동시에 여러 처리가 시작될 여지가 있습니다(특히 다중 스레드 이벤트 진입 시). Interlocked로 원자적으로 전환하세요.

적용 예(외부 변경 포함):

+using System.Threading;
@@
-        private bool _isProcessing = false;
+        private int _isProcessingFlag = 0;
@@
-        private async UniTaskVoid ProcessMessageQueueAsync()
+        private async UniTaskVoid ProcessMessageQueueAsync()
         {
-            if (_isProcessing)
-                return;
-                
-            _isProcessing = true;
+            if (Interlocked.Exchange(ref _isProcessingFlag, 1) == 1)
+                return;
@@
-            finally
-            {
-                _isProcessing = false;
-            }
+            finally
+            {
+                Volatile.Write(ref _isProcessingFlag, 0);
+            }

139-140: SendUserMessage 메서드: async void → UniTask 반환으로 전환 권장

  • 파일: Assets/Domain/Chat/Service/ChatManager.cs, 139행 (public async void SendUserMessage(string message))
  • 이미 using Cysharp.Threading.Tasks;가 선언되어 있어 별도 import 불필요

다음과 같이 시그니처를 변경하고, 필요 시 CancellationToken을 파라미터로 받아 UI 취소(뒤로가기·씬 종료 등)에도 대응하세요.

-public async void SendUserMessage(string message)
+public async UniTask SendUserMessage(string message, CancellationToken cancellationToken = default)

또한 호출부에서 await 처리하고, 예외 전파 및 취소 로직을 구현해 유지보수성을 높이시기 바랍니다.

♻️ Duplicate comments (1)
Assets/Domain/Chat/Service/ChatManager.cs (1)

49-61: 싱글톤 Instance: 스레드 안전성 부족 + null 반환 가능성

이전 코멘트와 동일한 이슈입니다. 멀티스레드 진입 시 레이스가 가능하고, 아직 씬에 활성 오브젝트가 없으면 null을 반환할 수 있습니다(리턴 타입은 ChatManager로 non-null 계약인데 실제 null 가능). 락과 명시적 예외로 보강하세요.

적용 예:

-        private static ChatManager? _instance;
+        private static ChatManager? _instance;
+        private static readonly object _instanceLock = new object();

         public static ChatManager Instance
         {
             get
             {
-                if (_instance == null)
-                {
-                    _instance = FindAnyObjectByType<ChatManager>();
-                }
-                return _instance;
+                if (_instance != null) return _instance;
+                lock (_instanceLock)
+                {
+                    if (_instance == null)
+                    {
+                        _instance = FindAnyObjectByType<ChatManager>();
+                    }
+                    if (_instance == null)
+                        throw new InvalidOperationException("ChatManager instance not found in scene.");
+                    return _instance;
+                }
             }
         }
🧹 Nitpick comments (5)
Assets/Domain/Chat/Service/ChatManager.cs (5)

62-71: 씬 전환 시 싱글톤 유지 필요 여부 확인

ChatManager를 전역으로 유지해야 한다면 DontDestroyOnLoad을 추가하는 것이 안전합니다. 현재는 씬 리로드 시 파괴될 수 있습니다.

         private void Awake()
         {
             if (_instance != null && _instance != this)
             {
                 Destroy(gameObject);
                 return;
             }
             
             _instance = this;
+            DontDestroyOnLoad(gameObject);
         }

128-130: IsConnected 플래그가 실제 네트워크 상태와 불일치할 수 있음

초기화 성공만으로 _isConnected = true로 두면 실제 소켓 연결 상태와 어긋날 수 있습니다. 실연결 이벤트(예: Open/Close/Error)와 연동하거나 WebSocketManager의 상태를 반영하도록 해주세요.

가능하다면 다음처럼 이벤트에 연동하세요(이벤트 명은 예시입니다):

_webSocketManager.OnOpened += () => _isConnected = true;
_webSocketManager.OnClosed += () => _isConnected = false;
_webSocketManager.OnError  += _ => _isConnected = false;
// 초기 상태 동기화
_isConnected = _webSocketManager.IsConnected;

42-43: QueueCount 동시 접근 시 일관성 보장

_messageQueue 접근은 락으로 보호하는데, QueueCount는 락 없이 읽고 있어 경쟁 상태에서 부정확/예외 가능성이 있습니다. 읽기도 동일 락으로 보호하는 것이 안전합니다.

-public int QueueCount => _messageQueue.Count;
+public int QueueCount
+{
+    get { lock (_queueLock) { return _messageQueue.Count; } }
+}

19-21: 필드 의존성 회복 전략 보강 제안

Inspector 미세팅/런타임 생성을 고려해, 초기화 타임아웃 직전에 한 번 FindAnyObjectByType<ChatBubblePanel>()로 보정 시도를 해두면 복원력이 올라갑니다.

예:

if (_chatBubblePanel == null)
{
    _chatBubblePanel = FindAnyObjectByType<ChatBubblePanel>();
}
if (_chatBubblePanel == null)
{
    Debug.LogError("[ChatManager] ChatBubblePanel 초기화 타임아웃");
    yield break;
}

260-268: 동기/비동기 오디오 호출 혼재

동기 PlayVoice와 비동기 PlayVoiceAsync가 혼재되어 있습니다. 일관된 호출 모델(선호: 비동기)로 통일하면 흐름 제어와 오류 처리에 유리합니다.

  • 실시간 재생이 필요해 fire-and-forget이라면 둘 다 Async 버전 사용 후 .Forget() 또는 상위 레벨에서 await.
  • 동기 버전을 남길 경우, 호출부도 모두 동기로 맞추고 예외 경계를 통일.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e1bd36d and c1d353a.

📒 Files selected for processing (2)
  • Assets/Core/DebugConsole/GameDebugConsoleManager.cs (3 hunks)
  • Assets/Domain/Chat/Service/ChatManager.cs (9 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Assets/Core/DebugConsole/GameDebugConsoleManager.cs
🧰 Additional context used
🧬 Code Graph Analysis (1)
Assets/Domain/Chat/Service/ChatManager.cs (2)
Assets/Infrastructure/Network/WebSocket/WebSocketManager.cs (3)
  • WebSocketManager (14-472)
  • Awake (53-56)
  • WebSocketManager (17-465)
Assets/Core/Audio/AudioManager.cs (4)
  • AudioManager (11-334)
  • Awake (50-53)
  • PlayVoice (97-100)
  • PlayVoice (102-105)
🔇 Additional comments (2)
Assets/Domain/Chat/Service/ChatManager.cs (2)

119-122: 기본 매니저 참조 바인딩 흐름은 합리적입니다

WebSocketManager/AudioManager 인스턴스 주입 흐름이 명확해졌습니다. 👍


100-106: 이벤트 정리 LGTM

파괴 시 WebSocketManager 이벤트 구독 해제가 명확합니다. 👍

@ImGdevel ImGdevel force-pushed the refactory/project-structure branch from c1d353a to 71db53e Compare August 14, 2025 04:43
@ImGdevel ImGdevel merged commit 0c28c9d into develop Aug 14, 2025
1 check passed
@ImGdevel ImGdevel deleted the refactory/project-structure branch August 14, 2025 04:50
This was referenced Aug 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants