@su-record/vibe 2.7.14 → 2.7.16
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 +134 -126
- package/LICENSE +21 -21
- package/README.md +449 -449
- 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 +294 -294
- 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 +258 -258
- package/commands/vibe.analyze.md +11 -13
- package/commands/vibe.review.md +43 -1
- package/commands/vibe.run.md +2124 -2078
- package/commands/vibe.spec.md +9 -4
- package/commands/vibe.spec.review.md +569 -565
- package/commands/vibe.utils.md +413 -413
- package/commands/vibe.verify.md +33 -8
- package/dist/cli/collaborator.js +52 -52
- package/dist/cli/commands/evolution.js +12 -12
- package/dist/cli/commands/info.d.ts.map +1 -1
- package/dist/cli/commands/info.js +51 -55
- package/dist/cli/commands/info.js.map +1 -1
- package/dist/cli/commands/init.js +5 -5
- package/dist/cli/commands/remove.js +14 -14
- package/dist/cli/commands/sentinel.js +27 -27
- package/dist/cli/commands/skills.js +5 -5
- package/dist/cli/commands/slack.js +10 -10
- package/dist/cli/commands/telegram.js +12 -12
- package/dist/cli/detect.js +32 -32
- package/dist/cli/index.js +51 -51
- package/dist/cli/llm/claude-commands.js +16 -16
- package/dist/cli/llm/config.js +18 -18
- package/dist/cli/llm/gemini-commands.js +16 -16
- package/dist/cli/llm/gpt-commands.js +19 -19
- package/dist/cli/llm/help.js +21 -21
- package/dist/cli/postinstall/constants.d.ts.map +1 -1
- package/dist/cli/postinstall/constants.js +7 -8
- 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/setup/Provisioner.js +42 -42
- 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.js +21 -21
- 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.js +23 -23
- package/dist/infra/lib/evolution/SkillEvalRunner.js +50 -50
- package/dist/infra/lib/evolution/SkillGapDetector.js +10 -10
- package/dist/infra/lib/evolution/UsageTracker.js +28 -28
- package/dist/infra/lib/gemini/orchestration.js +5 -5
- 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 +77 -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 +475 -475
- 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 -180
- package/skills/brand-assets/SKILL.md +146 -146
- package/skills/capability-loop/SKILL.md +167 -167
- package/skills/characterization-test/SKILL.md +206 -206
- package/skills/commerce-patterns/SKILL.md +63 -59
- package/skills/commit-push-pr/SKILL.md +75 -75
- package/skills/context7-usage/SKILL.md +105 -105
- package/skills/core-capabilities/SKILL.md +13 -48
- package/skills/e2e-commerce/SKILL.md +61 -57
- package/skills/exec-plan/SKILL.md +147 -147
- package/skills/frontend-design/SKILL.md +12 -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 +66 -66
- package/skills/ui-ux-pro-max/SKILL.md +221 -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,470 +1,470 @@
|
|
|
1
|
-
# 🎮 GDScript + Godot 4 Quality Rules
|
|
2
|
-
|
|
3
|
-
## Core Principles (inherited from core)
|
|
4
|
-
|
|
5
|
-
```markdown
|
|
6
|
-
✅ Single Responsibility (SRP)
|
|
7
|
-
✅ Don't Repeat Yourself (DRY)
|
|
8
|
-
✅ Reusability
|
|
9
|
-
✅ Low Complexity
|
|
10
|
-
✅ Functions ≤ 30 lines
|
|
11
|
-
✅ Nesting ≤ 3 levels
|
|
12
|
-
✅ Cyclomatic complexity ≤ 10
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Godot Architecture
|
|
16
|
-
|
|
17
|
-
```
|
|
18
|
-
┌─────────────────────────────────────────────┐
|
|
19
|
-
│ Node (Scene Tree Building Block) │
|
|
20
|
-
│ - Composition over inheritance │
|
|
21
|
-
│ - Signals for communication │
|
|
22
|
-
├─────────────────────────────────────────────┤
|
|
23
|
-
│ Scene (Reusable Node Tree) │
|
|
24
|
-
│ - Prefab equivalent │
|
|
25
|
-
│ - Instantiate at runtime │
|
|
26
|
-
├─────────────────────────────────────────────┤
|
|
27
|
-
│ Autoload (Singleton) │
|
|
28
|
-
│ - Global state, managers │
|
|
29
|
-
│ - Use sparingly │
|
|
30
|
-
└─────────────────────────────────────────────┘
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## GDScript 2.0 Patterns (Godot 4)
|
|
34
|
-
|
|
35
|
-
### 1. Type Annotations (Always Use)
|
|
36
|
-
|
|
37
|
-
```gdscript
|
|
38
|
-
# ✅ Typed GDScript
|
|
39
|
-
extends CharacterBody2D
|
|
40
|
-
class_name Player
|
|
41
|
-
|
|
42
|
-
@export var speed: float = 200.0
|
|
43
|
-
@export var jump_force: float = 400.0
|
|
44
|
-
|
|
45
|
-
var health: int = 100
|
|
46
|
-
var is_dead: bool = false
|
|
47
|
-
var inventory: Array[Item] = []
|
|
48
|
-
var stats: Dictionary = {}
|
|
49
|
-
|
|
50
|
-
func take_damage(amount: int) -> void:
|
|
51
|
-
health -= amount
|
|
52
|
-
if health <= 0:
|
|
53
|
-
die()
|
|
54
|
-
|
|
55
|
-
func get_damage_multiplier() -> float:
|
|
56
|
-
return 1.0 + (stats.get("strength", 0) * 0.1)
|
|
57
|
-
|
|
58
|
-
# ❌ Untyped (avoid)
|
|
59
|
-
var speed = 200
|
|
60
|
-
func take_damage(amount):
|
|
61
|
-
pass
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### 2. Signal Pattern
|
|
65
|
-
|
|
66
|
-
```gdscript
|
|
67
|
-
# ✅ Signal definitions
|
|
68
|
-
extends Node
|
|
69
|
-
class_name Player
|
|
70
|
-
|
|
71
|
-
signal health_changed(new_health: int, max_health: int)
|
|
72
|
-
signal died
|
|
73
|
-
signal item_collected(item: Item)
|
|
74
|
-
signal level_up(new_level: int)
|
|
75
|
-
|
|
76
|
-
@export var max_health: int = 100
|
|
77
|
-
var _health: int = max_health
|
|
78
|
-
|
|
79
|
-
var health: int:
|
|
80
|
-
get:
|
|
81
|
-
return _health
|
|
82
|
-
set(value):
|
|
83
|
-
var old_health := _health
|
|
84
|
-
_health = clampi(value, 0, max_health)
|
|
85
|
-
if _health != old_health:
|
|
86
|
-
health_changed.emit(_health, max_health)
|
|
87
|
-
if _health <= 0 and old_health > 0:
|
|
88
|
-
died.emit()
|
|
89
|
-
|
|
90
|
-
func _ready() -> void:
|
|
91
|
-
# Connect signals
|
|
92
|
-
health_changed.connect(_on_health_changed)
|
|
93
|
-
died.connect(_on_died)
|
|
94
|
-
|
|
95
|
-
func _on_health_changed(new_health: int, _max: int) -> void:
|
|
96
|
-
print("Health: %d" % new_health)
|
|
97
|
-
|
|
98
|
-
func _on_died() -> void:
|
|
99
|
-
queue_free()
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### 3. State Machine Pattern
|
|
103
|
-
|
|
104
|
-
```gdscript
|
|
105
|
-
# state_machine.gd
|
|
106
|
-
extends Node
|
|
107
|
-
class_name StateMachine
|
|
108
|
-
|
|
109
|
-
signal state_changed(from_state: String, to_state: String)
|
|
110
|
-
|
|
111
|
-
@export var initial_state: State
|
|
112
|
-
var current_state: State
|
|
113
|
-
var states: Dictionary = {}
|
|
114
|
-
|
|
115
|
-
func _ready() -> void:
|
|
116
|
-
for child in get_children():
|
|
117
|
-
if child is State:
|
|
118
|
-
states[child.name.to_lower()] = child
|
|
119
|
-
child.state_machine = self
|
|
120
|
-
|
|
121
|
-
if initial_state:
|
|
122
|
-
current_state = initial_state
|
|
123
|
-
current_state.enter()
|
|
124
|
-
|
|
125
|
-
func _process(delta: float) -> void:
|
|
126
|
-
if current_state:
|
|
127
|
-
current_state.update(delta)
|
|
128
|
-
|
|
129
|
-
func _physics_process(delta: float) -> void:
|
|
130
|
-
if current_state:
|
|
131
|
-
current_state.physics_update(delta)
|
|
132
|
-
|
|
133
|
-
func change_state(new_state_name: String) -> void:
|
|
134
|
-
var new_state: State = states.get(new_state_name.to_lower())
|
|
135
|
-
if not new_state:
|
|
136
|
-
push_error("State not found: " + new_state_name)
|
|
137
|
-
return
|
|
138
|
-
|
|
139
|
-
if current_state:
|
|
140
|
-
current_state.exit()
|
|
141
|
-
|
|
142
|
-
var old_state_name := current_state.name if current_state else ""
|
|
143
|
-
current_state = new_state
|
|
144
|
-
current_state.enter()
|
|
145
|
-
state_changed.emit(old_state_name, new_state_name)
|
|
146
|
-
|
|
147
|
-
# state.gd
|
|
148
|
-
extends Node
|
|
149
|
-
class_name State
|
|
150
|
-
|
|
151
|
-
var state_machine: StateMachine
|
|
152
|
-
|
|
153
|
-
func enter() -> void:
|
|
154
|
-
pass
|
|
155
|
-
|
|
156
|
-
func exit() -> void:
|
|
157
|
-
pass
|
|
158
|
-
|
|
159
|
-
func update(_delta: float) -> void:
|
|
160
|
-
pass
|
|
161
|
-
|
|
162
|
-
func physics_update(_delta: float) -> void:
|
|
163
|
-
pass
|
|
164
|
-
|
|
165
|
-
# player_idle_state.gd
|
|
166
|
-
extends State
|
|
167
|
-
|
|
168
|
-
@onready var player: Player = owner
|
|
169
|
-
|
|
170
|
-
func enter() -> void:
|
|
171
|
-
player.animation_player.play("idle")
|
|
172
|
-
|
|
173
|
-
func update(_delta: float) -> void:
|
|
174
|
-
if Input.is_action_pressed("move_left") or Input.is_action_pressed("move_right"):
|
|
175
|
-
state_machine.change_state("walk")
|
|
176
|
-
|
|
177
|
-
if Input.is_action_just_pressed("jump"):
|
|
178
|
-
state_machine.change_state("jump")
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
### 4. Object Pool Pattern
|
|
182
|
-
|
|
183
|
-
```gdscript
|
|
184
|
-
# object_pool.gd
|
|
185
|
-
extends Node
|
|
186
|
-
class_name ObjectPool
|
|
187
|
-
|
|
188
|
-
@export var scene: PackedScene
|
|
189
|
-
@export var initial_size: int = 10
|
|
190
|
-
@export var max_size: int = 50
|
|
191
|
-
|
|
192
|
-
var _pool: Array[Node] = []
|
|
193
|
-
var _active: Array[Node] = []
|
|
194
|
-
|
|
195
|
-
func _ready() -> void:
|
|
196
|
-
for i in initial_size:
|
|
197
|
-
_create_instance()
|
|
198
|
-
|
|
199
|
-
func get_object() -> Node:
|
|
200
|
-
var obj: Node
|
|
201
|
-
|
|
202
|
-
if _pool.is_empty():
|
|
203
|
-
if _active.size() < max_size:
|
|
204
|
-
obj = _create_instance()
|
|
205
|
-
else:
|
|
206
|
-
push_warning("Pool exhausted")
|
|
207
|
-
return null
|
|
208
|
-
else:
|
|
209
|
-
obj = _pool.pop_back()
|
|
210
|
-
|
|
211
|
-
_active.append(obj)
|
|
212
|
-
obj.set_process(true)
|
|
213
|
-
obj.set_physics_process(true)
|
|
214
|
-
obj.show()
|
|
215
|
-
|
|
216
|
-
return obj
|
|
217
|
-
|
|
218
|
-
func return_object(obj: Node) -> void:
|
|
219
|
-
if obj not in _active:
|
|
220
|
-
return
|
|
221
|
-
|
|
222
|
-
_active.erase(obj)
|
|
223
|
-
_pool.append(obj)
|
|
224
|
-
obj.set_process(false)
|
|
225
|
-
obj.set_physics_process(false)
|
|
226
|
-
obj.hide()
|
|
227
|
-
|
|
228
|
-
if obj.has_method("reset"):
|
|
229
|
-
obj.reset()
|
|
230
|
-
|
|
231
|
-
func _create_instance() -> Node:
|
|
232
|
-
var obj := scene.instantiate()
|
|
233
|
-
add_child(obj)
|
|
234
|
-
obj.set_process(false)
|
|
235
|
-
obj.set_physics_process(false)
|
|
236
|
-
obj.hide()
|
|
237
|
-
_pool.append(obj)
|
|
238
|
-
return obj
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
### 5. Resource Pattern (Data Containers)
|
|
242
|
-
|
|
243
|
-
```gdscript
|
|
244
|
-
# item_data.gd
|
|
245
|
-
extends Resource
|
|
246
|
-
class_name ItemData
|
|
247
|
-
|
|
248
|
-
@export var id: String
|
|
249
|
-
@export var name: String
|
|
250
|
-
@export var description: String
|
|
251
|
-
@export var icon: Texture2D
|
|
252
|
-
@export var max_stack: int = 99
|
|
253
|
-
@export var value: int = 0
|
|
254
|
-
@export_category("Combat")
|
|
255
|
-
@export var damage: int = 0
|
|
256
|
-
@export var defense: int = 0
|
|
257
|
-
|
|
258
|
-
func get_tooltip() -> String:
|
|
259
|
-
var text := "[b]%s[/b]\n%s" % [name, description]
|
|
260
|
-
if damage > 0:
|
|
261
|
-
text += "\n[color=red]Damage: %d[/color]" % damage
|
|
262
|
-
if defense > 0:
|
|
263
|
-
text += "\n[color=blue]Defense: %d[/color]" % defense
|
|
264
|
-
return text
|
|
265
|
-
|
|
266
|
-
# weapon_data.gd
|
|
267
|
-
extends ItemData
|
|
268
|
-
class_name WeaponData
|
|
269
|
-
|
|
270
|
-
@export var attack_speed: float = 1.0
|
|
271
|
-
@export var range: float = 50.0
|
|
272
|
-
@export var projectile_scene: PackedScene
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
### 6. Component Pattern
|
|
276
|
-
|
|
277
|
-
```gdscript
|
|
278
|
-
# health_component.gd
|
|
279
|
-
extends Node
|
|
280
|
-
class_name HealthComponent
|
|
281
|
-
|
|
282
|
-
signal health_changed(current: int, maximum: int)
|
|
283
|
-
signal died
|
|
284
|
-
signal damage_taken(amount: int)
|
|
285
|
-
signal healed(amount: int)
|
|
286
|
-
|
|
287
|
-
@export var max_health: int = 100
|
|
288
|
-
@export var invincibility_time: float = 0.5
|
|
289
|
-
|
|
290
|
-
var current_health: int
|
|
291
|
-
var _invincible: bool = false
|
|
292
|
-
|
|
293
|
-
func _ready() -> void:
|
|
294
|
-
current_health = max_health
|
|
295
|
-
|
|
296
|
-
func take_damage(amount: int, ignore_invincibility: bool = false) -> void:
|
|
297
|
-
if _invincible and not ignore_invincibility:
|
|
298
|
-
return
|
|
299
|
-
|
|
300
|
-
var actual_damage := mini(amount, current_health)
|
|
301
|
-
current_health -= actual_damage
|
|
302
|
-
damage_taken.emit(actual_damage)
|
|
303
|
-
health_changed.emit(current_health, max_health)
|
|
304
|
-
|
|
305
|
-
if current_health <= 0:
|
|
306
|
-
died.emit()
|
|
307
|
-
elif invincibility_time > 0:
|
|
308
|
-
_start_invincibility()
|
|
309
|
-
|
|
310
|
-
func heal(amount: int) -> void:
|
|
311
|
-
var actual_heal := mini(amount, max_health - current_health)
|
|
312
|
-
current_health += actual_heal
|
|
313
|
-
healed.emit(actual_heal)
|
|
314
|
-
health_changed.emit(current_health, max_health)
|
|
315
|
-
|
|
316
|
-
func _start_invincibility() -> void:
|
|
317
|
-
_invincible = true
|
|
318
|
-
await get_tree().create_timer(invincibility_time).timeout
|
|
319
|
-
_invincible = false
|
|
320
|
-
|
|
321
|
-
# Usage
|
|
322
|
-
# var health_comp := $HealthComponent as HealthComponent
|
|
323
|
-
# health_comp.died.connect(_on_died)
|
|
324
|
-
# health_comp.take_damage(10)
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
### 7. Autoload (Singleton) Pattern
|
|
328
|
-
|
|
329
|
-
```gdscript
|
|
330
|
-
# game_manager.gd (Autoload)
|
|
331
|
-
extends Node
|
|
332
|
-
|
|
333
|
-
signal game_paused
|
|
334
|
-
signal game_resumed
|
|
335
|
-
signal score_changed(new_score: int)
|
|
336
|
-
|
|
337
|
-
var score: int = 0:
|
|
338
|
-
set(value):
|
|
339
|
-
score = value
|
|
340
|
-
score_changed.emit(score)
|
|
341
|
-
|
|
342
|
-
var is_paused: bool = false
|
|
343
|
-
|
|
344
|
-
func pause_game() -> void:
|
|
345
|
-
get_tree().paused = true
|
|
346
|
-
is_paused = true
|
|
347
|
-
game_paused.emit()
|
|
348
|
-
|
|
349
|
-
func resume_game() -> void:
|
|
350
|
-
get_tree().paused = false
|
|
351
|
-
is_paused = false
|
|
352
|
-
game_resumed.emit()
|
|
353
|
-
|
|
354
|
-
func toggle_pause() -> void:
|
|
355
|
-
if is_paused:
|
|
356
|
-
resume_game()
|
|
357
|
-
else:
|
|
358
|
-
pause_game()
|
|
359
|
-
|
|
360
|
-
func add_score(amount: int) -> void:
|
|
361
|
-
score += amount
|
|
362
|
-
|
|
363
|
-
func reset() -> void:
|
|
364
|
-
score = 0
|
|
365
|
-
is_paused = false
|
|
366
|
-
|
|
367
|
-
# audio_manager.gd (Autoload)
|
|
368
|
-
extends Node
|
|
369
|
-
|
|
370
|
-
@onready var music_player: AudioStreamPlayer = $MusicPlayer
|
|
371
|
-
@onready var sfx_players: Array[AudioStreamPlayer] = [$SFX1, $SFX2, $SFX3, $SFX4]
|
|
372
|
-
|
|
373
|
-
var _sfx_index: int = 0
|
|
374
|
-
|
|
375
|
-
func play_music(stream: AudioStream, volume_db: float = 0.0) -> void:
|
|
376
|
-
music_player.stream = stream
|
|
377
|
-
music_player.volume_db = volume_db
|
|
378
|
-
music_player.play()
|
|
379
|
-
|
|
380
|
-
func play_sfx(stream: AudioStream, volume_db: float = 0.0) -> void:
|
|
381
|
-
var player := sfx_players[_sfx_index]
|
|
382
|
-
player.stream = stream
|
|
383
|
-
player.volume_db = volume_db
|
|
384
|
-
player.play()
|
|
385
|
-
_sfx_index = (_sfx_index + 1) % sfx_players.size()
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
### 8. Input Handling
|
|
389
|
-
|
|
390
|
-
```gdscript
|
|
391
|
-
# player_controller.gd
|
|
392
|
-
extends CharacterBody2D
|
|
393
|
-
class_name PlayerController
|
|
394
|
-
|
|
395
|
-
@export var speed: float = 200.0
|
|
396
|
-
@export var acceleration: float = 1000.0
|
|
397
|
-
@export var friction: float = 800.0
|
|
398
|
-
@export var jump_force: float = 400.0
|
|
399
|
-
|
|
400
|
-
var _input_direction: Vector2 = Vector2.ZERO
|
|
401
|
-
|
|
402
|
-
func _process(_delta: float) -> void:
|
|
403
|
-
_input_direction = Input.get_vector(
|
|
404
|
-
"move_left", "move_right",
|
|
405
|
-
"move_up", "move_down"
|
|
406
|
-
)
|
|
407
|
-
|
|
408
|
-
func _physics_process(delta: float) -> void:
|
|
409
|
-
# Horizontal movement
|
|
410
|
-
if _input_direction.x != 0:
|
|
411
|
-
velocity.x = move_toward(
|
|
412
|
-
velocity.x,
|
|
413
|
-
_input_direction.x * speed,
|
|
414
|
-
acceleration * delta
|
|
415
|
-
)
|
|
416
|
-
else:
|
|
417
|
-
velocity.x = move_toward(velocity.x, 0, friction * delta)
|
|
418
|
-
|
|
419
|
-
# Gravity
|
|
420
|
-
if not is_on_floor():
|
|
421
|
-
velocity.y += get_gravity().y * delta
|
|
422
|
-
|
|
423
|
-
# Jump
|
|
424
|
-
if Input.is_action_just_pressed("jump") and is_on_floor():
|
|
425
|
-
velocity.y = -jump_force
|
|
426
|
-
|
|
427
|
-
move_and_slide()
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
## Project Structure
|
|
431
|
-
|
|
432
|
-
```
|
|
433
|
-
project/
|
|
434
|
-
├── project.godot
|
|
435
|
-
├── addons/
|
|
436
|
-
├── assets/
|
|
437
|
-
│ ├── sprites/
|
|
438
|
-
│ ├── audio/
|
|
439
|
-
│ └── fonts/
|
|
440
|
-
├── scenes/
|
|
441
|
-
│ ├── main.tscn
|
|
442
|
-
│ ├── levels/
|
|
443
|
-
│ ├── ui/
|
|
444
|
-
│ └── entities/
|
|
445
|
-
│ ├── player/
|
|
446
|
-
│ │ ├── player.tscn
|
|
447
|
-
│ │ └── player.gd
|
|
448
|
-
│ └── enemies/
|
|
449
|
-
├── scripts/
|
|
450
|
-
│ ├── autoloads/
|
|
451
|
-
│ ├── components/
|
|
452
|
-
│ ├── resources/
|
|
453
|
-
│ └── state_machine/
|
|
454
|
-
└── resources/
|
|
455
|
-
├── items/
|
|
456
|
-
└── characters/
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
## Checklist
|
|
460
|
-
|
|
461
|
-
- [ ] Use type annotations everywhere
|
|
462
|
-
- [ ] Use signals for decoupled communication
|
|
463
|
-
- [ ] Prefer composition over inheritance
|
|
464
|
-
- [ ] Use Resources for data
|
|
465
|
-
- [ ] Use Object Pools for frequently spawned objects
|
|
466
|
-
- [ ] Implement State Machine for complex behaviors
|
|
467
|
-
- [ ] Use Components for reusable functionality
|
|
468
|
-
- [ ] Minimize Autoloads (singletons)
|
|
469
|
-
- [ ] Cache node references with @onready
|
|
470
|
-
- [ ] Handle input in _process, physics in _physics_process
|
|
1
|
+
# 🎮 GDScript + Godot 4 Quality Rules
|
|
2
|
+
|
|
3
|
+
## Core Principles (inherited from core)
|
|
4
|
+
|
|
5
|
+
```markdown
|
|
6
|
+
✅ Single Responsibility (SRP)
|
|
7
|
+
✅ Don't Repeat Yourself (DRY)
|
|
8
|
+
✅ Reusability
|
|
9
|
+
✅ Low Complexity
|
|
10
|
+
✅ Functions ≤ 30 lines
|
|
11
|
+
✅ Nesting ≤ 3 levels
|
|
12
|
+
✅ Cyclomatic complexity ≤ 10
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Godot Architecture
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
┌─────────────────────────────────────────────┐
|
|
19
|
+
│ Node (Scene Tree Building Block) │
|
|
20
|
+
│ - Composition over inheritance │
|
|
21
|
+
│ - Signals for communication │
|
|
22
|
+
├─────────────────────────────────────────────┤
|
|
23
|
+
│ Scene (Reusable Node Tree) │
|
|
24
|
+
│ - Prefab equivalent │
|
|
25
|
+
│ - Instantiate at runtime │
|
|
26
|
+
├─────────────────────────────────────────────┤
|
|
27
|
+
│ Autoload (Singleton) │
|
|
28
|
+
│ - Global state, managers │
|
|
29
|
+
│ - Use sparingly │
|
|
30
|
+
└─────────────────────────────────────────────┘
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## GDScript 2.0 Patterns (Godot 4)
|
|
34
|
+
|
|
35
|
+
### 1. Type Annotations (Always Use)
|
|
36
|
+
|
|
37
|
+
```gdscript
|
|
38
|
+
# ✅ Typed GDScript
|
|
39
|
+
extends CharacterBody2D
|
|
40
|
+
class_name Player
|
|
41
|
+
|
|
42
|
+
@export var speed: float = 200.0
|
|
43
|
+
@export var jump_force: float = 400.0
|
|
44
|
+
|
|
45
|
+
var health: int = 100
|
|
46
|
+
var is_dead: bool = false
|
|
47
|
+
var inventory: Array[Item] = []
|
|
48
|
+
var stats: Dictionary = {}
|
|
49
|
+
|
|
50
|
+
func take_damage(amount: int) -> void:
|
|
51
|
+
health -= amount
|
|
52
|
+
if health <= 0:
|
|
53
|
+
die()
|
|
54
|
+
|
|
55
|
+
func get_damage_multiplier() -> float:
|
|
56
|
+
return 1.0 + (stats.get("strength", 0) * 0.1)
|
|
57
|
+
|
|
58
|
+
# ❌ Untyped (avoid)
|
|
59
|
+
var speed = 200
|
|
60
|
+
func take_damage(amount):
|
|
61
|
+
pass
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 2. Signal Pattern
|
|
65
|
+
|
|
66
|
+
```gdscript
|
|
67
|
+
# ✅ Signal definitions
|
|
68
|
+
extends Node
|
|
69
|
+
class_name Player
|
|
70
|
+
|
|
71
|
+
signal health_changed(new_health: int, max_health: int)
|
|
72
|
+
signal died
|
|
73
|
+
signal item_collected(item: Item)
|
|
74
|
+
signal level_up(new_level: int)
|
|
75
|
+
|
|
76
|
+
@export var max_health: int = 100
|
|
77
|
+
var _health: int = max_health
|
|
78
|
+
|
|
79
|
+
var health: int:
|
|
80
|
+
get:
|
|
81
|
+
return _health
|
|
82
|
+
set(value):
|
|
83
|
+
var old_health := _health
|
|
84
|
+
_health = clampi(value, 0, max_health)
|
|
85
|
+
if _health != old_health:
|
|
86
|
+
health_changed.emit(_health, max_health)
|
|
87
|
+
if _health <= 0 and old_health > 0:
|
|
88
|
+
died.emit()
|
|
89
|
+
|
|
90
|
+
func _ready() -> void:
|
|
91
|
+
# Connect signals
|
|
92
|
+
health_changed.connect(_on_health_changed)
|
|
93
|
+
died.connect(_on_died)
|
|
94
|
+
|
|
95
|
+
func _on_health_changed(new_health: int, _max: int) -> void:
|
|
96
|
+
print("Health: %d" % new_health)
|
|
97
|
+
|
|
98
|
+
func _on_died() -> void:
|
|
99
|
+
queue_free()
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 3. State Machine Pattern
|
|
103
|
+
|
|
104
|
+
```gdscript
|
|
105
|
+
# state_machine.gd
|
|
106
|
+
extends Node
|
|
107
|
+
class_name StateMachine
|
|
108
|
+
|
|
109
|
+
signal state_changed(from_state: String, to_state: String)
|
|
110
|
+
|
|
111
|
+
@export var initial_state: State
|
|
112
|
+
var current_state: State
|
|
113
|
+
var states: Dictionary = {}
|
|
114
|
+
|
|
115
|
+
func _ready() -> void:
|
|
116
|
+
for child in get_children():
|
|
117
|
+
if child is State:
|
|
118
|
+
states[child.name.to_lower()] = child
|
|
119
|
+
child.state_machine = self
|
|
120
|
+
|
|
121
|
+
if initial_state:
|
|
122
|
+
current_state = initial_state
|
|
123
|
+
current_state.enter()
|
|
124
|
+
|
|
125
|
+
func _process(delta: float) -> void:
|
|
126
|
+
if current_state:
|
|
127
|
+
current_state.update(delta)
|
|
128
|
+
|
|
129
|
+
func _physics_process(delta: float) -> void:
|
|
130
|
+
if current_state:
|
|
131
|
+
current_state.physics_update(delta)
|
|
132
|
+
|
|
133
|
+
func change_state(new_state_name: String) -> void:
|
|
134
|
+
var new_state: State = states.get(new_state_name.to_lower())
|
|
135
|
+
if not new_state:
|
|
136
|
+
push_error("State not found: " + new_state_name)
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
if current_state:
|
|
140
|
+
current_state.exit()
|
|
141
|
+
|
|
142
|
+
var old_state_name := current_state.name if current_state else ""
|
|
143
|
+
current_state = new_state
|
|
144
|
+
current_state.enter()
|
|
145
|
+
state_changed.emit(old_state_name, new_state_name)
|
|
146
|
+
|
|
147
|
+
# state.gd
|
|
148
|
+
extends Node
|
|
149
|
+
class_name State
|
|
150
|
+
|
|
151
|
+
var state_machine: StateMachine
|
|
152
|
+
|
|
153
|
+
func enter() -> void:
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
func exit() -> void:
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
func update(_delta: float) -> void:
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
func physics_update(_delta: float) -> void:
|
|
163
|
+
pass
|
|
164
|
+
|
|
165
|
+
# player_idle_state.gd
|
|
166
|
+
extends State
|
|
167
|
+
|
|
168
|
+
@onready var player: Player = owner
|
|
169
|
+
|
|
170
|
+
func enter() -> void:
|
|
171
|
+
player.animation_player.play("idle")
|
|
172
|
+
|
|
173
|
+
func update(_delta: float) -> void:
|
|
174
|
+
if Input.is_action_pressed("move_left") or Input.is_action_pressed("move_right"):
|
|
175
|
+
state_machine.change_state("walk")
|
|
176
|
+
|
|
177
|
+
if Input.is_action_just_pressed("jump"):
|
|
178
|
+
state_machine.change_state("jump")
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### 4. Object Pool Pattern
|
|
182
|
+
|
|
183
|
+
```gdscript
|
|
184
|
+
# object_pool.gd
|
|
185
|
+
extends Node
|
|
186
|
+
class_name ObjectPool
|
|
187
|
+
|
|
188
|
+
@export var scene: PackedScene
|
|
189
|
+
@export var initial_size: int = 10
|
|
190
|
+
@export var max_size: int = 50
|
|
191
|
+
|
|
192
|
+
var _pool: Array[Node] = []
|
|
193
|
+
var _active: Array[Node] = []
|
|
194
|
+
|
|
195
|
+
func _ready() -> void:
|
|
196
|
+
for i in initial_size:
|
|
197
|
+
_create_instance()
|
|
198
|
+
|
|
199
|
+
func get_object() -> Node:
|
|
200
|
+
var obj: Node
|
|
201
|
+
|
|
202
|
+
if _pool.is_empty():
|
|
203
|
+
if _active.size() < max_size:
|
|
204
|
+
obj = _create_instance()
|
|
205
|
+
else:
|
|
206
|
+
push_warning("Pool exhausted")
|
|
207
|
+
return null
|
|
208
|
+
else:
|
|
209
|
+
obj = _pool.pop_back()
|
|
210
|
+
|
|
211
|
+
_active.append(obj)
|
|
212
|
+
obj.set_process(true)
|
|
213
|
+
obj.set_physics_process(true)
|
|
214
|
+
obj.show()
|
|
215
|
+
|
|
216
|
+
return obj
|
|
217
|
+
|
|
218
|
+
func return_object(obj: Node) -> void:
|
|
219
|
+
if obj not in _active:
|
|
220
|
+
return
|
|
221
|
+
|
|
222
|
+
_active.erase(obj)
|
|
223
|
+
_pool.append(obj)
|
|
224
|
+
obj.set_process(false)
|
|
225
|
+
obj.set_physics_process(false)
|
|
226
|
+
obj.hide()
|
|
227
|
+
|
|
228
|
+
if obj.has_method("reset"):
|
|
229
|
+
obj.reset()
|
|
230
|
+
|
|
231
|
+
func _create_instance() -> Node:
|
|
232
|
+
var obj := scene.instantiate()
|
|
233
|
+
add_child(obj)
|
|
234
|
+
obj.set_process(false)
|
|
235
|
+
obj.set_physics_process(false)
|
|
236
|
+
obj.hide()
|
|
237
|
+
_pool.append(obj)
|
|
238
|
+
return obj
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### 5. Resource Pattern (Data Containers)
|
|
242
|
+
|
|
243
|
+
```gdscript
|
|
244
|
+
# item_data.gd
|
|
245
|
+
extends Resource
|
|
246
|
+
class_name ItemData
|
|
247
|
+
|
|
248
|
+
@export var id: String
|
|
249
|
+
@export var name: String
|
|
250
|
+
@export var description: String
|
|
251
|
+
@export var icon: Texture2D
|
|
252
|
+
@export var max_stack: int = 99
|
|
253
|
+
@export var value: int = 0
|
|
254
|
+
@export_category("Combat")
|
|
255
|
+
@export var damage: int = 0
|
|
256
|
+
@export var defense: int = 0
|
|
257
|
+
|
|
258
|
+
func get_tooltip() -> String:
|
|
259
|
+
var text := "[b]%s[/b]\n%s" % [name, description]
|
|
260
|
+
if damage > 0:
|
|
261
|
+
text += "\n[color=red]Damage: %d[/color]" % damage
|
|
262
|
+
if defense > 0:
|
|
263
|
+
text += "\n[color=blue]Defense: %d[/color]" % defense
|
|
264
|
+
return text
|
|
265
|
+
|
|
266
|
+
# weapon_data.gd
|
|
267
|
+
extends ItemData
|
|
268
|
+
class_name WeaponData
|
|
269
|
+
|
|
270
|
+
@export var attack_speed: float = 1.0
|
|
271
|
+
@export var range: float = 50.0
|
|
272
|
+
@export var projectile_scene: PackedScene
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### 6. Component Pattern
|
|
276
|
+
|
|
277
|
+
```gdscript
|
|
278
|
+
# health_component.gd
|
|
279
|
+
extends Node
|
|
280
|
+
class_name HealthComponent
|
|
281
|
+
|
|
282
|
+
signal health_changed(current: int, maximum: int)
|
|
283
|
+
signal died
|
|
284
|
+
signal damage_taken(amount: int)
|
|
285
|
+
signal healed(amount: int)
|
|
286
|
+
|
|
287
|
+
@export var max_health: int = 100
|
|
288
|
+
@export var invincibility_time: float = 0.5
|
|
289
|
+
|
|
290
|
+
var current_health: int
|
|
291
|
+
var _invincible: bool = false
|
|
292
|
+
|
|
293
|
+
func _ready() -> void:
|
|
294
|
+
current_health = max_health
|
|
295
|
+
|
|
296
|
+
func take_damage(amount: int, ignore_invincibility: bool = false) -> void:
|
|
297
|
+
if _invincible and not ignore_invincibility:
|
|
298
|
+
return
|
|
299
|
+
|
|
300
|
+
var actual_damage := mini(amount, current_health)
|
|
301
|
+
current_health -= actual_damage
|
|
302
|
+
damage_taken.emit(actual_damage)
|
|
303
|
+
health_changed.emit(current_health, max_health)
|
|
304
|
+
|
|
305
|
+
if current_health <= 0:
|
|
306
|
+
died.emit()
|
|
307
|
+
elif invincibility_time > 0:
|
|
308
|
+
_start_invincibility()
|
|
309
|
+
|
|
310
|
+
func heal(amount: int) -> void:
|
|
311
|
+
var actual_heal := mini(amount, max_health - current_health)
|
|
312
|
+
current_health += actual_heal
|
|
313
|
+
healed.emit(actual_heal)
|
|
314
|
+
health_changed.emit(current_health, max_health)
|
|
315
|
+
|
|
316
|
+
func _start_invincibility() -> void:
|
|
317
|
+
_invincible = true
|
|
318
|
+
await get_tree().create_timer(invincibility_time).timeout
|
|
319
|
+
_invincible = false
|
|
320
|
+
|
|
321
|
+
# Usage
|
|
322
|
+
# var health_comp := $HealthComponent as HealthComponent
|
|
323
|
+
# health_comp.died.connect(_on_died)
|
|
324
|
+
# health_comp.take_damage(10)
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### 7. Autoload (Singleton) Pattern
|
|
328
|
+
|
|
329
|
+
```gdscript
|
|
330
|
+
# game_manager.gd (Autoload)
|
|
331
|
+
extends Node
|
|
332
|
+
|
|
333
|
+
signal game_paused
|
|
334
|
+
signal game_resumed
|
|
335
|
+
signal score_changed(new_score: int)
|
|
336
|
+
|
|
337
|
+
var score: int = 0:
|
|
338
|
+
set(value):
|
|
339
|
+
score = value
|
|
340
|
+
score_changed.emit(score)
|
|
341
|
+
|
|
342
|
+
var is_paused: bool = false
|
|
343
|
+
|
|
344
|
+
func pause_game() -> void:
|
|
345
|
+
get_tree().paused = true
|
|
346
|
+
is_paused = true
|
|
347
|
+
game_paused.emit()
|
|
348
|
+
|
|
349
|
+
func resume_game() -> void:
|
|
350
|
+
get_tree().paused = false
|
|
351
|
+
is_paused = false
|
|
352
|
+
game_resumed.emit()
|
|
353
|
+
|
|
354
|
+
func toggle_pause() -> void:
|
|
355
|
+
if is_paused:
|
|
356
|
+
resume_game()
|
|
357
|
+
else:
|
|
358
|
+
pause_game()
|
|
359
|
+
|
|
360
|
+
func add_score(amount: int) -> void:
|
|
361
|
+
score += amount
|
|
362
|
+
|
|
363
|
+
func reset() -> void:
|
|
364
|
+
score = 0
|
|
365
|
+
is_paused = false
|
|
366
|
+
|
|
367
|
+
# audio_manager.gd (Autoload)
|
|
368
|
+
extends Node
|
|
369
|
+
|
|
370
|
+
@onready var music_player: AudioStreamPlayer = $MusicPlayer
|
|
371
|
+
@onready var sfx_players: Array[AudioStreamPlayer] = [$SFX1, $SFX2, $SFX3, $SFX4]
|
|
372
|
+
|
|
373
|
+
var _sfx_index: int = 0
|
|
374
|
+
|
|
375
|
+
func play_music(stream: AudioStream, volume_db: float = 0.0) -> void:
|
|
376
|
+
music_player.stream = stream
|
|
377
|
+
music_player.volume_db = volume_db
|
|
378
|
+
music_player.play()
|
|
379
|
+
|
|
380
|
+
func play_sfx(stream: AudioStream, volume_db: float = 0.0) -> void:
|
|
381
|
+
var player := sfx_players[_sfx_index]
|
|
382
|
+
player.stream = stream
|
|
383
|
+
player.volume_db = volume_db
|
|
384
|
+
player.play()
|
|
385
|
+
_sfx_index = (_sfx_index + 1) % sfx_players.size()
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### 8. Input Handling
|
|
389
|
+
|
|
390
|
+
```gdscript
|
|
391
|
+
# player_controller.gd
|
|
392
|
+
extends CharacterBody2D
|
|
393
|
+
class_name PlayerController
|
|
394
|
+
|
|
395
|
+
@export var speed: float = 200.0
|
|
396
|
+
@export var acceleration: float = 1000.0
|
|
397
|
+
@export var friction: float = 800.0
|
|
398
|
+
@export var jump_force: float = 400.0
|
|
399
|
+
|
|
400
|
+
var _input_direction: Vector2 = Vector2.ZERO
|
|
401
|
+
|
|
402
|
+
func _process(_delta: float) -> void:
|
|
403
|
+
_input_direction = Input.get_vector(
|
|
404
|
+
"move_left", "move_right",
|
|
405
|
+
"move_up", "move_down"
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
func _physics_process(delta: float) -> void:
|
|
409
|
+
# Horizontal movement
|
|
410
|
+
if _input_direction.x != 0:
|
|
411
|
+
velocity.x = move_toward(
|
|
412
|
+
velocity.x,
|
|
413
|
+
_input_direction.x * speed,
|
|
414
|
+
acceleration * delta
|
|
415
|
+
)
|
|
416
|
+
else:
|
|
417
|
+
velocity.x = move_toward(velocity.x, 0, friction * delta)
|
|
418
|
+
|
|
419
|
+
# Gravity
|
|
420
|
+
if not is_on_floor():
|
|
421
|
+
velocity.y += get_gravity().y * delta
|
|
422
|
+
|
|
423
|
+
# Jump
|
|
424
|
+
if Input.is_action_just_pressed("jump") and is_on_floor():
|
|
425
|
+
velocity.y = -jump_force
|
|
426
|
+
|
|
427
|
+
move_and_slide()
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
## Project Structure
|
|
431
|
+
|
|
432
|
+
```
|
|
433
|
+
project/
|
|
434
|
+
├── project.godot
|
|
435
|
+
├── addons/
|
|
436
|
+
├── assets/
|
|
437
|
+
│ ├── sprites/
|
|
438
|
+
│ ├── audio/
|
|
439
|
+
│ └── fonts/
|
|
440
|
+
├── scenes/
|
|
441
|
+
│ ├── main.tscn
|
|
442
|
+
│ ├── levels/
|
|
443
|
+
│ ├── ui/
|
|
444
|
+
│ └── entities/
|
|
445
|
+
│ ├── player/
|
|
446
|
+
│ │ ├── player.tscn
|
|
447
|
+
│ │ └── player.gd
|
|
448
|
+
│ └── enemies/
|
|
449
|
+
├── scripts/
|
|
450
|
+
│ ├── autoloads/
|
|
451
|
+
│ ├── components/
|
|
452
|
+
│ ├── resources/
|
|
453
|
+
│ └── state_machine/
|
|
454
|
+
└── resources/
|
|
455
|
+
├── items/
|
|
456
|
+
└── characters/
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
## Checklist
|
|
460
|
+
|
|
461
|
+
- [ ] Use type annotations everywhere
|
|
462
|
+
- [ ] Use signals for decoupled communication
|
|
463
|
+
- [ ] Prefer composition over inheritance
|
|
464
|
+
- [ ] Use Resources for data
|
|
465
|
+
- [ ] Use Object Pools for frequently spawned objects
|
|
466
|
+
- [ ] Implement State Machine for complex behaviors
|
|
467
|
+
- [ ] Use Components for reusable functionality
|
|
468
|
+
- [ ] Minimize Autoloads (singletons)
|
|
469
|
+
- [ ] Cache node references with @onready
|
|
470
|
+
- [ ] Handle input in _process, physics in _physics_process
|