Bevy Forge 0.1.0: visual scene editor for Bevy 0.20 BSN
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
# Local Bevy build cache (several GB on the first check).
|
||||
/target/
|
||||
Generated
+4232
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "bsn-smoke"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
description = "Compiles Bevy Forge's generated BSN against real Bevy 0.20.0 (excluded verification crate)"
|
||||
|
||||
# Standalone crate: it is excluded from the Bevy Forge workspace so that the
|
||||
# editor/core/CLI crates never depend on Bevy. Do not add this to the root
|
||||
# workspace.
|
||||
[workspace]
|
||||
|
||||
[dependencies]
|
||||
# Keep this feature set in sync with `.swarm/recon/R02.md`: it is the minimal
|
||||
# set that provides scenes, sprites, text, 3D primitives/materials, cameras and
|
||||
# lights for the code-generation smoke test.
|
||||
bevy = { version = "0.20", default-features = false, features = [
|
||||
"std",
|
||||
"bevy_scene",
|
||||
"bevy_sprite",
|
||||
"bevy_asset",
|
||||
"bevy_color",
|
||||
"bevy_text",
|
||||
"bevy_pbr",
|
||||
"bevy_mesh",
|
||||
"bevy_camera",
|
||||
"bevy_light",
|
||||
"bevy_image",
|
||||
] }
|
||||
@@ -0,0 +1,126 @@
|
||||
# bsn-smoke — Bevy 0.20 generated-code compile check
|
||||
|
||||
`bsn-smoke` is Bevy Forge's proof that exported scenes are **real Bevy 0.20 code**, not
|
||||
just plausible-looking text. It is the only crate in this repository that depends on
|
||||
Bevy; it is excluded from the workspace (`Cargo.toml` → `exclude = ["compat/bsn-smoke"]`)
|
||||
so the editor, core and CLI crates stay Bevy-free and build in seconds.
|
||||
|
||||
```text
|
||||
compat/bsn-smoke/
|
||||
├── Cargo.toml standalone package (empty [workspace]), pinned Bevy 0.20.0
|
||||
├── Cargo.lock lockfile for the verification dependency graph
|
||||
├── src/lib.rs hand-written scenes mirroring every codegen shape + support types
|
||||
├── src/generated.rs BSN export of the 3D garden demo (written by the gate script)
|
||||
├── src/generated_2d.rs BSN export of the 2D platformer demo (written by the gate script)
|
||||
└── src/rust_export.rs imperative `Commands::spawn` export shape, also compiled
|
||||
```
|
||||
|
||||
## What is verified
|
||||
|
||||
Compile-checked against `bevy 0.20.0` (`default-features = false`):
|
||||
|
||||
- **Roots:** single root `bsn!` / `-> impl Scene`; multiple roots `bsn_list!` /
|
||||
`-> impl SceneList` with `--` separators.
|
||||
- **Hierarchy:** nested `Children [ ... -- ... ]`, including a `Visibility` patch on a
|
||||
grandchild and components before/after `Children`.
|
||||
- **Names and transforms:** `Name("...")`, full three-field `Transform` with
|
||||
`Quat::from_euler(EulerRot::XYZ, x.to_radians(), ...)`.
|
||||
- **Sprites:** image path handles, `Color::srgba`, `flip_x`/`flip_y`, and braced
|
||||
`custom_size: { Some(Vec2::new(w, h)) }` (the brace form is mandatory in BSN patches).
|
||||
- **Text:** `Text2d`, `TextFont { font_size: FontSize::Px(..) }`, `TextColor`.
|
||||
- **Cameras:** bare `Camera2d`, `Camera3d::default()`.
|
||||
- **Lights:** `PointLight` (color/intensity/range/shadows) and `DirectionalLight`
|
||||
(color/illuminance/shadows).
|
||||
- **Custom components:** struct-with-fields patches (`Health { current: 10.0, max: 10.0 }`,
|
||||
`forge_demo::Orbit { radius: 2.4, speed: 0.45 }`) and bare unit-component patches
|
||||
(`Marker`).
|
||||
- **3D meshes:** all five primitive shapes (`Cuboid`, `Sphere`, `Plane3d`, `Cylinder`,
|
||||
`Capsule3d`) with braced method-call handles (`Mesh3d({ meshes.add(..) })`) and inline
|
||||
materials (`MeshMaterial3d::<StandardMaterial>({ materials.add(StandardMaterial { ..default() }) })`),
|
||||
plus an asset mesh path (`Mesh3d("models/...#Mesh0/Primitive0")`) with an inline material.
|
||||
- **Generated signatures:** mesh scenes take `&mut Assets<Mesh>` and
|
||||
`&mut Assets<StandardMaterial>` only when needed.
|
||||
- **Imperative Rust export shape** (`src/rust_export.rs`): `commands.spawn`,
|
||||
`with_children`, `Name::new`, `Transform::from_xyz(..).with_rotation(..).with_scale(..)`,
|
||||
`Sprite { ..default() }`, `Mesh3d`/`MeshMaterial3d`, `Text2d::new`, `TextFont`,
|
||||
`TextColor`, `Camera3d::default()`, lights and `Visibility`.
|
||||
|
||||
The committed `src/generated*.rs` files are the CLI exports produced by the last
|
||||
successful gate run (plus the shim described below), so the crate compiles on a fresh
|
||||
checkout even before the script runs. `scripts/check-generated-code.sh` regenerates them
|
||||
from the demo project and recompiles, verifying the generator + demo scenes end to end.
|
||||
The exports are deterministic, so re-running the gate leaves the files unchanged.
|
||||
|
||||
### Compatibility shim for custom type paths
|
||||
|
||||
Generated BSN references custom components by their project type path, e.g.
|
||||
`forge_demo::Orbit`. In a real Bevy project the generated function is pasted where that
|
||||
path is already in scope. Here the generated file is a module, so the gate script prepends
|
||||
four lines (`#![allow(unused_imports)]`, two comments, and `use crate::*;`) to expose the
|
||||
crate-root support types; **the generated code itself is appended unchanged**. `lib.rs`
|
||||
provides `forge_demo::Orbit` with the same fields as `examples/demo-project/src/main.rs`.
|
||||
If a demo scene introduces another custom type, add its support type to `lib.rs`.
|
||||
|
||||
## Bevy version and feature set
|
||||
|
||||
Pinned to **Bevy 0.20.0** through `Cargo.lock`. Feature set (chosen by
|
||||
`.swarm/recon/R02.md` to keep compile time and APIs aligned with the verified BSN forms):
|
||||
|
||||
```toml
|
||||
bevy = { version = "0.20", default-features = false, features = [
|
||||
"std", "bevy_scene", "bevy_sprite", "bevy_asset", "bevy_color", "bevy_text",
|
||||
"bevy_pbr", "bevy_mesh", "bevy_camera", "bevy_light", "bevy_image",
|
||||
] }
|
||||
```
|
||||
|
||||
No windowing or audio backends are compiled in and no GPU device is initialized: this is a
|
||||
**compile** check that needs no display, asset files or `App`.
|
||||
|
||||
## Running
|
||||
|
||||
Directly (the load-bearing check):
|
||||
|
||||
```bash
|
||||
cd compat/bsn-smoke
|
||||
CARGO_NET_OFFLINE=true cargo check
|
||||
```
|
||||
|
||||
End to end (generator → CLI → Bevy compile):
|
||||
|
||||
```bash
|
||||
bash scripts/check-generated-code.sh
|
||||
```
|
||||
|
||||
The script exports `examples/demo-project/scenes/garden.forge.ron` and
|
||||
`.../platformer.forge.ron` with `cargo run -q -p bevy_forge_cli -- export ... --format bsn`,
|
||||
writes the results into this crate, and runs the check. It fails loudly on any error; if
|
||||
the CLI or demo project does not exist yet it fails with a clear message instead of
|
||||
silently passing. `FORGE_SKIP_GENERATED=1` skips with an explicit message (bootstrap only);
|
||||
`FORGE_SKIP_SECOND_SCENE=1` skips the 2D export; `FORGE_GARDEN_SCENE` /
|
||||
`FORGE_PLATFORMER_SCENE` override the scene paths; `CARGO_TARGET_DIR` is passed through.
|
||||
|
||||
## Expected cost
|
||||
|
||||
Measured on this Linux machine (Rust 1.99.1, 2026-10):
|
||||
|
||||
| Build | Wall time | `target/` size |
|
||||
|---|---|---|
|
||||
| First `cargo check`, cold target dir | **2 min 15 s** | ~803 MB |
|
||||
| Subsequent `cargo check`, warm target dir | < 1 s | — |
|
||||
| Reusing an existing Bevy 0.20 `target/` (e.g. recon) | ~1 s | — |
|
||||
|
||||
`compat/bsn-smoke/target/` is ignored by `.gitignore`; never commit it. If you already
|
||||
have a warm Bevy 0.20 target directory, point `CARGO_TARGET_DIR` at it to avoid the cold
|
||||
build (the dependency feature set must match exactly).
|
||||
|
||||
## Runtime caveats (compile ≠ loaded assets)
|
||||
|
||||
- Asset **paths** (`Sprite { image: "textures/x.png" }`, `Mesh3d("models/...gltf#...")`)
|
||||
compile, but Bevy turns them into `HandleTemplate::Path` values that are resolved by the
|
||||
`AssetServer` at spawn time. If the project has no loader/asset for the path, the handle
|
||||
stays loading/failed and nothing renders — it is not a compile error (see R02 §9).
|
||||
- Bevy Forge never emits material **asset paths** (`MeshMaterial3d::<StandardMaterial>("...")`),
|
||||
because those need a project-registered material loader; materials are always inline
|
||||
`materials.add(StandardMaterial { ..default() })` handles.
|
||||
- The check is compile-only: it does not spawn the scenes or validate asset existence.
|
||||
Asset references are validated separately by `forge validate` / the editor.
|
||||
@@ -0,0 +1,130 @@
|
||||
// compat/bsn-smoke shim: expose crate-root support types (demo custom components).
|
||||
// The generated code below this line is unchanged.
|
||||
#![allow(unused_imports)] // the crate::* glob also provides the Bevy prelude names
|
||||
use crate::*;
|
||||
use bevy::prelude::*;
|
||||
use bevy::scene::bsn_list;
|
||||
|
||||
pub fn scene(
|
||||
meshes: &mut Assets<Mesh>,
|
||||
materials: &mut Assets<StandardMaterial>,
|
||||
) -> impl SceneList {
|
||||
bsn_list! {
|
||||
Name("Camera")
|
||||
Transform {
|
||||
translation: Vec3::new(7.5, 5.0, 12.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, -16.0f32.to_radians(), 30.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Camera3d::default()
|
||||
--
|
||||
Name("Sun")
|
||||
Transform {
|
||||
translation: Vec3::new(0.0, 0.0, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, -55.0f32.to_radians(), -25.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
DirectionalLight { color: Color::srgba(1.0, 0.95, 0.85, 1.0), illuminance: 11000.0, shadow_maps_enabled: true }
|
||||
--
|
||||
Name("Ground")
|
||||
Transform {
|
||||
translation: Vec3::new(0.0, 0.0, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Mesh3d({ meshes.add(Plane3d::new(Vec3::Y, Vec2::new(13.0, 13.0))) })
|
||||
MeshMaterial3d::<StandardMaterial>({ materials.add(StandardMaterial {
|
||||
base_color: Color::srgba(0.31, 0.48, 0.23, 1.0),
|
||||
metallic: 0.0,
|
||||
perceptual_roughness: 0.92,
|
||||
unlit: false,
|
||||
..default()
|
||||
}) })
|
||||
--
|
||||
Name("Shrub")
|
||||
Transform {
|
||||
translation: Vec3::new(-3.4, 1.0, 1.8),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.25, 0.9, 1.25),
|
||||
}
|
||||
Mesh3d({ meshes.add(Sphere::new(1.0)) })
|
||||
MeshMaterial3d::<StandardMaterial>({ materials.add(StandardMaterial {
|
||||
base_color: Color::srgba(0.24, 0.45, 0.22, 1.0),
|
||||
metallic: 0.0,
|
||||
perceptual_roughness: 0.85,
|
||||
unlit: false,
|
||||
..default()
|
||||
}) })
|
||||
--
|
||||
Name("Flower Stem")
|
||||
Transform {
|
||||
translation: Vec3::new(2.6, 0.75, 1.4),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Mesh3d({ meshes.add(Cylinder::new(0.09, 1.5)) })
|
||||
MeshMaterial3d::<StandardMaterial>({ materials.add(StandardMaterial {
|
||||
base_color: Color::srgba(0.35, 0.55, 0.27, 1.0),
|
||||
metallic: 0.0,
|
||||
perceptual_roughness: 0.8,
|
||||
unlit: false,
|
||||
..default()
|
||||
}) })
|
||||
Children [
|
||||
Name("Flower Bloom")
|
||||
Transform {
|
||||
translation: Vec3::new(0.0, 1.0, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 0.75, 1.0),
|
||||
}
|
||||
Mesh3d({ meshes.add(Sphere::new(0.42)) })
|
||||
MeshMaterial3d::<StandardMaterial>({ materials.add(StandardMaterial {
|
||||
base_color: Color::srgba(0.91, 0.42, 0.65, 1.0),
|
||||
metallic: 0.0,
|
||||
perceptual_roughness: 0.4,
|
||||
unlit: false,
|
||||
..default()
|
||||
}) })
|
||||
]
|
||||
--
|
||||
Name("Orbit Crystal")
|
||||
Transform {
|
||||
translation: Vec3::new(0.0, 2.0, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Mesh3d({ meshes.add(Sphere::new(0.8)) })
|
||||
MeshMaterial3d::<StandardMaterial>({ materials.add(StandardMaterial {
|
||||
base_color: Color::srgba(0.27, 0.76, 0.69, 1.0),
|
||||
metallic: 0.15,
|
||||
perceptual_roughness: 0.2,
|
||||
unlit: false,
|
||||
..default()
|
||||
}) })
|
||||
forge_demo::Orbit { radius: 2.4, speed: 0.45 }
|
||||
--
|
||||
Name("Lantern Post")
|
||||
Transform {
|
||||
translation: Vec3::new(3.8, 1.2, -1.2),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Mesh3d({ meshes.add(Cylinder::new(0.12, 2.4)) })
|
||||
MeshMaterial3d::<StandardMaterial>({ materials.add(StandardMaterial {
|
||||
base_color: Color::srgba(0.18, 0.2, 0.25, 1.0),
|
||||
metallic: 0.6,
|
||||
perceptual_roughness: 0.35,
|
||||
unlit: false,
|
||||
..default()
|
||||
}) })
|
||||
Children [
|
||||
Name("Lantern Glow")
|
||||
Transform {
|
||||
translation: Vec3::new(0.0, 1.25, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
PointLight { color: Color::srgba(1.0, 0.82, 0.55, 1.0), intensity: 600.0, range: 12.0, shadow_maps_enabled: true }
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
// compat/bsn-smoke shim: expose crate-root support types (demo custom components).
|
||||
// The generated code below this line is unchanged.
|
||||
#![allow(unused_imports)] // the crate::* glob also provides the Bevy prelude names
|
||||
use crate::*;
|
||||
use bevy::prelude::*;
|
||||
use bevy::scene::bsn_list;
|
||||
|
||||
pub fn scene() -> impl SceneList {
|
||||
bsn_list! {
|
||||
Name("Backdrop")
|
||||
Transform {
|
||||
translation: Vec3::new(0.0, 0.0, -30.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { image: "textures/background.png", color: Color::srgba(1.0, 1.0, 1.0, 1.0), custom_size: { Some(Vec2::new(960.0, 540.0)) }, flip_x: false, flip_y: false }
|
||||
--
|
||||
Name("Camera")
|
||||
Transform {
|
||||
translation: Vec3::new(0.0, 0.0, 10.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Camera2d
|
||||
--
|
||||
Name("Title")
|
||||
Transform {
|
||||
translation: Vec3::new(0.0, 214.0, 5.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Text2d("Bevy Forge Platformer")
|
||||
TextFont { font_size: FontSize::Px(44.0) }
|
||||
TextColor(Color::srgba(0.96, 0.94, 0.88, 1.0))
|
||||
--
|
||||
Name("Hint")
|
||||
Transform {
|
||||
translation: Vec3::new(0.0, 176.0, 5.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Text2d("Collect the coins!")
|
||||
TextFont { font_size: FontSize::Px(20.0) }
|
||||
TextColor(Color::srgba(0.95, 0.67, 0.25, 1.0))
|
||||
--
|
||||
Name("Ground 1")
|
||||
Transform {
|
||||
translation: Vec3::new(-336.0, -224.0, -2.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { image: "textures/tile.png", color: Color::srgba(1.0, 1.0, 1.0, 1.0), custom_size: { Some(Vec2::new(96.0, 96.0)) }, flip_x: false, flip_y: false }
|
||||
--
|
||||
Name("Ground 2")
|
||||
Transform {
|
||||
translation: Vec3::new(-240.0, -224.0, -2.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { image: "textures/tile.png", color: Color::srgba(1.0, 1.0, 1.0, 1.0), custom_size: { Some(Vec2::new(96.0, 96.0)) }, flip_x: false, flip_y: false }
|
||||
--
|
||||
Name("Ground 3")
|
||||
Transform {
|
||||
translation: Vec3::new(-144.0, -224.0, -2.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { image: "textures/tile.png", color: Color::srgba(1.0, 1.0, 1.0, 1.0), custom_size: { Some(Vec2::new(96.0, 96.0)) }, flip_x: false, flip_y: false }
|
||||
--
|
||||
Name("Ground 4")
|
||||
Transform {
|
||||
translation: Vec3::new(-48.0, -224.0, -2.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { image: "textures/tile.png", color: Color::srgba(1.0, 1.0, 1.0, 1.0), custom_size: { Some(Vec2::new(96.0, 96.0)) }, flip_x: false, flip_y: false }
|
||||
--
|
||||
Name("Ground 5")
|
||||
Transform {
|
||||
translation: Vec3::new(48.0, -224.0, -2.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { image: "textures/tile.png", color: Color::srgba(1.0, 1.0, 1.0, 1.0), custom_size: { Some(Vec2::new(96.0, 96.0)) }, flip_x: false, flip_y: false }
|
||||
--
|
||||
Name("Ground 6")
|
||||
Transform {
|
||||
translation: Vec3::new(144.0, -224.0, -2.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { image: "textures/tile.png", color: Color::srgba(1.0, 1.0, 1.0, 1.0), custom_size: { Some(Vec2::new(96.0, 96.0)) }, flip_x: false, flip_y: false }
|
||||
--
|
||||
Name("Ground 7")
|
||||
Transform {
|
||||
translation: Vec3::new(240.0, -224.0, -2.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { image: "textures/tile.png", color: Color::srgba(1.0, 1.0, 1.0, 1.0), custom_size: { Some(Vec2::new(96.0, 96.0)) }, flip_x: false, flip_y: false }
|
||||
--
|
||||
Name("Ground 8")
|
||||
Transform {
|
||||
translation: Vec3::new(336.0, -224.0, -2.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { image: "textures/tile.png", color: Color::srgba(1.0, 1.0, 1.0, 1.0), custom_size: { Some(Vec2::new(96.0, 96.0)) }, flip_x: false, flip_y: false }
|
||||
--
|
||||
Name("Ledge 1")
|
||||
Transform {
|
||||
translation: Vec3::new(-48.0, -64.0, -2.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { image: "textures/tile.png", color: Color::srgba(1.0, 1.0, 1.0, 1.0), custom_size: { Some(Vec2::new(96.0, 96.0)) }, flip_x: false, flip_y: false }
|
||||
--
|
||||
Name("Ledge 2")
|
||||
Transform {
|
||||
translation: Vec3::new(48.0, -64.0, -2.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { image: "textures/tile.png", color: Color::srgba(1.0, 1.0, 1.0, 1.0), custom_size: { Some(Vec2::new(96.0, 96.0)) }, flip_x: false, flip_y: false }
|
||||
--
|
||||
Name("Ledge 3")
|
||||
Transform {
|
||||
translation: Vec3::new(144.0, -64.0, -2.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { image: "textures/tile.png", color: Color::srgba(1.0, 1.0, 1.0, 1.0), custom_size: { Some(Vec2::new(96.0, 96.0)) }, flip_x: false, flip_y: false }
|
||||
--
|
||||
Name("Player")
|
||||
Transform {
|
||||
translation: Vec3::new(-192.0, -128.0, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { image: "textures/player.png", color: Color::srgba(1.0, 1.0, 1.0, 1.0), custom_size: { Some(Vec2::new(64.0, 96.0)) }, flip_x: false, flip_y: false }
|
||||
Children [
|
||||
Name("Hat")
|
||||
Transform {
|
||||
translation: Vec3::new(0.0, 42.0, 1.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { image: "textures/hat.png", color: Color::srgba(1.0, 1.0, 1.0, 1.0), custom_size: { Some(Vec2::new(56.0, 28.0)) }, flip_x: false, flip_y: false }
|
||||
]
|
||||
--
|
||||
Name("Coin 1")
|
||||
Transform {
|
||||
translation: Vec3::new(-288.0, -104.0, 1.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { image: "textures/coin.png", color: Color::srgba(1.0, 1.0, 1.0, 1.0), custom_size: { Some(Vec2::new(48.0, 48.0)) }, flip_x: false, flip_y: false }
|
||||
--
|
||||
Name("Coin 2")
|
||||
Transform {
|
||||
translation: Vec3::new(48.0, 28.0, 1.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { image: "textures/coin.png", color: Color::srgba(1.0, 1.0, 1.0, 1.0), custom_size: { Some(Vec2::new(48.0, 48.0)) }, flip_x: false, flip_y: false }
|
||||
--
|
||||
Name("Coin 3")
|
||||
Transform {
|
||||
translation: Vec3::new(240.0, -96.0, 1.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { image: "textures/coin.png", color: Color::srgba(1.0, 1.0, 1.0, 1.0), custom_size: { Some(Vec2::new(48.0, 48.0)) }, flip_x: false, flip_y: false }
|
||||
--
|
||||
Name("Crate")
|
||||
Transform {
|
||||
translation: Vec3::new(352.0, -144.0, -1.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { image: "textures/crate.png", color: Color::srgba(1.0, 1.0, 1.0, 1.0), custom_size: { Some(Vec2::new(64.0, 64.0)) }, flip_x: false, flip_y: false }
|
||||
--
|
||||
Name("Crate 2")
|
||||
Transform {
|
||||
translation: Vec3::new(352.0, -80.0, -1.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { image: "textures/crate.png", color: Color::srgba(1.0, 1.0, 1.0, 1.0), custom_size: { Some(Vec2::new(64.0, 64.0)) }, flip_x: false, flip_y: false }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
//! Compile-smoke crate for Bevy Forge's generated code.
|
||||
//!
|
||||
//! This crate is intentionally **not** a workspace member: it is the only place
|
||||
//! in the repository that depends on Bevy (0.20.0). It compiles:
|
||||
//!
|
||||
//! * hand-written scenes that mirror `ExportFormat::Bsn` output for every
|
||||
//! supported component, hierarchy shape and asset pattern (this file), and
|
||||
//! * the real files written by `scripts/check-generated-code.sh`
|
||||
//! (`src/generated.rs`, `src/generated_2d.rs`).
|
||||
//!
|
||||
//! See `README.md` for the feature set, commands and expected build cost.
|
||||
//! The generated code targets the compile-verified forms in `.swarm/recon/R02.md`.
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::scene::bsn;
|
||||
use bevy::scene::bsn_list;
|
||||
|
||||
/// Bevy Forge's BSN export for `examples/demo-project/scenes/garden.forge.ron`.
|
||||
///
|
||||
/// Committed with a harmless starter scene so this crate compiles before the
|
||||
/// end-to-end gate has run; `scripts/check-generated-code.sh` replaces it with
|
||||
/// the real CLI export.
|
||||
#[path = "generated.rs"]
|
||||
pub mod generated;
|
||||
|
||||
/// Bevy Forge's BSN export for `examples/demo-project/scenes/platformer.forge.ron`.
|
||||
#[path = "generated_2d.rs"]
|
||||
pub mod generated_2d;
|
||||
|
||||
/// Imperative `Commands::spawn` export shape (`ExportFormat::Rust`), mirroring
|
||||
/// what the CLI emits. It is compiled here so the secondary export format is
|
||||
/// verified against real Bevy types as well.
|
||||
pub mod rust_export;
|
||||
|
||||
/// A custom component with fields, as emitted for `ComponentData::Custom`.
|
||||
///
|
||||
/// `Component + Default + Clone` is enough for Bevy 0.20's blanket
|
||||
/// `FromTemplate` implementation (see R02 §4), so generated
|
||||
/// `Health { current: 10.0, max: 10.0 }` patches compile.
|
||||
#[derive(Component, Default, Clone)]
|
||||
pub struct Health {
|
||||
pub current: f32,
|
||||
pub max: f32,
|
||||
}
|
||||
|
||||
/// A unit custom component: Bevy Forge emits the bare type path when a custom
|
||||
/// component has no fields.
|
||||
#[derive(Component, Default, Clone)]
|
||||
pub struct Marker;
|
||||
|
||||
/// Support types from `examples/demo-project/src/main.rs`.
|
||||
///
|
||||
/// The demo garden scene carries a custom `forge_demo::Orbit` component, so the
|
||||
/// generated garden BSN references that path. Providing the module here lets the
|
||||
/// export compile in the smoke crate without editing the generated file.
|
||||
pub mod forge_demo {
|
||||
use bevy::prelude::*;
|
||||
|
||||
/// Slowly orbits an entity around the world origin (demo project component).
|
||||
#[derive(Component, Default, Clone)]
|
||||
pub struct Orbit {
|
||||
pub radius: f32,
|
||||
pub speed: f32,
|
||||
}
|
||||
}
|
||||
|
||||
/// 2D scene: single root, `Name`/`Transform`/`Sprite` with image and braced
|
||||
/// `custom_size`, a custom component with fields, a unit custom component, and
|
||||
/// nested `Children` (including a visibility patch on a grandchild).
|
||||
///
|
||||
/// Mirrors the shape generated for a typical platformer root entity.
|
||||
pub fn scene_2d() -> impl Scene {
|
||||
bsn! {
|
||||
Name("Player")
|
||||
Transform {
|
||||
translation: Vec3::new(0.0, 1.0, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0, 0.0, 0.0),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite {
|
||||
image: "textures/player.png",
|
||||
color: Color::srgba(1.0, 1.0, 1.0, 1.0),
|
||||
custom_size: { Some(Vec2::new(64.0, 64.0)) },
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
}
|
||||
Health { current: 10.0, max: 10.0 }
|
||||
forge_demo::Orbit { radius: 2.5, speed: 0.8 }
|
||||
Children [
|
||||
Name("Hat")
|
||||
Transform {
|
||||
translation: Vec3::new(0.0, 40.0, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0, 0.0, 0.0),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { color: Color::srgba(1.0, 0.5, 0.2, 1.0) }
|
||||
Marker
|
||||
Children [
|
||||
Name("Glow")
|
||||
Transform {
|
||||
translation: Vec3::new(0.0, 0.5, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0, 0.0, 0.0),
|
||||
scale: Vec3::new(0.5, 0.5, 1.0),
|
||||
}
|
||||
Sprite { color: Color::srgba(1.0, 1.0, 0.4, 0.6) }
|
||||
Visibility::Visible
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/// Multiple roots: the generator emits `bsn_list!` plus `--` separators and a
|
||||
/// `-> impl SceneList` return type.
|
||||
pub fn scene_2d_list() -> impl SceneList {
|
||||
bsn_list! {
|
||||
Name("Left")
|
||||
Transform {
|
||||
translation: Vec3::new(-2.0, 0.0, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0, 0.0, 0.0),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { image: "textures/tile.png", color: Color::srgba(1.0, 1.0, 1.0, 1.0) }
|
||||
--
|
||||
Name("Right")
|
||||
Transform {
|
||||
translation: Vec3::new(2.0, 0.0, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0, 0.0, 0.0),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { color: Color::srgba(0.9, 0.6, 0.3, 1.0) }
|
||||
Children [
|
||||
Name("Right Child")
|
||||
Transform {
|
||||
translation: Vec3::new(0.0, 1.0, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0, 0.0, 0.0),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { color: Color::srgba(0.2, 0.8, 0.9, 1.0) }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/// Text: `Text2d` payload plus `TextFont`/`TextColor` patches.
|
||||
pub fn scene_text() -> impl Scene {
|
||||
bsn! {
|
||||
Name("Title")
|
||||
Text2d("Bevy Forge\nHello \"BSN\"")
|
||||
TextFont { font_size: FontSize::Px(32.0) }
|
||||
TextColor(Color::srgba(1.0, 0.9, 0.8, 1.0))
|
||||
Transform {
|
||||
translation: Vec3::new(0.0, 200.0, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0, 0.0, 0.0),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Bare `Camera2d` patch, as emitted for 2D scenes.
|
||||
pub fn scene_camera_2d() -> impl Scene {
|
||||
bsn! {
|
||||
Name("Main Camera")
|
||||
Camera2d
|
||||
Transform {
|
||||
translation: Vec3::new(0.0, 0.0, 999.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0, 0.0, 0.0),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 3D stage: `Camera3d::default()`, both light types and the `Visibility`
|
||||
/// variants on children (a single-root scene with three child entities).
|
||||
pub fn scene_3d_stage() -> impl Scene {
|
||||
bsn! {
|
||||
Name("Stage")
|
||||
Transform {
|
||||
translation: Vec3::new(0.0, 0.0, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0, 0.0, 0.0),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Children [
|
||||
Name("Main Camera")
|
||||
Camera3d::default()
|
||||
Transform {
|
||||
translation: Vec3::new(0.0, 4.0, 10.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, -0.35, 0.0, 0.0),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
--
|
||||
Name("Sun")
|
||||
DirectionalLight {
|
||||
color: Color::srgb(1.0, 0.95, 0.9),
|
||||
illuminance: 10000.0,
|
||||
shadow_maps_enabled: true,
|
||||
}
|
||||
Transform {
|
||||
translation: Vec3::new(0.0, 8.0, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, -0.8, 0.4, 0.0),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Visibility::Visible
|
||||
--
|
||||
Name("Lantern")
|
||||
PointLight {
|
||||
color: Color::srgb(1.0, 0.6, 0.25),
|
||||
intensity: 800.0,
|
||||
range: 20.0,
|
||||
shadow_maps_enabled: true,
|
||||
}
|
||||
Transform {
|
||||
translation: Vec3::new(1.5, 1.0, 1.5),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0, 0.0, 0.0),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Visibility::Inherited
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/// 3D primitives and inline materials: the generated function takes the
|
||||
/// `meshes`/`materials` asset parameters when a scene contains meshes.
|
||||
///
|
||||
/// Covers all five `PrimitiveShape` variants from the codegen contract.
|
||||
pub fn scene_3d_primitives(
|
||||
meshes: &mut Assets<Mesh>,
|
||||
materials: &mut Assets<StandardMaterial>,
|
||||
) -> impl Scene {
|
||||
bsn! {
|
||||
Name("Primitives")
|
||||
Transform {
|
||||
translation: Vec3::new(0.0, 0.0, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0, 0.0, 0.0),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Children [
|
||||
Name("Cube")
|
||||
Mesh3d({ meshes.add(Cuboid::new(1.0, 1.0, 1.0)) })
|
||||
MeshMaterial3d::<StandardMaterial>({ materials.add(StandardMaterial {
|
||||
base_color: Color::srgb(0.9, 0.35, 0.3),
|
||||
metallic: 0.0,
|
||||
perceptual_roughness: 0.5,
|
||||
unlit: false,
|
||||
..default()
|
||||
}) })
|
||||
Transform {
|
||||
translation: Vec3::new(-2.0, 0.5, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0, 0.0, 0.0),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
--
|
||||
Name("Sphere")
|
||||
Mesh3d({ meshes.add(Sphere::new(0.5)) })
|
||||
MeshMaterial3d::<StandardMaterial>({ materials.add(StandardMaterial {
|
||||
base_color: Color::srgb(0.3, 0.6, 0.9),
|
||||
metallic: 0.0,
|
||||
perceptual_roughness: 0.5,
|
||||
unlit: false,
|
||||
..default()
|
||||
}) })
|
||||
Transform {
|
||||
translation: Vec3::new(-1.0, 0.5, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0, 0.0, 0.0),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
--
|
||||
Name("Plane")
|
||||
Mesh3d({ meshes.add(Plane3d::new(Vec3::Y, Vec2::new(6.0, 6.0))) })
|
||||
MeshMaterial3d::<StandardMaterial>({ materials.add(StandardMaterial {
|
||||
base_color: Color::srgb(0.35, 0.5, 0.3),
|
||||
metallic: 0.0,
|
||||
perceptual_roughness: 0.9,
|
||||
unlit: false,
|
||||
..default()
|
||||
}) })
|
||||
Transform {
|
||||
translation: Vec3::new(0.0, 0.0, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0, 0.0, 0.0),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
--
|
||||
Name("Cylinder")
|
||||
Mesh3d({ meshes.add(Cylinder::new(0.4, 1.6)) })
|
||||
MeshMaterial3d::<StandardMaterial>({ materials.add(StandardMaterial {
|
||||
base_color: Color::srgb(0.8, 0.7, 0.25),
|
||||
metallic: 0.1,
|
||||
perceptual_roughness: 0.4,
|
||||
unlit: false,
|
||||
..default()
|
||||
}) })
|
||||
Transform {
|
||||
translation: Vec3::new(1.0, 0.8, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0, 0.0, 0.0),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
--
|
||||
Name("Capsule")
|
||||
Mesh3d({ meshes.add(Capsule3d::new(0.3, 1.0)) })
|
||||
MeshMaterial3d::<StandardMaterial>({ materials.add(StandardMaterial {
|
||||
base_color: Color::srgb(0.55, 0.4, 0.8),
|
||||
metallic: 0.0,
|
||||
perceptual_roughness: 0.6,
|
||||
unlit: false,
|
||||
..default()
|
||||
}) })
|
||||
Transform {
|
||||
translation: Vec3::new(2.0, 0.8, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0, 0.0, 0.0),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/// Asset mesh: `Mesh3d("<path>")` uses a path handle while the material is an
|
||||
/// inline `materials.add(..)` handle (the generator never emits asset material
|
||||
/// paths, which would need a project-registered loader at runtime).
|
||||
pub fn scene_asset_mesh(materials: &mut Assets<StandardMaterial>) -> impl Scene {
|
||||
bsn! {
|
||||
Name("Crate")
|
||||
Mesh3d("models/crate.gltf#Mesh0/Primitive0")
|
||||
MeshMaterial3d::<StandardMaterial>({ materials.add(StandardMaterial {
|
||||
base_color: Color::srgb(0.6, 0.45, 0.3),
|
||||
metallic: 0.0,
|
||||
perceptual_roughness: 0.8,
|
||||
unlit: false,
|
||||
..default()
|
||||
}) })
|
||||
Transform {
|
||||
translation: Vec3::new(0.0, 0.5, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0, 0.0, 0.0),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
//! Compile check for Bevy Forge's secondary export format: imperative
|
||||
//! `Commands::spawn` code (`ExportFormat::Rust`, contract §3.2).
|
||||
//!
|
||||
//! This mirrors what a user gets when pasting the Rust export into a system.
|
||||
//! It is hand-written from the contract shape (A04's generator output is
|
||||
//! verified separately by its tests); the value here is that every Bevy API call
|
||||
//! the exporter emits is checked against real Bevy 0.20.0.
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::Health;
|
||||
|
||||
/// Representative Rust export: a 2D sprite with a child and a custom component,
|
||||
/// a primitive mesh with an inline material, text and a 3D camera + light.
|
||||
///
|
||||
/// The parameter set matches the contract: `commands` always, `asset_server`
|
||||
/// only because a sprite image is loaded, `meshes`/`materials` only because a
|
||||
/// primitive mesh is spawned.
|
||||
pub fn scene(
|
||||
commands: &mut Commands,
|
||||
asset_server: &AssetServer,
|
||||
meshes: &mut Assets<Mesh>,
|
||||
materials: &mut Assets<StandardMaterial>,
|
||||
) {
|
||||
commands
|
||||
.spawn((
|
||||
Name::new("Player"),
|
||||
Transform::from_xyz(0.0, 1.0, 0.0)
|
||||
.with_rotation(Quat::from_euler(EulerRot::XYZ, 0.0, 0.0, 0.0))
|
||||
.with_scale(Vec3::new(1.0, 1.0, 1.0)),
|
||||
Sprite {
|
||||
image: asset_server.load("textures/player.png"),
|
||||
color: Color::srgba(1.0, 1.0, 1.0, 1.0),
|
||||
custom_size: Some(Vec2::new(64.0, 64.0)),
|
||||
..default()
|
||||
},
|
||||
Health {
|
||||
current: 10.0,
|
||||
max: 10.0,
|
||||
},
|
||||
))
|
||||
.with_children(|parent| {
|
||||
parent.spawn((
|
||||
Name::new("Hat"),
|
||||
Transform::from_xyz(0.0, 40.0, 0.0),
|
||||
Sprite {
|
||||
color: Color::srgba(1.0, 0.5, 0.2, 1.0),
|
||||
..default()
|
||||
},
|
||||
));
|
||||
});
|
||||
|
||||
commands.spawn((
|
||||
Name::new("Cube"),
|
||||
Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
|
||||
MeshMaterial3d(materials.add(StandardMaterial {
|
||||
base_color: Color::srgb(0.9, 0.35, 0.3),
|
||||
metallic: 0.0,
|
||||
perceptual_roughness: 0.5,
|
||||
unlit: false,
|
||||
..default()
|
||||
})),
|
||||
Transform::from_xyz(0.0, 0.5, 0.0),
|
||||
));
|
||||
|
||||
commands.spawn((
|
||||
Name::new("Title"),
|
||||
Text2d::new("Bevy Forge"),
|
||||
TextFont {
|
||||
font_size: FontSize::Px(32.0),
|
||||
..default()
|
||||
},
|
||||
TextColor(Color::WHITE),
|
||||
Transform::from_xyz(0.0, 2.0, 0.0),
|
||||
));
|
||||
|
||||
commands.spawn((
|
||||
Name::new("Main Camera"),
|
||||
Camera3d::default(),
|
||||
Transform::from_xyz(0.0, 4.0, 10.0),
|
||||
));
|
||||
|
||||
commands.spawn((
|
||||
Name::new("Sun"),
|
||||
DirectionalLight {
|
||||
illuminance: 10000.0,
|
||||
shadow_maps_enabled: true,
|
||||
..default()
|
||||
},
|
||||
Transform::default(),
|
||||
));
|
||||
|
||||
commands.spawn((
|
||||
Name::new("Hidden Helper"),
|
||||
Transform::default(),
|
||||
Visibility::Hidden,
|
||||
));
|
||||
}
|
||||
Reference in New Issue
Block a user