use bevy::{
asset::AssetPath,
ecs::{
schedule::StateData,
system::{
EntityCommands,
SystemParam,
},
world::EntityRef,
},
prelude::*,
scene::SceneInstance,
};
use iyes_loopless::prelude::*;
use crate::{
animation::{
AnimationServer,
Tween,
},
camera::{
CameraSpot,
IsCameraSpot,
},
interactives::{
interactive,
prepare_interaction,
reset_interaction,
},
AudioServer,
CurrentSpot,
Interactive,
MAIN_CAMERA,
};
pub struct SceneManagerPlugin;
#[rustfmt::skip]
impl Plugin for SceneManagerPlugin {
fn build(&self, app: &mut App) {
app
.add_system(run_hooks);
}
}
#[derive(Component, Debug)]
pub struct SceneHooked;
#[allow(clippy::type_complexity)]
#[derive(Component)]
pub struct SceneHook {
hook: Box<dyn Fn(&EntityRef, &mut EntityCommands) + Send + Sync + 'static>,
}
impl SceneHook {
pub fn new<F: Fn(&EntityRef, &mut EntityCommands) + Send + Sync + 'static>(hook: F) -> Self {
Self {
hook: Box::new(hook),
}
}
}
#[allow(clippy::needless_pass_by_value)]
fn run_hooks(
world: &World,
mut commands: Commands,
spawner: Res<SceneSpawner>,
unloaded: Query<(Entity, &SceneInstance, &SceneHook), Without<SceneHooked>>,
) {
for (entity, instance, hooked) in unloaded.iter() {
if spawner.instance_is_ready(**instance) {
commands.entity(entity).insert(SceneHooked);
}
let entities = spawner.iter_instance_entities(**instance);
for entity_ref in entities.filter_map(|e| world.get_entity(e)) {
let mut cmd = commands.entity(entity_ref.id());
(hooked.hook)(&entity_ref, &mut cmd);
}
}
}
#[derive(SystemParam)]
pub struct SceneManager<'w, 's> {
commands: Commands<'w, 's>,
asset_server: Res<'w, AssetServer>,
cameras: Query<'w, 's, Entity, With<Camera>>,
unloaded: Query<'w, 's, Entity, (With<SceneHook>, Without<SceneHooked>)>,
}
impl<'w, 's> SceneManager<'w, 's> {
pub fn ready(&self) -> bool {
self.unloaded.is_empty()
}
pub fn load<'a, P, F>(&mut self, path: P, hook: F) -> impl Bundle
where
P: Into<AssetPath<'a>>,
F: Fn(&EntityRef, &mut EntityCommands) + Send + Sync + 'static,
{
let camera;
if let Ok(res) = self.cameras.get_single() {
camera = res;
} else {
camera = self.commands.spawn(()).id();
}
(
SceneBundle {
scene: self.asset_server.load(path),
..default()
},
SceneHook::new(move |entity, commands| {
if entity.contains::<Camera>() {
if let Some(name) = entity.get::<Name>() {
if name.as_str() == MAIN_CAMERA {
let tf = entity.get::<Transform>().unwrap();
let spot = CameraSpot::new(name, entity.id(), *tf);
commands.commands().insert_resource(CurrentSpot::new(spot));
commands
.commands()
.entity(camera)
.insert(Camera3dBundle {
transform: *tf,
..default()
})
.insert(Tween::new(*tf));
}
}
commands.remove::<Camera>();
commands.insert(IsCameraSpot);
}
if let Some(light) = entity.get::<PointLight>() {
let mut light = *light;
light.shadows_enabled = true;
commands.insert(light);
}
hook(entity, commands);
}),
)
}
pub fn spawn<'a, P, F>(&mut self, path: P, hook: F)
where
P: Into<AssetPath<'a>>,
F: Fn(&EntityRef, &mut EntityCommands) + Send + Sync + 'static,
{
let bundle = self.load(path, hook);
self.commands.spawn(bundle);
}
}
#[allow(unused_variables)]
pub trait Scene {
type State: StateData;
fn state() -> Self::State;
fn scene<'a>() -> &'a str;
fn animations(server: &mut AnimationServer) {}
fn audio(server: &mut AudioServer) {}
fn setup(app: &mut App) {}
fn spawn(entity: &EntityRef, commands: &mut EntityCommands);
}
pub trait AppSceneStateExt {
fn add_adventure_state<T: StateData>(&mut self, init: T) -> &mut App;
fn add_scene<S: Scene + 'static>(&mut self) -> &mut App;
fn add_interactive<S, I>(&mut self) -> &mut App
where
S: Scene + 'static,
I: Interactive + Component;
}
impl AppSceneStateExt for App {
fn add_adventure_state<T: StateData>(&mut self, init: T) -> &mut App {
self .add_loopless_state_after_stage(CoreStage::Update, init)
}
fn add_scene<S: Scene + 'static>(&mut self) -> &mut App {
S::setup(self);
self .add_system_to_stage(CoreStage::First, reset_interaction)
.add_system_to_stage(CoreStage::PreUpdate, prepare_interaction::<S::State>)
.add_enter_system(S::state(), spawn_scene::<S>)
.add_exit_system(S::state(), cleanup_scene)
}
fn add_interactive<S, I>(&mut self) -> &mut App
where
S: Scene + 'static,
I: Interactive + Component,
{
self.add_system_set(
ConditionSet::new()
.run_in_state(S::state())
.with_system(interactive::<I>)
.into(),
)
}
}
fn spawn_scene<S: Scene + 'static>(
mut manager: SceneManager,
mut animation_server: AnimationServer,
mut audio_server: AudioServer,
) {
S::animations(&mut animation_server);
S::audio(&mut audio_server);
manager.spawn(S::scene(), S::spawn);
}
#[allow(clippy::needless_pass_by_value)]
fn cleanup_scene(
mut commands: Commands,
scenes: Query<Entity, With<Handle<bevy::prelude::Scene>>>,
) {
for scene in &scenes {
commands.entity(scene).despawn_recursive();
}
}