feat: Basic implementation of ref #11 and ref #16

This commit is contained in:
demenik
2025-11-17 18:42:04 +01:00
parent 929500734a
commit fc180ce085
16 changed files with 6061 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
target/

5618
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

19
Cargo.toml Normal file
View File

@@ -0,0 +1,19 @@
[package]
name = "pomomon-garden"
version = "0.1.0"
edition = "2024"
[profile.dev]
opt-level = 1
[profile.dev.package."*"]
opt-level = 3
[profile.release]
codegen-units = 1
lto = "thin"
[dependencies]
bevy = "0.17.2"
bevy_aseprite_ultra = "0.7.0"
bevy_dev_tools = "0.17.2"

BIN
assets/pom-idle.aseprite Normal file

Binary file not shown.

BIN
assets/pom-sleep.aseprite Normal file

Binary file not shown.

BIN
assets/pom.aseprite Normal file

Binary file not shown.

116
flake.lock generated Normal file
View File

@@ -0,0 +1,116 @@
{
"nodes": {
"crane": {
"locked": {
"lastModified": 1762538466,
"narHash": "sha256-8zrIPl6J+wLm9MH5ksHcW7BUHo7jSNOu0/hA0ohOOaM=",
"owner": "ipetkov",
"repo": "crane",
"rev": "0cea393fffb39575c46b7a0318386467272182fe",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"fenix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1763361733,
"narHash": "sha256-ka7dpwH3HIXCyD2wl5F7cPLeRbqZoY2ullALsvxdPt8=",
"owner": "nix-community",
"repo": "fenix",
"rev": "6c8d48e3b0ae371b19ac1485744687b788e80193",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1760524057,
"narHash": "sha256-EVAqOteLBFmd7pKkb0+FIUyzTF61VKi7YmvP1tw4nEw=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "544961dfcce86422ba200ed9a0b00dd4b1486ec5",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"crane": "crane",
"fenix": "fenix",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1762860488,
"narHash": "sha256-rMfWMCOo/pPefM2We0iMBLi2kLBAnYoB9thi4qS7uk4=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "2efc80078029894eec0699f62ec8d5c1a56af763",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

116
flake.nix Normal file
View File

@@ -0,0 +1,116 @@
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
fenix = {
url = "github:nix-community/fenix";
inputs.nixpkgs.follows = "nixpkgs";
};
crane.url = "github:ipetkov/crane";
};
outputs = {
nixpkgs,
fenix,
crane,
flake-utils,
...
}:
flake-utils.lib.eachDefaultSystem (system: let
pkgs = import nixpkgs {inherit system;};
pname = "pomomon-garden";
version = "0.1.0";
rust-toolchain = with fenix.packages.${system};
combine [
beta.rustc
beta.cargo
beta.rust-src
beta.rust-analyzer
];
bevyDeps = with pkgs; [
pkg-config
# Audio
alsa-lib
# Vulkan
vulkan-loader
vulkan-tools
libudev-zero
# X11
xorg.libX11
xorg.libXcursor
xorg.libXi
xorg.libXrandr
# Wayland
wayland
libxkbcommon
# linker
lld
];
runtimeLibs = pkgs.lib.makeLibraryPath bevyDeps;
craneLib = (crane.mkLib pkgs).overrideToolchain rust-toolchain;
cargoArtifacts = craneLib.buildDepsOnly {
pname = "${pname}-deps";
src = craneLib.cleanCargoSource (craneLib.path ./.);
nativeBuildInputs = with pkgs; [pkg-config];
buildInputs = bevyDeps;
};
in {
packages.default = craneLib.buildPackage {
inherit pname version;
src = craneLib.cleanCargoSource (craneLib.path ./.);
inherit cargoArtifacts;
nativeBuildInputs = with pkgs; [
pkg-config
rust-toolchain
lld
makeWrapper
];
buildInputs = bevyDeps;
CARGO_PROFILE_RELEASE_LTO = "thin";
CARGO_PROFILE_RELEASE_CODEGEN_UNITS = 1;
CARGO_PROFILE_RELEASE_STRIP = true;
RUSTFLAGS = "-C link-arg=-fuse-ld=lld";
postInstall = ''
wrapProgram "$out/bin/${pname}" \
--prefix LD_LIBRARY_PATH : ${runtimeLibs}
'';
meta = with pkgs.lib; {
description = "A pomodoro garden management game.";
license = licenses.mit;
};
};
devShells.default = pkgs.mkShell {
nativeBuildInputs = with pkgs; [
pkg-config
lld
];
packages = with pkgs;
[
rust-toolchain
cargo-watch
cargo-edit
cargo-tarpaulin
]
++ bevyDeps;
shellHook = ''
export RUST_SRC_PATH=${fenix.packages.${system}.stable.rust-src}/lib/rustlib/src/rust/library
export LD_LIBRARY_PATH=${runtimeLibs}:$LD_LIBRARY_PATH
'';
};
formatter = pkgs.nixpkgs-fmt;
});
}

21
src/components.rs Normal file
View File

@@ -0,0 +1,21 @@
use bevy::prelude::*;
#[derive(Component)]
pub struct GameRootNode;
#[derive(Component)]
pub struct GameScreenRoot;
#[derive(Component)]
pub struct StartScreenRoot;
#[derive(Component)]
pub struct DynamicMenuText {
pub text: String,
}
#[derive(Component)]
pub struct StartButtonText;
#[derive(Component)]
pub struct FpsText;

3
src/lib.rs Normal file
View File

@@ -0,0 +1,3 @@
pub mod components;
pub mod plugins;
pub mod states;

30
src/main.rs Normal file
View File

@@ -0,0 +1,30 @@
use bevy::prelude::*;
use bevy_aseprite_ultra::prelude::*;
use bevy_dev_tools::fps_overlay::*;
use pomomon_garden::plugins;
fn main() {
App::new()
.add_plugins((
DefaultPlugins.set(ImagePlugin::default_nearest()),
AsepriteUltraPlugin,
))
.add_plugins((FpsOverlayPlugin {
config: FpsOverlayConfig {
refresh_interval: core::time::Duration::from_millis(100),
enabled: true,
frame_time_graph_config: FrameTimeGraphConfig {
enabled: true,
min_fps: 30.0,
target_fps: 144.0,
},
..default()
},
},))
.add_plugins((
plugins::CorePlugin,
plugins::StartScreenPlugin,
plugins::GameScreenPlugin,
))
.run();
}

15
src/plugins/core.rs Normal file
View File

@@ -0,0 +1,15 @@
use crate::states::*;
use bevy::prelude::*;
pub struct CorePlugin;
impl Plugin for CorePlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, setup_camera);
app.init_state::<AppState>();
}
}
fn setup_camera(mut commands: Commands) {
commands.spawn(Camera2d::default());
}

