veebee 1.1.1

A Simple Rust Game Engine For 2D
Documentation
use crate::{
    actor::{Actor, ActorPreset},
    audio::AudioManager,
    mouse::{CursorMoved, MouseButtonInput, MouseMotion, MousePlugin, MouseWheel},
    prelude::{
        AudioManagerPlugin, CollisionEvent, KeyboardInput, KeyboardPlugin, KeyboardState,
        MouseState, PhysicsPlugin,
    },
    text_actor::TextActor,
};
use bevy::{app::AppExit, input::system::exit_on_esc_system, prelude::*, utils::HashMap};
use bevy_kira_audio::*;
use lazy_static::lazy_static;
use log::{debug, info};
use std::{sync::Mutex, time::Duration};

pub type GameLogicFunction = fn(&mut GameState);
pub use bevy::window::{WindowDescriptor, WindowMode, WindowResizeConstraints};

lazy_static! {
    pub(crate) static ref GAME_LOGIC_FUNCTIONS: Mutex<Vec<GameLogicFunction>> = Mutex::new(vec![]);
}

pub struct Game {
    app_builder: AppBuilder,
    game_state: GameState,
    window_descriptor: WindowDescriptor,
}

impl Default for Game {
    fn default() -> Self {
        Self {
            app_builder: App::build(),
            game_state: GameState::default(),
            window_descriptor: WindowDescriptor {
                title: "VeeBee".into(),
                ..Default::default()
            },
        }
    }
}

impl Game {

    pub fn new() -> Self {
        Default::default()
    }

    #[must_use]

    pub fn add_actor<T: Into<String>>(&mut self, label: T, preset: ActorPreset) -> &mut Actor {
        let label = label.into();
        self.game_state
            .actors
            .insert(label.clone(), preset.build(label.clone()));

        self.game_state.actors.get_mut(&label).unwrap()
    }

    #[must_use]

    pub fn add_text_actor<T, S>(&mut self, label: T, text: S) -> &mut TextActor
    where
        T: Into<String>,
        S: Into<String>,
    {
        let label = label.into();
        let text = text.into();
        let text_actor = TextActor {
            label: label.clone(),
            text,
            ..Default::default()
        };
        self.game_state
            .text_actors
            .insert(label.clone(), text_actor);

        self.game_state.text_actors.get_mut(&label).unwrap()
    }


    pub fn game_state_mut(&mut self) -> &mut GameState {
        &mut self.game_state
    }


    pub fn window_settings(&mut self, window_descriptor: WindowDescriptor) -> &mut Self {
        self.window_descriptor = window_descriptor;
        debug!("window descriptor is: {:?}", self.window_descriptor);
        self
    }

    pub fn run(&mut self, func: GameLogicFunction) {
        self.app_builder
            .insert_resource::<WindowDescriptor>(self.window_descriptor.clone());
        self.app_builder
            // Built-ins
            .add_plugins_with(DefaultPlugins, |group| {
                group.disable::<bevy::audio::AudioPlugin>()
            })
            .add_system(exit_on_esc_system.system())

            .add_plugin(AudioPlugin) 

            .add_plugin(AudioManagerPlugin)
            .add_plugin(KeyboardPlugin)
            .add_plugin(MousePlugin)
            .add_plugin(PhysicsPlugin)

            .add_system(game_logic_sync.system().label("game_logic_sync"))
            .add_startup_system(setup.system());

        GAME_LOGIC_FUNCTIONS.lock().unwrap().push(func);
        let world = self.app_builder.world_mut();
        world
            .spawn()
            .insert_bundle(OrthographicCameraBundle::new_2d());
        let game_state = std::mem::take(&mut self.game_state);
        self.app_builder.insert_resource(game_state);
        self.app_builder.run();
    }
}

#[derive(Default, Debug)]
pub struct GameState {

    pub bool_map: HashMap<String, bool>,

    pub f32_map: HashMap<String, f32>,

    pub i32_map: HashMap<String, i32>,

    pub u8_map: HashMap<String, u8>,

    pub u32_map: HashMap<String, u32>,

    pub usize_map: HashMap<String, usize>,

    pub string_map: HashMap<String, String>,

    pub timer_map: HashMap<String, Timer>,

    pub vec2_map: HashMap<String, Vec2>,

    pub bool_vec: Vec<bool>,

    pub f32_vec: Vec<f32>,

    pub i32_vec: Vec<i32>,

    pub u8_vec: Vec<u8>,

    pub u32_vec: Vec<u32>,

    pub usize_vec: Vec<usize>,

    pub string_vec: Vec<String>,

    pub timer_vec: Vec<Timer>,

    pub vec2_vec: Vec<Vec2>,

    pub actors: HashMap<String, Actor>,

    pub text_actors: HashMap<String, TextActor>,

    pub collision_events: Vec<CollisionEvent>,

    pub mouse_state: MouseState,

    pub mouse_button_events: Vec<MouseButtonInput>,

    pub mouse_location_events: Vec<CursorMoved>,

    pub mouse_motion_events: Vec<MouseMotion>,

    pub mouse_wheel_events: Vec<MouseWheel>,

    pub keyboard_state: KeyboardState,

    pub keyboard_events: Vec<KeyboardInput>,

    pub delta: Duration,

    pub delta_f32: f32,

    pub time_since_startup: Duration,

    pub time_since_startup_f64: f64,

    pub audio_manager: AudioManager,

    pub screen_dimensions: Vec2,

    should_exit: bool,
}

impl GameState {
    pub fn exit(&mut self) {
        self.should_exit = true;
    }

