This commit is contained in:
Arkitu 2025-05-13 22:02:40 +02:00
parent d210af93f8
commit 52dde29d13
9 changed files with 1223 additions and 1058 deletions

1994
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -6,9 +6,7 @@ edition = "2021"
[lib] [lib]
name = "forestiles" name = "forestiles"
path = "src/lib.rs" path = "src/lib.rs"
crate-type=[ crate-type = ["cdylib"]
"cdylib"
]
[[bin]] [[bin]]
path = "src/lib.rs" path = "src/lib.rs"
@ -32,31 +30,30 @@ bevy = { version = "0.15", default-features = false, features = [
"sysinfo_plugin", "sysinfo_plugin",
"webgl2", "webgl2",
"wayland", "wayland",
"png" "png",
]} "tonemapping_luts",
bevy-inspector-egui = { version = "0.28", default-features = false, features = [ "bevy_pbr",
"bevy_image", ] }
"bevy_render", #bevy-inspector-egui = { version = "0.28", default-features = false, features = [
"egui_open_url" # "bevy_image",
]} # "bevy_render",
# "egui_open_url",
#] }
mevy = "0.1" mevy = "0.1"
log = "0.4" log = "0.4"
rand = { version = "0.8", features = ["small_rng"] } rand = { version = "0.8", features = ["small_rng"] }
voronoice = "0.2" voronoice = "0.2"
noise = "0.9" noise = "0.9"
bevy_editor_pls = { version = "0.11", git = "https://github.com/jakobhellermann/bevy_editor_pls.git" }
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.6" console_error_panic_hook = "0.1.6"
console_log = "1.0" console_log = "1.0"
wgpu = { version = "22", features = ["webgl"]} wgpu = { version = "22", features = ["webgl"] }
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = [ web-sys = { version = "0.3", features = ["Document", "Window", "Element"] }
"Document", getrandom = { version = "*", features = ["js"] }
"Window",
"Element",
]}
getrandom ={ version = "*", features = ["js"]}
[target.'cfg(target_os = "android")'.dependencies] [target.'cfg(target_os = "android")'.dependencies]
android_logger = "0.14" android_logger = "0.14"

View File

