Bevy Forge 0.1.0: visual scene editor for Bevy 0.20 BSN
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "forge-demo"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
bevy = "0.20"
|
||||
|
||||
# Standalone project on purpose: Bevy Forge opens this directory as a Bevy project,
|
||||
# and the repository's editor workspace must not build Bevy.
|
||||
[workspace]
|
||||
@@ -0,0 +1,57 @@
|
||||
# Forge Demo
|
||||
|
||||
A tiny Bevy 0.20 project used as the demo project for **Bevy Forge**, the visual scene
|
||||
editor for Bevy BSN scenes. It contains two editable scenes and a small set of generated
|
||||
pixel-art textures.
|
||||
|
||||
## Open it in Bevy Forge
|
||||
|
||||
From the repository root:
|
||||
|
||||
```bash
|
||||
cargo run -p bevy_forge_editor -- examples/demo-project
|
||||
```
|
||||
|
||||
Bevy Forge discovers the Bevy project from `Cargo.toml`, scans `assets/` and opens the
|
||||
first scene automatically. Open the other scene from the File menu, edit entities in the
|
||||
hierarchy/inspector, and save with `Ctrl+S`. Use **Export** (or the `forge` CLI) to turn a
|
||||
scene into paste-ready BSN Rust code or imperative `Commands::spawn` code.
|
||||
|
||||
## Scenes
|
||||
|
||||
| Scene | Contents |
|
||||
|---|---|
|
||||
| `scenes/garden.forge.ron` | 3D garden: camera, sun, point light, ground plane, shrub, flower, lantern post and an orbiting crystal with the custom `forge_demo::Orbit` component. |
|
||||
| `scenes/platformer.forge.ron` | 2D platformer: camera, dusk background, ground tiles, floating ledge, player with a hat child, coins, crates and two Text2d labels. |
|
||||
|
||||
Both scenes are plain RON and human-readable. Every referenced texture exists in
|
||||
`assets/textures/`.
|
||||
|
||||
## Textures
|
||||
|
||||
All textures are procedurally generated (no external art, no network):
|
||||
|
||||
| File | Size |
|
||||
|---|---|
|
||||
| `assets/textures/background.png` | 960x540 |
|
||||
| `assets/textures/player.png` | 64x96 |
|
||||
| `assets/textures/hat.png` | 64x32 |
|
||||
| `assets/textures/tile.png` | 96x96 |
|
||||
| `assets/textures/coin.png` | 64x64 |
|
||||
| `assets/textures/crate.png` | 64x64 |
|
||||
|
||||
Regenerate them with Pillow:
|
||||
|
||||
```bash
|
||||
python3 examples/demo-project/tools/generate_textures.py
|
||||
```
|
||||
|
||||
## Running it as a Bevy app
|
||||
|
||||
`src/main.rs` is a minimal Bevy app. The `.forge.ron` scenes are editor documents, not
|
||||
Bevy runtime assets yet, so paste an export into `src/main.rs` to run it. The custom
|
||||
`Orbit` component used by the garden scene is defined there as
|
||||
`forge_demo::Orbit { radius, speed }`, matching the exported BSN path.
|
||||
|
||||
This demo is deliberately **not** a member of the editor workspace: only the excluded
|
||||
`compat/bsn-smoke` crate may depend on Bevy.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 345 B |
Binary file not shown.
|
After Width: | Height: | Size: 336 B |
Binary file not shown.
|
After Width: | Height: | Size: 217 B |
Binary file not shown.
|
After Width: | Height: | Size: 496 B |
Binary file not shown.
|
After Width: | Height: | Size: 583 B |
@@ -0,0 +1,437 @@
|
||||
(
|
||||
format_version: 1,
|
||||
name: "Garden Demo",
|
||||
entities: {
|
||||
1: (
|
||||
id: 1,
|
||||
name: "Camera",
|
||||
parent: None,
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: 7.5,
|
||||
y: 5.0,
|
||||
z: 12.0,
|
||||
),
|
||||
rotation: (
|
||||
x: -16.0,
|
||||
y: 30.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: camera3d,
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
2: (
|
||||
id: 2,
|
||||
name: "Sun",
|
||||
parent: None,
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
rotation: (
|
||||
x: -55.0,
|
||||
y: -25.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: directional_light,
|
||||
data: (
|
||||
color: (
|
||||
r: 1.0,
|
||||
g: 0.95,
|
||||
b: 0.85,
|
||||
a: 1.0,
|
||||
),
|
||||
illuminance: 11000.0,
|
||||
shadows: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
3: (
|
||||
id: 3,
|
||||
name: "Ground",
|
||||
parent: None,
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: mesh3d,
|
||||
data: (
|
||||
mesh: (
|
||||
source: primitive,
|
||||
value: plane(
|
||||
size: (
|
||||
x: 26.0,
|
||||
y: 26.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
material: (
|
||||
base_color: (
|
||||
r: 0.31,
|
||||
g: 0.48,
|
||||
b: 0.23,
|
||||
a: 1.0,
|
||||
),
|
||||
metallic: 0.0,
|
||||
perceptual_roughness: 0.92,
|
||||
unlit: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
4: (
|
||||
id: 4,
|
||||
name: "Shrub",
|
||||
parent: None,
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: -3.4,
|
||||
y: 1.0,
|
||||
z: 1.8,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.25,
|
||||
y: 0.9,
|
||||
z: 1.25,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: mesh3d,
|
||||
data: (
|
||||
mesh: (
|
||||
source: primitive,
|
||||
value: sphere(
|
||||
radius: 1.0,
|
||||
),
|
||||
),
|
||||
material: (
|
||||
base_color: (
|
||||
r: 0.24,
|
||||
g: 0.45,
|
||||
b: 0.22,
|
||||
a: 1.0,
|
||||
),
|
||||
metallic: 0.0,
|
||||
perceptual_roughness: 0.85,
|
||||
unlit: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
5: (
|
||||
id: 5,
|
||||
name: "Flower Stem",
|
||||
parent: None,
|
||||
children: [
|
||||
6,
|
||||
],
|
||||
transform: (
|
||||
translation: (
|
||||
x: 2.6,
|
||||
y: 0.75,
|
||||
z: 1.4,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: mesh3d,
|
||||
data: (
|
||||
mesh: (
|
||||
source: primitive,
|
||||
value: cylinder(
|
||||
radius: 0.09,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
material: (
|
||||
base_color: (
|
||||
r: 0.35,
|
||||
g: 0.55,
|
||||
b: 0.27,
|
||||
a: 1.0,
|
||||
),
|
||||
metallic: 0.0,
|
||||
perceptual_roughness: 0.8,
|
||||
unlit: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
6: (
|
||||
id: 6,
|
||||
name: "Flower Bloom",
|
||||
parent: Some(5),
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: 0.0,
|
||||
y: 1.0,
|
||||
z: 0.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 0.75,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: mesh3d,
|
||||
data: (
|
||||
mesh: (
|
||||
source: primitive,
|
||||
value: sphere(
|
||||
radius: 0.42,
|
||||
),
|
||||
),
|
||||
material: (
|
||||
base_color: (
|
||||
r: 0.91,
|
||||
g: 0.42,
|
||||
b: 0.65,
|
||||
a: 1.0,
|
||||
),
|
||||
metallic: 0.0,
|
||||
perceptual_roughness: 0.4,
|
||||
unlit: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
7: (
|
||||
id: 7,
|
||||
name: "Orbit Crystal",
|
||||
parent: None,
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: 0.0,
|
||||
y: 2.0,
|
||||
z: 0.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: mesh3d,
|
||||
data: (
|
||||
mesh: (
|
||||
source: primitive,
|
||||
value: sphere(
|
||||
radius: 0.8,
|
||||
),
|
||||
),
|
||||
material: (
|
||||
base_color: (
|
||||
r: 0.27,
|
||||
g: 0.76,
|
||||
b: 0.69,
|
||||
a: 1.0,
|
||||
),
|
||||
metallic: 0.15,
|
||||
perceptual_roughness: 0.2,
|
||||
unlit: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
(
|
||||
type: custom,
|
||||
data: (
|
||||
type_path: "forge_demo::Orbit",
|
||||
fields: [
|
||||
(
|
||||
name: "radius",
|
||||
expr: "2.4",
|
||||
),
|
||||
(
|
||||
name: "speed",
|
||||
expr: "0.45",
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
8: (
|
||||
id: 8,
|
||||
name: "Lantern Post",
|
||||
parent: None,
|
||||
children: [
|
||||
9,
|
||||
],
|
||||
transform: (
|
||||
translation: (
|
||||
x: 3.8,
|
||||
y: 1.2,
|
||||
z: -1.2,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: mesh3d,
|
||||
data: (
|
||||
mesh: (
|
||||
source: primitive,
|
||||
value: cylinder(
|
||||
radius: 0.12,
|
||||
height: 2.4,
|
||||
),
|
||||
),
|
||||
material: (
|
||||
base_color: (
|
||||
r: 0.18,
|
||||
g: 0.2,
|
||||
b: 0.25,
|
||||
a: 1.0,
|
||||
),
|
||||
metallic: 0.6,
|
||||
perceptual_roughness: 0.35,
|
||||
unlit: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
9: (
|
||||
id: 9,
|
||||
name: "Lantern Glow",
|
||||
parent: Some(8),
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: 0.0,
|
||||
y: 1.25,
|
||||
z: 0.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: point_light,
|
||||
data: (
|
||||
color: (
|
||||
r: 1.0,
|
||||
g: 0.82,
|
||||
b: 0.55,
|
||||
a: 1.0,
|
||||
),
|
||||
intensity: 600.0,
|
||||
range: 12.0,
|
||||
shadows: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
},
|
||||
roots: [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
7,
|
||||
8,
|
||||
],
|
||||
next_id: 10,
|
||||
)
|
||||
@@ -0,0 +1,975 @@
|
||||
(
|
||||
format_version: 1,
|
||||
name: "Platformer Demo",
|
||||
entities: {
|
||||
1: (
|
||||
id: 1,
|
||||
name: "Backdrop",
|
||||
parent: None,
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: -30.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: sprite,
|
||||
data: (
|
||||
image: Some("textures/background.png"),
|
||||
color: (
|
||||
r: 1.0,
|
||||
g: 1.0,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
),
|
||||
custom_size: Some((
|
||||
x: 960.0,
|
||||
y: 540.0,
|
||||
)),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
2: (
|
||||
id: 2,
|
||||
name: "Camera",
|
||||
parent: None,
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 10.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: camera2d,
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
3: (
|
||||
id: 3,
|
||||
name: "Title",
|
||||
parent: None,
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: 0.0,
|
||||
y: 214.0,
|
||||
z: 5.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: text2d,
|
||||
data: (
|
||||
text: "Bevy Forge Platformer",
|
||||
font_size: 44.0,
|
||||
color: (
|
||||
r: 0.96,
|
||||
g: 0.94,
|
||||
b: 0.88,
|
||||
a: 1.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
4: (
|
||||
id: 4,
|
||||
name: "Hint",
|
||||
parent: None,
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: 0.0,
|
||||
y: 176.0,
|
||||
z: 5.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: text2d,
|
||||
data: (
|
||||
text: "Collect the coins!",
|
||||
font_size: 20.0,
|
||||
color: (
|
||||
r: 0.95,
|
||||
g: 0.67,
|
||||
b: 0.25,
|
||||
a: 1.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
5: (
|
||||
id: 5,
|
||||
name: "Ground 1",
|
||||
parent: None,
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: -336.0,
|
||||
y: -224.0,
|
||||
z: -2.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: sprite,
|
||||
data: (
|
||||
image: Some("textures/tile.png"),
|
||||
color: (
|
||||
r: 1.0,
|
||||
g: 1.0,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
),
|
||||
custom_size: Some((
|
||||
x: 96.0,
|
||||
y: 96.0,
|
||||
)),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
6: (
|
||||
id: 6,
|
||||
name: "Ground 2",
|
||||
parent: None,
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: -240.0,
|
||||
y: -224.0,
|
||||
z: -2.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: sprite,
|
||||
data: (
|
||||
image: Some("textures/tile.png"),
|
||||
color: (
|
||||
r: 1.0,
|
||||
g: 1.0,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
),
|
||||
custom_size: Some((
|
||||
x: 96.0,
|
||||
y: 96.0,
|
||||
)),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
7: (
|
||||
id: 7,
|
||||
name: "Ground 3",
|
||||
parent: None,
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: -144.0,
|
||||
y: -224.0,
|
||||
z: -2.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: sprite,
|
||||
data: (
|
||||
image: Some("textures/tile.png"),
|
||||
color: (
|
||||
r: 1.0,
|
||||
g: 1.0,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
),
|
||||
custom_size: Some((
|
||||
x: 96.0,
|
||||
y: 96.0,
|
||||
)),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
8: (
|
||||
id: 8,
|
||||
name: "Ground 4",
|
||||
parent: None,
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: -48.0,
|
||||
y: -224.0,
|
||||
z: -2.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: sprite,
|
||||
data: (
|
||||
image: Some("textures/tile.png"),
|
||||
color: (
|
||||
r: 1.0,
|
||||
g: 1.0,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
),
|
||||
custom_size: Some((
|
||||
x: 96.0,
|
||||
y: 96.0,
|
||||
)),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
9: (
|
||||
id: 9,
|
||||
name: "Ground 5",
|
||||
parent: None,
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: 48.0,
|
||||
y: -224.0,
|
||||
z: -2.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: sprite,
|
||||
data: (
|
||||
image: Some("textures/tile.png"),
|
||||
color: (
|
||||
r: 1.0,
|
||||
g: 1.0,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
),
|
||||
custom_size: Some((
|
||||
x: 96.0,
|
||||
y: 96.0,
|
||||
)),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
10: (
|
||||
id: 10,
|
||||
name: "Ground 6",
|
||||
parent: None,
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: 144.0,
|
||||
y: -224.0,
|
||||
z: -2.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: sprite,
|
||||
data: (
|
||||
image: Some("textures/tile.png"),
|
||||
color: (
|
||||
r: 1.0,
|
||||
g: 1.0,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
),
|
||||
custom_size: Some((
|
||||
x: 96.0,
|
||||
y: 96.0,
|
||||
)),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
11: (
|
||||
id: 11,
|
||||
name: "Ground 7",
|
||||
parent: None,
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: 240.0,
|
||||
y: -224.0,
|
||||
z: -2.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: sprite,
|
||||
data: (
|
||||
image: Some("textures/tile.png"),
|
||||
color: (
|
||||
r: 1.0,
|
||||
g: 1.0,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
),
|
||||
custom_size: Some((
|
||||
x: 96.0,
|
||||
y: 96.0,
|
||||
)),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
12: (
|
||||
id: 12,
|
||||
name: "Ground 8",
|
||||
parent: None,
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: 336.0,
|
||||
y: -224.0,
|
||||
z: -2.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: sprite,
|
||||
data: (
|
||||
image: Some("textures/tile.png"),
|
||||
color: (
|
||||
r: 1.0,
|
||||
g: 1.0,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
),
|
||||
custom_size: Some((
|
||||
x: 96.0,
|
||||
y: 96.0,
|
||||
)),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
13: (
|
||||
id: 13,
|
||||
name: "Ledge 1",
|
||||
parent: None,
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: -48.0,
|
||||
y: -64.0,
|
||||
z: -2.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: sprite,
|
||||
data: (
|
||||
image: Some("textures/tile.png"),
|
||||
color: (
|
||||
r: 1.0,
|
||||
g: 1.0,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
),
|
||||
custom_size: Some((
|
||||
x: 96.0,
|
||||
y: 96.0,
|
||||
)),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
14: (
|
||||
id: 14,
|
||||
name: "Ledge 2",
|
||||
parent: None,
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: 48.0,
|
||||
y: -64.0,
|
||||
z: -2.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: sprite,
|
||||
data: (
|
||||
image: Some("textures/tile.png"),
|
||||
color: (
|
||||
r: 1.0,
|
||||
g: 1.0,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
),
|
||||
custom_size: Some((
|
||||
x: 96.0,
|
||||
y: 96.0,
|
||||
)),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
15: (
|
||||
id: 15,
|
||||
name: "Ledge 3",
|
||||
parent: None,
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: 144.0,
|
||||
y: -64.0,
|
||||
z: -2.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: sprite,
|
||||
data: (
|
||||
image: Some("textures/tile.png"),
|
||||
color: (
|
||||
r: 1.0,
|
||||
g: 1.0,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
),
|
||||
custom_size: Some((
|
||||
x: 96.0,
|
||||
y: 96.0,
|
||||
)),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
16: (
|
||||
id: 16,
|
||||
name: "Player",
|
||||
parent: None,
|
||||
children: [
|
||||
17,
|
||||
],
|
||||
transform: (
|
||||
translation: (
|
||||
x: -192.0,
|
||||
y: -128.0,
|
||||
z: 0.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: sprite,
|
||||
data: (
|
||||
image: Some("textures/player.png"),
|
||||
color: (
|
||||
r: 1.0,
|
||||
g: 1.0,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
),
|
||||
custom_size: Some((
|
||||
x: 64.0,
|
||||
y: 96.0,
|
||||
)),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
17: (
|
||||
id: 17,
|
||||
name: "Hat",
|
||||
parent: Some(16),
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: 0.0,
|
||||
y: 42.0,
|
||||
z: 1.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: sprite,
|
||||
data: (
|
||||
image: Some("textures/hat.png"),
|
||||
color: (
|
||||
r: 1.0,
|
||||
g: 1.0,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
),
|
||||
custom_size: Some((
|
||||
x: 56.0,
|
||||
y: 28.0,
|
||||
)),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
18: (
|
||||
id: 18,
|
||||
name: "Coin 1",
|
||||
parent: None,
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: -288.0,
|
||||
y: -104.0,
|
||||
z: 1.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: sprite,
|
||||
data: (
|
||||
image: Some("textures/coin.png"),
|
||||
color: (
|
||||
r: 1.0,
|
||||
g: 1.0,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
),
|
||||
custom_size: Some((
|
||||
x: 48.0,
|
||||
y: 48.0,
|
||||
)),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
19: (
|
||||
id: 19,
|
||||
name: "Coin 2",
|
||||
parent: None,
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: 48.0,
|
||||
y: 28.0,
|
||||
z: 1.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: sprite,
|
||||
data: (
|
||||
image: Some("textures/coin.png"),
|
||||
color: (
|
||||
r: 1.0,
|
||||
g: 1.0,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
),
|
||||
custom_size: Some((
|
||||
x: 48.0,
|
||||
y: 48.0,
|
||||
)),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
20: (
|
||||
id: 20,
|
||||
name: "Coin 3",
|
||||
parent: None,
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: 240.0,
|
||||
y: -96.0,
|
||||
z: 1.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: sprite,
|
||||
data: (
|
||||
image: Some("textures/coin.png"),
|
||||
color: (
|
||||
r: 1.0,
|
||||
g: 1.0,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
),
|
||||
custom_size: Some((
|
||||
x: 48.0,
|
||||
y: 48.0,
|
||||
)),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
21: (
|
||||
id: 21,
|
||||
name: "Crate",
|
||||
parent: None,
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: 352.0,
|
||||
y: -144.0,
|
||||
z: -1.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: sprite,
|
||||
data: (
|
||||
image: Some("textures/crate.png"),
|
||||
color: (
|
||||
r: 1.0,
|
||||
g: 1.0,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
),
|
||||
custom_size: Some((
|
||||
x: 64.0,
|
||||
y: 64.0,
|
||||
)),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
22: (
|
||||
id: 22,
|
||||
name: "Crate 2",
|
||||
parent: None,
|
||||
children: [],
|
||||
transform: (
|
||||
translation: (
|
||||
x: 352.0,
|
||||
y: -80.0,
|
||||
z: -1.0,
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
),
|
||||
),
|
||||
components: [
|
||||
(
|
||||
type: sprite,
|
||||
data: (
|
||||
image: Some("textures/crate.png"),
|
||||
color: (
|
||||
r: 1.0,
|
||||
g: 1.0,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
),
|
||||
custom_size: Some((
|
||||
x: 64.0,
|
||||
y: 64.0,
|
||||
)),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
locked: false,
|
||||
),
|
||||
},
|
||||
roots: [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
],
|
||||
next_id: 23,
|
||||
)
|
||||
@@ -0,0 +1,57 @@
|
||||
//! Minimal Bevy 0.20 app for the Bevy Forge demo project.
|
||||
//!
|
||||
//! Bevy Forge opens this directory as a Bevy project:
|
||||
//!
|
||||
//! ```text
|
||||
//! cargo run -p bevy_forge_editor -- examples/demo-project
|
||||
//! ```
|
||||
//!
|
||||
//! The files in `scenes/` are editor documents (`.forge.ron`), not runtime Bevy
|
||||
//! assets. To run a scene, export it from Bevy Forge (or the `forge` CLI) and paste
|
||||
//! the generated function into this file, then call it from a startup system:
|
||||
//!
|
||||
//! - a single-root scene uses `bsn!`, returns `impl Scene` and is spawned with
|
||||
//! `commands.spawn_scene(scene(..))`;
|
||||
//! - a multi-root scene uses `bsn_list!`, returns `impl SceneList` and is spawned
|
||||
//! with `commands.spawn_scene_list(scene(..))`;
|
||||
//! - generated mesh scenes take `&mut Assets<Mesh>` and
|
||||
//! `&mut Assets<StandardMaterial>` parameters; the imperative Rust export also
|
||||
//! takes `Commands` and `AssetServer`.
|
||||
//!
|
||||
//! The exported garden scene refers to the custom `forge_demo::Orbit` component, so
|
||||
//! that type lives in this crate (see the module below). Keep it, or replace the
|
||||
//! path in the generated code, when pasting an export here.
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::new().add_plugins(DefaultPlugins).run();
|
||||
}
|
||||
|
||||
/// Project-specific components used by the demo scenes.
|
||||
///
|
||||
/// `scenes/garden.forge.ron` attaches `forge_demo::Orbit` to the Orbit Crystal, and
|
||||
/// the BSN export emits `forge_demo::Orbit { radius: 2.4, speed: 0.45 }`. Deriving
|
||||
/// `Component` plus `Default`/`Clone` is enough for Bevy 0.20's `FromTemplate`
|
||||
/// blanket implementation, so the pasted scene compiles in this crate.
|
||||
pub mod forge_demo {
|
||||
use bevy::prelude::*;
|
||||
|
||||
/// Animates an entity in a circle around its spawn point.
|
||||
#[derive(Component, Debug, Clone)]
|
||||
pub struct Orbit {
|
||||
/// Orbit radius in world units.
|
||||
pub radius: f32,
|
||||
/// Angular speed in radians per second.
|
||||
pub speed: f32,
|
||||
}
|
||||
|
||||
impl Default for Orbit {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
radius: 2.4,
|
||||
speed: 0.45,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate the pixel-art textures used by the Bevy Forge demo project.
|
||||
|
||||
All textures are drawn procedurally from a small palette so the demo stays
|
||||
license-clean and can be regenerated on any machine with Pillow installed:
|
||||
|
||||
python3 examples/demo-project/tools/generate_textures.py
|
||||
|
||||
The output is written to ``examples/demo-project/assets/textures``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
OUT = ROOT / "assets" / "textures"
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Shared palette (deliberately small, dusk / forest leaning).
|
||||
OUTLINE = (43, 32, 51, 255)
|
||||
SKIN = (244, 201, 158, 255)
|
||||
SKIN_SHADOW = (217, 164, 122, 255)
|
||||
HAIR = (122, 78, 45, 255)
|
||||
TUNIC = (54, 126, 143, 255)
|
||||
TUNIC_DARK = (38, 95, 110, 255)
|
||||
BELT = (91, 62, 38, 255)
|
||||
BUCKLE = (242, 179, 61, 255)
|
||||
PANTS = (64, 79, 105, 255)
|
||||
BOOT = (74, 56, 43, 255)
|
||||
CREAM = (255, 244, 214, 255)
|
||||
|
||||
|
||||
def logical(width: int, height: int) -> tuple[Image.Image, ImageDraw.ImageDraw]:
|
||||
image = Image.new("RGBA", (width, height), (0, 0, 0, 0))
|
||||
return image, ImageDraw.Draw(image)
|
||||
|
||||
|
||||
def upscale(image: Image.Image, factor: int) -> Image.Image:
|
||||
return image.resize(
|
||||
(image.width * factor, image.height * factor),
|
||||
Image.Resampling.NEAREST,
|
||||
)
|
||||
|
||||
|
||||
def save(image: Image.Image, name: str) -> None:
|
||||
image.save(OUT / name, optimize=True)
|
||||
print(f"wrote {name}: {image.width}x{image.height}")
|
||||
|
||||
|
||||
def draw_player() -> None:
|
||||
image, draw = logical(16, 24)
|
||||
# Boots.
|
||||
draw.rectangle((3, 20, 7, 23), fill=OUTLINE)
|
||||
draw.rectangle((8, 20, 12, 23), fill=OUTLINE)
|
||||
draw.rectangle((4, 21, 7, 22), fill=BOOT)
|
||||
draw.rectangle((8, 21, 11, 22), fill=BOOT)
|
||||
# Legs.
|
||||
draw.rectangle((4, 16, 11, 20), fill=OUTLINE)
|
||||
draw.rectangle((5, 17, 10, 20), fill=PANTS)
|
||||
draw.rectangle((7, 17, 7, 19), fill=TUNIC_DARK)
|
||||
# Torso and arms.
|
||||
draw.rectangle((3, 10, 12, 16), fill=OUTLINE)
|
||||
draw.rectangle((4, 11, 11, 15), fill=TUNIC)
|
||||
draw.rectangle((2, 11, 3, 15), fill=OUTLINE)
|
||||
draw.rectangle((12, 11, 13, 15), fill=OUTLINE)
|
||||
draw.rectangle((3, 12, 3, 14), fill=TUNIC)
|
||||
draw.rectangle((12, 12, 12, 14), fill=TUNIC)
|
||||
draw.rectangle((3, 16, 4, 16), fill=SKIN)
|
||||
draw.rectangle((11, 16, 12, 16), fill=SKIN)
|
||||
draw.rectangle((3, 14, 12, 15), fill=BELT)
|
||||
draw.rectangle((7, 14, 8, 15), fill=BUCKLE)
|
||||
draw.rectangle((4, 11, 11, 11), fill=TUNIC_DARK)
|
||||
# Head with a friendly face.
|
||||
draw.rectangle((4, 2, 11, 10), fill=OUTLINE)
|
||||
draw.rectangle((5, 3, 10, 9), fill=SKIN)
|
||||
draw.rectangle((5, 2, 10, 4), fill=HAIR)
|
||||
draw.rectangle((4, 4, 4, 6), fill=HAIR)
|
||||
draw.rectangle((11, 4, 11, 6), fill=HAIR)
|
||||
draw.rectangle((6, 6, 6, 6), fill=OUTLINE)
|
||||
draw.rectangle((9, 6, 9, 6), fill=OUTLINE)
|
||||
draw.rectangle((7, 8, 8, 8), fill=SKIN_SHADOW)
|
||||
draw.rectangle((5, 8, 5, 8), fill=SKIN_SHADOW)
|
||||
draw.rectangle((10, 8, 10, 8), fill=SKIN_SHADOW)
|
||||
save(upscale(image, 4), "player.png")
|
||||
|
||||
|
||||
def draw_hat() -> None:
|
||||
image, draw = logical(16, 8)
|
||||
hat = (228, 87, 46, 255)
|
||||
hat_shine = (244, 138, 54, 255)
|
||||
brim = (188, 62, 34, 255)
|
||||
hat_outline = (63, 35, 28, 255)
|
||||
draw.rectangle((3, 1, 12, 5), fill=hat_outline)
|
||||
draw.rectangle((4, 2, 11, 4), fill=hat)
|
||||
draw.rectangle((5, 2, 8, 3), fill=hat_shine)
|
||||
draw.rectangle((7, 0, 8, 0), fill=hat_outline)
|
||||
draw.rectangle((1, 5, 14, 7), fill=hat_outline)
|
||||
draw.rectangle((2, 5, 13, 6), fill=brim)
|
||||
draw.rectangle((4, 4, 11, 4), fill=brim)
|
||||
save(upscale(image, 4), "hat.png")
|
||||
|
||||
|
||||
def draw_tile() -> None:
|
||||
image, draw = logical(24, 24)
|
||||
dirt = (122, 78, 45, 255)
|
||||
dirt_dark = (92, 58, 34, 255)
|
||||
dirt_light = (155, 118, 75, 255)
|
||||
grass = (78, 159, 61, 255)
|
||||
grass_light = (111, 207, 95, 255)
|
||||
grass_dark = (46, 91, 38, 255)
|
||||
draw.rectangle((0, 0, 23, 23), fill=dirt)
|
||||
draw.rectangle((0, 0, 23, 1), fill=grass_light)
|
||||
draw.rectangle((0, 2, 23, 5), fill=grass)
|
||||
draw.rectangle((0, 6, 23, 6), fill=grass_dark)
|
||||
draw.rectangle((0, 7, 23, 7), fill=dirt_dark)
|
||||
# Grass dribbling over the soil edge.
|
||||
for x in (2, 3, 8, 9, 15, 20, 21):
|
||||
draw.rectangle((x, 7, x, 8), fill=grass)
|
||||
draw.rectangle((3, 7, 6, 8), fill=grass_dark)
|
||||
draw.rectangle((16, 7, 18, 9), fill=grass_dark)
|
||||
# Soil speckles and stones.
|
||||
rng = random.Random(11)
|
||||
for _ in range(16):
|
||||
x = rng.randint(1, 21)
|
||||
y = rng.randint(10, 22)
|
||||
draw.rectangle((x, y, x + 1, y), fill=dirt_dark)
|
||||
for _ in range(8):
|
||||
x = rng.randint(2, 20)
|
||||
y = rng.randint(12, 21)
|
||||
draw.rectangle((x, y, x, y), fill=dirt_light)
|
||||
draw.rectangle((5, 14, 7, 14), fill=(155, 122, 83, 255))
|
||||
draw.rectangle((17, 18, 19, 18), fill=(155, 122, 83, 255))
|
||||
draw.rectangle((0, 22, 23, 23), fill=dirt_dark)
|
||||
# Tiny flowers so a long ground row reads as a garden.
|
||||
for x in (6, 19):
|
||||
draw.rectangle((x, 1, x, 1), fill=CREAM)
|
||||
draw.rectangle((x - 1, 1, x + 1, 1), fill=CREAM)
|
||||
draw.rectangle((x, 0, x, 0), fill=CREAM)
|
||||
draw.rectangle((x, 3, x, 3), fill=(78, 159, 61, 255))
|
||||
draw.rectangle((x, 2, x, 2), fill=(242, 179, 61, 255))
|
||||
save(upscale(image, 4), "tile.png")
|
||||
|
||||
|
||||
def draw_coin() -> None:
|
||||
image, draw = logical(16, 16)
|
||||
draw.ellipse((1, 1, 14, 14), fill=(140, 90, 22, 255))
|
||||
draw.ellipse((2, 2, 13, 13), fill=(242, 179, 61, 255))
|
||||
draw.ellipse((4, 4, 11, 11), fill=(255, 217, 102, 255))
|
||||
draw.rectangle((7, 5, 8, 10), fill=CREAM)
|
||||
draw.rectangle((5, 7, 10, 8), fill=CREAM)
|
||||
draw.rectangle((5, 4, 6, 4), fill=(255, 255, 255, 255))
|
||||
draw.rectangle((4, 5, 4, 5), fill=(255, 255, 255, 255))
|
||||
save(upscale(image, 4), "coin.png")
|
||||
|
||||
|
||||
def draw_crate() -> None:
|
||||
image, draw = logical(16, 16)
|
||||
outline = (63, 43, 28, 255)
|
||||
wood = (169, 116, 63, 255)
|
||||
wood_light = (201, 143, 82, 255)
|
||||
wood_dark = (122, 78, 45, 255)
|
||||
draw.rectangle((0, 0, 15, 15), fill=outline)
|
||||
draw.rectangle((1, 1, 14, 14), fill=wood)
|
||||
draw.rectangle((1, 1, 14, 1), fill=wood_light)
|
||||
draw.rectangle((1, 14, 14, 14), fill=wood_dark)
|
||||
draw.rectangle((1, 1, 3, 14), fill=wood_light)
|
||||
draw.rectangle((12, 1, 14, 14), fill=wood_dark)
|
||||
draw.line((4, 3, 11, 12), fill=wood_dark)
|
||||
draw.line((11, 3, 4, 12), fill=wood_dark)
|
||||
for x, y in ((2, 2), (13, 2), (2, 13), (13, 13)):
|
||||
draw.rectangle((x, y, x, y), fill=(217, 187, 130, 255))
|
||||
save(upscale(image, 4), "crate.png")
|
||||
|
||||
|
||||
def dusk_gradient(t: float) -> tuple[int, int, int]:
|
||||
stops = [
|
||||
(0.00, (15, 14, 34)),
|
||||
(0.42, (45, 34, 70)),
|
||||
(0.62, (96, 58, 78)),
|
||||
(0.74, (176, 92, 70)),
|
||||
(0.82, (232, 146, 84)),
|
||||
(0.90, (244, 180, 110)),
|
||||
(1.00, (28, 26, 48)),
|
||||
]
|
||||
for index in range(len(stops) - 1):
|
||||
start_t, start = stops[index]
|
||||
end_t, end = stops[index + 1]
|
||||
if start_t <= t <= end_t:
|
||||
span = max(end_t - start_t, 1e-6)
|
||||
blend = (t - start_t) / span
|
||||
return tuple(
|
||||
round(start[channel] + (end[channel] - start[channel]) * blend)
|
||||
for channel in range(3)
|
||||
)
|
||||
return stops[-1][1]
|
||||
|
||||
|
||||
def draw_background() -> None:
|
||||
width, height = 240, 135
|
||||
image = Image.new("RGB", (width, height))
|
||||
draw = ImageDraw.Draw(image)
|
||||
for y in range(height):
|
||||
draw.line(((0, y), (width, y)), fill=dusk_gradient(y / (height - 1)))
|
||||
# Stars.
|
||||
rng = random.Random(7)
|
||||
for _ in range(46):
|
||||
x = rng.randint(2, width - 3)
|
||||
y = rng.randint(3, 62)
|
||||
brightness = rng.choice([(255, 255, 255), (255, 244, 214), (210, 205, 235)])
|
||||
draw.point((x, y), fill=brightness)
|
||||
if rng.random() < 0.18:
|
||||
draw.point((x + 1, y), fill=brightness)
|
||||
# Soft evening clouds.
|
||||
draw.rectangle((0, 26, width, 28), fill=(66, 52, 92))
|
||||
draw.rectangle((20, 38, 190, 39), fill=(78, 60, 104))
|
||||
draw.rectangle((60, 47, 230, 48), fill=(90, 66, 105))
|
||||
# Sun low over the horizon.
|
||||
draw.ellipse((150, 64, 191, 105), fill=(247, 190, 120))
|
||||
draw.ellipse((157, 71, 184, 98), fill=(255, 226, 164))
|
||||
draw.ellipse((162, 76, 179, 93), fill=(255, 244, 214))
|
||||
# Far mountain range.
|
||||
draw.polygon(
|
||||
[
|
||||
(0, 101),
|
||||
(25, 82),
|
||||
(48, 94),
|
||||
(72, 76),
|
||||
(95, 90),
|
||||
(120, 80),
|
||||
(150, 97),
|
||||
(175, 84),
|
||||
(205, 96),
|
||||
(230, 88),
|
||||
(240, 96),
|
||||
(240, 135),
|
||||
(0, 135),
|
||||
],
|
||||
fill=(66, 52, 96),
|
||||
)
|
||||
# Mid range.
|
||||
draw.polygon(
|
||||
[
|
||||
(0, 112),
|
||||
(30, 100),
|
||||
(55, 108),
|
||||
(80, 94),
|
||||
(110, 106),
|
||||
(140, 98),
|
||||
(168, 110),
|
||||
(196, 100),
|
||||
(220, 108),
|
||||
(240, 102),
|
||||
(240, 135),
|
||||
(0, 135),
|
||||
],
|
||||
fill=(47, 38, 72),
|
||||
)
|
||||
# Rolling hills.
|
||||
draw.polygon(
|
||||
[
|
||||
(0, 123),
|
||||
(35, 114),
|
||||
(70, 121),
|
||||
(110, 112),
|
||||
(145, 120),
|
||||
(180, 113),
|
||||
(215, 121),
|
||||
(240, 115),
|
||||
(240, 135),
|
||||
(0, 135),
|
||||
],
|
||||
fill=(31, 29, 52),
|
||||
)
|
||||
# Foreground silhouette with a few conifers.
|
||||
draw.polygon(
|
||||
[
|
||||
(0, 129),
|
||||
(30, 126),
|
||||
(60, 129),
|
||||
(90, 125),
|
||||
(120, 129),
|
||||
(150, 126),
|
||||
(180, 130),
|
||||
(210, 126),
|
||||
(240, 129),
|
||||
(240, 135),
|
||||
(0, 135),
|
||||
],
|
||||
fill=(20, 19, 34),
|
||||
)
|
||||
for x, base in ((28, 129), (66, 130), (148, 127), (196, 131)):
|
||||
draw.polygon([(x, base - 12), (x - 4, base), (x + 4, base)], fill=(17, 24, 30))
|
||||
draw.rectangle((x - 1, base - 13, x + 1, base - 10), fill=(17, 24, 30))
|
||||
draw.rectangle((x, base - 14, x, base - 13), fill=(17, 24, 30))
|
||||
for x in range(4, width, 17):
|
||||
draw.point((x, 132), fill=(36, 55, 42))
|
||||
image = upscale(image, 4)
|
||||
image = image.quantize(colors=64, method=Image.Quantize.MEDIANCUT, dither=Image.Dither.NONE)
|
||||
save(image.convert("RGB"), "background.png")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
draw_background()
|
||||
draw_player()
|
||||
draw_hat()
|
||||
draw_tile()
|
||||
draw_coin()
|
||||
draw_crate()
|
||||
total = sum(path.stat().st_size for path in OUT.glob("*.png"))
|
||||
print(f"total texture size: {total} bytes")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user