@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,242 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: game-engine-selection
|
|
3
|
+
description: Engine evaluation framework, Unity vs Unreal vs Godot comparison, middleware selection, and platform considerations
|
|
4
|
+
topics: [game-dev, engine, unity, unreal, godot, middleware]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Choosing a game engine is the single most consequential technical decision in game development. It determines your rendering capabilities, supported platforms, available middleware, hiring pool, and long-term maintenance cost. Unlike web framework selection where migration is painful but possible, switching game engines mid-project is effectively starting over. This decision must be made deliberately, with explicit tradeoff acknowledgment, and documented as an ADR.
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
### Engine Evaluation Framework
|
|
12
|
+
|
|
13
|
+
Evaluate engines across seven dimensions, weighted by project priorities:
|
|
14
|
+
|
|
15
|
+
1. **Target Platform Fit** — Does the engine support all required platforms natively? Mobile, console, PC, VR/AR, and web each have different engine strengths. Console SDK access requires platform holder approval and some engines handle this better than others.
|
|
16
|
+
|
|
17
|
+
2. **Rendering Capabilities** — Does the engine's rendering pipeline match the game's visual target? A stylized 2D game does not need ray tracing. A photorealistic open-world game needs advanced LOD, streaming, and global illumination.
|
|
18
|
+
|
|
19
|
+
3. **Team Expertise** — What does the team already know? A team of C# developers will be more productive in Unity or Godot (C#) than Unreal (C++). Ramp-up time is real cost.
|
|
20
|
+
|
|
21
|
+
4. **Ecosystem & Marketplace** — Asset stores, plugin ecosystems, and community tools dramatically accelerate development. Evaluate the quantity and quality of available assets, plugins, and community resources.
|
|
22
|
+
|
|
23
|
+
5. **Licensing & Revenue Model** — Upfront cost, royalty structures, runtime fees, and source code access all factor in. A free engine with a 5% royalty above $1M revenue is different economics than a subscription model.
|
|
24
|
+
|
|
25
|
+
6. **Tooling & Workflow** — Editor quality, iteration speed (compile times, hot reload), debugging tools, profiling tools, and version control integration affect daily productivity.
|
|
26
|
+
|
|
27
|
+
7. **Scalability & Performance** — Maximum scene complexity, entity counts, physics performance, networking architecture, and memory management capabilities set upper bounds on what the game can do.
|
|
28
|
+
|
|
29
|
+
### Key Decision Factors
|
|
30
|
+
|
|
31
|
+
The most common selection mistakes come from over-weighting one dimension:
|
|
32
|
+
|
|
33
|
+
- **Do not choose Unreal just for graphics** — if your game is a 2D puzzle game, Unreal's rendering capabilities are wasted and its complexity is a liability
|
|
34
|
+
- **Do not choose Unity just for market share** — if your game requires advanced rendering or large open worlds, Unity's default pipeline may require extensive custom work
|
|
35
|
+
- **Do not choose Godot just because it is free** — if your game requires console publishing, Godot's console support is limited and requires third-party porting
|
|
36
|
+
- **Do not choose a custom engine unless you have engine programmers** — building an engine is a multi-year investment that delays game development
|
|
37
|
+
|
|
38
|
+
### Middleware Categories
|
|
39
|
+
|
|
40
|
+
No engine does everything well. Middleware fills the gaps:
|
|
41
|
+
|
|
42
|
+
- **Physics**: Havok, PhysX (built into Unity/Unreal), Jolt, Box2D
|
|
43
|
+
- **Audio**: FMOD, Wwise, Criware ADX2
|
|
44
|
+
- **Networking**: Photon, Mirror (Unity), Steam Networking, EOS
|
|
45
|
+
- **UI**: Coherent Gameface, NoesisGUI, Dear ImGui (debug), engine-native UI
|
|
46
|
+
- **Animation**: Morpheme, engine-native state machines, procedural IK solutions
|
|
47
|
+
- **AI**: custom behavior trees and utility AI are more common than middleware in this space
|
|
48
|
+
|
|
49
|
+
## Deep Guidance
|
|
50
|
+
|
|
51
|
+
### Engine Comparison Matrix
|
|
52
|
+
|
|
53
|
+
```yaml
|
|
54
|
+
# Engine Decision Matrix — score each 1-5, multiply by weight
|
|
55
|
+
# Adjust weights to match your project priorities
|
|
56
|
+
|
|
57
|
+
criteria:
|
|
58
|
+
- name: "Target Platform Fit"
|
|
59
|
+
weight: 5
|
|
60
|
+
unity: 5 # Best cross-platform support, all major platforms
|
|
61
|
+
unreal: 4 # Excellent console/PC, weaker mobile/web
|
|
62
|
+
godot: 3 # Good PC/mobile, limited console, decent web
|
|
63
|
+
custom: 2 # Only what you build
|
|
64
|
+
|
|
65
|
+
- name: "2D Capability"
|
|
66
|
+
weight: 3 # Adjust: 5 for 2D games, 1 for 3D-only
|
|
67
|
+
unity: 4 # Solid 2D tools, Tilemap, SpriteRenderer
|
|
68
|
+
unreal: 2 # Paper2D exists but is underprioritized
|
|
69
|
+
godot: 5 # Best-in-class 2D with dedicated node types
|
|
70
|
+
custom: 3 # Depends on implementation
|
|
71
|
+
|
|
72
|
+
- name: "3D Rendering Quality"
|
|
73
|
+
weight: 4 # Adjust: 5 for AAA visual target
|
|
74
|
+
unity: 3 # URP adequate, HDRP approaching Unreal
|
|
75
|
+
unreal: 5 # Industry-leading Nanite, Lumen, MetaHumans
|
|
76
|
+
godot: 2 # Improving rapidly but behind on advanced features
|
|
77
|
+
custom: 1 # Years of investment required
|
|
78
|
+
|
|
79
|
+
- name: "Team Ramp-Up (C# team)"
|
|
80
|
+
weight: 4 # Adjust based on team language background
|
|
81
|
+
unity: 5 # Native C#
|
|
82
|
+
unreal: 1 # C++ with Blueprints; steep curve for C# devs
|
|
83
|
+
godot: 4 # GDScript is easy; C# support is stable
|
|
84
|
+
custom: 2 # Depends on language choice
|
|
85
|
+
|
|
86
|
+
- name: "Asset Marketplace"
|
|
87
|
+
weight: 3
|
|
88
|
+
unity: 5 # Largest asset store by volume
|
|
89
|
+
unreal: 4 # Smaller but high quality, many free monthly assets
|
|
90
|
+
godot: 2 # Growing but still limited
|
|
91
|
+
custom: 1 # No marketplace
|
|
92
|
+
|
|
93
|
+
- name: "Source Code Access"
|
|
94
|
+
weight: 2
|
|
95
|
+
unity: 2 # Reference source available, not modifiable freely
|
|
96
|
+
unreal: 4 # Full source on GitHub with license
|
|
97
|
+
godot: 5 # Fully open source (MIT)
|
|
98
|
+
custom: 5 # You own it all
|
|
99
|
+
|
|
100
|
+
- name: "Build & Iteration Speed"
|
|
101
|
+
weight: 4
|
|
102
|
+
unity: 4 # Hot reload improving, reasonable compile times
|
|
103
|
+
unreal: 2 # C++ compilation is slow; Blueprints iterate faster
|
|
104
|
+
godot: 5 # Fastest iteration cycle, near-instant scene reload
|
|
105
|
+
custom: 3 # Depends on build system
|
|
106
|
+
|
|
107
|
+
- name: "License Cost"
|
|
108
|
+
weight: 2
|
|
109
|
+
unity: 3 # Free tier (Unity Personal), then subscription (Plus/Pro); runtime fee rescinded September 2024, reverted to seat-based licensing
|
|
110
|
+
unreal: 4 # Free until $1M revenue, then 5% royalty
|
|
111
|
+
godot: 5 # MIT license, completely free forever
|
|
112
|
+
custom: 5 # No license cost (but massive dev cost)
|
|
113
|
+
|
|
114
|
+
# Scoring: Sum of (score * weight) per engine
|
|
115
|
+
# Do NOT let the matrix make the decision — it structures the conversation
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Unity In Depth
|
|
119
|
+
|
|
120
|
+
**Strengths:**
|
|
121
|
+
- Broadest platform support in the industry (mobile, console, PC, VR, AR, WebGL)
|
|
122
|
+
- Largest asset store — thousands of ready-made solutions for common problems
|
|
123
|
+
- C# is accessible to a wide talent pool; lower barrier to entry than C++
|
|
124
|
+
- Strong 2D tooling alongside capable 3D
|
|
125
|
+
- Extensive documentation and tutorial ecosystem
|
|
126
|
+
|
|
127
|
+
**Weaknesses:**
|
|
128
|
+
- Rendering quality historically trails Unreal for AAA-grade visuals (HDRP narrows this gap)
|
|
129
|
+
- Runtime fee was announced September 2023, rescinded September 2024 under new leadership with return to seat-based licensing — but the episode damaged developer trust and prompted some studios to migrate to other engines
|
|
130
|
+
- Legacy code and architectural debt in some subsystems (old Input System, UI systems, networking)
|
|
131
|
+
- DOTS/ECS is powerful but has had a long and unstable development path
|
|
132
|
+
|
|
133
|
+
**Best for:** Mobile games, indie/AA multi-platform titles, VR/AR applications, 2D games that need cross-platform, rapid prototyping.
|
|
134
|
+
|
|
135
|
+
**Avoid for:** Games that require Unreal-grade visual fidelity without extensive custom rendering work.
|
|
136
|
+
|
|
137
|
+
### Unreal Engine In Depth
|
|
138
|
+
|
|
139
|
+
**Strengths:**
|
|
140
|
+
- Industry-leading rendering: Nanite (virtualized geometry), Lumen (global illumination), MetaHumans
|
|
141
|
+
- Full C++ source code access enables deep engine customization
|
|
142
|
+
- Blueprint visual scripting allows designers and artists to prototype gameplay without code
|
|
143
|
+
- Mature networking and replication system for multiplayer
|
|
144
|
+
- Excellent large-world support (World Partition, level streaming)
|
|
145
|
+
|
|
146
|
+
**Weaknesses:**
|
|
147
|
+
- C++ compilation times are slow; iteration speed suffers for gameplay programming
|
|
148
|
+
- Steep learning curve — the engine is enormous and documentation can lag behind features
|
|
149
|
+
- Binary asset format makes version control painful (requires Perforce or Git LFS)
|
|
150
|
+
- Mobile support exists but is secondary to console/PC
|
|
151
|
+
- Royalty model takes 5% above $1M, which is significant for successful indie titles
|
|
152
|
+
|
|
153
|
+
**Best for:** AAA/AA 3D games, photorealistic visuals, large open worlds, multiplayer shooters, cinematic experiences.
|
|
154
|
+
|
|
155
|
+
**Avoid for:** Small 2D games, solo developers without C++ experience, projects where iteration speed is more important than visual fidelity.
|
|
156
|
+
|
|
157
|
+
### Godot In Depth
|
|
158
|
+
|
|
159
|
+
**Strengths:**
|
|
160
|
+
- Fully open source (MIT license) — no royalties, no fees, no restrictions
|
|
161
|
+
- Best-in-class 2D engine with dedicated 2D physics, rendering, and node types
|
|
162
|
+
- GDScript is purpose-built for game development and extremely approachable
|
|
163
|
+
- Fastest iteration speed of any major engine — scene changes are near-instant
|
|
164
|
+
- Lightweight: the full engine editor is under 100MB
|
|
165
|
+
- Scene composition model (everything is a scene/node tree) is elegant and intuitive
|
|
166
|
+
|
|
167
|
+
**Weaknesses:**
|
|
168
|
+
- 3D rendering is functional but trails Unity and Unreal significantly
|
|
169
|
+
- Console export requires third-party porting services (no official console SDK support in open source)
|
|
170
|
+
- Smaller asset marketplace and plugin ecosystem
|
|
171
|
+
- Smaller community means fewer tutorials, fewer Stack Overflow answers, fewer ready-made solutions
|
|
172
|
+
- C# support is stable but GDScript is the first-class citizen; documentation and examples favor GDScript
|
|
173
|
+
|
|
174
|
+
**Best for:** 2D games, solo developers, small indie teams, game jams, projects where open source matters, educational contexts.
|
|
175
|
+
|
|
176
|
+
**Avoid for:** AAA 3D games, projects requiring guaranteed console support from day one, teams that need extensive middleware ecosystem.
|
|
177
|
+
|
|
178
|
+
### Custom Engine Considerations
|
|
179
|
+
|
|
180
|
+
Building a custom engine is justified only when:
|
|
181
|
+
- The game requires capabilities no existing engine provides (novel rendering techniques, extreme performance requirements)
|
|
182
|
+
- The team has dedicated engine programmers with years of experience
|
|
183
|
+
- The studio plans to ship multiple titles on this engine, amortizing the investment
|
|
184
|
+
- Learning and IP ownership are explicit business goals
|
|
185
|
+
|
|
186
|
+
Building a custom engine is NOT justified when:
|
|
187
|
+
- The team wants to "learn how engines work" during a production project
|
|
188
|
+
- A single game feature is hard in an existing engine (solve that one feature, do not rebuild everything)
|
|
189
|
+
- The team is small and the game is on a tight timeline
|
|
190
|
+
|
|
191
|
+
### Middleware Selection Guide
|
|
192
|
+
|
|
193
|
+
**Audio middleware (FMOD vs Wwise):**
|
|
194
|
+
- FMOD: Simpler API, generous indie licensing, good Unity/Unreal integration, lower learning curve
|
|
195
|
+
- Wwise: More powerful spatial audio and interactive music features, industry standard for AAA, steeper learning curve, more complex licensing
|
|
196
|
+
- Rule of thumb: FMOD for indie/AA, Wwise for AAA or games where adaptive audio is a pillar
|
|
197
|
+
|
|
198
|
+
**Networking middleware:**
|
|
199
|
+
- Photon: Managed servers, good for casual multiplayer, per-CCU pricing
|
|
200
|
+
- Mirror (Unity): Free, community-driven, server-authoritative, self-hosted
|
|
201
|
+
- Steam Networking: Free for Steam-published games, P2P with relay fallback
|
|
202
|
+
- EOS (Epic Online Services): Free, cross-platform, accounts/matchmaking/lobbies
|
|
203
|
+
- Rule of thumb: prototype with the simplest option, migrate to a production solution when you know your scale requirements
|
|
204
|
+
|
|
205
|
+
**Physics middleware:**
|
|
206
|
+
- PhysX: Built into Unity and Unreal; adequate for most games
|
|
207
|
+
- Havok: Premium physics with better large-world and destruction support; used in many AAA titles
|
|
208
|
+
- Jolt: Modern, open-source alternative gaining traction; good performance characteristics
|
|
209
|
+
- Box2D: The standard for 2D physics; built into most 2D engines
|
|
210
|
+
|
|
211
|
+
### Rendering API Considerations
|
|
212
|
+
|
|
213
|
+
The choice of rendering API is usually dictated by the engine and target platform:
|
|
214
|
+
|
|
215
|
+
- **Vulkan**: Cross-platform (PC, Android, Switch via NVN/Vulkan-like), low-level, maximum control, highest learning curve
|
|
216
|
+
- **DirectX 12**: Windows and Xbox only, low-level like Vulkan, best Windows-specific optimization
|
|
217
|
+
- **Metal**: Apple platforms only (iOS, macOS), required for iOS/macOS, good performance
|
|
218
|
+
- **OpenGL/OpenGL ES**: Legacy but still relevant for web (WebGL) and older mobile devices
|
|
219
|
+
- **WebGPU**: Emerging standard for browser-based 3D, successor to WebGL
|
|
220
|
+
|
|
221
|
+
For most projects using an established engine, the engine abstracts the rendering API. Rendering API selection becomes critical only for custom engines or when writing custom render passes/shaders within an engine.
|
|
222
|
+
|
|
223
|
+
### Platform SDK Requirements
|
|
224
|
+
|
|
225
|
+
Console development requires platform holder approval and NDA-protected SDK access:
|
|
226
|
+
|
|
227
|
+
- **PlayStation**: Must apply to Sony's PlayStation Partners Program, receive DevKit hardware, and sign NDA. Unity and Unreal handle PS SDK integration; Godot requires third-party porting.
|
|
228
|
+
- **Xbox**: Microsoft's ID@Xbox program for indie developers. Unity and Unreal have integrated GDK support. Somewhat more accessible than PlayStation for smaller studios.
|
|
229
|
+
- **Nintendo Switch**: Most restrictive approval process. Must apply to Nintendo Developer Portal. Unity and Unreal support Switch natively. Godot requires third-party porting services.
|
|
230
|
+
|
|
231
|
+
Console SDK access should be secured early in development — approval processes can take weeks to months, and DevKit hardware has lead times.
|
|
232
|
+
|
|
233
|
+
### Decision Documentation Template
|
|
234
|
+
|
|
235
|
+
Every engine decision should be recorded as an Architecture Decision Record:
|
|
236
|
+
|
|
237
|
+
- **Context**: What kind of game, target platforms, team composition, timeline
|
|
238
|
+
- **Options Considered**: List each engine evaluated with weighted matrix scores
|
|
239
|
+
- **Decision**: Which engine was chosen
|
|
240
|
+
- **Rationale**: Which weights drove the decision, which tradeoffs were accepted
|
|
241
|
+
- **Consequences**: What capabilities are gained, what limitations are accepted
|
|
242
|
+
- **Revisit Triggers**: Under what conditions this decision should be reconsidered (e.g., "if we add VR support," "if the team grows beyond 20 engineers")
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: game-input-systems
|
|
3
|
+
description: Input abstraction patterns, action mapping, dead zones, aim assist, haptic feedback, cross-play fairness, and accessibility
|
|
4
|
+
topics: [game-dev, input, controls, rebinding, haptics, accessibility]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
The input system is the player's sole interface with the game world. Every millisecond of input latency, every unintuitive binding, and every inaccessible control scheme directly erodes the player's experience. A well-designed input system abstracts hardware differences behind a unified action layer, supports rebinding without code changes, handles device hotswap gracefully, and provides accessibility options that let every player engage with the game. Input systems are deceptively complex — the gap between "reading a button press" and "shipping a polished, accessible, cross-platform input system" is enormous.
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
### Input Abstraction Architecture
|
|
12
|
+
|
|
13
|
+
Never read hardware inputs directly in gameplay code. Instead, build a three-layer abstraction:
|
|
14
|
+
|
|
15
|
+
1. **Hardware Layer**: Reads raw input from devices (keyboard, mouse, gamepad, touch, motion). This is the only layer that knows about specific hardware APIs (XInput, DirectInput, HID, touchscreen events).
|
|
16
|
+
|
|
17
|
+
2. **Action Mapping Layer**: Maps raw inputs to semantic actions. "Press A button" or "Press Space bar" both map to the action "Jump." This layer handles rebinding, combo detection, input buffering, and modifier keys. Actions are defined in data (config files), not code.
|
|
18
|
+
|
|
19
|
+
3. **Gameplay Layer**: Reads only actions, never raw inputs. Gameplay code asks "is the Jump action active?" not "is the space bar pressed?" This separation means the same gameplay code works across all input devices without modification.
|
|
20
|
+
|
|
21
|
+
### Dead Zones and Sensitivity
|
|
22
|
+
|
|
23
|
+
Analog sticks have manufacturing variance — they may not return exactly (0,0) at rest. Dead zones prevent this drift from causing unwanted input.
|
|
24
|
+
|
|
25
|
+
- **Axial dead zone**: Apply dead zone independently to each axis. Simple but creates a diamond-shaped dead zone that can eat diagonal input near the center.
|
|
26
|
+
- **Radial dead zone**: Apply dead zone to the magnitude of the stick vector. Creates a circular dead zone that preserves diagonal input direction. Preferred for most games.
|
|
27
|
+
- **Default inner dead zone**: 0.15–0.25 (15–25% of stick range). Too small = stick drift; too large = lost precision near center.
|
|
28
|
+
- **Outer dead zone**: 0.90–0.95. Analog sticks rarely reach perfect 1.0 at the edge. The outer dead zone maps the usable range to 0.0–1.0 so players can achieve maximum input.
|
|
29
|
+
|
|
30
|
+
After applying dead zones, **rescale** the remaining range to 0.0–1.0 so gameplay code receives the full input range.
|
|
31
|
+
|
|
32
|
+
Sensitivity curves (response curves) map the rescaled stick input to gameplay speed:
|
|
33
|
+
- **Linear**: Output = Input. Simple, predictable, but may feel too fast near the edge.
|
|
34
|
+
- **Quadratic**: Output = Input^2. Slower near center for precision aiming, faster at edge. Most common default.
|
|
35
|
+
- **Custom curve**: Expose a curve editor in settings for players who want fine control.
|
|
36
|
+
|
|
37
|
+
### Aim Assist and Aim Friction
|
|
38
|
+
|
|
39
|
+
Controller players in shooters need aim assist to compete with mouse precision. Aim assist is not "cheating" — it compensates for the physical limitations of thumbsticks.
|
|
40
|
+
|
|
41
|
+
**Aim assist types:**
|
|
42
|
+
- **Bullet magnetism**: Shots that miss by a small margin are redirected to hit the target. Invisible to the player. Typically 2–5 degrees of correction.
|
|
43
|
+
- **Reticle friction / aim slowdown**: Sensitivity decreases when the reticle is near a valid target, making it easier to track. The player feels like they are aiming well.
|
|
44
|
+
- **Target snap / aim lock**: Briefly snaps the reticle toward a target when aiming down sights. Used sparingly — too aggressive feels like the game is playing itself.
|
|
45
|
+
- **Rotation assist**: Slightly rotates the player's view to track a moving target that passes through the reticle area.
|
|
46
|
+
|
|
47
|
+
**Tuning rules:**
|
|
48
|
+
- Aim assist should be strongest on controller and weakest (or absent) on mouse
|
|
49
|
+
- Never apply aim assist to mouse input — mouse players perceive it as interference
|
|
50
|
+
- Scale aim assist with difficulty level (higher assist on easier difficulties)
|
|
51
|
+
- Disable or reduce aim assist in competitive multiplayer when fairness matters more than accessibility
|
|
52
|
+
- Expose aim assist strength as a player setting (0–100%)
|
|
53
|
+
|
|
54
|
+
### Cross-Play Input Fairness
|
|
55
|
+
|
|
56
|
+
When keyboard/mouse and controller players compete in the same lobby, input disparity creates balance problems. Mouse has superior precision; controller has aim assist compensation.
|
|
57
|
+
|
|
58
|
+
**Strategies:**
|
|
59
|
+
- **Input-based matchmaking**: Match controller players with controller players and KB/M with KB/M by default. Allow opt-in to mixed lobbies.
|
|
60
|
+
- **Input detection**: Detect input device changes mid-match (player switches from controller to KB/M). Either lock the detected input type for the match or adjust aim assist immediately.
|
|
61
|
+
- **Separate leaderboards**: Maintain input-type-specific rankings when input method significantly affects competitive performance.
|
|
62
|
+
- **Asymmetric aim assist**: Controller players receive aim assist; KB/M players do not. This is the standard approach in cross-play shooters.
|
|
63
|
+
|
|
64
|
+
## Deep Guidance
|
|
65
|
+
|
|
66
|
+
### Action Mapping System
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// Input abstraction: Hardware -> Action Mapping -> Gameplay
|
|
70
|
+
|
|
71
|
+
// --- Action Definitions (data-driven) ---
|
|
72
|
+
|
|
73
|
+
enum ActionType {
|
|
74
|
+
Button, // Discrete: pressed, released, held
|
|
75
|
+
Axis1D, // Continuous scalar: -1 to 1 (triggers, single stick axis)
|
|
76
|
+
Axis2D, // Continuous vector: { x, y } (stick, mouse delta)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface ActionBinding {
|
|
80
|
+
action: string; // "Jump", "Move", "Fire", "AimDirection"
|
|
81
|
+
type: ActionType;
|
|
82
|
+
bindings: InputBinding[]; // Multiple bindings per action (KB + gamepad)
|
|
83
|
+
modifiers?: string[]; // Required modifier actions (e.g., "Sprint" + "Jump")
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface InputBinding {
|
|
87
|
+
device: "keyboard" | "mouse" | "gamepad" | "touch";
|
|
88
|
+
input: string; // "Space", "ButtonSouth", "LeftStick", etc.
|
|
89
|
+
scale?: number; // For axis inversion: -1 inverts
|
|
90
|
+
deadZone?: number; // Override per-binding dead zone
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Example action map (would be loaded from JSON/YAML config)
|
|
94
|
+
const defaultActionMap: ActionBinding[] = [
|
|
95
|
+
{
|
|
96
|
+
action: "Move",
|
|
97
|
+
type: ActionType.Axis2D,
|
|
98
|
+
bindings: [
|
|
99
|
+
{ device: "gamepad", input: "LeftStick" },
|
|
100
|
+
// WASD mapped to 2D axis via composite
|
|
101
|
+
{ device: "keyboard", input: "Composite:W,S,A,D" },
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
action: "Jump",
|
|
106
|
+
type: ActionType.Button,
|
|
107
|
+
bindings: [
|
|
108
|
+
{ device: "gamepad", input: "ButtonSouth" }, // A on Xbox, X on PS
|
|
109
|
+
{ device: "keyboard", input: "Space" },
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
action: "Fire",
|
|
114
|
+
type: ActionType.Button,
|
|
115
|
+
bindings: [
|
|
116
|
+
{ device: "gamepad", input: "RightTrigger" },
|
|
117
|
+
{ device: "mouse", input: "LeftButton" },
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
action: "AimDirection",
|
|
122
|
+
type: ActionType.Axis2D,
|
|
123
|
+
bindings: [
|
|
124
|
+
{ device: "gamepad", input: "RightStick" },
|
|
125
|
+
{ device: "mouse", input: "Delta" },
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
// --- Input System Core ---
|
|
131
|
+
|
|
132
|
+
class InputSystem {
|
|
133
|
+
private actionMap: ActionBinding[];
|
|
134
|
+
private actionStates: Map<string, ActionState> = new Map();
|
|
135
|
+
private rebinds: Map<string, InputBinding[]> = new Map();
|
|
136
|
+
|
|
137
|
+
constructor(actionMap: ActionBinding[]) {
|
|
138
|
+
this.actionMap = actionMap;
|
|
139
|
+
for (const binding of actionMap) {
|
|
140
|
+
this.actionStates.set(binding.action, {
|
|
141
|
+
pressed: false,
|
|
142
|
+
released: false,
|
|
143
|
+
held: false,
|
|
144
|
+
value: 0,
|
|
145
|
+
axis2D: { x: 0, y: 0 },
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Called once per frame before gameplay update
|
|
151
|
+
update(rawInputs: RawInputSnapshot): void {
|
|
152
|
+
for (const binding of this.actionMap) {
|
|
153
|
+
const state = this.actionStates.get(binding.action)!;
|
|
154
|
+
const effectiveBindings = this.rebinds.get(binding.action)
|
|
155
|
+
?? binding.bindings;
|
|
156
|
+
|
|
157
|
+
// Evaluate all bindings; take the one with highest magnitude
|
|
158
|
+
let bestValue = 0;
|
|
159
|
+
let bestAxis2D = { x: 0, y: 0 };
|
|
160
|
+
|
|
161
|
+
for (const b of effectiveBindings) {
|
|
162
|
+
const raw = rawInputs.get(b.device, b.input);
|
|
163
|
+
if (raw === undefined) continue;
|
|
164
|
+
|
|
165
|
+
if (binding.type === ActionType.Axis2D) {
|
|
166
|
+
const vec = raw as { x: number; y: number };
|
|
167
|
+
const deadZoned = this.applyRadialDeadZone(
|
|
168
|
+
vec, b.deadZone ?? 0.2
|
|
169
|
+
);
|
|
170
|
+
if (magnitude(deadZoned) > magnitude(bestAxis2D)) {
|
|
171
|
+
bestAxis2D = deadZoned;
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
const val = (raw as number) * (b.scale ?? 1);
|
|
175
|
+
if (Math.abs(val) > Math.abs(bestValue)) {
|
|
176
|
+
bestValue = val;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Update state transitions
|
|
182
|
+
const wasHeld = state.held;
|
|
183
|
+
state.held = binding.type === ActionType.Axis2D
|
|
184
|
+
? magnitude(bestAxis2D) > 0.01
|
|
185
|
+
: Math.abs(bestValue) > 0.5;
|
|
186
|
+
state.pressed = state.held && !wasHeld;
|
|
187
|
+
state.released = !state.held && wasHeld;
|
|
188
|
+
state.value = bestValue;
|
|
189
|
+
state.axis2D = bestAxis2D;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Gameplay code reads actions, never raw inputs
|
|
194
|
+
isPressed(action: string): boolean {
|
|
195
|
+
return this.actionStates.get(action)?.pressed ?? false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
isHeld(action: string): boolean {
|
|
199
|
+
return this.actionStates.get(action)?.held ?? false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
getAxis2D(action: string): { x: number; y: number } {
|
|
203
|
+
return this.actionStates.get(action)?.axis2D ?? { x: 0, y: 0 };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Runtime rebinding
|
|
207
|
+
rebind(action: string, newBindings: InputBinding[]): void {
|
|
208
|
+
this.rebinds.set(action, newBindings);
|
|
209
|
+
this.saveRebinds(); // Persist to player prefs
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private applyRadialDeadZone(
|
|
213
|
+
input: { x: number; y: number },
|
|
214
|
+
deadZone: number
|
|
215
|
+
): { x: number; y: number } {
|
|
216
|
+
const mag = magnitude(input);
|
|
217
|
+
if (mag < deadZone) return { x: 0, y: 0 };
|
|
218
|
+
// Rescale remaining range to 0-1
|
|
219
|
+
const rescaled = (mag - deadZone) / (1 - deadZone);
|
|
220
|
+
const normalized = { x: input.x / mag, y: input.y / mag };
|
|
221
|
+
return {
|
|
222
|
+
x: normalized.x * rescaled,
|
|
223
|
+
y: normalized.y * rescaled,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private saveRebinds(): void { /* persist to localStorage/file */ }
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
interface ActionState {
|
|
231
|
+
pressed: boolean;
|
|
232
|
+
released: boolean;
|
|
233
|
+
held: boolean;
|
|
234
|
+
value: number;
|
|
235
|
+
axis2D: { x: number; y: number };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
interface RawInputSnapshot {
|
|
239
|
+
get(device: string, input: string): number | { x: number; y: number } | undefined;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function magnitude(v: { x: number; y: number }): number {
|
|
243
|
+
return Math.sqrt(v.x * v.x + v.y * v.y);
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Input Buffering
|
|
248
|
+
|
|
249
|
+
Input buffering accepts player inputs slightly before they would be valid and executes them at the first valid opportunity. This forgives imprecise timing and makes controls feel responsive.
|
|
250
|
+
|
|
251
|
+
**How it works:**
|
|
252
|
+
- When a player presses Jump while still in the air (e.g., 100 ms before landing), the input is stored in a buffer
|
|
253
|
+
- When the character lands, the buffer is checked — if a Jump input exists within the buffer window, the character immediately jumps
|
|
254
|
+
- Buffer window: typically 100–200 ms (6–12 frames at 60 fps)
|
|
255
|
+
|
|
256
|
+
**Where to apply buffering:**
|
|
257
|
+
- **Combat combos**: Buffer the next attack input during the current attack animation. Player presses "attack" during the swing of the first hit, and the second hit fires immediately when the first hit's recovery ends.
|
|
258
|
+
- **Platforming**: Buffer jump input during landing animation. Buffer wall-jump input during wall slide.
|
|
259
|
+
- **Dodge/roll**: Buffer dodge during attack recovery or hitstun.
|
|
260
|
+
- **Interaction**: Buffer "interact" input while walking toward an interactable object.
|
|
261
|
+
|
|
262
|
+
**Where NOT to apply buffering:**
|
|
263
|
+
- Menu navigation (buffer causes double-selection)
|
|
264
|
+
- Aim (buffered aim inputs create jittery movement)
|
|
265
|
+
- Anything where the "was this intentional?" ambiguity outweighs the responsiveness benefit
|
|
266
|
+
|
|
267
|
+
### Haptic Feedback Design
|
|
268
|
+
|
|
269
|
+
Modern controllers (DualSense, Switch Joy-Cons, Xbox Impulse Triggers) provide nuanced haptic feedback beyond simple rumble.
|
|
270
|
+
|
|
271
|
+
**DualSense (PS5) advanced haptics:**
|
|
272
|
+
- **Adaptive triggers**: Variable resistance on L2/R2. Simulate bowstring tension, trigger pull weight, brake resistance, mud/sand resistance.
|
|
273
|
+
- **HD haptics**: High-fidelity vibration motors that can simulate textures, impacts, and environmental feedback through the controller. Walking on gravel feels different from walking on metal.
|
|
274
|
+
- **Speaker**: The controller has a built-in speaker for close-proximity sound effects (walkie-talkie chatter, weapon click, UI confirmation).
|
|
275
|
+
|
|
276
|
+
**Nintendo Switch HD Rumble:**
|
|
277
|
+
- Linear resonant actuators provide frequency-specific vibration
|
|
278
|
+
- Can simulate the feeling of ice cubes in a glass, rolling a ball, or counting objects by feel
|
|
279
|
+
- Lower fidelity than DualSense but still far beyond binary rumble
|
|
280
|
+
|
|
281
|
+
**Haptic design principles:**
|
|
282
|
+
- Haptics should reinforce, not replace, visual and audio feedback
|
|
283
|
+
- Every player action that has a physical analog should have a haptic response (firing a weapon, landing from a jump, taking damage)
|
|
284
|
+
- Environmental haptics add immersion: rain, vehicle engine vibration, walking surface texture
|
|
285
|
+
- Intensity should scale with gameplay intensity — constant high-intensity rumble causes fatigue and numbness
|
|
286
|
+
- Always provide an option to disable haptics entirely and to adjust intensity (0–100%)
|
|
287
|
+
- Test haptic design with the controller in hand, not just as parameter values on screen
|
|
288
|
+
|
|
289
|
+
### Controller Disconnect Handling
|
|
290
|
+
|
|
291
|
+
Players disconnect controllers during gameplay (battery dies, cable pulled, Bluetooth interference). The game must handle this gracefully.
|
|
292
|
+
|
|
293
|
+
**Required behaviors:**
|
|
294
|
+
1. **Pause immediately**: When a controller disconnects during single-player gameplay, pause the game and display a reconnection prompt. Never let gameplay continue without input — the player will take damage, fall off ledges, or lose progress.
|
|
295
|
+
2. **Reconnect seamlessly**: When the controller reconnects, resume gameplay from the paused state. Do not require navigating a menu.
|
|
296
|
+
3. **Player identification**: In local multiplayer, track which controller belongs to which player. When controller 2 disconnects and reconnects, it must re-associate with player 2, not become a new player.
|
|
297
|
+
4. **Battery warning**: Display a low-battery warning before disconnection occurs. Platform APIs provide battery level (iOS: `UIDevice.batteryLevel`, Android: `BatteryManager`, PlayStation/Xbox SDK battery APIs).
|
|
298
|
+
5. **Save protection**: If the game auto-saves on a timer, do not auto-save during a disconnect state — the player may be in an invalid position (falling, taking damage).
|
|
299
|
+
|
|
300
|
+
### IME and Text Input
|
|
301
|
+
|
|
302
|
+
Text input in games is complex because it must handle hardware keyboards, on-screen keyboards, and Input Method Editors (IME) for CJK (Chinese, Japanese, Korean) languages.
|
|
303
|
+
|
|
304
|
+
**Keyboard text input:**
|
|
305
|
+
- Use the platform's text input API, not raw key events. Raw key events do not handle dead keys (accents), compose sequences, or IME.
|
|
306
|
+
- Support clipboard paste (Ctrl+V / Cmd+V) in all text fields.
|
|
307
|
+
- Handle key repeat rate (initial delay + repeat interval) for held keys in text fields.
|
|
308
|
+
|
|
309
|
+
**On-screen keyboards:**
|
|
310
|
+
- Mobile and console games must trigger the platform's on-screen keyboard when a text field is focused.
|
|
311
|
+
- Adjust the UI layout to prevent the on-screen keyboard from covering the active text field.
|
|
312
|
+
- Specify keyboard type per field: default, email, numeric, URL, password.
|
|
313
|
+
|
|
314
|
+
**IME support:**
|
|
315
|
+
- IME is required for CJK language input. The player types phonetic characters that are composed into ideographs through a candidate selection process.
|
|
316
|
+
- Display the IME composition string (the in-progress text) inline in the text field.
|
|
317
|
+
- Display the candidate list (possible character choices) near the text field.
|
|
318
|
+
- Do not consume keyboard events that the IME is processing — let the IME handle composition before the game receives the final character.
|
|
319
|
+
- Test IME input with native speakers — broken IME support is immediately obvious and blocks the game for millions of players in East Asian markets.
|
|
320
|
+
|
|
321
|
+
### Accessibility in Input Design
|
|
322
|
+
|
|
323
|
+
Accessible input design is a legal requirement in some markets (CVAA in the US for communication features, European Accessibility Act) and a moral imperative everywhere.
|
|
324
|
+
|
|
325
|
+
**Minimum accessibility requirements:**
|
|
326
|
+
- **Full rebinding**: Every action must be rebindable to any key/button. No hardcoded bindings.
|
|
327
|
+
- **One-handed play**: Provide control schemes that work with only the left or right side of the controller/keyboard.
|
|
328
|
+
- **Hold vs toggle**: Every "hold to sprint/aim/crouch" input must have a toggle alternative.
|
|
329
|
+
- **Stick sensitivity**: Expose dead zone size, sensitivity curve, and axis inversion as player settings.
|
|
330
|
+
- **Button mashing alternatives**: Replace button-mash QTEs with hold-to-fill or automatic alternatives.
|
|
331
|
+
- **Auto-aim**: Provide a strong auto-aim option for players who cannot use fine analog control.
|
|
332
|
+
- **Sequential inputs**: Allow combo inputs to be performed sequentially (one press at a time) rather than requiring simultaneous button presses.
|
|
333
|
+
- **Touch target size**: On mobile, interactive touch targets must be at least 44x44 points (Apple HIG) or 48x48 dp (Material Design).
|
|
334
|
+
|
|
335
|
+
**Beyond minimum:**
|
|
336
|
+
- Copilot mode: Two controllers control the same character (one player assists another)
|
|
337
|
+
- Switch/adaptive controller support: Ensure the game works with accessibility controllers (Xbox Adaptive Controller)
|
|
338
|
+
- Eye tracking input: Support Tobii and similar eye tracking devices as input sources
|
|
339
|
+
- Voice input: Support voice commands for basic actions (requires platform speech-to-text API)
|
|
340
|
+
|
|
341
|
+
### Input Latency Measurement
|
|
342
|
+
|
|
343
|
+
Input latency is the time from the player pressing a button to the corresponding visual change on screen. For competitive games, this must be as low as possible.
|
|
344
|
+
|
|
345
|
+
**Latency sources (cumulative):**
|
|
346
|
+
- Input polling: 0–8 ms (depends on polling rate: 1000 Hz USB = 1 ms, Bluetooth = 4–8 ms)
|
|
347
|
+
- Game simulation: 0–16.67 ms (depends on where in the frame the input is processed)
|
|
348
|
+
- Render pipeline: 16.67–33.33 ms (1–2 frames of render-ahead buffering)
|
|
349
|
+
- Display: 1–20 ms (monitor response time + display processing)
|
|
350
|
+
- Total typical: 40–80 ms (target < 60 ms for competitive games)
|
|
351
|
+
|
|
352
|
+
**Reducing input latency:**
|
|
353
|
+
- Poll input as late as possible in the frame (just before simulation, not at frame start)
|
|
354
|
+
- Minimize render-ahead buffers (trade GPU utilization for latency)
|
|
355
|
+
- Use "late latch" techniques: update camera/aim after the final input poll, just before GPU submission
|
|
356
|
+
- On PC, support NVIDIA Reflex / AMD Anti-Lag for driver-level latency reduction
|
|
357
|
+
- Measure with a high-speed camera (240+ fps) pointed at the display while pressing a button connected to an LED — count frames between LED and screen change
|
|
358
|
+
|
|
359
|
+
## Genre-Specific Input Patterns
|
|
360
|
+
|
|
361
|
+
### Touch and Mobile Input
|
|
362
|
+
|
|
363
|
+
Beyond minimum target sizes (44x44pt iOS, 48x48dp Android), mobile games need:
|
|
364
|
+
|
|
365
|
+
- **Virtual joystick**: Floating (appears at touch point) preferred over fixed. Dead zone: 10-15% of joystick radius. Visual feedback: thumb indicator follows touch position.
|
|
366
|
+
- **Gesture recognition**: Swipe threshold 50-100px to distinguish from taps. Multi-touch: track up to 5 simultaneous touches for action games, 2 for casual.
|
|
367
|
+
- **Auto-play patterns**: Common in mobile RPGs — tap to toggle auto-battle with manual override for skills. Implement as a state machine: Manual → Auto → Manual on any input.
|
|
368
|
+
- **Portrait vs landscape**: Design thumb-zone heat maps for each orientation. Critical actions within 60px of bottom corners.
|
|
369
|
+
|
|
370
|
+
### Strategy and Management Input
|
|
371
|
+
|
|
372
|
+
- **Box-select**: Click-drag rectangle, select all units inside. Add to selection with Shift+click. Deselect with right-click on empty space.
|
|
373
|
+
- **Command queuing**: Shift+right-click appends to command queue. Display queue as waypoint markers. Max queue depth: 10-20 commands.
|
|
374
|
+
- **Camera controls**: Edge scroll (mouse at screen edge), WASD pan, middle-mouse drag. Zoom: scroll wheel with min/max zoom limits. Minimap click-to-jump.
|
|
375
|
+
|
|
376
|
+
### Turn-Based Input
|
|
377
|
+
|
|
378
|
+
- **Select-confirm pattern**: Click to select, click again to confirm. Show preview of action result before confirmation. Undo: allow undo of last action if turn is not yet submitted.
|
|
379
|
+
- **Hover-preview**: On hover, show range/area-of-effect highlight. On select, show detailed outcome prediction.
|