@ -1,130 +1,24 @@
use std::time::Duration; use bevy::prelude::*;
use bevy::{
input::mouse::MouseWheel,
math::NormedVectorSpace,
picking::{focus::HoverMap, pointer::PointerId},
prelude::*,
utils::HashMap,
window::PrimaryWindow,
};
use crate::ui;
pub struct Plugin; pub struct Plugin;
impl bevy::prelude::Plugin for Plugin { impl bevy::prelude::Plugin for Plugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Startup, setup); app.add_systems(Startup, setup);
// .add_systems(Update, move_cam)
// .init_resource::<Pointers>();
} }
} }
#[derive(Component)]
pub struct CameraMarker;
fn setup(mut cmds: Commands, window: Query<&Window>) { fn setup(mut cmds: Commands, window: Query<&Window>) {
let zoom = 2. / window.single().width().min(window.single().height()); let zoom = 2. / window.single().width().min(window.single().height());
cmds.spawn((Camera2d, Transform::from_scale(Vec3::new(zoom, zoom, zoom)))); cmds.spawn((
} CameraMarker,
Camera3d {
#[derive(Resource, Default)] ..Default::default()
struct Pointers(HashMap<PointerId, (Vec2, Option<Duration>)>); },
Transform::from_scale(Vec3::new(zoom, zoom, zoom))
fn move_cam( .with_translation(Vec3::new(0., -1., 1.))
mut cam: Query<&mut Transform, With<Camera2d>>, .looking_to(Vec3::new(0., 1., -1.), Vec3::Y),
map_ui_entity: Query<Entity, With<ui::MapUIComponent>>, ));
mouse_buttons: Res<ButtonInput<MouseButton>>,
mut ev_scroll: EventReader<MouseWheel>,
touches: Res<Touches>,
window: Query<&Window, With<PrimaryWindow>>,
mut pointers: ResMut<Pointers>,
hover_map: Res<HoverMap>,
time: Res<Time>,
) {
let window = window.single();
let mut cam = cam.single_mut();
let map_ui_entity = map_ui_entity.single();
let ps = hover_map
.iter()
.filter_map(|(id, hit_map)| {
match id {
PointerId::Mouse => window.cursor_position().map(|p| match pointers.0.get(id) {
Some(p_cache) => (
p_cache
.1
.filter(|_| mouse_buttons.pressed(MouseButton::Left)),
p,
p_cache.0,
),
None => (None, p, p),
}),
PointerId::Touch(i) => touches.get_pressed(*i).map(|t| {
(
pointers
.0
.get(id)
.map(|(pos, start)| (*start).unwrap_or(time.elapsed())),
t.position(),
t.previous_position(),
)
}),
_ => None,
}
.map(|(pressed_start, new_pos, old_pos)| (pressed_start, new_pos, old_pos, id, hit_map))
})
.collect::<Vec<_>>();
let pressed_on_map = ps
.iter()
.filter(|p| p.0.is_some() && p.4.contains_key(&map_ui_entity))
.collect::<Vec<_>>();
let old_midpoint = pressed_on_map
.iter()
.fold(Vec2::ZERO, |acc, (_, _, old_pos, _, _)| {
acc + (old_pos / pressed_on_map.len() as f32)
});
let new_midpoint = pressed_on_map
.iter()
.fold(Vec2::ZERO, |acc, (_, new_pos, _, _, _)| {
acc + (new_pos / pressed_on_map.len() as f32)
});
// move camera
cam.translation.x -= (new_midpoint.x - old_midpoint.x) * cam.scale.x;
cam.translation.y += (new_midpoint.y - old_midpoint.y) * cam.scale.y;
// multiple fingers zoom
if pressed_on_map.len() > 1 {
let old_d_to_midpoint = pressed_on_map
.iter()
.fold(0., |acc, (_, _, old_pos, _, _)| {
acc + (old_midpoint - old_pos).norm()
});
let new_d_to_midpoint = pressed_on_map
.iter()
.fold(0., |acc, (_, new_pos, _, _, _)| {
acc + (new_midpoint - new_pos).norm()
});
let zoom = new_d_to_midpoint / old_d_to_midpoint;
cam.scale /= zoom;
}
// mouse scroll zoom
for ev in ev_scroll.read() {
let scale = (cam.scale.x - (ev.y * 0.1 / window.width().min(window.height())))
.clamp(0.0001, 2. / window.width().min(window.height()));
cam.scale = Vec3::new(scale, scale, scale);
}
// update cached pointer positions
pointers.0.clear();
for (pressed_start, new_pos, _, id, _) in ps {
match id {
PointerId::Mouse => {
pointers.0.insert(*id, (new_pos, pressed_start));
}
PointerId::Touch(_) => {
pointers.0.insert(*id, (new_pos, pressed_start));
}
_ => {}
}
}
} }

View File

