├── .gitignore ├── Cargo.toml ├── LICENSE ├── src ├── plugin.rs ├── events.rs ├── transition.rs ├── tests.rs └── lib.rs ├── .github └── workflows │ └── ci.yml ├── examples └── signal.rs └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "moonshine-behavior" 3 | version = "0.4.0" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "Minimalistic state machine for Bevy game engine." 7 | homepage = "https://github.com/Zeenobit/moonshine_behavior" 8 | repository = "https://github.com/Zeenobit/moonshine_behavior" 9 | 10 | [dependencies] 11 | bevy_app = "0.17" 12 | bevy_ecs = "0.17" 13 | bevy_reflect = "0.17" 14 | bevy_log = "0.17" 15 | bevy_derive = "0.17" 16 | moonshine-kind = { version = "0.4.1" } 17 | moonshine-util = { version = "0.4" } 18 | 19 | [dev-dependencies] 20 | bevy = "0.17" 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Zeenobit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/plugin.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use bevy_app::prelude::*; 4 | use moonshine_util::reflect::Registerable; 5 | 6 | use crate::{Behavior, Memory, Transition, TransitionQueue}; 7 | 8 | /// A [`Plugin`] for any [`Behavior`] type. 9 | /// 10 | /// This plugin must be added to the [`App`] for behavior [`Transitions`](Transition) to work correctly. 11 | /// You must also add the [`transition`](crate::transition::transition) system separately somewhere in your schedule. 12 | /// 13 | /// # Example 14 | /// ```rust 15 | /// use bevy::prelude::*; 16 | /// use moonshine_behavior::prelude::*; 17 | /// 18 | /// #[derive(Component, Debug, Reflect)] 19 | /// #[reflect(Component)] 20 | /// struct B; 21 | /// 22 | /// impl Behavior for B {} 23 | /// 24 | /// App::new() 25 | /// .add_plugins(BehaviorPlugin::::default()) 26 | /// .add_systems(Update, transition::); 27 | /// 28 | /// // ... 29 | /// ``` 30 | pub struct BehaviorPlugin(PhantomData); 31 | 32 | impl Default for BehaviorPlugin { 33 | fn default() -> Self { 34 | Self(PhantomData) 35 | } 36 | } 37 | 38 | impl Plugin for BehaviorPlugin { 39 | fn build(&self, app: &mut App) { 40 | app.register_type::>() 41 | .register_type::>() 42 | .register_type::>() 43 | .register_required_components::>(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: [ "main" ] 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | # Sparse cargo registry for faster updates 11 | CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse 12 | RUSTFLAGS: "-D warnings" 13 | RUSTDOCFLAGS: '--deny warnings' 14 | 15 | jobs: 16 | lint: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v3 20 | with: 21 | submodules: 'true' 22 | - uses: dtolnay/rust-toolchain@master 23 | with: 24 | toolchain: stable 25 | components: clippy 26 | - name: Populate target directory from cache 27 | uses: Swatinem/rust-cache@v2 28 | with: 29 | save-if: ${{ github.ref == 'refs/heads/main' }} 30 | - name: Install alsa and udev 31 | run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev 32 | - name: Run cargo clippy 33 | run: cargo clippy --tests --examples 34 | 35 | format: 36 | runs-on: ubuntu-latest 37 | env: 38 | RUSTFLAGS: "-D warnings" 39 | steps: 40 | - uses: actions/checkout@v3 41 | with: 42 | submodules: 'true' 43 | - uses: dtolnay/rust-toolchain@master 44 | with: 45 | toolchain: stable 46 | components: rustfmt 47 | - name: Run cargo fmt 48 | run: cargo fmt --check --all 49 | 50 | doc: 51 | runs-on: ubuntu-latest 52 | env: 53 | RUSTFLAGS: "-D warnings" 54 | RUSTDOCFLAGS: '--deny warnings' 55 | steps: 56 | - uses: actions/checkout@v3 57 | with: 58 | submodules: 'true' 59 | - uses: dtolnay/rust-toolchain@master 60 | with: 61 | toolchain: stable 62 | - name: Populate target directory from cache 63 | uses: Swatinem/rust-cache@v2 64 | with: 65 | save-if: ${{ github.ref == 'refs/heads/main' }} 66 | - name: Install alsa and udev 67 | run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev 68 | - name: Run cargo doc 69 | run: cargo doc --no-deps --all-features 70 | 71 | test: 72 | runs-on: ubuntu-latest 73 | steps: 74 | - uses: actions/checkout@v3 75 | with: 76 | submodules: 'true' 77 | - uses: dtolnay/rust-toolchain@master 78 | with: 79 | toolchain: stable 80 | - name: Populate target directory from cache 81 | uses: Swatinem/rust-cache@v2 82 | with: 83 | save-if: ${{ github.ref == 'refs/heads/main' }} 84 | - name: Install alsa and udev 85 | run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev 86 | - name: Run cargo test 87 | run: cargo test --all-features --features bevy/x11 88 | - name: Run doc tests 89 | run: LD_LIBRARY_PATH="$(rustc --print target-libdir)" cargo test --doc --features bevy/x11 90 | -------------------------------------------------------------------------------- /examples/signal.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use bevy::prelude::*; 4 | use moonshine_behavior::prelude::*; 5 | 6 | fn main() { 7 | App::new() 8 | .add_plugins((DefaultPlugins, BehaviorPlugin::::default())) 9 | .add_systems(Startup, (setup, spawn_lights)) 10 | .add_systems( 11 | Update, 12 | (update_signal, transition::, update_lights).chain(), 13 | ) 14 | .add_observer(signal_start) 15 | .add_observer(signal_pause) 16 | .add_observer(signal_resume) 17 | .add_observer(signal_activate) 18 | .add_observer(signal_stop) 19 | .run(); 20 | } 21 | 22 | #[derive(Component, Debug, Reflect)] 23 | #[reflect(Component)] 24 | enum Signal { 25 | Green, 26 | Yellow(Duration), 27 | Red, 28 | } 29 | 30 | impl Behavior for Signal { 31 | fn filter_next(&self, next: &Self) -> bool { 32 | use Signal::*; 33 | match_next! { 34 | self => next, 35 | Green => Yellow(..), 36 | Yellow(..) => Red, 37 | } 38 | } 39 | } 40 | 41 | const GREEN_COLOR: Color = Color::srgb(0.2, 1., 0.); 42 | const YELLOW_COLOR: Color = Color::srgb(1., 0.8, 0.); 43 | const RED_COLOR: Color = Color::srgb(1., 0.2, 0.); 44 | const OFF_COLOR: Color = Color::srgb(0.1, 0.1, 0.1); 45 | 46 | #[derive(Component)] 47 | #[require(Transform, GlobalTransform)] 48 | struct GreenLight; 49 | 50 | #[derive(Component)] 51 | #[require(Transform, GlobalTransform)] 52 | struct YellowLight; 53 | 54 | #[derive(Component)] 55 | #[require(Transform, GlobalTransform)] 56 | struct RedLight; 57 | 58 | fn setup(mut configs: ResMut, mut commands: Commands) { 59 | const HELP_TEXT: &str = " 60 | Press 'Space' to start/reset the Signal cycle!\n"; 61 | 62 | commands.spawn(Camera2d); 63 | commands.spawn(Text::new(HELP_TEXT)); 64 | commands.spawn(Signal::Green); 65 | 66 | let (config, ..) = configs.config_mut::(); 67 | config.enabled = true; 68 | config.line.width = 3.; 69 | } 70 | 71 | fn spawn_lights(mut commands: Commands) { 72 | commands.spawn((Transform::from_xyz(-50., 0., 0.), GreenLight)); 73 | commands.spawn((Transform::from_xyz(0., 0., 0.), YellowLight)); 74 | commands.spawn((Transform::from_xyz(50., 0., 0.), RedLight)); 75 | } 76 | 77 | fn update_signal( 78 | time: Res