Skip to content

Commit d11b14f

Browse files
authored
feat: add deposit spawns with perlin noise (#67)
1 parent f636931 commit d11b14f

File tree

8 files changed

+190
-71
lines changed

8 files changed

+190
-71
lines changed

Cargo.lock

Lines changed: 42 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ bevy-inspector-egui = "0.35.0"
99
bevy_aseprite_ultra = "0.7.0"
1010
bevy_ecs_tilemap = { version = "0.17.0" }
1111
humantime-serde = "1.1.1"
12+
noise = "0.9.0"
1213
rand = "0.9.2"
1314
serde = { version = "1.0.228", features = ["derive"] }
1415
thiserror = "2.0.17"
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
id = "fauna_a"
22
name = "Fauna A Deposit"
33
recipe_id = "fauna_a"
4-
quantity = 10
4+
seed = 463724
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
id = "flora_a"
22
name = "Flora A Deposit"
33
recipe_id = "flora_a"
4-
quantity = 10
4+
seed = 821954

src/camera.rs

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,47 @@
11
use bevy::prelude::*;
22

3-
const CAMERA_DRAG_SMOOTHING: f32 = 1.0;
3+
const CAMERA_DRAG_SCALING: f32 = 1.0;
44

5-
const CAMERA_ZOOM_INTERVAL: f32 = 0.1;
5+
const CAMERA_ZOOM_SCALING: f32 = 0.1;
66
const CAMERA_ZOOM_MIN: f32 = 0.25;
7-
const CAMERA_ZOOM_MAX: f32 = 2.0;
7+
const CAMERA_ZOOM_MAX: f32 = 4.0;
88

99
pub fn plugin(app: &mut App) {
1010
app.add_systems(Startup, setup_camera);
11-
app.add_systems(Update, (move_camera, zoom_camera));
11+
app.add_observer(move_camera);
12+
app.add_observer(zoom_camera);
1213
}
1314

1415
fn setup_camera(mut commands: Commands) {
1516
commands.spawn((Camera2d, Msaa::Off));
1617
}
1718

1819
fn move_camera(
19-
mut pointer_drags: MessageReader<Pointer<Drag>>,
20-
mut camera_position: Single<&mut Transform, With<Camera>>,
20+
pointer_drag: On<Pointer<Drag>>,
21+
mut camera: Single<(&mut Transform, &Projection), With<Camera>>,
2122
) {
22-
for pointer_drag in pointer_drags.read() {
23-
if pointer_drag.button != PointerButton::Secondary {
24-
return;
25-
}
26-
27-
camera_position.translation +=
28-
pointer_drag.delta.extend(0.0) * Vec3::new(-1.0, 1.0, 1.0) * CAMERA_DRAG_SMOOTHING;
23+
if pointer_drag.button != PointerButton::Secondary {
24+
return;
2925
}
26+
27+
let Projection::Orthographic(ortho) = camera.1 else {
28+
return;
29+
};
30+
31+
camera.0.translation += pointer_drag.delta.extend(0.0)
32+
* ortho.scale
33+
* Vec3::new(-1.0, 1.0, 1.0)
34+
* CAMERA_DRAG_SCALING;
3035
}
3136

3237
fn zoom_camera(
33-
mut pointer_scrolls: MessageReader<Pointer<Scroll>>,
34-
projection: Single<&mut Projection, With<Camera>>,
38+
pointer_scroll: On<Pointer<Scroll>>,
39+
mut projection: Single<&mut Projection, With<Camera>>,
3540
) {
36-
let Projection::Orthographic(ref mut ortho) = *projection.into_inner() else {
41+
let Projection::Orthographic(ref mut ortho) = **projection else {
3742
return;
3843
};
3944

40-
for pointer_scroll in pointer_scrolls.read() {
41-
ortho.scale *= 1.0 - (pointer_scroll.y * CAMERA_ZOOM_INTERVAL);
42-
ortho.scale = ortho.scale.clamp(CAMERA_ZOOM_MIN, CAMERA_ZOOM_MAX);
43-
}
45+
ortho.scale *= 1.0 - (pointer_scroll.y * CAMERA_ZOOM_SCALING);
46+
ortho.scale = ortho.scale.clamp(CAMERA_ZOOM_MIN, CAMERA_ZOOM_MAX);
4447
}

src/gameplay/sprite_sort.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,10 @@ pub struct ZIndexSprite(pub u32);
1515

1616
fn sort_sprites(query: Query<(&mut Transform, &ZIndexSprite, Option<&YSortSprite>)>) {
1717
for (mut transform, z_index, y_sort) in query {
18-
let mut z_coordinate = z_index.0 as f32;
18+
let mut z_coordinate = z_index.0 as f32 * 10.0;
1919

2020
if y_sort.is_some() {
21-
let atan_mapping = 1.0 - transform.translation.y.atan() / std::f32::consts::PI + 0.5;
22-
z_coordinate += atan_mapping;
21+
z_coordinate -= transform.translation.y * 0.001;
2322
}
2423

2524
transform.translation.z = z_coordinate;

src/gameplay/world/deposit.rs

Lines changed: 102 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,42 @@
1+
use std::collections::HashMap;
2+
13
use bevy::{asset::LoadedFolder, prelude::*, sprite::Anchor};
2-
use rand::Rng;
4+
use noise::{Fbm, MultiFractal, NoiseFn, Perlin};
35
use serde::Deserialize;
46

57
use crate::{
68
assets::{loaders::toml::TomlAssetPlugin, tracking::LoadResource},
79
gameplay::{
810
sprite_sort::{YSortSprite, ZIndexSprite},
911
world::{
10-
WorldSpawnSystems,
1112
construction::Constructions,
12-
tilemap::{CHUNK_SIZE, TILE_SIZE, coord::Coord},
13+
tilemap::{
14+
CHUNK_SIZE, TILE_SIZE,
15+
chunk::{Chunk, ChunkLoaded, ChunkUnloaded},
16+
coord::Coord,
17+
},
1318
},
1419
},
1520
screens::Screen,
1621
};
1722

1823
pub fn plugin(app: &mut App) {
1924
app.add_plugins(TomlAssetPlugin::<DepositDef>::extensions(&["deposit.toml"]));
20-
2125
app.load_resource::<DepositAssets>();
2226

23-
app.add_systems(
24-
OnEnter(Screen::Gameplay),
25-
spawn_deposits.in_set(WorldSpawnSystems::SpawnDeposits),
26-
);
27+
app.init_resource::<DepositNoise>();
28+
app.add_systems(OnEnter(Screen::Gameplay), create_noise);
29+
30+
app.add_observer(spawn_deposits);
31+
app.add_observer(unload_deposits);
2732
}
2833

2934
#[derive(Asset, Deserialize, Reflect)]
3035
pub struct DepositDef {
3136
pub id: String,
3237
pub name: String,
3338
pub recipe_id: String,
34-
pub quantity: u32,
39+
pub seed: u32,
3540
}
3641

3742
#[derive(Asset, Resource, Reflect, Clone)]
@@ -56,38 +61,100 @@ impl FromWorld for DepositAssets {
5661
#[reflect(Component)]
5762
pub struct DepositRecipe(pub String);
5863

64+
#[derive(Resource, Debug, Default)]
65+
pub struct DepositNoise {
66+
pub noises: HashMap<AssetId<DepositDef>, Fbm<Perlin>>,
67+
}
68+
69+
fn create_noise(
70+
mut deposit_noise: ResMut<DepositNoise>,
71+
deposit_definitions: Res<Assets<DepositDef>>,
72+
) {
73+
for (deposit_id, deposit_def) in deposit_definitions.iter() {
74+
let fbm = Fbm::<Perlin>::new(deposit_def.seed)
75+
.set_octaves(5)
76+
.set_frequency(1.0 / 70.0)
77+
.set_lacunarity(2.5)
78+
.set_persistence(0.45);
79+
80+
deposit_noise.noises.insert(deposit_id, fbm);
81+
}
82+
}
83+
5984
fn spawn_deposits(
85+
chunk_loaded: On<ChunkLoaded>,
86+
chunk_query: Query<&Chunk>,
6087
mut commands: Commands,
6188
deposit_definitions: Res<Assets<DepositDef>>,
6289
asset_server: Res<AssetServer>,
90+
deposit_noise: Res<DepositNoise>,
91+
mut constructions: ResMut<Constructions>,
92+
) {
93+
let chunk = chunk_query.get(chunk_loaded.chunk).unwrap();
94+
95+
let absolute_chunk_position = chunk.0 * CHUNK_SIZE.as_ivec2();
96+
97+
for (deposit_id, deposit_def) in deposit_definitions.iter() {
98+
let Some(fbm) = deposit_noise.noises.get(&deposit_id) else {
99+
return;
100+
};
101+
102+
for x in 0..CHUNK_SIZE.x {
103+
for y in 0..CHUNK_SIZE.y {
104+
let absolute_tile_pos = absolute_chunk_position + ivec2(x as i32, y as i32);
105+
if constructions.contains_key(&absolute_tile_pos) {
106+
continue;
107+
}
108+
109+
let value = fbm.get(absolute_tile_pos.as_dvec2().into());
110+
let normalized_value = (value + 1.0) / 2.0;
111+
if normalized_value < 0.75 {
112+
continue;
113+
}
114+
115+
let entity = commands
116+
.spawn((
117+
Name::new(deposit_def.name.clone()),
118+
Coord(absolute_tile_pos),
119+
Anchor(Vec2::new(0.0, -0.25)),
120+
YSortSprite,
121+
ZIndexSprite(10),
122+
Sprite {
123+
image: asset_server
124+
.load(format!("sprites/deposits/{}.png", deposit_def.id)),
125+
custom_size: Vec2::new(TILE_SIZE.x, TILE_SIZE.y).into(),
126+
..default()
127+
},
128+
DepositRecipe(deposit_def.recipe_id.clone()),
129+
))
130+
.id();
131+
132+
constructions.insert(absolute_tile_pos, entity);
133+
}
134+
}
135+
}
136+
}
137+
138+
fn unload_deposits(
139+
chunk_unloaded: On<ChunkUnloaded>,
140+
chunk_query: Query<&Chunk>,
63141
mut constructions: ResMut<Constructions>,
142+
mut commands: Commands,
64143
) {
65-
let mut rng = rand::rng();
66-
67-
for (_, deposit) in deposit_definitions.iter() {
68-
for _ in 0..deposit.quantity {
69-
let pos = IVec2::new(
70-
rng.random_range(-(CHUNK_SIZE.x as i32)..CHUNK_SIZE.x as i32),
71-
rng.random_range(-(CHUNK_SIZE.y as i32)..CHUNK_SIZE.y as i32),
72-
);
73-
74-
let entity = commands
75-
.spawn((
76-
Name::new(deposit.name.clone()),
77-
Coord(pos),
78-
Anchor(Vec2::new(0.0, -0.25)),
79-
YSortSprite,
80-
ZIndexSprite(10),
81-
Sprite {
82-
image: asset_server.load(format!("sprites/deposits/{}.png", deposit.id)),
83-
custom_size: Vec2::new(TILE_SIZE.x, TILE_SIZE.y).into(),
84-
..default()
85-
},
86-
DepositRecipe(deposit.recipe_id.clone()),
87-
))
88-
.id();
89-
90-
constructions.insert(pos, entity);
144+
let chunk = chunk_query.get(chunk_unloaded.chunk).unwrap();
145+
146+
let absolute_chunk_position = chunk.0 * CHUNK_SIZE.as_ivec2();
147+
148+
for x in 0..CHUNK_SIZE.x {
149+
for y in 0..CHUNK_SIZE.y {
150+
let absolute_tile_pos = absolute_chunk_position + ivec2(x as i32, y as i32);
151+
152+
let Some(construction) = constructions.get(&absolute_tile_pos) else {
153+
continue;
154+
};
155+
156+
commands.entity(*construction).despawn();
157+
constructions.remove(&absolute_tile_pos);
91158
}
92159
}
93160
}

0 commit comments

Comments
 (0)