@zigrivers/scaffold 3.4.1 → 3.5.1
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/README.md +91 -0
- package/content/knowledge/game/game-accessibility.md +328 -0
- package/content/knowledge/game/game-ai-patterns.md +567 -0
- package/content/knowledge/game/game-asset-pipeline.md +363 -0
- package/content/knowledge/game/game-audio-design.md +344 -0
- package/content/knowledge/game/game-binary-vcs-strategy.md +396 -0
- package/content/knowledge/game/game-design-document.md +269 -0
- package/content/knowledge/game/game-domain-patterns.md +299 -0
- package/content/knowledge/game/game-economy-design.md +355 -0
- package/content/knowledge/game/game-engine-selection.md +242 -0
- package/content/knowledge/game/game-input-systems.md +379 -0
- package/content/knowledge/game/game-level-content-design.md +483 -0
- package/content/knowledge/game/game-liveops-analytics.md +280 -0
- package/content/knowledge/game/game-localization.md +323 -0
- package/content/knowledge/game/game-milestone-definitions.md +337 -0
- package/content/knowledge/game/game-modding-ugc.md +390 -0
- package/content/knowledge/game/game-narrative-design.md +404 -0
- package/content/knowledge/game/game-networking.md +393 -0
- package/content/knowledge/game/game-performance-budgeting.md +389 -0
- package/content/knowledge/game/game-platform-certification.md +417 -0
- package/content/knowledge/game/game-project-structure.md +360 -0
- package/content/knowledge/game/game-save-systems.md +452 -0
- package/content/knowledge/game/game-testing-strategy.md +470 -0
- package/content/knowledge/game/game-ui-patterns.md +477 -0
- package/content/knowledge/game/game-vr-ar-design.md +313 -0
- package/content/knowledge/review/review-art-bible.md +305 -0
- package/content/knowledge/review/review-game-design.md +303 -0
- package/content/knowledge/review/review-game-economy.md +272 -0
- package/content/knowledge/review/review-game-ui.md +293 -0
- package/content/knowledge/review/review-netcode.md +280 -0
- package/content/knowledge/review/review-platform-cert.md +341 -0
- package/content/methodology/custom-defaults.yml +25 -0
- package/content/methodology/deep.yml +25 -0
- package/content/methodology/game-overlay.yml +145 -0
- package/content/methodology/mvp.yml +25 -0
- package/content/pipeline/architecture/ai-behavior-design.md +87 -0
- package/content/pipeline/architecture/netcode-spec.md +86 -0
- package/content/pipeline/architecture/review-netcode.md +78 -0
- package/content/pipeline/foundation/performance-budgets.md +91 -0
- package/content/pipeline/modeling/narrative-bible.md +84 -0
- package/content/pipeline/pre/game-design-document.md +90 -0
- package/content/pipeline/pre/review-gdd.md +74 -0
- package/content/pipeline/quality/analytics-telemetry.md +98 -0
- package/content/pipeline/quality/live-ops-plan.md +99 -0
- package/content/pipeline/quality/platform-cert-prep.md +129 -0
- package/content/pipeline/quality/playtest-plan.md +84 -0
- package/content/pipeline/specification/art-bible.md +87 -0
- package/content/pipeline/specification/audio-design.md +97 -0
- package/content/pipeline/specification/content-structure-design.md +142 -0
- package/content/pipeline/specification/economy-design.md +105 -0
- package/content/pipeline/specification/game-accessibility.md +82 -0
- package/content/pipeline/specification/game-ui-spec.md +97 -0
- package/content/pipeline/specification/input-controls-spec.md +81 -0
- package/content/pipeline/specification/localization-plan.md +113 -0
- package/content/pipeline/specification/modding-ugc-spec.md +116 -0
- package/content/pipeline/specification/online-services-spec.md +104 -0
- package/content/pipeline/specification/review-economy.md +87 -0
- package/content/pipeline/specification/review-game-ui.md +73 -0
- package/content/pipeline/specification/save-system-spec.md +116 -0
- package/dist/cli/commands/adopt.d.ts.map +1 -1
- package/dist/cli/commands/adopt.js +25 -0
- package/dist/cli/commands/adopt.js.map +1 -1
- package/dist/cli/commands/adopt.test.js +28 -1
- package/dist/cli/commands/adopt.test.js.map +1 -1
- package/dist/cli/commands/build.test.js +3 -0
- package/dist/cli/commands/build.test.js.map +1 -1
- package/dist/cli/commands/init.d.ts +1 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +6 -0
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/init.test.js +12 -1
- package/dist/cli/commands/init.test.js.map +1 -1
- package/dist/cli/commands/knowledge.test.js +8 -0
- package/dist/cli/commands/knowledge.test.js.map +1 -1
- package/dist/cli/commands/next.d.ts.map +1 -1
- package/dist/cli/commands/next.js +19 -5
- package/dist/cli/commands/next.js.map +1 -1
- package/dist/cli/commands/next.test.js +56 -0
- package/dist/cli/commands/next.test.js.map +1 -1
- package/dist/cli/commands/rework.d.ts.map +1 -1
- package/dist/cli/commands/rework.js +11 -2
- package/dist/cli/commands/rework.js.map +1 -1
- package/dist/cli/commands/rework.test.js +5 -0
- package/dist/cli/commands/rework.test.js.map +1 -1
- package/dist/cli/commands/run.d.ts.map +1 -1
- package/dist/cli/commands/run.js +54 -4
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/run.test.js +384 -0
- package/dist/cli/commands/run.test.js.map +1 -1
- package/dist/cli/commands/skip.test.js +3 -0
- package/dist/cli/commands/skip.test.js.map +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +16 -3
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/status.test.js +55 -0
- package/dist/cli/commands/status.test.js.map +1 -1
- package/dist/cli/output/auto.d.ts +3 -0
- package/dist/cli/output/auto.d.ts.map +1 -1
- package/dist/cli/output/auto.js +9 -0
- package/dist/cli/output/auto.js.map +1 -1
- package/dist/cli/output/context.d.ts +6 -0
- package/dist/cli/output/context.d.ts.map +1 -1
- package/dist/cli/output/context.js.map +1 -1
- package/dist/cli/output/context.test.js +87 -0
- package/dist/cli/output/context.test.js.map +1 -1
- package/dist/cli/output/error-display.test.js +3 -0
- package/dist/cli/output/error-display.test.js.map +1 -1
- package/dist/cli/output/interactive.d.ts +3 -0
- package/dist/cli/output/interactive.d.ts.map +1 -1
- package/dist/cli/output/interactive.js +76 -0
- package/dist/cli/output/interactive.js.map +1 -1
- package/dist/cli/output/json.d.ts +3 -0
- package/dist/cli/output/json.d.ts.map +1 -1
- package/dist/cli/output/json.js +9 -0
- package/dist/cli/output/json.js.map +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +3 -2
- package/dist/config/loader.js.map +1 -1
- package/dist/config/schema.d.ts +641 -15
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +26 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/config/schema.test.js +192 -1
- package/dist/config/schema.test.js.map +1 -1
- package/dist/core/assembly/overlay-loader.d.ts +24 -0
- package/dist/core/assembly/overlay-loader.d.ts.map +1 -0
- package/dist/core/assembly/overlay-loader.js +190 -0
- package/dist/core/assembly/overlay-loader.js.map +1 -0
- package/dist/core/assembly/overlay-loader.test.d.ts +2 -0
- package/dist/core/assembly/overlay-loader.test.d.ts.map +1 -0
- package/dist/core/assembly/overlay-loader.test.js +106 -0
- package/dist/core/assembly/overlay-loader.test.js.map +1 -0
- package/dist/core/assembly/overlay-resolver.d.ts +15 -0
- package/dist/core/assembly/overlay-resolver.d.ts.map +1 -0
- package/dist/core/assembly/overlay-resolver.js +58 -0
- package/dist/core/assembly/overlay-resolver.js.map +1 -0
- package/dist/core/assembly/overlay-resolver.test.d.ts +2 -0
- package/dist/core/assembly/overlay-resolver.test.d.ts.map +1 -0
- package/dist/core/assembly/overlay-resolver.test.js +246 -0
- package/dist/core/assembly/overlay-resolver.test.js.map +1 -0
- package/dist/core/assembly/overlay-state-resolver.d.ts +26 -0
- package/dist/core/assembly/overlay-state-resolver.d.ts.map +1 -0
- package/dist/core/assembly/overlay-state-resolver.js +63 -0
- package/dist/core/assembly/overlay-state-resolver.js.map +1 -0
- package/dist/core/assembly/overlay-state-resolver.test.d.ts +2 -0
- package/dist/core/assembly/overlay-state-resolver.test.d.ts.map +1 -0
- package/dist/core/assembly/overlay-state-resolver.test.js +256 -0
- package/dist/core/assembly/overlay-state-resolver.test.js.map +1 -0
- package/dist/core/assembly/preset-loader.d.ts +1 -0
- package/dist/core/assembly/preset-loader.d.ts.map +1 -1
- package/dist/core/assembly/preset-loader.js +2 -0
- package/dist/core/assembly/preset-loader.js.map +1 -1
- package/dist/core/dependency/eligibility.test.js +3 -0
- package/dist/core/dependency/eligibility.test.js.map +1 -1
- package/dist/e2e/game-pipeline.test.d.ts +10 -0
- package/dist/e2e/game-pipeline.test.d.ts.map +1 -0
- package/dist/e2e/game-pipeline.test.js +298 -0
- package/dist/e2e/game-pipeline.test.js.map +1 -0
- package/dist/e2e/init.test.js +3 -0
- package/dist/e2e/init.test.js.map +1 -1
- package/dist/project/adopt.d.ts +3 -1
- package/dist/project/adopt.d.ts.map +1 -1
- package/dist/project/adopt.js +29 -1
- package/dist/project/adopt.js.map +1 -1
- package/dist/project/adopt.test.js +51 -1
- package/dist/project/adopt.test.js.map +1 -1
- package/dist/types/config.d.ts +50 -4
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.test.d.ts +2 -0
- package/dist/types/config.test.d.ts.map +1 -0
- package/dist/types/config.test.js +97 -0
- package/dist/types/config.test.js.map +1 -0
- package/dist/utils/eligible.d.ts +3 -2
- package/dist/utils/eligible.d.ts.map +1 -1
- package/dist/utils/eligible.js +18 -4
- package/dist/utils/eligible.js.map +1 -1
- package/dist/utils/errors.d.ts +4 -0
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +31 -0
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/errors.test.js +4 -1
- package/dist/utils/errors.test.js.map +1 -1
- package/dist/wizard/questions.d.ts +4 -0
- package/dist/wizard/questions.d.ts.map +1 -1
- package/dist/wizard/questions.js +59 -1
- package/dist/wizard/questions.js.map +1 -1
- package/dist/wizard/questions.test.js +178 -4
- package/dist/wizard/questions.test.js.map +1 -1
- package/dist/wizard/wizard.d.ts +1 -0
- package/dist/wizard/wizard.d.ts.map +1 -1
- package/dist/wizard/wizard.js +4 -1
- package/dist/wizard/wizard.js.map +1 -1
- package/dist/wizard/wizard.test.js +102 -4
- package/dist/wizard/wizard.test.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: game-level-content-design
|
|
3
|
+
description: Level metrics, greyboxing standards, flow and pacing, streaming strategies, encounter design, procedural generation, and difficulty curves
|
|
4
|
+
topics: [game-dev, level-design, world-design, procedural, streaming]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Level design is the discipline of building the spaces that players inhabit and the experiences they have within those spaces. It bridges game design, environment art, and engineering — a level designer must understand player movement metrics (how high can they jump, how fast do they run, how wide is their collision capsule), pacing principles (tension-release cycles, difficulty ramps), and technical constraints (streaming budgets, draw call limits, memory footprints). Good level design is invisible: the player feels guided without feeling railroaded, challenged without feeling frustrated, and rewarded without feeling manipulated.
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
### Player Movement Metrics
|
|
12
|
+
|
|
13
|
+
Every level must be designed around precise, measured player capabilities. These metrics are established during prototyping and become the dimensional constants of level construction.
|
|
14
|
+
|
|
15
|
+
**Core metrics to define and document:**
|
|
16
|
+
- **Player capsule dimensions**: Width (diameter) and height — determines minimum corridor width and door height
|
|
17
|
+
- **Walk speed**: Meters per second at normal movement
|
|
18
|
+
- **Run/sprint speed**: Meters per second at maximum movement
|
|
19
|
+
- **Jump height**: Maximum vertical reach from standing (and running if different)
|
|
20
|
+
- **Jump distance**: Maximum horizontal gap clearable at sprint speed
|
|
21
|
+
- **Mantle height**: Maximum ledge height the player can grab and climb over
|
|
22
|
+
- **Crouch height**: Reduced capsule height for crawl spaces
|
|
23
|
+
- **Camera height**: Eye level relative to ground — affects sightlines and cover design
|
|
24
|
+
|
|
25
|
+
**Typical metric ranges (scale in meters, 1 unit = 1 meter):**
|
|
26
|
+
- Corridor width: player width x 2.5 minimum (to prevent claustrophobic feel), 3–4m for comfortable traversal
|
|
27
|
+
- Door width: player width x 2 minimum, 1.5–2m typical
|
|
28
|
+
- Door height: player height x 1.3 minimum, 2.4–3m typical
|
|
29
|
+
- Stair step height: 0.15–0.25m (match player step-up threshold)
|
|
30
|
+
- Railing/cover height: 0.8–1.2m (must block standing camera but allow aim-over)
|
|
31
|
+
- Jump gap: max jump distance minus 20% safety margin
|
|
32
|
+
- Ceiling height: 3–4m for interiors, creates comfortable proportion
|
|
33
|
+
|
|
34
|
+
### Greyboxing Standards
|
|
35
|
+
|
|
36
|
+
Greyboxing (also called blockout or whiteboxing) is the practice of building levels with simple geometric shapes to validate layout, flow, and gameplay before investing in environment art.
|
|
37
|
+
|
|
38
|
+
**Greybox rules:**
|
|
39
|
+
- Use untextured or single-color primitives (boxes, cylinders, planes)
|
|
40
|
+
- Build to exact player metrics — every jump, every door, every cover piece must be precisely measured
|
|
41
|
+
- Include gameplay-critical elements: spawn points, objective locations, AI patrol paths, cover positions, item pickups
|
|
42
|
+
- Playtest the greybox before any art pass — fix layout problems when the cost of change is zero
|
|
43
|
+
- Color-code greybox elements by function: grey for static geometry, blue for interactable, red for hazards, green for objectives, yellow for spawns
|
|
44
|
+
- The greybox IS the level — art replaces the geometry but does not change the layout
|
|
45
|
+
|
|
46
|
+
### Flow and Pacing Principles
|
|
47
|
+
|
|
48
|
+
Level flow describes the intended path and experience arc of the player through a space. Pacing is the rhythm of intensity — alternating between high-action moments and rest/exploration moments.
|
|
49
|
+
|
|
50
|
+
**Flow patterns:**
|
|
51
|
+
- **Linear corridor**: Player moves from A to B through a series of connected spaces. Easiest to pace, lowest replayability. Used in narrative-driven games.
|
|
52
|
+
- **Hub and spoke**: Central area with branches leading to objectives. Player chooses order. Provides agency while maintaining structure.
|
|
53
|
+
- **Open arena**: Large space with multiple traversal options and objective points. Used for combat encounters, boss fights, multiplayer maps.
|
|
54
|
+
- **Metroidvania loop**: Interconnected rooms that loop back on themselves, gated by ability acquisition. High exploration satisfaction, complex to design.
|
|
55
|
+
- **Open world**: Player-driven exploration with points of interest distributed across a large map. Requires streaming, landmark navigation, and density management.
|
|
56
|
+
|
|
57
|
+
**Pacing rhythm (tension curve):**
|
|
58
|
+
1. Introduction — safe space, establish new mechanic or environment
|
|
59
|
+
2. Rising tension — encounters escalate in difficulty or complexity
|
|
60
|
+
3. Climax — peak challenge (mini-boss, puzzle climax, setpiece)
|
|
61
|
+
4. Release — reward, safe space, narrative payoff
|
|
62
|
+
5. Repeat with escalation
|
|
63
|
+
|
|
64
|
+
### Streaming Strategies
|
|
65
|
+
|
|
66
|
+
Large levels and open worlds exceed memory budgets. Streaming loads and unloads content as the player moves through the world.
|
|
67
|
+
|
|
68
|
+
**Approaches:**
|
|
69
|
+
- **Unreal World Partition**: Automatic grid-based streaming. World divided into cells loaded/unloaded based on player distance. Recommended for Unreal open worlds.
|
|
70
|
+
- **Unity Addressable Scenes**: Additive scene loading with reference-counted asset bundles. Manual streaming control via scene triggers or distance checks.
|
|
71
|
+
- **Godot scene loading**: `ResourceLoader.load_threaded_request()` for async loading, manual additive scene management.
|
|
72
|
+
- **Chunk-based**: World divided into fixed-size chunks (like Minecraft). Simple to implement, predictable memory budget per chunk, natural for voxel or grid-based games.
|
|
73
|
+
|
|
74
|
+
## Deep Guidance
|
|
75
|
+
|
|
76
|
+
### Level Metrics Reference Sheet
|
|
77
|
+
|
|
78
|
+
Every project should maintain a metrics reference sheet that all level designers use. This ensures consistency across levels built by different designers.
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
# level_metrics.yaml — Player capability reference for level construction
|
|
82
|
+
# All values in meters unless otherwise noted
|
|
83
|
+
# Update these values when player controller tuning changes
|
|
84
|
+
|
|
85
|
+
player:
|
|
86
|
+
capsule:
|
|
87
|
+
radius: 0.35
|
|
88
|
+
standing_height: 1.8
|
|
89
|
+
crouch_height: 1.0
|
|
90
|
+
prone_height: 0.5 # if applicable
|
|
91
|
+
|
|
92
|
+
movement:
|
|
93
|
+
walk_speed: 3.5 # m/s
|
|
94
|
+
run_speed: 6.0 # m/s
|
|
95
|
+
sprint_speed: 8.5 # m/s (if sprint is separate from run)
|
|
96
|
+
crouch_speed: 1.5 # m/s
|
|
97
|
+
swim_speed: 3.0 # m/s
|
|
98
|
+
|
|
99
|
+
jump:
|
|
100
|
+
standing_height: 1.2 # m — max vertical from standing
|
|
101
|
+
running_height: 1.4 # m — slightly higher with momentum
|
|
102
|
+
standing_distance: 2.5 # m — horizontal from standing
|
|
103
|
+
running_distance: 5.0 # m — horizontal at sprint speed
|
|
104
|
+
double_jump_height: 2.5 # m — if applicable
|
|
105
|
+
wall_jump_height: 2.0 # m — if applicable
|
|
106
|
+
|
|
107
|
+
traversal:
|
|
108
|
+
mantle_height: 2.0 # m — max ledge grab height
|
|
109
|
+
step_up_height: 0.35 # m — auto-step over small obstacles
|
|
110
|
+
slide_under_height: 1.0 # m — gap that slide can pass through
|
|
111
|
+
ladder_speed: 2.0 # m/s — vertical climbing speed
|
|
112
|
+
zipline_speed: 10.0 # m/s — if applicable
|
|
113
|
+
|
|
114
|
+
camera:
|
|
115
|
+
eye_height_standing: 1.65 # m from ground
|
|
116
|
+
eye_height_crouching: 0.85
|
|
117
|
+
fov_horizontal: 90 # degrees (adjustable in settings)
|
|
118
|
+
|
|
119
|
+
# Level construction guides derived from player metrics
|
|
120
|
+
construction:
|
|
121
|
+
doors:
|
|
122
|
+
min_width: 1.0 # player diameter * 1.4 rounded up
|
|
123
|
+
standard_width: 1.5
|
|
124
|
+
double_door_width: 2.5
|
|
125
|
+
min_height: 2.2 # player height * 1.2
|
|
126
|
+
standard_height: 2.5
|
|
127
|
+
|
|
128
|
+
corridors:
|
|
129
|
+
min_width: 1.5 # feels claustrophobic — use intentionally
|
|
130
|
+
standard_width: 3.0 # comfortable single-lane movement
|
|
131
|
+
wide_width: 5.0 # allows two players side-by-side
|
|
132
|
+
min_height: 2.5
|
|
133
|
+
standard_height: 3.5
|
|
134
|
+
|
|
135
|
+
stairs:
|
|
136
|
+
step_height: 0.2 # within step_up threshold
|
|
137
|
+
step_depth: 0.3 # comfortable foot placement
|
|
138
|
+
width: 1.5 # standard corridor width
|
|
139
|
+
landing_depth: 2.0 # turning landing for U-stairs
|
|
140
|
+
|
|
141
|
+
cover:
|
|
142
|
+
low_cover_height: 1.0 # player can aim over when standing
|
|
143
|
+
high_cover_height: 1.8 # player cannot aim over, must peek around
|
|
144
|
+
cover_width: 1.5 # min width to fully hide player capsule
|
|
145
|
+
peek_gap: 0.3 # space between cover pieces for peeking
|
|
146
|
+
|
|
147
|
+
jumps:
|
|
148
|
+
safe_gap: 3.5 # running jump distance * 0.7 safety margin
|
|
149
|
+
max_gap: 4.5 # running jump distance * 0.9 — expert-only
|
|
150
|
+
safe_height: 1.0 # standing jump height * 0.83
|
|
151
|
+
max_height: 1.2 # standing jump height — requires precision
|
|
152
|
+
|
|
153
|
+
sightlines:
|
|
154
|
+
engagement_close: 10 # m — shotgun/melee range
|
|
155
|
+
engagement_mid: 30 # m — assault rifle optimal
|
|
156
|
+
engagement_long: 80 # m — sniper/marksman range
|
|
157
|
+
max_render: 500 # m — LOD and fog limit visibility beyond this
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Greyboxing Workflow
|
|
161
|
+
|
|
162
|
+
A disciplined greyboxing workflow ensures levels are gameplay-validated before art investment.
|
|
163
|
+
|
|
164
|
+
**Phase 1: Paper design (1–2 days)**
|
|
165
|
+
- Top-down sketch of the level layout on paper or whiteboard
|
|
166
|
+
- Mark critical path, secondary paths, secret areas
|
|
167
|
+
- Note encounter locations with enemy counts and types
|
|
168
|
+
- Note pickup locations with item types
|
|
169
|
+
- Review with design lead before building
|
|
170
|
+
|
|
171
|
+
**Phase 2: Blockout in engine (2–5 days)**
|
|
172
|
+
- Build the level using primitive shapes at exact player metrics
|
|
173
|
+
- Place player start, objectives, enemy spawners, item pickups
|
|
174
|
+
- Implement basic AI navigation (navmesh bake) and pathfinding
|
|
175
|
+
- Add temporary lighting (bright, even, functional — not artistic)
|
|
176
|
+
- No art assets, no textures, no decorative geometry
|
|
177
|
+
|
|
178
|
+
**Phase 3: Internal playtest (1 day)**
|
|
179
|
+
- All level designers play each other's greyboxes
|
|
180
|
+
- Evaluate: flow (does the path feel natural?), pacing (are encounters spaced well?), readability (does the player know where to go?), fun (is it enjoyable?)
|
|
181
|
+
- Document issues by location and priority
|
|
182
|
+
- Iterate on blockout based on feedback
|
|
183
|
+
|
|
184
|
+
**Phase 4: Art pass begins only after greybox approval**
|
|
185
|
+
- Environment artists replace primitives with final geometry
|
|
186
|
+
- Keep collision volumes from the greybox — do not let art meshes change collision
|
|
187
|
+
- If art requires layout changes, re-validate with a greybox-only playtest
|
|
188
|
+
|
|
189
|
+
### Encounter Design
|
|
190
|
+
|
|
191
|
+
Encounters are designed gameplay moments — typically combat, but also puzzles, traversal challenges, or narrative beats. Each encounter exists within a defined space and has a designed experience arc.
|
|
192
|
+
|
|
193
|
+
**Combat encounter anatomy:**
|
|
194
|
+
|
|
195
|
+
1. **Approach** — Player sees or anticipates the encounter space before engaging. The space telegraphs what is coming: cover placement suggests a firefight, elevation suggests a sniper, tight corridors suggest close-quarters.
|
|
196
|
+
|
|
197
|
+
2. **Engagement** — Combat begins. The encounter should have a designed "shape":
|
|
198
|
+
- **Wave encounters**: Enemies arrive in groups with brief pauses between waves. Each wave escalates (more enemies, tougher types, flanking positions).
|
|
199
|
+
- **Arena encounters**: Fixed set of enemies in an open space. Player chooses engagement order and positioning. Boss fights are a specialized arena encounter.
|
|
200
|
+
- **Gauntlet encounters**: Player moves through a space while enemies attack continuously. Tests movement and prioritization.
|
|
201
|
+
|
|
202
|
+
3. **Resolution** — Combat ends with a clear signal (music change, door opens, loot drops). Give the player a moment to breathe before the next encounter.
|
|
203
|
+
|
|
204
|
+
**Encounter spacing:**
|
|
205
|
+
- Minimum 30 seconds of non-combat traversal between combat encounters (prevents fatigue)
|
|
206
|
+
- Major encounters (mini-boss, setpiece) should be preceded by 1–2 minutes of low-intensity gameplay
|
|
207
|
+
- After a climactic encounter, provide a rest area with ambient storytelling, loot, or narrative content
|
|
208
|
+
- The ratio of combat time to non-combat time depends on genre: action games ~60/40, narrative games ~20/80, survival horror ~30/70
|
|
209
|
+
|
|
210
|
+
### Procedural Generation Rulesets
|
|
211
|
+
|
|
212
|
+
Procedural generation creates level content algorithmically rather than by hand. It requires explicit rulesets to produce coherent, playable results.
|
|
213
|
+
|
|
214
|
+
**Room-based dungeon generation (roguelike pattern):**
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
# dungeon_generator.py — Rule-based procedural dungeon layout
|
|
218
|
+
# Generates a graph of rooms connected by corridors
|
|
219
|
+
|
|
220
|
+
import random
|
|
221
|
+
from dataclasses import dataclass, field
|
|
222
|
+
from enum import Enum, auto
|
|
223
|
+
|
|
224
|
+
class RoomType(Enum):
|
|
225
|
+
SPAWN = auto()
|
|
226
|
+
COMBAT = auto()
|
|
227
|
+
TREASURE = auto()
|
|
228
|
+
SHOP = auto()
|
|
229
|
+
BOSS = auto()
|
|
230
|
+
SECRET = auto()
|
|
231
|
+
REST = auto() # Safe room with no enemies
|
|
232
|
+
|
|
233
|
+
@dataclass
|
|
234
|
+
class Room:
|
|
235
|
+
id: int
|
|
236
|
+
room_type: RoomType
|
|
237
|
+
width: float # meters
|
|
238
|
+
height: float # meters
|
|
239
|
+
connections: list[int] = field(default_factory=list)
|
|
240
|
+
|
|
241
|
+
@dataclass
|
|
242
|
+
class DungeonConfig:
|
|
243
|
+
"""Configuration for dungeon generation rules."""
|
|
244
|
+
total_rooms: int = 15
|
|
245
|
+
min_room_size: float = 8.0 # meters
|
|
246
|
+
max_room_size: float = 25.0 # meters
|
|
247
|
+
boss_room_size: float = 30.0 # boss room is always large
|
|
248
|
+
min_combat_rooms: int = 5
|
|
249
|
+
max_combat_rooms: int = 8
|
|
250
|
+
treasure_rooms: int = 2
|
|
251
|
+
shop_rooms: int = 1
|
|
252
|
+
rest_rooms: int = 2
|
|
253
|
+
secret_room_chance: float = 0.3 # 30% chance to add a secret room
|
|
254
|
+
min_rooms_before_boss: int = 8 # minimum path length to boss
|
|
255
|
+
max_connections_per_room: int = 4
|
|
256
|
+
|
|
257
|
+
class DungeonGenerator:
|
|
258
|
+
def __init__(self, config: DungeonConfig, seed: int | None = None):
|
|
259
|
+
self.config = config
|
|
260
|
+
self.rng = random.Random(seed)
|
|
261
|
+
self.rooms: list[Room] = []
|
|
262
|
+
|
|
263
|
+
def generate(self) -> list[Room]:
|
|
264
|
+
self._place_rooms()
|
|
265
|
+
self._connect_rooms()
|
|
266
|
+
self._assign_types()
|
|
267
|
+
self._validate()
|
|
268
|
+
return self.rooms
|
|
269
|
+
|
|
270
|
+
def _place_rooms(self):
|
|
271
|
+
"""Create rooms with random sizes within budget."""
|
|
272
|
+
for i in range(self.config.total_rooms):
|
|
273
|
+
size = self.rng.uniform(
|
|
274
|
+
self.config.min_room_size,
|
|
275
|
+
self.config.max_room_size,
|
|
276
|
+
)
|
|
277
|
+
self.rooms.append(Room(
|
|
278
|
+
id=i,
|
|
279
|
+
room_type=RoomType.COMBAT, # placeholder, assigned later
|
|
280
|
+
width=size,
|
|
281
|
+
height=size * self.rng.uniform(0.7, 1.3),
|
|
282
|
+
))
|
|
283
|
+
|
|
284
|
+
def _connect_rooms(self):
|
|
285
|
+
"""Build a spanning tree, then add extra connections for loops."""
|
|
286
|
+
# Spanning tree ensures all rooms are reachable
|
|
287
|
+
unconnected = list(range(1, len(self.rooms)))
|
|
288
|
+
connected = [0]
|
|
289
|
+
while unconnected:
|
|
290
|
+
from_room = self.rng.choice(connected)
|
|
291
|
+
to_room = self.rng.choice(unconnected)
|
|
292
|
+
self.rooms[from_room].connections.append(to_room)
|
|
293
|
+
self.rooms[to_room].connections.append(from_room)
|
|
294
|
+
connected.append(to_room)
|
|
295
|
+
unconnected.remove(to_room)
|
|
296
|
+
|
|
297
|
+
# Add extra connections for loops (optional paths)
|
|
298
|
+
extra = self.rng.randint(2, 5)
|
|
299
|
+
for _ in range(extra):
|
|
300
|
+
a = self.rng.randint(0, len(self.rooms) - 1)
|
|
301
|
+
b = self.rng.randint(0, len(self.rooms) - 1)
|
|
302
|
+
if (a != b
|
|
303
|
+
and b not in self.rooms[a].connections
|
|
304
|
+
and len(self.rooms[a].connections) < self.config.max_connections_per_room):
|
|
305
|
+
self.rooms[a].connections.append(b)
|
|
306
|
+
self.rooms[b].connections.append(a)
|
|
307
|
+
|
|
308
|
+
def _assign_types(self):
|
|
309
|
+
"""Assign room types following generation rules."""
|
|
310
|
+
# Room 0 is always spawn
|
|
311
|
+
self.rooms[0].room_type = RoomType.SPAWN
|
|
312
|
+
self.rooms[0].width = self.config.min_room_size
|
|
313
|
+
self.rooms[0].height = self.config.min_room_size
|
|
314
|
+
|
|
315
|
+
# Last room is always boss
|
|
316
|
+
boss_idx = len(self.rooms) - 1
|
|
317
|
+
self.rooms[boss_idx].room_type = RoomType.BOSS
|
|
318
|
+
self.rooms[boss_idx].width = self.config.boss_room_size
|
|
319
|
+
self.rooms[boss_idx].height = self.config.boss_room_size
|
|
320
|
+
|
|
321
|
+
# Room before boss is always rest (save point)
|
|
322
|
+
if boss_idx > 1:
|
|
323
|
+
pre_boss = self.rooms[boss_idx].connections[0]
|
|
324
|
+
self.rooms[pre_boss].room_type = RoomType.REST
|
|
325
|
+
|
|
326
|
+
# Distribute remaining types
|
|
327
|
+
available = [i for i in range(1, boss_idx)
|
|
328
|
+
if self.rooms[i].room_type == RoomType.COMBAT]
|
|
329
|
+
self.rng.shuffle(available)
|
|
330
|
+
|
|
331
|
+
idx = 0
|
|
332
|
+
for _ in range(self.config.treasure_rooms):
|
|
333
|
+
if idx < len(available):
|
|
334
|
+
self.rooms[available[idx]].room_type = RoomType.TREASURE
|
|
335
|
+
idx += 1
|
|
336
|
+
for _ in range(self.config.shop_rooms):
|
|
337
|
+
if idx < len(available):
|
|
338
|
+
self.rooms[available[idx]].room_type = RoomType.SHOP
|
|
339
|
+
idx += 1
|
|
340
|
+
for _ in range(self.config.rest_rooms - 1): # -1 for pre-boss rest
|
|
341
|
+
if idx < len(available):
|
|
342
|
+
self.rooms[available[idx]].room_type = RoomType.REST
|
|
343
|
+
idx += 1
|
|
344
|
+
# Remaining rooms stay as COMBAT
|
|
345
|
+
|
|
346
|
+
# Secret room (chance-based, added as branch off existing room)
|
|
347
|
+
if self.rng.random() < self.config.secret_room_chance:
|
|
348
|
+
secret = Room(
|
|
349
|
+
id=len(self.rooms),
|
|
350
|
+
room_type=RoomType.SECRET,
|
|
351
|
+
width=self.config.min_room_size,
|
|
352
|
+
height=self.config.min_room_size,
|
|
353
|
+
)
|
|
354
|
+
attach_to = self.rng.choice(
|
|
355
|
+
[r for r in self.rooms if r.room_type == RoomType.COMBAT]
|
|
356
|
+
)
|
|
357
|
+
secret.connections.append(attach_to.id)
|
|
358
|
+
attach_to.connections.append(secret.id)
|
|
359
|
+
self.rooms.append(secret)
|
|
360
|
+
|
|
361
|
+
def _validate(self):
|
|
362
|
+
"""Verify generation rules are satisfied."""
|
|
363
|
+
combat_count = sum(
|
|
364
|
+
1 for r in self.rooms if r.room_type == RoomType.COMBAT
|
|
365
|
+
)
|
|
366
|
+
assert combat_count >= self.config.min_combat_rooms, (
|
|
367
|
+
f"Too few combat rooms: {combat_count}"
|
|
368
|
+
)
|
|
369
|
+
# Verify connectivity (BFS from spawn)
|
|
370
|
+
visited = set()
|
|
371
|
+
queue = [0]
|
|
372
|
+
while queue:
|
|
373
|
+
current = queue.pop(0)
|
|
374
|
+
if current in visited:
|
|
375
|
+
continue
|
|
376
|
+
visited.add(current)
|
|
377
|
+
queue.extend(self.rooms[current].connections)
|
|
378
|
+
assert len(visited) == len(self.rooms), "Not all rooms are reachable"
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Open-World POI Distribution
|
|
382
|
+
|
|
383
|
+
Points of Interest (POIs) in open worlds must be distributed to maintain a consistent density of discoverable content without feeling repetitive or overwhelming.
|
|
384
|
+
|
|
385
|
+
**Distribution rules:**
|
|
386
|
+
- **Minimum spacing**: No two POIs of the same type within 200m of each other (prevents clustering)
|
|
387
|
+
- **Maximum spacing**: No point in the traversable world should be more than 60 seconds of travel from the nearest POI (prevents empty stretches)
|
|
388
|
+
- **Density gradient**: Higher POI density near hubs and main paths, lower density in wilderness areas. The player should always see at least one undiscovered POI from any high vantage point.
|
|
389
|
+
- **Type variety**: Within any 500m radius, the player should encounter at least 3 different POI types (camp, landmark, puzzle, combat challenge, resource node, etc.)
|
|
390
|
+
- **Landmark visibility**: Major POIs should be visible from 300m+ to serve as navigation landmarks. Use vertical elements (towers, distinctive trees, rock formations) that stand above the terrain horizon.
|
|
391
|
+
|
|
392
|
+
**POI placement workflow:**
|
|
393
|
+
1. Place major landmarks first (towns, dungeons, geographic features) — these anchor the world
|
|
394
|
+
2. Place main quest locations relative to landmarks — ensure the critical path visits diverse biomes
|
|
395
|
+
3. Fill with secondary POIs using a Poisson disk sampling algorithm (guarantees minimum spacing while feeling natural)
|
|
396
|
+
4. Playtest by traversing every major route — if any 2-minute stretch feels empty, add content
|
|
397
|
+
|
|
398
|
+
### Difficulty Curves Within Levels
|
|
399
|
+
|
|
400
|
+
Difficulty within a single level should follow a shaped curve, not a flat line or monotonic increase.
|
|
401
|
+
|
|
402
|
+
**Intra-level difficulty patterns:**
|
|
403
|
+
|
|
404
|
+
- **Ramp**: Starts easy, steadily increases. Good for tutorial levels and the early game. Risk: becomes predictable.
|
|
405
|
+
- **Sawtooth**: Alternating peaks and valleys. Each peak is slightly higher than the last. The valleys provide recovery and reward. Most common and effective pattern for sustained engagement.
|
|
406
|
+
- **Plateau**: Moderate difficulty with a sharp spike at the end (boss encounter). Effective for levels that build toward a climactic moment.
|
|
407
|
+
- **Inverted U**: Difficulty peaks in the middle of the level, then eases toward the end. Used when the level's narrative arc has a mid-point climax with a falling-action resolution.
|
|
408
|
+
|
|
409
|
+
**Difficulty levers available to level designers:**
|
|
410
|
+
- Enemy count and composition (more enemies, tougher enemy types)
|
|
411
|
+
- Arena size and cover density (less cover = harder)
|
|
412
|
+
- Resource availability (fewer health pickups = harder)
|
|
413
|
+
- Time pressure (timed sections, advancing hazards)
|
|
414
|
+
- Sightline length (longer sightlines favor ranged players, shorter favor melee)
|
|
415
|
+
- Verticality (enemies at different elevations are harder to deal with simultaneously)
|
|
416
|
+
- Environmental hazards (fire, pits, moving platforms)
|
|
417
|
+
- Checkpoint frequency (fewer checkpoints = higher stakes per encounter)
|
|
418
|
+
|
|
419
|
+
### World Streaming Implementation
|
|
420
|
+
|
|
421
|
+
Open worlds require streaming subsystems that load and unload content based on player position.
|
|
422
|
+
|
|
423
|
+
**Streaming budget management:**
|
|
424
|
+
- Define a "streaming radius" around the player — all content within this radius must be loaded
|
|
425
|
+
- The streaming radius must account for maximum player speed (if the player can travel 100m/s in a vehicle, the streaming radius must be at least 100m * load-time-seconds ahead)
|
|
426
|
+
- Memory budget: streaming pool size = total memory budget minus persistent content (UI, player, audio banks, core systems)
|
|
427
|
+
- Priority loading: always load terrain and collision first, then large landmarks, then detail objects (foliage, debris, decals)
|
|
428
|
+
|
|
429
|
+
**Level-of-detail streaming:**
|
|
430
|
+
- Distance band 0 (0–50m): Full detail geometry, full-resolution textures, all physics bodies active
|
|
431
|
+
- Distance band 1 (50–200m): LOD1 geometry, half-resolution textures, simplified physics (or no physics)
|
|
432
|
+
- Distance band 2 (200–500m): LOD2 geometry, quarter-resolution textures, no physics, impostor rendering for trees
|
|
433
|
+
- Distance band 3 (500m+): Billboard impostors, terrain-only, atmosphere/fog handles the rest
|
|
434
|
+
|
|
435
|
+
**Transition management:**
|
|
436
|
+
- Cross-fade between LOD levels over 0.5–1.0 seconds to hide pop-in
|
|
437
|
+
- Use noise-based dithering during LOD transitions (perceptually smoother than alpha fade)
|
|
438
|
+
- Stream loading should never cause a hitch — if a load is taking too long, the player should see simplified content rather than nothing (empty space or T-poses are certification failures)
|
|
439
|
+
- Budget loading I/O bandwidth: on HDD-based platforms, reserve 50% of disk bandwidth for audio streaming, 50% for asset streaming. On SSD, this constraint is relaxed.
|
|
440
|
+
|
|
441
|
+
### Level Design Documentation Template
|
|
442
|
+
|
|
443
|
+
Every level should have a design document before construction begins.
|
|
444
|
+
|
|
445
|
+
**Essential sections:**
|
|
446
|
+
- **Overview**: Level name, position in the game's progression, estimated play time, primary gameplay focus
|
|
447
|
+
- **Narrative context**: What story events occur here? What is the player's goal? What do they learn?
|
|
448
|
+
- **Map layout**: Top-down sketch with critical path marked, secondary paths, and secret areas
|
|
449
|
+
- **Encounter list**: Each encounter with enemy types, count, intended difficulty (1–10 scale), designed player strategy
|
|
450
|
+
- **Metrics compliance**: Confirmation that all jumps, doors, and corridors match the project metrics sheet
|
|
451
|
+
- **Streaming plan**: How the level is divided into streaming cells, estimated memory per cell, transition points
|
|
452
|
+
- **Art direction notes**: Mood, lighting intent, color palette, reference images
|
|
453
|
+
- **Audio direction notes**: Ambient soundscape, music transitions, key audio events
|
|
454
|
+
- **Unique mechanics**: Any level-specific mechanics (vehicles, zero-gravity, underwater) with design notes
|
|
455
|
+
- **Dependencies**: What player abilities are required? What story prerequisites must be met?
|
|
456
|
+
|
|
457
|
+
## 2D and Non-3D Level Design
|
|
458
|
+
|
|
459
|
+
### 2D Platformer Metrics
|
|
460
|
+
|
|
461
|
+
The design unit for 2D platformers is the screen or screen segment, not 3D meters:
|
|
462
|
+
|
|
463
|
+
- **Screen dimensions**: Design at target resolution (e.g., 1920x1080). One screen = one design unit.
|
|
464
|
+
- **Tile grid**: Standard tile size 16x16, 32x32, or 64x64 pixels. Character occupies 1-2 tiles wide, 2-3 tiles tall.
|
|
465
|
+
- **Jump arc**: Defined by initial velocity and gravity. Standard platformer: peak height = 3-5 tiles, horizontal distance = 4-8 tiles. Coyote time: 6-10 frames (100-167ms at 60fps).
|
|
466
|
+
- **Platform spacing**: Safe gap = 60-80% of max jump distance. Challenge gap = 85-95%. Death gap = 100%+.
|
|
467
|
+
- **Enemy placement**: One new mechanic or enemy per 3-5 screens. Tutorial screens introduce mechanics in isolation before combining.
|
|
468
|
+
|
|
469
|
+
### Tile-Based Level Design
|
|
470
|
+
|
|
471
|
+
For grid-based games (roguelikes, tactics, puzzle games):
|
|
472
|
+
|
|
473
|
+
- **Room generation**: Define room templates as rectangles with connection points. Minimum room size: 5x5 tiles. Maximum: 15x15 for tactical games. Connect rooms via corridors 1-3 tiles wide.
|
|
474
|
+
- **Difficulty distribution**: In procedural dungeon generation, difficulty increases with distance from start. Use weighted room selection: rooms with harder enemies have higher weight at greater distances.
|
|
475
|
+
- **Puzzle stage design**: Each stage introduces one new mechanic. Progression: tutorial (1 mechanic, 1 solution), practice (1 mechanic, multiple applications), combination (2+ mechanics together), mastery (all mechanics under constraint).
|
|
476
|
+
|
|
477
|
+
### Non-Spatial Content Design
|
|
478
|
+
|
|
479
|
+
For games without spatial levels (card games, visual novels, management sims):
|
|
480
|
+
|
|
481
|
+
- **Card game stage progression**: Introduce card types gradually. New card pool expansion every 3-5 encounters. Total card pool at launch: 100-300 for competitive, 50-100 for single-player.
|
|
482
|
+
- **Visual novel branching**: Key decision points every 5-10 minutes of reading. Total playthrough length: 2-4 hours per route. Route count: 3-5 for manageable scope. Flag variable tracking: use boolean flags + integer counters, avoid floating-point relationship values.
|
|
483
|
+
- **Management sim progression**: Unlock new building/unit types every 15-30 minutes of play. Each unlock should enable at least one new strategy the player couldn't execute before.
|