230 lines
8.5 KiB
Markdown
230 lines
8.5 KiB
Markdown
|
|
# Unity 개발 지침 (UI Toolkit · MVVM · 성능 · Nullable)
|
||
|
|
|
||
|
|
본 지침은 Unity UI Toolkit 기반 프로젝트의 아키텍처, 성능, 코드 품질을 일관되게 유지하기 위한 범용 규칙입니다.
|
||
|
|
프로젝트 스타일: `#nullable enable`, UniTask, DOTween, 한국어 주석
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 0) 작업 진행 규칙
|
||
|
|
|
||
|
|
**⚠️ 최우선 규칙: 임의로 진행하지 않고, 반드시 사용자에게 확인 후 진행합니다.**
|
||
|
|
|
||
|
|
- 코드 수정, 파일 생성/삭제, 리팩토링 등 **모든 변경 작업은 사전에 계획을 설명하고 승인을 받은 후** 진행합니다.
|
||
|
|
- 요구사항이 모호하거나 여러 접근 방식이 가능한 경우, **추측하지 말고 질문**합니다.
|
||
|
|
- 버그 수정이라도 원인 분석 결과를 먼저 공유하고, 수정 방향에 대해 합의 후 코드를 변경합니다.
|
||
|
|
- 단순한 오타 수정, 한 줄 변경 등 **명백하고 사소한 작업**만 즉시 진행할 수 있습니다.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 1) 핵심 원칙
|
||
|
|
|
||
|
|
### UI 프레임워크
|
||
|
|
- **UI Toolkit(UIElements) 필수 사용**. uGUI(Canvas 기반)는 레거시로 취급합니다.
|
||
|
|
- UXML(구조)과 USS(스타일)를 분리하고, C# 코드에서 인라인 스타일 지정을 지양합니다.
|
||
|
|
|
||
|
|
### 이벤트 콜백 등록/해제
|
||
|
|
**`RegisterValueChangedCallback` 대신 `RegisterCallback<ChangeEvent<T>>`를 사용합니다.**
|
||
|
|
`RegisterValueChangedCallback`은 확장 메서드로 `UnregisterCallback`과 대칭이 맞지 않아 이벤트 해제가 불가능합니다.
|
||
|
|
|
||
|
|
```csharp
|
||
|
|
// ❌ 금지: 해제 불가
|
||
|
|
field.RegisterValueChangedCallback(OnValueChanged);
|
||
|
|
|
||
|
|
// ✅ 권장: 대칭적 등록/해제
|
||
|
|
field.RegisterCallback<ChangeEvent<float>>(OnValueChanged);
|
||
|
|
field.UnregisterCallback<ChangeEvent<float>>(OnValueChanged);
|
||
|
|
```
|
||
|
|
|
||
|
|
### 커스텀 VisualElement (Unity 6)
|
||
|
|
레거시 `UxmlFactory`/`UxmlTraits` 방식을 사용하지 않고, 소스 생성기 기반의 `[UxmlElement]`와 `[UxmlAttribute]`를 사용합니다.
|
||
|
|
|
||
|
|
- 클래스에 `[UxmlElement]` 어트리뷰트 추가
|
||
|
|
- 클래스를 `partial`로 선언 (소스 생성기 요구사항)
|
||
|
|
- UXML 속성은 `[UxmlAttribute("케밥-케이스")]`로 명시
|
||
|
|
|
||
|
|
```csharp
|
||
|
|
// ✅ 권장 (Unity 6)
|
||
|
|
[UxmlElement]
|
||
|
|
public partial class UTKExample : VisualElement
|
||
|
|
{
|
||
|
|
[UxmlAttribute("is-enabled")]
|
||
|
|
public bool IsEnabled { get; set; }
|
||
|
|
}
|
||
|
|
|
||
|
|
// ❌ 금지 (레거시)
|
||
|
|
public class UTKExample : VisualElement
|
||
|
|
{
|
||
|
|
public new class UxmlFactory : UxmlFactory<UTKExample, UxmlTraits> { }
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 아키텍처 (MVVM/MVC)
|
||
|
|
|
||
|
|
| 레이어 | 책임 | 금지 사항 |
|
||
|
|
|--------|------|-----------|
|
||
|
|
| **View** | 표시/레이아웃, 이벤트 라우팅 | 비즈니스 로직, 상태 보유 |
|
||
|
|
| **ViewModel/Presenter** | 상태 관리, 데이터 변환, 바인딩 속성 | Unity API 직접 호출 |
|
||
|
|
| **Model/Service** | 도메인 로직, 데이터 접근 | UI 참조 |
|
||
|
|
|
||
|
|
- **MVVM**: UI 상태/양방향 동기화가 많을 때
|
||
|
|
- **MVC**: 입력 → 도메인 액션 → UI 반영 흐름이 단순할 때
|
||
|
|
|
||
|
|
### 필수 규약
|
||
|
|
- 파일 선두에 `#nullable enable`, 모든 참조형에 `?` 명시
|
||
|
|
- 비동기는 `UniTask` + `CancellationToken` 사용 (`Task`/코루틴 지양)
|
||
|
|
- 느슨한 결합: 인터페이스/이벤트로 연결
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 2) 폴더 구조
|
||
|
|
|
||
|
|
```
|
||
|
|
Assets/
|
||
|
|
├── Scripts/
|
||
|
|
│ ├── {프로젝트명}/ # 프로젝트별 코드
|
||
|
|
│ └── UVC/ # 공통 라이브러리 ⭐
|
||
|
|
│ ├── Core/ # DI, Injector, Singleton
|
||
|
|
│ ├── Data/ # DataMapper, MQTT/HTTP 통신
|
||
|
|
│ ├── Pool/ # 오브젝트 풀링
|
||
|
|
│ ├── UI/ # uGUI 컴포넌트 (레거시)
|
||
|
|
│ └── UIToolkit/ # UI Toolkit 컴포넌트 ⭐
|
||
|
|
├── Resources/
|
||
|
|
│ └── UIToolkit/ # 공통 UI 리소스 ⭐
|
||
|
|
│ ├── Style/ # USS 스타일 파일 (우선 참조)
|
||
|
|
│ ├── Common/ # 공통 UXML 컴포넌트
|
||
|
|
│ ├── List/ # 리스트 컴포넌트
|
||
|
|
│ ├── Modal/ # 모달 컴포넌트
|
||
|
|
│ ├── Property/ # 속성 편집기
|
||
|
|
│ └── Window/ # 윈도우 컴포넌트
|
||
|
|
├── Plugins/ # 서드파티 (Best.HTTP, DOTween 등)
|
||
|
|
├── Sample/ # 샘플 씬
|
||
|
|
└── Scenes/ # 앱 씬
|
||
|
|
```
|
||
|
|
|
||
|
|
> **참고**: 각 폴더에 `CLAUDE.md` 파일이 있어 모듈별 상세 가이드를 제공합니다.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 3) 네이밍 규칙
|
||
|
|
|
||
|
|
| 항목 | 규칙 | 예시 |
|
||
|
|
|------|------|------|
|
||
|
|
| UTK 컴포넌트 클래스 | `UTK` 접두사 + PascalCase | `UTKButton`, `UTKAccordionList` |
|
||
|
|
| UXML 파일 | `{ComponentName}.uxml` | `UTKAccordionListWindow.uxml` |
|
||
|
|
| USS 파일 | `{ComponentName}Uss.uss` | `UTKAccordionListWindowUss.uss` |
|
||
|
|
| Private 필드 | `_camelCase` | `_button`, `_labelCache` |
|
||
|
|
| 이벤트 | `On` 접두사 | `OnValueChanged`, `OnClicked` |
|
||
|
|
| 네임스페이스 | `UVC.UIToolkit` (공통), 프로젝트별 별도 | `UVC.UIToolkit`, `Factory` |
|
||
|
|
|
||
|
|
**⚠️ UXML과 USS 파일명은 반드시 다르게 지정해야 합니다.**
|
||
|
|
`Resources.Load<T>(path)`는 확장자 없이 경로를 받으므로, 동일한 경로명이면 로드 충돌이 발생합니다.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 4) 성능 원칙
|
||
|
|
|
||
|
|
- `Q<T>()`, `Query<T>()` 결과는 필드에 캐싱 (매 프레임 쿼리 금지)
|
||
|
|
- 대량 목록은 `ListView`/`TreeView` 가상화 활용
|
||
|
|
- 동적 생성/파괴 대신 풀링 또는 `display: none` 토글
|
||
|
|
- Update에서 GC 할당 금지 (LINQ/문자열 연결/클로저 지양)
|
||
|
|
- DOTween: 핸들 보관, 수명 종료 시 `Kill()`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 5) 메모리 관리 원칙
|
||
|
|
|
||
|
|
- `RegisterCallback<T>` ↔ `UnregisterCallback<T>` 반드시 대칭
|
||
|
|
- `AttachToPanelEvent` / `DetachFromPanelEvent`로 VisualElement 생명주기에 맞춰 이벤트 관리
|
||
|
|
- `CancellationTokenSource`는 `OnDestroy`에서 `Cancel` → `Dispose`
|
||
|
|
- VisualTreeAsset/USS 동일 리소스 반복 로드 방지 (캐싱)
|
||
|
|
- 클로저/람다 캡처로 인한 누수 점검
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 6) 비동기 (UniTask)
|
||
|
|
|
||
|
|
```csharp
|
||
|
|
// 공개 API는 UniTask 반환, CancellationToken 필수
|
||
|
|
public async UniTask<Data?> LoadDataAsync(CancellationToken ct)
|
||
|
|
{
|
||
|
|
return await _repository.FetchAsync().AttachExternalCancellation(ct);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fire-and-forget은 예외 로깅 후 .Forget()
|
||
|
|
LoadDataAsync(_cts.Token).Forget(ex => Debug.LogError(ex));
|
||
|
|
```
|
||
|
|
|
||
|
|
- `async void` 지양, `UniTask`/`UniTask<T>` 반환
|
||
|
|
- 토큰 결합: `CreateLinkedTokenSource(parent, local)`
|
||
|
|
- 타임아웃: `cts.CancelAfter(TimeSpan.FromSeconds(5))`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 7) 리소스 로드 규칙
|
||
|
|
|
||
|
|
- `Resources.Load<T>()` 사용 시 UXML과 USS 경로명 중복 금지 (USS에 `Uss` 접미사)
|
||
|
|
- `Assets/Resources/UIToolkit/Style/` 의 USS를 우선 참조하고, 없는 경우 `StyleGuide/` 폴더 참조
|
||
|
|
- Addressables 사용 시 `USE_ADDRESSABLES` 전처리기 정의
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 8) USS 스타일 규칙
|
||
|
|
|
||
|
|
- BEM 네이밍: `.block`, `.block__element`, `.block__element--modifier`
|
||
|
|
- 반복 값은 USS 변수 사용: `--color-primary`, `--spacing-md`
|
||
|
|
- 라이트/다크 테마는 별도 USS로 분리 (`UTKThemeManager` 사용)
|
||
|
|
- 새 컴포넌트 개발 시 `StyleGuide/` 이미지와 일치하는 스타일 적용
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 9) 아이콘 사용 우선순위
|
||
|
|
|
||
|
|
1. **1순위**: `UTKMaterialIcons` — 폰트 기반 Unicode 아이콘 (메모리 효율 우수)
|
||
|
|
2. **2순위**: `UTKImageIcons` — Material Icons에 없는 경우에만 사용
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 10) 주석 원칙
|
||
|
|
|
||
|
|
- 한국어 주석 사용
|
||
|
|
- 공개 멤버는 XML 문서 주석 필수 (`/// <summary>`)
|
||
|
|
- 클래스: 역할/책임, 메서드: 요약 + 파라미터/반환, 속성: 한 줄 요약, 샘플코드
|
||
|
|
|
||
|
|
```csharp
|
||
|
|
/// <summary>
|
||
|
|
/// 사용자 데이터를 비동기로 로드합니다.
|
||
|
|
/// </summary>
|
||
|
|
/// <param name="userId">사용자 ID.</param>
|
||
|
|
/// <param name="ct">취소 토큰.</param>
|
||
|
|
/// <returns>사용자 데이터 또는 null.</returns>
|
||
|
|
public async UniTask<UserData?> LoadUserAsync(string userId, CancellationToken ct)
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 11) Unity Nullable 주의
|
||
|
|
|
||
|
|
```csharp
|
||
|
|
// Unity Object는 == null이 오버로드됨 (파괴된 객체도 true)
|
||
|
|
if (gameObject == null) { }
|
||
|
|
|
||
|
|
// 순수 참조 null 확인 시
|
||
|
|
if (ReferenceEquals(obj, null)) { }
|
||
|
|
```
|
||
|
|
|
||
|
|
- 직렬화 필드에 `?` 표기, 런타임 Null/파괴 상태 방어적 처리
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 12) 디자인 패턴 요약
|
||
|
|
|
||
|
|
| 패턴 | 사용 시점 |
|
||
|
|
|------|-----------|
|
||
|
|
| **DI/Composition Root** | 서비스/ViewModel 주입 일원화 |
|
||
|
|
| **Event Aggregator** | 컴포넌트 간 느슨한 통신 |
|
||
|
|
| **Command + Undo** | UI 액션에 되돌리기 필요 시 |
|
||
|
|
| **Strategy** | 정렬/필터 규칙 교체 |
|
||
|
|
| **State/FSM** | 모드 전환 (편집/선택/드래그) |
|
||
|
|
| **Object Pool** | 대량 아이템 재사용 |
|
||
|
|
| **Repository** | 데이터 소스 추상화 |
|