|
|
||
|---|---|---|
| .cargo | ||
| .cursor | ||
| .github/workflows | ||
| .vscode | ||
| assets | ||
| crates | ||
| docs | ||
| source_assets | ||
| third_party/transform-gizmo-bevy | ||
| xtask | ||
| .gitattributes | ||
| .gitignore | ||
| AGENTS.md | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
| rust-toolchain.toml | ||
Bevy FPS Foundation
A modular first-person game foundation and in-process editor built on Bevy 0.18 and Avian 0.6 physics.
The runtime game provides a high-fidelity PBR stack (HDR, procedural atmosphere + image-based lighting, cascaded shadows, SSAO, TAA, bloom, fog, ACES tonemapping) plus Hybrid Auto Solari raytraced indirect lighting where supported, and local input/presentation glue for a fixed-step kinematic FPS simulation. The editor provides dockable egui panels, a filtered scene hierarchy, click selection, transform gizmos, undo/redo, asset import/placement, native Bevy scene save/load, and BRP support for external tooling.
Requirements
- Rust (stable; pinned via
rust-toolchain.toml) - Git LFS for binary game/source assets: run
git lfs installonce, thengit lfs pullafter clone. - A Vulkan-capable GPU + drivers (developed against an NVIDIA RTX 3080 Ti)
- Linux build deps for
winit/wgpu(ALSA, udev, etc.) if not already present
Running
# Check everything
cargo fmt --check
cargo check --workspace
cargo clippy --workspace
cargo clippy -p game -p shared -p protocol -p sim -- -D warnings
cargo test -p game --lib
cargo test -p sim
# Runtime game
cargo run -p game
cargo run -p game --features dev
# In-engine editor
cargo run -p editor
cargo run -p editor --features dev
Hot reload (gameplay iteration)
--features dev turns on Bevy dynamic linking for faster rebuilds (recommended day-to-day).
Add --features hot-reload for in-process hot reload of game_hot (sim movement, player input/camera, world bootstrap). The editor process stays open; only gameplay system bodies swap when the dylib rebuilds.
Two-terminal workflow (hot reload):
# Terminal 1 — rebuild the hot dylib on save
cargo watch -w crates/game_hot -w crates/sim -x "build -p game_hot --features dylib"
# Terminal 2 — run the editor once
cargo run -p editor --features dev,hot-reload
Or use the VS Code task watch game_hot (hot reload) alongside run editor (dev fast-link) (enable the hot-reload feature on the editor run task when using dylib iteration).
| Input | Action |
|---|---|
Save game_hot / sim gameplay code |
Dylib rebuilds; status bar shows building… then reloaded |
| Ctrl+Shift+R | Manual refresh (on_hot_reload bootstrap: camera FX, ambient light) |
Safe to hot reload: system logic, movement tuning in code, input mapping.
Requires full editor restart: new/changed component fields, new components, plugin schedule changes, editor UI code.
Note: the workspace uses the
moldlinker viaclang(see.cargo/config.toml) for fast link times.
Target Cache Cleanup
Cargo/Bevy debug artifacts can grow quickly. Use the workspace cleanup task before reaching for a full cargo clean:
| Command | Effect |
|---|---|
cargo clean-target |
Dry run: prints reclaimable target/ cache. |
cargo clean-target --apply |
Safe cleanup: removes incremental and rust-analyzer flycheck cache. |
cargo clean-target --include-artifacts --days 3 --apply |
Deeper cleanup: also removes stale hashed deps, build, .fingerprint, and example artifacts older than 3 days. Cargo will rebuild anything still needed. |
VS Code tasks: target cleanup (dry run) and target cleanup (safe apply).
Launch Troubleshooting
- The native game/editor windows force an opaque Wayland surface and opaque camera clears to avoid compositor alpha issues on mixed HDR/SDR desktops.
- If the window maps but appears transparent on Hyprland or another Wayland compositor, launch with
BEVY_FPS_HDR=0to force the SDR camera path while debugging monitor/compositor behavior. - Debug launch configs and run tasks set
WGPU_VALIDATION=0to quiet the known wgpu/Vulkan validation-layer warningVUID-StandaloneSpirv-MemorySemantics-10871. This only disables the Vulkan validation layer for those launches; Rust panics and application errors still surface normally. - If CodeLLDB / mold fails with hundreds of
undefined symbollinker errors, the incrementaltarget/cache is stale. Run the VS Code task clean build editor (dev) orcargo clean -p editor && cargo build -p editor --features dev, then launch Debug editor again. Usecargo run -p editor --features devfrom the terminal if you needlibbevy_dylibonLD_LIBRARY_PATHautomatically.
Editor Controls
| Input | Action |
|---|---|
F5 |
Toggle Edit / Play in the same viewport |
F6 |
While playing: pause / resume simulation (stay in Play mode) |
F8 |
While playing: possess player / eject to editor camera |
G |
Toggle clean game-view overlay (hide editor widgets/proxies/gizmos) |
Ctrl+G |
Toggle viewport grid |
F |
Focus editor camera on selection |
Ctrl+Shift+1 / Ctrl+Shift+2 |
Save / recall viewport camera bookmark |
| RMB + mouse | Editor camera look |
RMB + W A S D |
Editor camera fly |
RMB + Q / E |
Editor camera down / up |
| Mouse wheel | Dolly editor camera |
| MMB + mouse | Pan editor camera |
| Click authored object, actor root icon, or visualizer in viewport | Select source entity |
Click empty viewport / Esc |
Deselect |
Delete / Backspace |
Delete selection |
Ctrl+D |
Duplicate selection |
Ctrl+Z / Ctrl+Shift+Z / Ctrl+Y |
Undo / redo |
F2 in Hierarchy |
Rename selection |
W / E / R |
Translate / rotate / scale gizmo |
X |
Toggle world/local gizmo orientation |
| Viewport toolbar (sun / brush / box icons) | Shading: Lit, Unlit (albedo), Colliders (mesh off) |
| Viewport eye/options | Toggle actor root icon categories, colliders, lights, spawns, prefab/model anchors, and runtime player/camera visualizers |
Tab in viewport |
Cycle selection through overlapping objects at last click |
| Click Player visualizer in Edit mode | Select or create the authored PlayerSpawn (Player Start) |
| Select Project Sun | Inspect project default lighting; create a scene sun override |
| Asset Browser project/file views | Browse assets/, search/filter/sort, switch grid/list, inspect import settings; drag assets into the viewport to place |
| Asset Browser texture action | Assign base-color texture to selection |
Ctrl+P |
Command palette (scene.reset_lighting, selection.group, selection.focus, play commands) |
F7 |
While paused in Play: advance one sim tick |
| Shift/Ctrl + click (Hierarchy) | Additive selection |
| Hierarchy context | Reparent to other selection / Unparent |
| File menu | New, Open, Save, Save As, Import Assets, Export Selection, Save Selection As Prefab, Recent Scenes |
| Inspector component card | Collapse with caret, toggle active with status dot, or use triple-dot menu for reset/copy/paste/move/remove actions |
| Inspector footer → Add Component | Pinned search/add control for registered authoring, rendering, physics, and gameplay components with undo |
| Edit → Project Settings… | Edit assets/project.ron (rendering, physics, input) |
Play Mode
- Press F5 (or Play menu) to run the real game in-process: same
GamePlugin, player, fixed-stepsim, and rendering stack as the standalonegamebinary. - The unified Viewport stays in place on play enter. The editor fly camera and player camera render to the same offscreen HDR texture depending on Edit/Play possession state.
- While editing, gameplay simulation and input are disabled (
SimEnabled/GameInputEnabled). Entering Play enables simulation; F6 pauses/resumes the sim without leaving Play; F8 toggles possess (player input + camera) vs eject (editor fly camera while sim keeps running). - Transport controls (Play, Pause, Stop, Eject) live on the main toolbar below the menu bar.
- The viewport uses the full project rendering stack from
assets/project.ron(Edit → Project Settings…). Only one 3D camera carries that stack at a time (editor fly cam while editing/ejected, player cam while possessed) so GPU bind limits are respected. - Add a
PlayerSpawnmarker component (via Inspector) on a level object to choose where Play starts; otherwise the default game spawn(0, 1.5, 10)is used. - Clean game-view overlay (
G) hides editor widgets, actor root icons, visualizers, selectable proxies, gizmos, selection outlines, and grid without changing the active camera. - F5 stops Play and restores player sim state only; authored scene edits made during PIE are kept. Esc frees the cursor mid-play.
Game Controls
| Input | Action |
|---|---|
W A S D |
Move |
| Mouse | Look |
Space |
Jump (coyote-time + buffered) |
Left Shift |
Sprint |
Left Ctrl |
Crouch |
Esc |
Release / re-grab mouse cursor |
Scene Workflow
- Levels are saved as native Bevy dynamic scenes under
assets/levels/. - Editable entities use reflectable authoring components from
crates/shared. - The editable player start is an authored
PlayerSpawn; moving it with the transform gizmo changes where Play mode boots the runtime player. - Project settings provide default ambient/sun lighting. A scene-authored directional
LightDescacts as a per-scene sun override and disables the project sun contribution. - Hydration systems turn authoring data into runtime meshes, GLTF scenes, materials, lights, and
Avian colliders. Persisted
ActorId/ComponentInstanceIdvalues are stable; raw BevyEntityIDs are runtime-only. - Inspector component active toggles are saved in
InspectorOrder. Inactive authoring components keep their data but do not hydrate runtime meshes, lights, physics, or post-process effects. - Dragging glTF/GLB/FBX assets creates
StaticMeshRendereractors by default from normalized artifacts inassets/meshes/generated/. Renderer slots reference imported content-browser mesh and material assets, while generated collision is stored separately inColliderDescplusRigidBodyDesc. Switch a model asset's placement mode to Scene Instance in Asset Browser details when you need fullSceneRootloading for animation/skinning/scene data. - Runtime-only handles/colliders are not serialized directly, keeping scenes stable and portable.
- Editor-only cameras and helper roots are filtered from selection, hierarchy, and scene save.
- PIE restores player sim only (transform, velocity, jump state) when you stop Play; authored
LevelObjectedits made during PIE remain in the scene (the level may show as dirty). - Play mode swaps the unified viewport between the player camera (possessed) and editor fly camera (ejected).
- Gameplay movement runs in a fixed timestep in
crates/sim. Raw keyboard/mouse input is translated bycrates/gameintoprotocol::PlayerInputIntentbefore it mutates simulation state.
Troubleshooting
Viewport is black or wireframe-only
- Check the mode badge (bottom-right of the viewport). Collider hides meshes — click the sun toolbar icon for Lit shading.
- Window → Rendering → Active Camera: check Runtime lights (directional/point/spot counts), scene vs project sun status, and contributing post-process volumes. Scene directionals override the project sun; use Scene → Lighting → Use project sun to restore project defaults.
- Solari requested but not visually changing: check Window → Rendering → Active Camera for requested/effective GI, fallback reason, tagged meshes, Solari-compatible mesh assets, render instances, bind-group readiness, and compatible lights. Effective Solari forces an HDR camera target because Bevy Solari writes through a storage texture; imported/custom meshes need TriangleList geometry, POSITION/NORMAL/UV_0/TANGENT attributes, and U32 indices for Bevy 0.18 Solari.
- Lighting changes do nothing in Solari: Bevy 0.18 Solari samples directional lights and emissive meshes, not point/spot lights. Use Directional lights or emissive materials in Solari; switch to Forward PBR for point/spot light authoring.
- GiMode Auto shows Forward: Solari RT wgpu features are unavailable on this GPU. Dev RT override:
BEVY_FPS_FORCE_SOLARI=1. - Volume overrides ignored: confirm camera is inside volume AABB; check priority in Rendering → Volumes tab.
- Custom post FX: RON under
assets/post_fx/; assign path in volume inspector (see docs/editor/rendering.md). - Scene Open / Recent runs hydration immediately; zero runtime lights after load usually means missing
LightDescon light actors (see Rendering panel).
Hierarchy sort jumps when clicking
Sort: Name / Type now use stable tie-breakers (duplicate names like several Pillar rows stay put). Use Sort: Manual and drag-and-drop for explicit sibling order.
Hierarchy scrolls during drag
Scroll-to-drag is disabled while dragging actors. Release the mouse to finish or cancel the drag.
Cursor / VSCode Setup
The .vscode/ folder is preconfigured:
extensions.jsonrecommends rust-analyzer, Even Better TOML, CodeLLDB, crates, and Error Lens.settings.jsonrunsclippyon save, enables proc-macro/build-script support, formats on save, and excludestarget/from search/watch.tasks.jsonprovides workspace check/clippy plus game/editor build/run tasks.launch.jsonprovides CodeLLDB launch configs for the editor and game..github/workflows/ci.ymlmirrors local formatting, check, clippy, test, and binary build verification.
Architecture Decisions
- Documentation index
- Mission — editor framework & principles
- Editor framework docs
- ADR 0001: Roadmap Architecture
- ADR 0002: Bevy Version And Migration Policy
- ADR 0003: Editor Framework Mission
- ADR 0014: Unified Viewport Model
- ADR 0016: Unified Rendering Contract
- ADR 0017: Normalized Static Mesh Assets
Project Layout
crates/
protocol/ Shared tick, input intent, command, and message/event protocol types
sim/ Fixed-step gameplay simulation and determinism test scaffolding
shared/ Reflectable authoring components + hydration systems
game/ Runtime FPS game library + standalone game binary
editor/ In-process egui editor binary
Implementation Checklist
- Workspace:
game,shared,editor,protocol,sim - Shared authoring components + hydration
- Protocol tick, input intent, command, and basic message/event types
- Fixed-step deterministic sim crate for player movement/controller state
- Runtime game refactored into
GamePlugin - Raw game input translated into protocol intent before simulation
- Docked egui editor scaffold
- Editor fly camera
- Mesh picking selection + highlights
- Transform gizmos
- Editor-side scene visualizers for colliders, lights, player spawns, prefab/model anchors, and runtime player/cameras
- Selectable actor root icons in the 3D viewport with per-category visibility controls
- Filtered hierarchy, inspector, viewport, toolbar, asset browser, and status panels
- Delete, duplicate, rename, and structural/material undo-redo
- Native Bevy scene New/Open/Save/Save As with dirty title tracking
- Asset import, static mesh/prefab placement, texture assignment, and selection export
- PIE player-only snapshot/restore (authored
LevelObjectedits persist on stop) - Unified viewport render-to-texture target + Play session bootstrap
PlayerSpawnauthoring marker for editor Play start location- BRP enabled in the editor
- Cursor/VSCode workspace tasks + launch configs
- Opaque Wayland window launch defaults + SDR/HDR runtime toggle
- CI workflow for format, check, clippy, tests, and binary builds
- ADRs for roadmap architecture and Bevy migration policy
- Determinism harness: same inputs over same ticks produce the same state summary/hash
- Verify:
cargo fmt --check/cargo check --workspace/cargo clippy --workspace/ strict foundation clippy /cargo test -p sim - Stable asset registry with UUIDs + import settings in asset browser details
- Prefab instances (
PrefabInstance) + save-as-prefab + unpack - Unsaved-scene confirm on New/Open/Recent
- Hierarchy multi-select, reparent undo, multi-entity gizmo transform
- Typed inspector undo (light, rigid body, collider, primitive, material, static mesh renderer) + registry-driven Add Component footer
- Gameplay authoring markers + visualizers (
WeaponSpawn,TriggerVolume, etc.) - Command palette execution; PIE sim step (F7);
xtask validate-levels - ADRs 0005–0012 (prefab/registry, scene schema, EditorPlugin, editor structure, authoring/hydration, ActorKind, sun policy, zero-debt)
ActorInspectorSectionregistry + game demo section (game::editor_ext)- Command palette: reset lighting, group selection, focus selection
- CI:
cargo test -p shared, scene authoring-only check on repo level,cargo check -p editor --features dev scenecrate schema stamp/migrate/validate on save/load + CIvalidate-levels- Project Settings draft + Apply (HDR/swapchain safe); File → Project New/Open
- Editor lib/bin split +
EditorPluginGroup; game EditorPlugin dogfood panel - FBX/glTF model import + normalized
StaticMeshRendererplacement; explicit scene-instance load viabevy_ufbx/ModelRef - Asset browser model thumbnails (unified
assets/thumbnails/pipeline;ThumbnailStatecache; FBX viaFbxThumbnailSource) - Material assets (
assets/materials/*.ron, inspector picker, drag-drop to selection) - Prefab instance overrides v2 (transform/material/child visibility apply-revert groups)
- Advanced rendering:
GiMode, post-process volumes, Rendering panel, requested/effective render stack, Solari integration, emissive materials, post FX assets (ADR 0013, ADR 0016, rendering guide) - Static mesh renderer component, generated normalized mesh artifacts, source/one-actor hierarchy placement, and inspector renderer slots (ADR 0017)
- Componentized actor inspector, unified component cards, thumbnail static mesh slots, imported asset Browse/Locate/Clear refs with inherited source defaults, collider split, shader-aware material data, and per-actor material overrides (ADR 0018)
Notes / Future Work
- Crouch lowers the camera and movement speed; the collider stays full-height for stability.
- The editor asset browser is filesystem-backed with folder/tree navigation, grid/list views,
texture and model thumbnails (glTF albedo fast-path; offscreen render studio for FBX and
untextured models), search/filter/sort controls, and a details pane. File → Import Assets
accepts glTF/GLB and FBX (binary; copies sibling
.fbmtexture folders when present). Model assets generate normalized static mesh manifests underassets/meshes/generated/; drag/drop usesStaticMeshRendererby default with imported asset refs and optional separate static mesh collider components. Asset details can switch placement to Scene Instance forModelRef/SceneRootplayback. Skeletal animation playback is not supported by the static mesh path yet. - Prefab instances use stable asset IDs (
PrefabInstance+ registry) with serializedPrefabOverrides(transform, material, per-child visibility by name). Inspector Apply/Revert per field group; Unpack removes the instance link. - Material assets are RON files under
assets/materials/; shader schemas live underassets/shaders/.MaterialDescstores shader kind, typed parameters, texture bindings, and StandardMaterial fields. Actor inspector edits create per-actorMaterialOverridedata for static mesh slots instead of mutating shared material assets. - Per-field reflect undo for all components remains future work; typed
sharedinspectors cover the common authoring path. - The authoring/hydration layer is intentionally small so richer asset workflows (terrain, material graphs, lighting profiles) can be added without changing the scene format foundation.
- The M2 sim split keeps camera pitch as local presentation state; body yaw and movement are fixed-step intent consumers. M3 should map the existing protocol envelopes onto Lightyear and add server-authoritative snapshots/prediction rather than broadening the local input path ad hoc.
- The editor renders egui on a dedicated full-window
Camera2d(PrimaryEguiContext,RenderLayers::none(),auto_create_primary_context = false) so the 3D viewport camera can be cropped to the viewport panel without cropping egui itself. Attaching egui to a viewport-cropped 3D camera makesegui_docklay out into a NaN rect and panics inadvance_cursor_after_rect.