@su-record/vibe 2.7.10 → 2.7.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +37 -37
- package/CLAUDE.md +126 -222
- package/LICENSE +21 -21
- package/README.md +580 -580
- package/agents/architect-low.md +41 -41
- package/agents/architect-medium.md +59 -59
- package/agents/architect.md +80 -80
- package/agents/build-error-resolver.md +115 -115
- package/agents/compounder.md +261 -261
- package/agents/diagrammer.md +178 -178
- package/agents/docs/api-documenter.md +99 -99
- package/agents/docs/changelog-writer.md +93 -93
- package/agents/e2e-tester.md +266 -266
- package/agents/explorer-low.md +42 -42
- package/agents/explorer-medium.md +59 -59
- package/agents/explorer.md +48 -48
- package/agents/implementer-low.md +43 -43
- package/agents/implementer-medium.md +52 -52
- package/agents/implementer.md +54 -54
- package/agents/junior-mentor.md +141 -141
- package/agents/planning/requirements-analyst.md +84 -84
- package/agents/planning/ux-advisor.md +83 -83
- package/agents/qa/acceptance-tester.md +86 -86
- package/agents/qa/edge-case-finder.md +93 -93
- package/agents/refactor-cleaner.md +143 -143
- package/agents/research/best-practices-agent.md +199 -199
- package/agents/research/codebase-patterns-agent.md +157 -157
- package/agents/research/framework-docs-agent.md +188 -188
- package/agents/research/security-advisory-agent.md +213 -213
- package/agents/review/architecture-reviewer.md +107 -107
- package/agents/review/complexity-reviewer.md +116 -116
- package/agents/review/data-integrity-reviewer.md +88 -88
- package/agents/review/git-history-reviewer.md +103 -103
- package/agents/review/performance-reviewer.md +86 -86
- package/agents/review/python-reviewer.md +150 -150
- package/agents/review/rails-reviewer.md +139 -139
- package/agents/review/react-reviewer.md +144 -144
- package/agents/review/security-reviewer.md +80 -80
- package/agents/review/simplicity-reviewer.md +140 -140
- package/agents/review/test-coverage-reviewer.md +116 -116
- package/agents/review/typescript-reviewer.md +127 -127
- package/agents/searcher.md +54 -54
- package/agents/simplifier.md +120 -120
- package/agents/tester.md +49 -49
- package/agents/ui/ui-a11y-auditor.md +93 -93
- package/agents/ui/ui-antipattern-detector.md +94 -94
- package/agents/ui/ui-dataviz-advisor.md +69 -69
- package/agents/ui/ui-design-system-gen.md +57 -57
- package/agents/ui/ui-industry-analyzer.md +49 -49
- package/agents/ui/ui-layout-architect.md +65 -65
- package/agents/ui/ui-stack-implementer.md +68 -68
- package/agents/ui/ux-compliance-reviewer.md +81 -81
- package/agents/ui-previewer.md +260 -260
- package/commands/vibe.run.md +83 -0
- package/commands/vibe.spec.review.md +558 -558
- package/commands/vibe.utils.md +413 -413
- package/commands/vibe.voice.md +79 -79
- package/dist/cli/auth.d.ts +1 -1
- package/dist/cli/auth.d.ts.map +1 -1
- package/dist/cli/auth.js +15 -7
- package/dist/cli/auth.js.map +1 -1
- package/dist/cli/collaborator.js +52 -52
- package/dist/cli/commands/evolution.js +12 -12
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +1 -0
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/info.d.ts.map +1 -1
- package/dist/cli/commands/info.js +62 -56
- package/dist/cli/commands/info.js.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +9 -6
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/remove.js +14 -14
- package/dist/cli/commands/sentinel.js +27 -27
- package/dist/cli/commands/skills.d.ts +13 -0
- package/dist/cli/commands/skills.d.ts.map +1 -0
- package/dist/cli/commands/skills.js +83 -0
- package/dist/cli/commands/skills.js.map +1 -0
- package/dist/cli/commands/slack.js +10 -10
- package/dist/cli/commands/telegram.js +12 -12
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +3 -0
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/detect.js +32 -32
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +64 -47
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/llm/claude-commands.js +16 -16
- package/dist/cli/llm/config.js +18 -18
- package/dist/cli/llm/gemini-commands.js +47 -47
- package/dist/cli/llm/gpt-commands.js +19 -19
- package/dist/cli/llm/help.js +21 -21
- package/dist/cli/postinstall/constants.d.ts +8 -0
- package/dist/cli/postinstall/constants.d.ts.map +1 -1
- package/dist/cli/postinstall/constants.js +33 -0
- package/dist/cli/postinstall/constants.js.map +1 -1
- package/dist/cli/postinstall/cursor-agents.js +32 -32
- package/dist/cli/postinstall/cursor-rules.js +83 -83
- package/dist/cli/postinstall/cursor-skills.js +743 -743
- package/dist/cli/postinstall/index.d.ts +1 -1
- package/dist/cli/postinstall/index.d.ts.map +1 -1
- package/dist/cli/postinstall/index.js +1 -1
- package/dist/cli/postinstall/index.js.map +1 -1
- package/dist/cli/setup/ProjectSetup.d.ts.map +1 -1
- package/dist/cli/setup/ProjectSetup.js +5 -0
- package/dist/cli/setup/ProjectSetup.js.map +1 -1
- package/dist/cli/setup/Provisioner.js +42 -42
- package/dist/cli/types.d.ts +1 -0
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/infra/lib/DeepInit.js +24 -24
- package/dist/infra/lib/IterationTracker.js +11 -11
- package/dist/infra/lib/PythonParser.js +108 -108
- package/dist/infra/lib/ReviewRace.js +96 -96
- package/dist/infra/lib/SkillFrontmatter.js +28 -28
- package/dist/infra/lib/SkillQualityGate.js +9 -9
- package/dist/infra/lib/SkillRepository.js +159 -159
- package/dist/infra/lib/UltraQA.js +99 -99
- package/dist/infra/lib/autonomy/AuditStore.js +41 -41
- package/dist/infra/lib/autonomy/ConfirmationStore.js +30 -30
- package/dist/infra/lib/autonomy/EventOutbox.js +38 -38
- package/dist/infra/lib/autonomy/PolicyEngine.js +18 -18
- package/dist/infra/lib/autonomy/SecuritySentinel.js +1 -1
- package/dist/infra/lib/autonomy/SuggestionStore.js +33 -33
- package/dist/infra/lib/embedding/VectorStore.js +22 -22
- package/dist/infra/lib/evolution/AgentAnalyzer.js +10 -10
- package/dist/infra/lib/evolution/DescriptionOptimizer.d.ts +79 -0
- package/dist/infra/lib/evolution/DescriptionOptimizer.d.ts.map +1 -0
- package/dist/infra/lib/evolution/DescriptionOptimizer.js +259 -0
- package/dist/infra/lib/evolution/DescriptionOptimizer.js.map +1 -0
- package/dist/infra/lib/evolution/GenerationRegistry.js +36 -36
- package/dist/infra/lib/evolution/InsightStore.js +90 -90
- package/dist/infra/lib/evolution/RollbackManager.js +5 -5
- package/dist/infra/lib/evolution/SkillBenchmark.d.ts +81 -0
- package/dist/infra/lib/evolution/SkillBenchmark.d.ts.map +1 -0
- package/dist/infra/lib/evolution/SkillBenchmark.js +233 -0
- package/dist/infra/lib/evolution/SkillBenchmark.js.map +1 -0
- package/dist/infra/lib/evolution/SkillClassifier.d.ts +35 -0
- package/dist/infra/lib/evolution/SkillClassifier.d.ts.map +1 -0
- package/dist/infra/lib/evolution/SkillClassifier.js +167 -0
- package/dist/infra/lib/evolution/SkillClassifier.js.map +1 -0
- package/dist/infra/lib/evolution/SkillEvalRunner.d.ts +102 -0
- package/dist/infra/lib/evolution/SkillEvalRunner.d.ts.map +1 -0
- package/dist/infra/lib/evolution/SkillEvalRunner.js +256 -0
- package/dist/infra/lib/evolution/SkillEvalRunner.js.map +1 -0
- package/dist/infra/lib/evolution/SkillGapDetector.js +10 -10
- package/dist/infra/lib/evolution/UsageTracker.js +28 -28
- package/dist/infra/lib/evolution/__tests__/eval.test.d.ts +2 -0
- package/dist/infra/lib/evolution/__tests__/eval.test.d.ts.map +1 -0
- package/dist/infra/lib/evolution/__tests__/eval.test.js +539 -0
- package/dist/infra/lib/evolution/__tests__/eval.test.js.map +1 -0
- package/dist/infra/lib/evolution/index.d.ts +8 -0
- package/dist/infra/lib/evolution/index.d.ts.map +1 -1
- package/dist/infra/lib/evolution/index.js +5 -0
- package/dist/infra/lib/evolution/index.js.map +1 -1
- package/dist/infra/lib/gemini/constants.js +14 -14
- package/dist/infra/lib/gemini/orchestration.js +5 -5
- package/dist/infra/lib/gpt/oauth.js +44 -44
- package/dist/infra/lib/gpt/orchestration.js +4 -4
- package/dist/infra/lib/memory/KnowledgeGraph.js +4 -4
- package/dist/infra/lib/memory/MemorySearch.js +57 -57
- package/dist/infra/lib/memory/MemoryStorage.js +181 -181
- package/dist/infra/lib/memory/ObservationStore.js +28 -28
- package/dist/infra/lib/memory/ReflectionStore.js +30 -30
- package/dist/infra/lib/memory/SessionRAGRetriever.js +7 -7
- package/dist/infra/lib/memory/SessionRAGStore.js +225 -225
- package/dist/infra/lib/memory/SessionSummarizer.js +9 -9
- package/dist/infra/orchestrator/AgentManager.js +12 -12
- package/dist/infra/orchestrator/AgentRegistry.js +65 -65
- package/dist/infra/orchestrator/MultiLlmResearch.js +8 -8
- package/dist/infra/orchestrator/SwarmOrchestrator.test.js +16 -16
- package/dist/infra/orchestrator/parallelResearch.js +24 -24
- package/dist/tools/convention/analyzeComplexity.test.js +115 -115
- package/dist/tools/convention/validateCodeQuality.test.js +104 -104
- package/dist/tools/memory/createMemoryTimeline.js +10 -10
- package/dist/tools/memory/getMemoryGraph.js +12 -12
- package/dist/tools/memory/getSessionContext.js +9 -9
- package/dist/tools/memory/linkMemories.js +14 -14
- package/dist/tools/memory/listMemories.js +4 -4
- package/dist/tools/memory/recallMemory.js +4 -4
- package/dist/tools/memory/saveMemory.js +4 -4
- package/dist/tools/memory/searchMemoriesAdvanced.js +23 -23
- package/dist/tools/semantic/analyzeDependencyGraph.js +12 -12
- package/dist/tools/semantic/astGrep.test.js +6 -6
- package/dist/tools/spec/prdParser.test.js +171 -171
- package/dist/tools/spec/specGenerator.js +169 -169
- package/dist/tools/spec/traceabilityMatrix.js +64 -64
- package/dist/tools/spec/traceabilityMatrix.test.js +28 -28
- package/hooks/gemini-hooks.json +73 -73
- package/hooks/hooks.json +137 -137
- package/hooks/scripts/code-check.js +70 -70
- package/hooks/scripts/context-save.js +212 -212
- package/hooks/scripts/hud-status.js +291 -291
- package/hooks/scripts/keyword-detector.js +214 -214
- package/hooks/scripts/llm-orchestrate.js +646 -646
- package/hooks/scripts/post-edit.js +32 -32
- package/hooks/scripts/pre-tool-guard.js +125 -125
- package/hooks/scripts/prompt-dispatcher.js +185 -185
- package/hooks/scripts/sentinel-guard.js +104 -104
- package/hooks/scripts/session-start.js +106 -106
- package/hooks/scripts/stop-notify.js +209 -209
- package/hooks/scripts/utils.js +100 -100
- package/languages/csharp-unity.md +515 -515
- package/languages/gdscript-godot.md +470 -470
- package/languages/ruby-rails.md +489 -489
- package/languages/typescript-angular.md +433 -433
- package/languages/typescript-astro.md +416 -416
- package/languages/typescript-electron.md +406 -406
- package/languages/typescript-nestjs.md +524 -524
- package/languages/typescript-svelte.md +407 -407
- package/languages/typescript-tauri.md +365 -365
- package/package.json +121 -121
- package/skills/agents-md/SKILL.md +120 -120
- package/skills/arch-guard/SKILL.md +180 -0
- package/skills/brand-assets/SKILL.md +146 -146
- package/skills/capability-loop/SKILL.md +167 -0
- package/skills/characterization-test/SKILL.md +206 -206
- package/skills/commerce-patterns/SKILL.md +59 -59
- package/skills/commit-push-pr/SKILL.md +75 -75
- package/skills/context7-usage/SKILL.md +105 -105
- package/skills/core-capabilities/SKILL.md +48 -48
- package/skills/e2e-commerce/SKILL.md +57 -57
- package/skills/exec-plan/SKILL.md +147 -0
- package/skills/frontend-design/SKILL.md +73 -73
- package/skills/git-worktree/SKILL.md +72 -72
- package/skills/handoff/SKILL.md +109 -109
- package/skills/parallel-research/SKILL.md +87 -87
- package/skills/priority-todos/SKILL.md +63 -63
- package/skills/seo-checklist/SKILL.md +57 -57
- package/skills/techdebt/SKILL.md +122 -122
- package/skills/tool-fallback/SKILL.md +103 -103
- package/skills/typescript-advanced-types/SKILL.md +65 -65
- package/skills/ui-ux-pro-max/SKILL.md +206 -206
- package/skills/vercel-react-best-practices/SKILL.md +59 -59
- package/skills/video-production/SKILL.md +51 -51
- package/vibe/config.json +29 -29
- package/vibe/constitution.md +227 -227
- package/vibe/rules/principles/communication-guide.md +98 -98
- package/vibe/rules/principles/development-philosophy.md +52 -52
- package/vibe/rules/principles/quick-start.md +102 -102
- package/vibe/rules/quality/bdd-contract-testing.md +393 -393
- package/vibe/rules/quality/checklist.md +276 -276
- package/vibe/rules/quality/performance.md +236 -236
- package/vibe/rules/quality/testing-strategy.md +440 -440
- package/vibe/rules/standards/anti-patterns.md +541 -541
- package/vibe/rules/standards/code-structure.md +291 -291
- package/vibe/rules/standards/complexity-metrics.md +313 -313
- package/vibe/rules/standards/git-workflow.md +237 -237
- package/vibe/rules/standards/naming-conventions.md +198 -198
- package/vibe/rules/standards/security.md +305 -305
- package/vibe/rules/writing/document-style.md +74 -74
- package/vibe/setup.sh +31 -31
- package/vibe/templates/constitution-template.md +252 -252
- package/vibe/templates/contract-backend-template.md +526 -526
- package/vibe/templates/contract-frontend-template.md +599 -599
- package/vibe/templates/feature-template.md +96 -96
- package/vibe/templates/spec-template.md +221 -221
- package/vibe/ui-ux-data/charts.csv +26 -26
- package/vibe/ui-ux-data/colors.csv +97 -97
- package/vibe/ui-ux-data/icons.csv +101 -101
- package/vibe/ui-ux-data/landing.csv +31 -31
- package/vibe/ui-ux-data/products.csv +96 -96
- package/vibe/ui-ux-data/react-performance.csv +45 -45
- package/vibe/ui-ux-data/stacks/astro.csv +54 -54
- package/vibe/ui-ux-data/stacks/flutter.csv +53 -53
- package/vibe/ui-ux-data/stacks/html-tailwind.csv +56 -56
- package/vibe/ui-ux-data/stacks/jetpack-compose.csv +53 -53
- package/vibe/ui-ux-data/stacks/nextjs.csv +53 -53
- package/vibe/ui-ux-data/stacks/nuxt-ui.csv +51 -51
- package/vibe/ui-ux-data/stacks/nuxtjs.csv +59 -59
- package/vibe/ui-ux-data/stacks/react-native.csv +52 -52
- package/vibe/ui-ux-data/stacks/react.csv +54 -54
- package/vibe/ui-ux-data/stacks/shadcn.csv +61 -61
- package/vibe/ui-ux-data/stacks/svelte.csv +54 -54
- package/vibe/ui-ux-data/stacks/swiftui.csv +51 -51
- package/vibe/ui-ux-data/stacks/vue.csv +50 -50
- package/vibe/ui-ux-data/styles.csv +68 -68
- package/vibe/ui-ux-data/typography.csv +57 -57
- package/vibe/ui-ux-data/ui-reasoning.csv +101 -101
- package/vibe/ui-ux-data/ux-guidelines.csv +99 -99
- package/vibe/ui-ux-data/version.json +31 -31
- package/vibe/ui-ux-data/web-interface.csv +31 -31
|
@@ -1,515 +1,515 @@
|
|
|
1
|
-
# C# + Unity Quality Rules
|
|
2
|
-
|
|
3
|
-
## Core Principles (inherited from core)
|
|
4
|
-
|
|
5
|
-
```markdown
|
|
6
|
-
# Core Principles (inherited from core)
|
|
7
|
-
Single Responsibility (SRP)
|
|
8
|
-
No Duplication (DRY)
|
|
9
|
-
Reusability
|
|
10
|
-
Low Complexity
|
|
11
|
-
Function <= 30 lines
|
|
12
|
-
Nesting <= 3 levels
|
|
13
|
-
Cyclomatic complexity <= 10
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
## Unity Architecture Understanding
|
|
17
|
-
|
|
18
|
-
```text
|
|
19
|
-
MonoBehaviour Lifecycle
|
|
20
|
-
Awake -> OnEnable -> Start -> Update -> ...
|
|
21
|
-
|
|
22
|
-
ScriptableObject (Data Assets)
|
|
23
|
-
- Settings, Events, Shared Data
|
|
24
|
-
|
|
25
|
-
Pure C# Classes (Non-MonoBehaviour)
|
|
26
|
-
- Game Logic, Utilities
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## C#/Unity Specific Rules
|
|
30
|
-
|
|
31
|
-
### 1. Minimize MonoBehaviour
|
|
32
|
-
|
|
33
|
-
```csharp
|
|
34
|
-
// Bad: All logic in MonoBehaviour
|
|
35
|
-
public class PlayerController : MonoBehaviour
|
|
36
|
-
{
|
|
37
|
-
public float health;
|
|
38
|
-
public float speed;
|
|
39
|
-
public int gold;
|
|
40
|
-
|
|
41
|
-
void Update()
|
|
42
|
-
{
|
|
43
|
-
// Movement, combat, inventory, UI update all here...
|
|
44
|
-
// Hundreds of lines of code
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Good: Separation of concerns
|
|
49
|
-
public class PlayerController : MonoBehaviour
|
|
50
|
-
{
|
|
51
|
-
[SerializeField] private PlayerData _data;
|
|
52
|
-
|
|
53
|
-
private PlayerMovement _movement;
|
|
54
|
-
private PlayerCombat _combat;
|
|
55
|
-
|
|
56
|
-
private void Awake()
|
|
57
|
-
{
|
|
58
|
-
_movement = new PlayerMovement(_data, transform);
|
|
59
|
-
_combat = new PlayerCombat(_data);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
private void Update()
|
|
63
|
-
{
|
|
64
|
-
_movement.Update(Time.deltaTime);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Pure C# class
|
|
69
|
-
public class PlayerMovement
|
|
70
|
-
{
|
|
71
|
-
private readonly PlayerData _data;
|
|
72
|
-
private readonly Transform _transform;
|
|
73
|
-
|
|
74
|
-
public PlayerMovement(PlayerData data, Transform transform)
|
|
75
|
-
{
|
|
76
|
-
_data = data;
|
|
77
|
-
_transform = transform;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
public void Update(float deltaTime)
|
|
81
|
-
{
|
|
82
|
-
// Movement logic only
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### 2. Using ScriptableObject
|
|
88
|
-
|
|
89
|
-
```csharp
|
|
90
|
-
// Good: Data Asset
|
|
91
|
-
[CreateAssetMenu(fileName = "PlayerData", menuName = "Game/PlayerData")]
|
|
92
|
-
public class PlayerData : ScriptableObject
|
|
93
|
-
{
|
|
94
|
-
[Header("Stats")]
|
|
95
|
-
public float maxHealth = 100f;
|
|
96
|
-
public float moveSpeed = 5f;
|
|
97
|
-
|
|
98
|
-
[Header("Combat")]
|
|
99
|
-
public float attackDamage = 10f;
|
|
100
|
-
public float attackRange = 2f;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Good: Event Channel
|
|
104
|
-
[CreateAssetMenu(fileName = "GameEvent", menuName = "Events/GameEvent")]
|
|
105
|
-
public class GameEvent : ScriptableObject
|
|
106
|
-
{
|
|
107
|
-
private readonly List<IGameEventListener> _listeners = new();
|
|
108
|
-
|
|
109
|
-
public void Raise()
|
|
110
|
-
{
|
|
111
|
-
for (int i = _listeners.Count - 1; i >= 0; i--)
|
|
112
|
-
{
|
|
113
|
-
_listeners[i].OnEventRaised();
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
public void RegisterListener(IGameEventListener listener) => _listeners.Add(listener);
|
|
118
|
-
public void UnregisterListener(IGameEventListener listener) => _listeners.Remove(listener);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
public interface IGameEventListener
|
|
122
|
-
{
|
|
123
|
-
void OnEventRaised();
|
|
124
|
-
}
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
### 3. Object Pooling
|
|
128
|
-
|
|
129
|
-
```csharp
|
|
130
|
-
// Good: Generic Object Pool
|
|
131
|
-
public class ObjectPool<T> where T : Component
|
|
132
|
-
{
|
|
133
|
-
private readonly T _prefab;
|
|
134
|
-
private readonly Transform _parent;
|
|
135
|
-
private readonly Queue<T> _pool = new();
|
|
136
|
-
|
|
137
|
-
public ObjectPool(T prefab, Transform parent, int initialSize = 10)
|
|
138
|
-
{
|
|
139
|
-
_prefab = prefab;
|
|
140
|
-
_parent = parent;
|
|
141
|
-
|
|
142
|
-
for (int i = 0; i < initialSize; i++)
|
|
143
|
-
{
|
|
144
|
-
CreateInstance();
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
public T Get()
|
|
149
|
-
{
|
|
150
|
-
T instance = _pool.Count > 0 ? _pool.Dequeue() : CreateInstance();
|
|
151
|
-
instance.gameObject.SetActive(true);
|
|
152
|
-
return instance;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
public void Return(T instance)
|
|
156
|
-
{
|
|
157
|
-
instance.gameObject.SetActive(false);
|
|
158
|
-
_pool.Enqueue(instance);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
private T CreateInstance()
|
|
162
|
-
{
|
|
163
|
-
T instance = Object.Instantiate(_prefab, _parent);
|
|
164
|
-
instance.gameObject.SetActive(false);
|
|
165
|
-
return instance;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Usage example
|
|
170
|
-
public class BulletManager : MonoBehaviour
|
|
171
|
-
{
|
|
172
|
-
[SerializeField] private Bullet _bulletPrefab;
|
|
173
|
-
|
|
174
|
-
private ObjectPool<Bullet> _bulletPool;
|
|
175
|
-
|
|
176
|
-
private void Awake()
|
|
177
|
-
{
|
|
178
|
-
_bulletPool = new ObjectPool<Bullet>(_bulletPrefab, transform, 50);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
public Bullet SpawnBullet(Vector3 position, Vector3 direction)
|
|
182
|
-
{
|
|
183
|
-
Bullet bullet = _bulletPool.Get();
|
|
184
|
-
bullet.Initialize(position, direction, () => _bulletPool.Return(bullet));
|
|
185
|
-
return bullet;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
### 4. Singleton Pattern (Use with Caution)
|
|
191
|
-
|
|
192
|
-
```csharp
|
|
193
|
-
// Good: Safe Singleton
|
|
194
|
-
public abstract class Singleton<T> : MonoBehaviour where T : MonoBehaviour
|
|
195
|
-
{
|
|
196
|
-
private static T _instance;
|
|
197
|
-
private static readonly object _lock = new();
|
|
198
|
-
private static bool _applicationIsQuitting;
|
|
199
|
-
|
|
200
|
-
public static T Instance
|
|
201
|
-
{
|
|
202
|
-
get
|
|
203
|
-
{
|
|
204
|
-
if (_applicationIsQuitting)
|
|
205
|
-
{
|
|
206
|
-
Debug.LogWarning($"[Singleton] Instance of {typeof(T)} already destroyed.");
|
|
207
|
-
return null;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
lock (_lock)
|
|
211
|
-
{
|
|
212
|
-
if (_instance == null)
|
|
213
|
-
{
|
|
214
|
-
_instance = FindObjectOfType<T>();
|
|
215
|
-
|
|
216
|
-
if (_instance == null)
|
|
217
|
-
{
|
|
218
|
-
var singleton = new GameObject($"[Singleton] {typeof(T)}");
|
|
219
|
-
_instance = singleton.AddComponent<T>();
|
|
220
|
-
DontDestroyOnLoad(singleton);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
return _instance;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
protected virtual void OnApplicationQuit()
|
|
229
|
-
{
|
|
230
|
-
_applicationIsQuitting = true;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Usage
|
|
235
|
-
public class GameManager : Singleton<GameManager>
|
|
236
|
-
{
|
|
237
|
-
public GameState CurrentState { get; private set; }
|
|
238
|
-
|
|
239
|
-
public void ChangeState(GameState newState)
|
|
240
|
-
{
|
|
241
|
-
CurrentState = newState;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
### 5. Coroutine vs async/await
|
|
247
|
-
|
|
248
|
-
```csharp
|
|
249
|
-
// Good: Coroutine (integrated with Unity lifecycle)
|
|
250
|
-
public class EnemySpawner : MonoBehaviour
|
|
251
|
-
{
|
|
252
|
-
[SerializeField] private float _spawnInterval = 2f;
|
|
253
|
-
|
|
254
|
-
private Coroutine _spawnCoroutine;
|
|
255
|
-
|
|
256
|
-
public void StartSpawning()
|
|
257
|
-
{
|
|
258
|
-
_spawnCoroutine = StartCoroutine(SpawnLoop());
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
public void StopSpawning()
|
|
262
|
-
{
|
|
263
|
-
if (_spawnCoroutine != null)
|
|
264
|
-
{
|
|
265
|
-
StopCoroutine(_spawnCoroutine);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
private IEnumerator SpawnLoop()
|
|
270
|
-
{
|
|
271
|
-
while (true)
|
|
272
|
-
{
|
|
273
|
-
SpawnEnemy();
|
|
274
|
-
yield return new WaitForSeconds(_spawnInterval);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Good: async/await (I/O operations)
|
|
280
|
-
public class SaveManager : MonoBehaviour
|
|
281
|
-
{
|
|
282
|
-
public async Task SaveGameAsync(GameSaveData data)
|
|
283
|
-
{
|
|
284
|
-
string json = JsonUtility.ToJson(data);
|
|
285
|
-
string path = Path.Combine(Application.persistentDataPath, "save.json");
|
|
286
|
-
|
|
287
|
-
await File.WriteAllTextAsync(path, json);
|
|
288
|
-
Debug.Log("Game saved!");
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
public async Task<GameSaveData> LoadGameAsync()
|
|
292
|
-
{
|
|
293
|
-
string path = Path.Combine(Application.persistentDataPath, "save.json");
|
|
294
|
-
|
|
295
|
-
if (!File.Exists(path))
|
|
296
|
-
return null;
|
|
297
|
-
|
|
298
|
-
string json = await File.ReadAllTextAsync(path);
|
|
299
|
-
return JsonUtility.FromJson<GameSaveData>(json);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
### 6. Event System
|
|
305
|
-
|
|
306
|
-
```csharp
|
|
307
|
-
// Good: C# Events
|
|
308
|
-
public class Health : MonoBehaviour
|
|
309
|
-
{
|
|
310
|
-
public event Action<float> OnHealthChanged;
|
|
311
|
-
public event Action OnDeath;
|
|
312
|
-
|
|
313
|
-
[SerializeField] private float _maxHealth = 100f;
|
|
314
|
-
private float _currentHealth;
|
|
315
|
-
|
|
316
|
-
public float CurrentHealth => _currentHealth;
|
|
317
|
-
public float MaxHealth => _maxHealth;
|
|
318
|
-
|
|
319
|
-
private void Awake()
|
|
320
|
-
{
|
|
321
|
-
_currentHealth = _maxHealth;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
public void TakeDamage(float damage)
|
|
325
|
-
{
|
|
326
|
-
_currentHealth = Mathf.Max(0, _currentHealth - damage);
|
|
327
|
-
OnHealthChanged?.Invoke(_currentHealth / _maxHealth);
|
|
328
|
-
|
|
329
|
-
if (_currentHealth <= 0)
|
|
330
|
-
{
|
|
331
|
-
OnDeath?.Invoke();
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// Subscription
|
|
337
|
-
public class HealthUI : MonoBehaviour
|
|
338
|
-
{
|
|
339
|
-
[SerializeField] private Health _health;
|
|
340
|
-
[SerializeField] private Slider _healthBar;
|
|
341
|
-
|
|
342
|
-
private void OnEnable()
|
|
343
|
-
{
|
|
344
|
-
_health.OnHealthChanged += UpdateHealthBar;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
private void OnDisable()
|
|
348
|
-
{
|
|
349
|
-
_health.OnHealthChanged -= UpdateHealthBar;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
private void UpdateHealthBar(float normalizedHealth)
|
|
353
|
-
{
|
|
354
|
-
_healthBar.value = normalizedHealth;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
### 7. Inspector Optimization
|
|
360
|
-
|
|
361
|
-
```csharp
|
|
362
|
-
// Good: SerializeField + private
|
|
363
|
-
public class Enemy : MonoBehaviour
|
|
364
|
-
{
|
|
365
|
-
[Header("Settings")]
|
|
366
|
-
[SerializeField] private float _moveSpeed = 3f;
|
|
367
|
-
[SerializeField] private float _attackRange = 1.5f;
|
|
368
|
-
|
|
369
|
-
[Header("References")]
|
|
370
|
-
[SerializeField] private Transform _target;
|
|
371
|
-
[SerializeField] private Animator _animator;
|
|
372
|
-
|
|
373
|
-
[Header("Debug")]
|
|
374
|
-
[SerializeField, ReadOnly] private float _distanceToTarget;
|
|
375
|
-
|
|
376
|
-
// Read-only access via public property
|
|
377
|
-
public float MoveSpeed => _moveSpeed;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// Good: RequireComponent
|
|
381
|
-
[RequireComponent(typeof(Rigidbody))]
|
|
382
|
-
[RequireComponent(typeof(Collider))]
|
|
383
|
-
public class PhysicsObject : MonoBehaviour
|
|
384
|
-
{
|
|
385
|
-
private Rigidbody _rb;
|
|
386
|
-
|
|
387
|
-
private void Awake()
|
|
388
|
-
{
|
|
389
|
-
_rb = GetComponent<Rigidbody>();
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
```
|
|
393
|
-
|
|
394
|
-
### 8. Performance Optimization
|
|
395
|
-
|
|
396
|
-
```csharp
|
|
397
|
-
// Good: GetComponent Caching
|
|
398
|
-
public class OptimizedBehaviour : MonoBehaviour
|
|
399
|
-
{
|
|
400
|
-
// Bad: GetComponent in Update
|
|
401
|
-
void Update()
|
|
402
|
-
{
|
|
403
|
-
GetComponent<Rigidbody>().AddForce(Vector3.up);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// Good: Caching
|
|
407
|
-
private Rigidbody _rb;
|
|
408
|
-
|
|
409
|
-
void Awake()
|
|
410
|
-
{
|
|
411
|
-
_rb = GetComponent<Rigidbody>();
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
void Update()
|
|
415
|
-
{
|
|
416
|
-
_rb.AddForce(Vector3.up);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// Good: String comparison optimization
|
|
421
|
-
public class TagChecker : MonoBehaviour
|
|
422
|
-
{
|
|
423
|
-
// Bad: String comparison
|
|
424
|
-
void OnTriggerEnter(Collider other)
|
|
425
|
-
{
|
|
426
|
-
if (other.tag == "Player") { }
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// Good: Use CompareTag
|
|
430
|
-
void OnTriggerEnter(Collider other)
|
|
431
|
-
{
|
|
432
|
-
if (other.CompareTag("Player")) { }
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// Good: Minimize allocations
|
|
437
|
-
public class NoAllocExample : MonoBehaviour
|
|
438
|
-
{
|
|
439
|
-
// Pre-allocate
|
|
440
|
-
private readonly Collider[] _hitBuffer = new Collider[10];
|
|
441
|
-
private readonly RaycastHit[] _rayBuffer = new RaycastHit[5];
|
|
442
|
-
|
|
443
|
-
void CheckOverlap(Vector3 position, float radius)
|
|
444
|
-
{
|
|
445
|
-
// Use NonAlloc version
|
|
446
|
-
int count = Physics.OverlapSphereNonAlloc(position, radius, _hitBuffer);
|
|
447
|
-
|
|
448
|
-
for (int i = 0; i < count; i++)
|
|
449
|
-
{
|
|
450
|
-
// Process _hitBuffer[i]
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
## Recommended Folder Structure
|
|
457
|
-
|
|
458
|
-
```text
|
|
459
|
-
Assets/
|
|
460
|
-
├── _Project/ # Project assets
|
|
461
|
-
│ ├── Scripts/
|
|
462
|
-
│ │ ├── Core/ # Core systems
|
|
463
|
-
│ │ ├── Player/
|
|
464
|
-
│ │ ├── Enemy/
|
|
465
|
-
│ │ ├── UI/
|
|
466
|
-
│ │ └── Utils/
|
|
467
|
-
│ ├── Prefabs/
|
|
468
|
-
│ ├── ScriptableObjects/
|
|
469
|
-
│ │ ├── Data/
|
|
470
|
-
│ │ └── Events/
|
|
471
|
-
│ ├── Materials/
|
|
472
|
-
│ ├── Textures/
|
|
473
|
-
│ └── Audio/
|
|
474
|
-
├── Scenes/
|
|
475
|
-
├── Resources/ # Runtime load (use with caution)
|
|
476
|
-
└── Plugins/
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
## Naming Conventions
|
|
480
|
-
|
|
481
|
-
```csharp
|
|
482
|
-
// Class: PascalCase
|
|
483
|
-
public class PlayerController { }
|
|
484
|
-
|
|
485
|
-
// Interface: I prefix
|
|
486
|
-
public interface IDamageable { }
|
|
487
|
-
|
|
488
|
-
// Private field: _ prefix + camelCase
|
|
489
|
-
private float _moveSpeed;
|
|
490
|
-
|
|
491
|
-
// SerializeField: Keep _ prefix
|
|
492
|
-
[SerializeField] private float _health;
|
|
493
|
-
|
|
494
|
-
// Constants: UPPER_SNAKE_CASE or PascalCase
|
|
495
|
-
private const float MAX_HEALTH = 100f;
|
|
496
|
-
private const float MaxHealth = 100f;
|
|
497
|
-
|
|
498
|
-
// Property: PascalCase
|
|
499
|
-
public float Health => _health;
|
|
500
|
-
|
|
501
|
-
// Method: PascalCase
|
|
502
|
-
public void TakeDamage(float damage) { }
|
|
503
|
-
```
|
|
504
|
-
|
|
505
|
-
## Checklist
|
|
506
|
-
|
|
507
|
-
- [ ] Minimize MonoBehaviour logic
|
|
508
|
-
- [ ] Cache GetComponent results
|
|
509
|
-
- [ ] Unsubscribe events (OnDisable)
|
|
510
|
-
- [ ] Apply object pooling
|
|
511
|
-
- [ ] Use SerializeField + private
|
|
512
|
-
- [ ] Use CompareTag
|
|
513
|
-
- [ ] Use NonAlloc APIs
|
|
514
|
-
- [ ] Minimize Update (only when needed)
|
|
515
|
-
- [ ] Separate data with ScriptableObject
|
|
1
|
+
# C# + Unity Quality Rules
|
|
2
|
+
|
|
3
|
+
## Core Principles (inherited from core)
|
|
4
|
+
|
|
5
|
+
```markdown
|
|
6
|
+
# Core Principles (inherited from core)
|
|
7
|
+
Single Responsibility (SRP)
|
|
8
|
+
No Duplication (DRY)
|
|
9
|
+
Reusability
|
|
10
|
+
Low Complexity
|
|
11
|
+
Function <= 30 lines
|
|
12
|
+
Nesting <= 3 levels
|
|
13
|
+
Cyclomatic complexity <= 10
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Unity Architecture Understanding
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
MonoBehaviour Lifecycle
|
|
20
|
+
Awake -> OnEnable -> Start -> Update -> ...
|
|
21
|
+
|
|
22
|
+
ScriptableObject (Data Assets)
|
|
23
|
+
- Settings, Events, Shared Data
|
|
24
|
+
|
|
25
|
+
Pure C# Classes (Non-MonoBehaviour)
|
|
26
|
+
- Game Logic, Utilities
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## C#/Unity Specific Rules
|
|
30
|
+
|
|
31
|
+
### 1. Minimize MonoBehaviour
|
|
32
|
+
|
|
33
|
+
```csharp
|
|
34
|
+
// Bad: All logic in MonoBehaviour
|
|
35
|
+
public class PlayerController : MonoBehaviour
|
|
36
|
+
{
|
|
37
|
+
public float health;
|
|
38
|
+
public float speed;
|
|
39
|
+
public int gold;
|
|
40
|
+
|
|
41
|
+
void Update()
|
|
42
|
+
{
|
|
43
|
+
// Movement, combat, inventory, UI update all here...
|
|
44
|
+
// Hundreds of lines of code
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Good: Separation of concerns
|
|
49
|
+
public class PlayerController : MonoBehaviour
|
|
50
|
+
{
|
|
51
|
+
[SerializeField] private PlayerData _data;
|
|
52
|
+
|
|
53
|
+
private PlayerMovement _movement;
|
|
54
|
+
private PlayerCombat _combat;
|
|
55
|
+
|
|
56
|
+
private void Awake()
|
|
57
|
+
{
|
|
58
|
+
_movement = new PlayerMovement(_data, transform);
|
|
59
|
+
_combat = new PlayerCombat(_data);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private void Update()
|
|
63
|
+
{
|
|
64
|
+
_movement.Update(Time.deltaTime);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Pure C# class
|
|
69
|
+
public class PlayerMovement
|
|
70
|
+
{
|
|
71
|
+
private readonly PlayerData _data;
|
|
72
|
+
private readonly Transform _transform;
|
|
73
|
+
|
|
74
|
+
public PlayerMovement(PlayerData data, Transform transform)
|
|
75
|
+
{
|
|
76
|
+
_data = data;
|
|
77
|
+
_transform = transform;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public void Update(float deltaTime)
|
|
81
|
+
{
|
|
82
|
+
// Movement logic only
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 2. Using ScriptableObject
|
|
88
|
+
|
|
89
|
+
```csharp
|
|
90
|
+
// Good: Data Asset
|
|
91
|
+
[CreateAssetMenu(fileName = "PlayerData", menuName = "Game/PlayerData")]
|
|
92
|
+
public class PlayerData : ScriptableObject
|
|
93
|
+
{
|
|
94
|
+
[Header("Stats")]
|
|
95
|
+
public float maxHealth = 100f;
|
|
96
|
+
public float moveSpeed = 5f;
|
|
97
|
+
|
|
98
|
+
[Header("Combat")]
|
|
99
|
+
public float attackDamage = 10f;
|
|
100
|
+
public float attackRange = 2f;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Good: Event Channel
|
|
104
|
+
[CreateAssetMenu(fileName = "GameEvent", menuName = "Events/GameEvent")]
|
|
105
|
+
public class GameEvent : ScriptableObject
|
|
106
|
+
{
|
|
107
|
+
private readonly List<IGameEventListener> _listeners = new();
|
|
108
|
+
|
|
109
|
+
public void Raise()
|
|
110
|
+
{
|
|
111
|
+
for (int i = _listeners.Count - 1; i >= 0; i--)
|
|
112
|
+
{
|
|
113
|
+
_listeners[i].OnEventRaised();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
public void RegisterListener(IGameEventListener listener) => _listeners.Add(listener);
|
|
118
|
+
public void UnregisterListener(IGameEventListener listener) => _listeners.Remove(listener);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
public interface IGameEventListener
|
|
122
|
+
{
|
|
123
|
+
void OnEventRaised();
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 3. Object Pooling
|
|
128
|
+
|
|
129
|
+
```csharp
|
|
130
|
+
// Good: Generic Object Pool
|
|
131
|
+
public class ObjectPool<T> where T : Component
|
|
132
|
+
{
|
|
133
|
+
private readonly T _prefab;
|
|
134
|
+
private readonly Transform _parent;
|
|
135
|
+
private readonly Queue<T> _pool = new();
|
|
136
|
+
|
|
137
|
+
public ObjectPool(T prefab, Transform parent, int initialSize = 10)
|
|
138
|
+
{
|
|
139
|
+
_prefab = prefab;
|
|
140
|
+
_parent = parent;
|
|
141
|
+
|
|
142
|
+
for (int i = 0; i < initialSize; i++)
|
|
143
|
+
{
|
|
144
|
+
CreateInstance();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
public T Get()
|
|
149
|
+
{
|
|
150
|
+
T instance = _pool.Count > 0 ? _pool.Dequeue() : CreateInstance();
|
|
151
|
+
instance.gameObject.SetActive(true);
|
|
152
|
+
return instance;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
public void Return(T instance)
|
|
156
|
+
{
|
|
157
|
+
instance.gameObject.SetActive(false);
|
|
158
|
+
_pool.Enqueue(instance);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private T CreateInstance()
|
|
162
|
+
{
|
|
163
|
+
T instance = Object.Instantiate(_prefab, _parent);
|
|
164
|
+
instance.gameObject.SetActive(false);
|
|
165
|
+
return instance;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Usage example
|
|
170
|
+
public class BulletManager : MonoBehaviour
|
|
171
|
+
{
|
|
172
|
+
[SerializeField] private Bullet _bulletPrefab;
|
|
173
|
+
|
|
174
|
+
private ObjectPool<Bullet> _bulletPool;
|
|
175
|
+
|
|
176
|
+
private void Awake()
|
|
177
|
+
{
|
|
178
|
+
_bulletPool = new ObjectPool<Bullet>(_bulletPrefab, transform, 50);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
public Bullet SpawnBullet(Vector3 position, Vector3 direction)
|
|
182
|
+
{
|
|
183
|
+
Bullet bullet = _bulletPool.Get();
|
|
184
|
+
bullet.Initialize(position, direction, () => _bulletPool.Return(bullet));
|
|
185
|
+
return bullet;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### 4. Singleton Pattern (Use with Caution)
|
|
191
|
+
|
|
192
|
+
```csharp
|
|
193
|
+
// Good: Safe Singleton
|
|
194
|
+
public abstract class Singleton<T> : MonoBehaviour where T : MonoBehaviour
|
|
195
|
+
{
|
|
196
|
+
private static T _instance;
|
|
197
|
+
private static readonly object _lock = new();
|
|
198
|
+
private static bool _applicationIsQuitting;
|
|
199
|
+
|
|
200
|
+
public static T Instance
|
|
201
|
+
{
|
|
202
|
+
get
|
|
203
|
+
{
|
|
204
|
+
if (_applicationIsQuitting)
|
|
205
|
+
{
|
|
206
|
+
Debug.LogWarning($"[Singleton] Instance of {typeof(T)} already destroyed.");
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
lock (_lock)
|
|
211
|
+
{
|
|
212
|
+
if (_instance == null)
|
|
213
|
+
{
|
|
214
|
+
_instance = FindObjectOfType<T>();
|
|
215
|
+
|
|
216
|
+
if (_instance == null)
|
|
217
|
+
{
|
|
218
|
+
var singleton = new GameObject($"[Singleton] {typeof(T)}");
|
|
219
|
+
_instance = singleton.AddComponent<T>();
|
|
220
|
+
DontDestroyOnLoad(singleton);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return _instance;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
protected virtual void OnApplicationQuit()
|
|
229
|
+
{
|
|
230
|
+
_applicationIsQuitting = true;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Usage
|
|
235
|
+
public class GameManager : Singleton<GameManager>
|
|
236
|
+
{
|
|
237
|
+
public GameState CurrentState { get; private set; }
|
|
238
|
+
|
|
239
|
+
public void ChangeState(GameState newState)
|
|
240
|
+
{
|
|
241
|
+
CurrentState = newState;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### 5. Coroutine vs async/await
|
|
247
|
+
|
|
248
|
+
```csharp
|
|
249
|
+
// Good: Coroutine (integrated with Unity lifecycle)
|
|
250
|
+
public class EnemySpawner : MonoBehaviour
|
|
251
|
+
{
|
|
252
|
+
[SerializeField] private float _spawnInterval = 2f;
|
|
253
|
+
|
|
254
|
+
private Coroutine _spawnCoroutine;
|
|
255
|
+
|
|
256
|
+
public void StartSpawning()
|
|
257
|
+
{
|
|
258
|
+
_spawnCoroutine = StartCoroutine(SpawnLoop());
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
public void StopSpawning()
|
|
262
|
+
{
|
|
263
|
+
if (_spawnCoroutine != null)
|
|
264
|
+
{
|
|
265
|
+
StopCoroutine(_spawnCoroutine);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private IEnumerator SpawnLoop()
|
|
270
|
+
{
|
|
271
|
+
while (true)
|
|
272
|
+
{
|
|
273
|
+
SpawnEnemy();
|
|
274
|
+
yield return new WaitForSeconds(_spawnInterval);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Good: async/await (I/O operations)
|
|
280
|
+
public class SaveManager : MonoBehaviour
|
|
281
|
+
{
|
|
282
|
+
public async Task SaveGameAsync(GameSaveData data)
|
|
283
|
+
{
|
|
284
|
+
string json = JsonUtility.ToJson(data);
|
|
285
|
+
string path = Path.Combine(Application.persistentDataPath, "save.json");
|
|
286
|
+
|
|
287
|
+
await File.WriteAllTextAsync(path, json);
|
|
288
|
+
Debug.Log("Game saved!");
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
public async Task<GameSaveData> LoadGameAsync()
|
|
292
|
+
{
|
|
293
|
+
string path = Path.Combine(Application.persistentDataPath, "save.json");
|
|
294
|
+
|
|
295
|
+
if (!File.Exists(path))
|
|
296
|
+
return null;
|
|
297
|
+
|
|
298
|
+
string json = await File.ReadAllTextAsync(path);
|
|
299
|
+
return JsonUtility.FromJson<GameSaveData>(json);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### 6. Event System
|
|
305
|
+
|
|
306
|
+
```csharp
|
|
307
|
+
// Good: C# Events
|
|
308
|
+
public class Health : MonoBehaviour
|
|
309
|
+
{
|
|
310
|
+
public event Action<float> OnHealthChanged;
|
|
311
|
+
public event Action OnDeath;
|
|
312
|
+
|
|
313
|
+
[SerializeField] private float _maxHealth = 100f;
|
|
314
|
+
private float _currentHealth;
|
|
315
|
+
|
|
316
|
+
public float CurrentHealth => _currentHealth;
|
|
317
|
+
public float MaxHealth => _maxHealth;
|
|
318
|
+
|
|
319
|
+
private void Awake()
|
|
320
|
+
{
|
|
321
|
+
_currentHealth = _maxHealth;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
public void TakeDamage(float damage)
|
|
325
|
+
{
|
|
326
|
+
_currentHealth = Mathf.Max(0, _currentHealth - damage);
|
|
327
|
+
OnHealthChanged?.Invoke(_currentHealth / _maxHealth);
|
|
328
|
+
|
|
329
|
+
if (_currentHealth <= 0)
|
|
330
|
+
{
|
|
331
|
+
OnDeath?.Invoke();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Subscription
|
|
337
|
+
public class HealthUI : MonoBehaviour
|
|
338
|
+
{
|
|
339
|
+
[SerializeField] private Health _health;
|
|
340
|
+
[SerializeField] private Slider _healthBar;
|
|
341
|
+
|
|
342
|
+
private void OnEnable()
|
|
343
|
+
{
|
|
344
|
+
_health.OnHealthChanged += UpdateHealthBar;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
private void OnDisable()
|
|
348
|
+
{
|
|
349
|
+
_health.OnHealthChanged -= UpdateHealthBar;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
private void UpdateHealthBar(float normalizedHealth)
|
|
353
|
+
{
|
|
354
|
+
_healthBar.value = normalizedHealth;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### 7. Inspector Optimization
|
|
360
|
+
|
|
361
|
+
```csharp
|
|
362
|
+
// Good: SerializeField + private
|
|
363
|
+
public class Enemy : MonoBehaviour
|
|
364
|
+
{
|
|
365
|
+
[Header("Settings")]
|
|
366
|
+
[SerializeField] private float _moveSpeed = 3f;
|
|
367
|
+
[SerializeField] private float _attackRange = 1.5f;
|
|
368
|
+
|
|
369
|
+
[Header("References")]
|
|
370
|
+
[SerializeField] private Transform _target;
|
|
371
|
+
[SerializeField] private Animator _animator;
|
|
372
|
+
|
|
373
|
+
[Header("Debug")]
|
|
374
|
+
[SerializeField, ReadOnly] private float _distanceToTarget;
|
|
375
|
+
|
|
376
|
+
// Read-only access via public property
|
|
377
|
+
public float MoveSpeed => _moveSpeed;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Good: RequireComponent
|
|
381
|
+
[RequireComponent(typeof(Rigidbody))]
|
|
382
|
+
[RequireComponent(typeof(Collider))]
|
|
383
|
+
public class PhysicsObject : MonoBehaviour
|
|
384
|
+
{
|
|
385
|
+
private Rigidbody _rb;
|
|
386
|
+
|
|
387
|
+
private void Awake()
|
|
388
|
+
{
|
|
389
|
+
_rb = GetComponent<Rigidbody>();
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### 8. Performance Optimization
|
|
395
|
+
|
|
396
|
+
```csharp
|
|
397
|
+
// Good: GetComponent Caching
|
|
398
|
+
public class OptimizedBehaviour : MonoBehaviour
|
|
399
|
+
{
|
|
400
|
+
// Bad: GetComponent in Update
|
|
401
|
+
void Update()
|
|
402
|
+
{
|
|
403
|
+
GetComponent<Rigidbody>().AddForce(Vector3.up);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Good: Caching
|
|
407
|
+
private Rigidbody _rb;
|
|
408
|
+
|
|
409
|
+
void Awake()
|
|
410
|
+
{
|
|
411
|
+
_rb = GetComponent<Rigidbody>();
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
void Update()
|
|
415
|
+
{
|
|
416
|
+
_rb.AddForce(Vector3.up);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Good: String comparison optimization
|
|
421
|
+
public class TagChecker : MonoBehaviour
|
|
422
|
+
{
|
|
423
|
+
// Bad: String comparison
|
|
424
|
+
void OnTriggerEnter(Collider other)
|
|
425
|
+
{
|
|
426
|
+
if (other.tag == "Player") { }
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Good: Use CompareTag
|
|
430
|
+
void OnTriggerEnter(Collider other)
|
|
431
|
+
{
|
|
432
|
+
if (other.CompareTag("Player")) { }
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Good: Minimize allocations
|
|
437
|
+
public class NoAllocExample : MonoBehaviour
|
|
438
|
+
{
|
|
439
|
+
// Pre-allocate
|
|
440
|
+
private readonly Collider[] _hitBuffer = new Collider[10];
|
|
441
|
+
private readonly RaycastHit[] _rayBuffer = new RaycastHit[5];
|
|
442
|
+
|
|
443
|
+
void CheckOverlap(Vector3 position, float radius)
|
|
444
|
+
{
|
|
445
|
+
// Use NonAlloc version
|
|
446
|
+
int count = Physics.OverlapSphereNonAlloc(position, radius, _hitBuffer);
|
|
447
|
+
|
|
448
|
+
for (int i = 0; i < count; i++)
|
|
449
|
+
{
|
|
450
|
+
// Process _hitBuffer[i]
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
## Recommended Folder Structure
|
|
457
|
+
|
|
458
|
+
```text
|
|
459
|
+
Assets/
|
|
460
|
+
├── _Project/ # Project assets
|
|
461
|
+
│ ├── Scripts/
|
|
462
|
+
│ │ ├── Core/ # Core systems
|
|
463
|
+
│ │ ├── Player/
|
|
464
|
+
│ │ ├── Enemy/
|
|
465
|
+
│ │ ├── UI/
|
|
466
|
+
│ │ └── Utils/
|
|
467
|
+
│ ├── Prefabs/
|
|
468
|
+
│ ├── ScriptableObjects/
|
|
469
|
+
│ │ ├── Data/
|
|
470
|
+
│ │ └── Events/
|
|
471
|
+
│ ├── Materials/
|
|
472
|
+
│ ├── Textures/
|
|
473
|
+
│ └── Audio/
|
|
474
|
+
├── Scenes/
|
|
475
|
+
├── Resources/ # Runtime load (use with caution)
|
|
476
|
+
└── Plugins/
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
## Naming Conventions
|
|
480
|
+
|
|
481
|
+
```csharp
|
|
482
|
+
// Class: PascalCase
|
|
483
|
+
public class PlayerController { }
|
|
484
|
+
|
|
485
|
+
// Interface: I prefix
|
|
486
|
+
public interface IDamageable { }
|
|
487
|
+
|
|
488
|
+
// Private field: _ prefix + camelCase
|
|
489
|
+
private float _moveSpeed;
|
|
490
|
+
|
|
491
|
+
// SerializeField: Keep _ prefix
|
|
492
|
+
[SerializeField] private float _health;
|
|
493
|
+
|
|
494
|
+
// Constants: UPPER_SNAKE_CASE or PascalCase
|
|
495
|
+
private const float MAX_HEALTH = 100f;
|
|
496
|
+
private const float MaxHealth = 100f;
|
|
497
|
+
|
|
498
|
+
// Property: PascalCase
|
|
499
|
+
public float Health => _health;
|
|
500
|
+
|
|
501
|
+
// Method: PascalCase
|
|
502
|
+
public void TakeDamage(float damage) { }
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
## Checklist
|
|
506
|
+
|
|
507
|
+
- [ ] Minimize MonoBehaviour logic
|
|
508
|
+
- [ ] Cache GetComponent results
|
|
509
|
+
- [ ] Unsubscribe events (OnDisable)
|
|
510
|
+
- [ ] Apply object pooling
|
|
511
|
+
- [ ] Use SerializeField + private
|
|
512
|
+
- [ ] Use CompareTag
|
|
513
|
+
- [ ] Use NonAlloc APIs
|
|
514
|
+
- [ ] Minimize Update (only when needed)
|
|
515
|
+
- [ ] Separate data with ScriptableObject
|