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
.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()
});
}
}