    #[must_use]

    pub fn add_actor<T: Into<String>>(&mut self, label: T, preset: ActorPreset) -> &mut Actor {
        let label = label.into();
        self.actors
            .insert(label.clone(), preset.build(label.clone()));

        self.actors.get_mut(&label).unwrap()
    }

    #[must_use]

    pub fn add_text_actor<T, S>(&mut self, label: T, text: S) -> &mut TextActor
    where
        T: Into<String>,
        S: Into<String>,
    {
        let label = label.into();
        let text = text.into();
        let text_actor = TextActor {
            label: label.clone(),
            text,
            ..Default::default()
        };
        self.text_actors.insert(label.clone(), text_actor);

        self.text_actors.get_mut(&label).unwrap()
    }
}

fn setup(
    mut commands: Commands,
    asset_server: Res<AssetServer>,
    materials: ResMut<Assets<ColorMaterial>>,
    windows: Res<Windows>,
    mut game_state: ResMut<GameState>,
) {

    let window = windows.get_primary().unwrap();
    game_state.screen_dimensions = Vec2::new(window.width(), window.height());
    info!("Window dimensions: {}", game_state.screen_dimensions);
    add_actors(&mut commands, &asset_server, materials, &mut game_state);
    add_text_actors(&mut commands, &asset_server, &mut game_state);
}


#[allow(clippy::type_complexity, clippy::too_many_arguments)]
fn game_logic_sync(
    mut commands: Commands,
    asset_server: Res<AssetServer>,
    materials: ResMut<Assets<ColorMaterial>>,
    mut game_state: ResMut<GameState>,
    keyboard_state: Res<KeyboardState>,
    mouse_state: Res<MouseState>,
    time: Res<Time>,
    mut app_exit_events: EventWriter<AppExit>,
    mut collision_events: EventReader<CollisionEvent>,

    mut query_set: QuerySet<(
        Query<&Actor>,
        Query<&TextActor>,
        Query<(Entity, &mut Actor, &mut Transform)>,
        Query<(Entity, &mut TextActor, &mut Transform, &mut Text)>,
    )>,
) {

    game_state.delta = time.delta();
    game_state.delta_f32 = time.delta_seconds();
    game_state.time_since_startup = time.time_since_startup();
    game_state.time_since_startup_f64 = time.seconds_since_startup();


    game_state.keyboard_state = keyboard_state.clone();

    game_state.mouse_state = mouse_state.clone();

    game_state.collision_events.clear();
    for collision_event in collision_events.iter() {
        game_state.collision_events.push(collision_event.clone());
    }

    game_state.actors.clear();
    for actor in query_set.q0().iter() {
        let _ = game_state
            .actors
            .insert(actor.label.clone(), (*actor).clone());
    }


    game_state.text_actors.clear();
    for text_actor in query_set.q1().iter() {
        let _ = game_state
            .text_actors
            .insert(text_actor.label.clone(), (*text_actor).clone());
    }

    for func in GAME_LOGIC_FUNCTIONS.lock().unwrap().iter() {
        func(&mut game_state);
    }

    for (entity, mut actor, mut transform) in query_set.q2_mut().iter_mut() {
        if let Some(actor_copy) = game_state.actors.remove(&actor.label) {
            *actor = actor_copy;
            *transform = actor.bevy_transform();
        } else {
            commands.entity(entity).despawn();
        }
    }


    for (entity, mut text_actor, mut transform, mut text) in query_set.q3_mut().iter_mut() {
        if let Some(text_actor_copy) = game_state.text_actors.remove(&text_actor.label) {
            *text_actor = text_actor_copy;
            *transform = text_actor.bevy_transform();
            if text_actor.text != text.sections[0].value {
                text.sections[0].value = text_actor.text.clone();
            }
            #[allow(clippy::float_cmp)]
            if text_actor.font_size != text.sections[0].style.font_size {
                text.sections[0].style.font_size = text_actor.font_size;
            }
        } else {
            commands.entity(entity).despawn();
        }
    }

    add_actors(&mut commands, &asset_server, materials, &mut game_state);

    add_text_actors(&mut commands, &asset_server, &mut game_state);

    if game_state.should_exit {
        app_exit_events.send(AppExit);
    }
}

fn add_actors(
    commands: &mut Commands,
    asset_server: &Res<AssetServer>,
    mut materials: ResMut<Assets<ColorMaterial>>,
    game_state: &mut GameState,
) {
    for (_, actor) in game_state.actors.drain() {

        let transform = actor.bevy_transform();
        let texture_handle = asset_server.load(actor.filename.as_str());
        commands.spawn().insert(actor).insert_bundle(SpriteBundle {
            material: materials.add(texture_handle.into()),
            transform,
            ..Default::default()
        });
    }
}

fn add_text_actors(
    commands: &mut Commands,
    asset_server: &Res<AssetServer>,
    game_state: &mut GameState,
) {
    for (_, text_actor) in game_state.text_actors.drain() {
        let transform = text_actor.bevy_transform();
        let font_size = text_actor.font_size;
        let text = text_actor.text.clone();
        commands
            .spawn()
            .insert(text_actor)
            .insert_bundle(Text2dBundle {
                text: Text::with_section(
                    text,
                    TextStyle {
                        font: asset_server.load("fonts/FiraSans-Bold.ttf"),
                        font_size,
                        color: Color::WHITE,
                    },
                    TextAlignment {
                        vertical: VerticalAlign::Center,
                        horizontal: HorizontalAlign::Center,
                    },
                ),
                transform,
                ..Default::default()
            });
    }
}