@ -3,9 +3,8 @@ use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
prelude::*, prelude::*,
}; };
use bevy_inspector_egui::quick::WorldInspectorPlugin; //use bevy_inspector_egui::quick::WorldInspectorPlugin;
pub mod animals;
pub mod camera; pub mod camera;
pub mod map; pub mod map;
pub mod time; pub mod time;
@ -16,12 +15,13 @@ pub fn main() {
App::new() App::new()
.add_plugins(( .add_plugins((
DefaultPlugins, DefaultPlugins,
// bevy_inspector_egui::DefaultInspectorConfigPlugin,
bevy_editor_pls::EditorPlugin::default(), // for debug
// WorldInspectorPlugin::default(), // WorldInspectorPlugin::default(),
camera::Plugin, camera::Plugin,
map::Plugin, map::Plugin,
ui::Plugin, ui::Plugin,
time::Plugin, time::Plugin,
animals::Plugin,
FrameTimeDiagnosticsPlugin, FrameTimeDiagnosticsPlugin,
LogDiagnosticsPlugin { LogDiagnosticsPlugin {
filter: Some(vec![FrameTimeDiagnosticsPlugin::FPS]), filter: Some(vec![FrameTimeDiagnosticsPlugin::FPS]),

View File

@ -10,14 +10,14 @@ use noise::{Fbm, MultiFractal, NoiseFn, Perlin};
use rand::{thread_rng, Rng, SeedableRng}; use rand::{thread_rng, Rng, SeedableRng};
use voronoice::{BoundingBox, Point, VoronoiBuilder}; use voronoice::{BoundingBox, Point, VoronoiBuilder};
mod animals;
mod cells; mod cells;
mod picking; mod picking;
pub use animals::AnimalKind;
pub use cells::CellKind; pub use cells::CellKind;
use cells::*; use cells::*;
use picking::*; use picking::*;
use crate::{time::GameTime, ui::CurrentAction};
pub struct Plugin; pub struct Plugin;
impl bevy::prelude::Plugin for Plugin { impl bevy::prelude::Plugin for Plugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
@ -57,7 +57,7 @@ pub struct MeshNeedsUpdate(bool);
fn setup( fn setup(
mut cmds: Commands, mut cmds: Commands,
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>, mut materials: ResMut<Assets<StandardMaterial>>,
seed: Res<Seed>, seed: Res<Seed>,
) { ) {
let mut rng = rand::rngs::SmallRng::seed_from_u64(seed.0 as u64); let mut rng = rand::rngs::SmallRng::seed_from_u64(seed.0 as u64);
@ -87,7 +87,7 @@ fn setup(
// Distance - [0; sqrt(2)] * 0.5 // Distance - [0; sqrt(2)] * 0.5
) )
.clamp(0., 1.); .clamp(0., 1.);
let m = ( let _m = (
(moisture_noise.get([site.x, site.y]) + 1.) / 2. (moisture_noise.get([site.x, site.y]) + 1.) / 2.
// Noise + [0; 1] // Noise + [0; 1]
) )
@ -123,7 +123,7 @@ fn setup(
poss.push(Vec3::new(v.x as f32, v.y as f32, 0.)); poss.push(Vec3::new(v.x as f32, v.y as f32, 0.));
} }
for v in 1..(vs.len() - 1) { for v in 1..(vs.len() - 1) {
indices.extend_from_slice(&[i as u32, (i + v) as u32, (i + v + 1) as u32]); indices.extend_from_slice(&[(i + v + 1) as u32, (i + v) as u32, i as u32]);
cd.vertices.extend_from_slice(&[i, i + v, i + v + 1]); cd.vertices.extend_from_slice(&[i, i + v, i + v + 1]);
} }
} }
@ -141,8 +141,9 @@ fn setup(
let mut cells_entities = Vec::with_capacity(cells.len()); let mut cells_entities = Vec::with_capacity(cells.len());
cmds.spawn(( cmds.spawn((
Mesh2d(meshes.add(mesh)), Mesh3d(meshes.add(mesh)),
MeshMaterial2d(materials.add(ColorMaterial::default())), // StandardMaterial
MeshMaterial3d(materials.add(StandardMaterial::default())),
Transform::default(), Transform::default(),
Voronoi(voronoi), Voronoi(voronoi),
MapColors(colors), MapColors(colors),
@ -174,7 +175,7 @@ fn setup(
fn update_map_mesh( fn update_map_mesh(
cells: Query<(&Cell, Option<&Wealth>)>, cells: Query<(&Cell, Option<&Wealth>)>,
mut map: Query<(&Mesh2d, &mut MapColors, &mut MeshNeedsUpdate), With<MapMarker>>, mut map: Query<(&Mesh3d, &mut MapColors, &mut MeshNeedsUpdate), With<MapMarker>>,
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
) { ) {
let (mesh, mut cols, mut needs_update) = map.single_mut(); let (mesh, mut cols, mut needs_update) = map.single_mut();

View File

@ -11,6 +11,7 @@ pub enum AnimalKind {
} }
#[derive(Component)] #[derive(Component)]
#[require(Transform)]
pub struct Animal { pub struct Animal {
kind: AnimalKind, pub kind: AnimalKind,
} }

View File

@ -3,9 +3,9 @@ use std::time::Duration;
use bevy::prelude::*; use bevy::prelude::*;
use rand::{seq::IteratorRandom, thread_rng, Rng}; use rand::{seq::IteratorRandom, thread_rng, Rng};
use crate::time::GameTime; use crate::{time::GameTime, ui::CurrentAction};
use super::{CellsEntities, MapMarker, MeshNeedsUpdate, Voronoi}; use super::{animals::Animal, AnimalKind, CellsEntities, MapMarker, MeshNeedsUpdate, Voronoi};
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CellKind { pub enum CellKind {
@ -24,6 +24,12 @@ impl CellKind {
CellKind::Grass => Duration::from_weeks(7), // Let's say that grass takes 7 weeks to reach its max CellKind::Grass => Duration::from_weeks(7), // Let's say that grass takes 7 weeks to reach its max
} }
} }
pub fn can_place_animal(&self, kind: AnimalKind) -> bool {
match self {
CellKind::Sea => false,
_ => true,
}
}
} }
#[derive(Debug, Component)] #[derive(Debug, Component)]
@ -167,6 +173,7 @@ pub fn expand(
pub fn on_click( pub fn on_click(
trigger: Trigger<Pointer<Click>>, trigger: Trigger<Pointer<Click>>,
mut cells: Query<&mut Cell>, mut cells: Query<&mut Cell>,
voronoi: Query<&Voronoi>,
mut map_needs_update: Query<&mut MeshNeedsUpdate, With<MapMarker>>, mut map_needs_update: Query<&mut MeshNeedsUpdate, With<MapMarker>>,
mut cmds: Commands, mut cmds: Commands,
ca: Res<CurrentAction>, ca: Res<CurrentAction>,
@ -175,39 +182,47 @@ pub fn on_click(
if trigger.duration > Duration::from_millis(200) { if trigger.duration > Duration::from_millis(200) {
return; return;
} }
let mut cell = cells.get_mut(trigger.target).unwrap();
match *ca { match *ca {
CurrentAction::ChangeCell(ck) => { CurrentAction::ChangeCell(ck) => match ck {
let mut cell = cells.get_mut(trigger.target).unwrap(); CellKind::Forest => match cell.kind {
match ck { CellKind::Dirt | CellKind::Grass => {
CellKind::Forest => match cell.kind { cmds.entity(trigger.target).insert((
CellKind::Dirt | CellKind::Grass => { Wealth(0),
cmds.entity(trigger.target).insert(( Regeneration {
Wealth(0), last_update: gt.current,
Regeneration { full_growth_duration: CellKind::Forest.regen_full_growth_duration(),
last_update: gt.current, },
full_growth_duration: CellKind::Forest.regen_full_growth_duration(), ));
}, cell.kind = CellKind::Forest;
)); map_needs_update.single_mut().0 = true;
cell.kind = CellKind::Forest; }
map_needs_update.single_mut().0 = true;
}
_ => {}
},
CellKind::Grass => match cell.kind {
CellKind::Dirt => {
cmds.entity(trigger.target).insert((
Wealth(0),
Regeneration {
last_update: gt.current,
full_growth_duration: CellKind::Grass.regen_full_growth_duration(),
},
));
cell.kind = CellKind::Grass;
map_needs_update.single_mut().0 = true;
}
_ => {}
},
_ => {} _ => {}
},
CellKind::Grass => match cell.kind {
CellKind::Dirt => {
cmds.entity(trigger.target).insert((
Wealth(0),
Regeneration {
last_update: gt.current,
full_growth_duration: CellKind::Grass.regen_full_growth_duration(),
},
));
cell.kind = CellKind::Grass;
map_needs_update.single_mut().0 = true;
}
_ => {}
},
_ => {}
},
CurrentAction::AddAnimal(ak) => {
if cell.kind.can_place_animal(ak) {
let v_cell = voronoi.single().0.cell(cell.voronoi_id);
let cell_pos = v_cell.site_position();
cmds.spawn((
Animal { kind: ak },
Transform::from_xyz(cell_pos.x as f32, cell_pos.y as f32, 0.),
));
} }
} }
_ => {} _ => {}

View File

@ -8,10 +8,11 @@ use bevy::{
}; };
use voronoice::Point; use voronoice::Point;
use crate::camera::CameraMarker;
use super::{CellsEntities, MapMarker, Voronoi}; use super::{CellsEntities, MapMarker, Voronoi};
pub fn picking_backend( pub fn picking_backend(
cam: Query<(&Transform, Entity), With<Camera2d>>, cam: Query<(&Transform, Entity), With<CameraMarker>>,
window: Query<&Window, With<PrimaryWindow>>, window: Query<&Window, With<PrimaryWindow>>,
pointers: Query<(&PointerId, &PointerLocation)>, pointers: Query<(&PointerId, &PointerLocation)>,
map: Query<(&Voronoi, &CellsEntities, &Transform), With<MapMarker>>, map: Query<(&Voronoi, &CellsEntities, &Transform), With<MapMarker>>,

View File

@ -1,18 +1,16 @@
use bevy::{ use bevy::{
asset::embedded_asset, asset::embedded_asset,
input::mouse::MouseWheel, input::mouse::MouseWheel,
math::{NormedVectorSpace, VectorSpace}, math::NormedVectorSpace,
picking::{focus::HoverMap, pointer::PointerId}, picking::{focus::HoverMap, pointer::PointerId},
prelude::*, prelude::*,
utils::{dbg, HashMap}, utils::HashMap,
window::PrimaryWindow, window::PrimaryWindow,
}; };
use mevy::*; use mevy::*;
use crate::{ use crate::map::{AnimalKind, CellKind};
animals::AnimalKind, use crate::camera::CameraMarker;
map::{self, CellKind, MapMarker},
};
// #77767b // #77767b
const TABBAR_COLOR: Color = Color::srgb(119. / 255., 118. / 255., 123. / 255.); const TABBAR_COLOR: Color = Color::srgb(119. / 255., 118. / 255., 123. / 255.);
@ -67,7 +65,7 @@ fn setup(mut world: Commands, asset_server: Res<AssetServer>) {
.observe(| .observe(|
trigger: Trigger<Pointer<Drag>>, trigger: Trigger<Pointer<Drag>>,
mut ptrs: Query<&mut PointersDragging>, mut ptrs: Query<&mut PointersDragging>,
mut cam: Query<&mut Transform, With<Camera2d>>, mut cam: Query<&mut Transform, With<CameraMarker>>,
| { | {
if trigger.button == PointerButton::Primary { if trigger.button == PointerButton::Primary {
let mut ptrs = ptrs.single_mut(); let mut ptrs = ptrs.single_mut();
@ -186,7 +184,7 @@ fn setup(mut world: Commands, asset_server: Res<AssetServer>) {
} }
fn zoom_with_scroll( fn zoom_with_scroll(
mut cam: Query<&mut Transform, With<Camera2d>>, mut cam: Query<&mut Transform, With<CameraMarker>>,
mut ev_scroll: EventReader<MouseWheel>, mut ev_scroll: EventReader<MouseWheel>,
hover_map: Res<HoverMap>, hover_map: Res<HoverMap>,
window: Query<&Window, With<PrimaryWindow>>, window: Query<&Window, With<PrimaryWindow>>,