View File

@@ -0,0 +1,24 @@
use crate::states::*;
use bevy::prelude::*;
use bevy_aseprite_ultra::prelude::*;
pub struct GameScreenPlugin;
impl Plugin for GameScreenPlugin {
fn build(&self, app: &mut App) {
app.add_systems(OnEnter(AppState::GameScreen), setup);
app.add_systems(OnExit(AppState::GameScreen), cleanup);
}
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn((
AseAnimation {
aseprite: asset_server.load("pom-sleep.aseprite"),
animation: Animation::tag("sleep-sit-start").with_repeat(AnimationRepeat::Loop),
},
Sprite::default(),
));
}
fn cleanup() {}

7
src/plugins/mod.rs Normal file
View File

@@ -0,0 +1,7 @@
pub mod core;
pub mod game_screen;
pub mod start_screen;
pub use core::CorePlugin;
pub use game_screen::GameScreenPlugin;
pub use start_screen::StartScreenPlugin;

View File

@@ -0,0 +1,83 @@
use crate::states::*;
use bevy::prelude::*;
pub struct StartScreenPlugin;
impl Plugin for StartScreenPlugin {
fn build(&self, app: &mut App) {
app.add_systems(OnEnter(AppState::StartScreen), setup);
app.add_systems(OnExit(AppState::StartScreen), cleanup);
app.add_systems(Update, menu.run_if(in_state(AppState::StartScreen)));
}
}
#[derive(Resource)]
struct MenuData {
button_entity: Entity,
}
const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15);
const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25);
const PRESSED_BUTTON: Color = Color::srgb(0.35, 0.75, 0.35);
fn setup(mut commands: Commands) {
let button_entity = commands
.spawn((
Node {
width: percent(100),
height: percent(100),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..default()
},
children![(
Button,
Node {
width: px(150),
height: px(65),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..default()
},
BackgroundColor(NORMAL_BUTTON),
children![(
Text::new("Play"),
TextFont {
font_size: 33.0,
..default()
},
TextColor(Color::srgb(0.9, 0.9, 0.9))
)]
)],
))
.id();
commands.insert_resource(MenuData { button_entity });
}
fn menu(
mut next_state: ResMut<NextState<AppState>>,
mut interaction_query: Query<
(&Interaction, &mut BackgroundColor),
(Changed<Interaction>, With<Button>),
>,
) {
for (interaction, mut color) in &mut interaction_query {
match *interaction {
Interaction::Pressed => {
*color = PRESSED_BUTTON.into();
next_state.set(AppState::GameScreen);
}
Interaction::Hovered => {
*color = HOVERED_BUTTON.into();
}
Interaction::None => {
*color = NORMAL_BUTTON.into();
}
}
}
}
fn cleanup(mut commands: Commands, menu_data: Res<MenuData>) {
commands.entity(menu_data.button_entity).despawn();
}

8
src/states.rs Normal file
View File

@@ -0,0 +1,8 @@
use bevy::prelude::*;
#[derive(States, Clone, PartialEq, Eq, Debug, Hash, Default, Reflect)]
pub enum AppState {
#[default]
StartScreen,
GameScreen,
}