Compare commits

..

No commits in common. '21e67e04ca7823d23c2f1eb709c729e8d63dd363' and '4bc704aff05a120ed5cf3180ca091647f51f6b1b' have entirely different histories.

@ -25,6 +25,10 @@ rustflags = [
[target.x86_64-pc-windows-msvc] [target.x86_64-pc-windows-msvc]
linker = "rust-lld.exe" # Use LLD Linker linker = "rust-lld.exe" # Use LLD Linker
rustflags = [
"-Zshare-generics=n",
"-Zthreads=0", # (Nightly) Use improved multithreading with the recommended amount of threads.
]
[target.wasm32-unknown-unknown] [target.wasm32-unknown-unknown]
runner = "wasm-server-runner" runner = "wasm-server-runner"

1097
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -4,23 +4,10 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
bevy = { version = "0.14", features = ["file_watcher", "dynamic_linking"] } bevy = "0.13"
wee_alloc = "*" wee_alloc = "*"
bevy_mod_picking = "0.20" bevy_mod_picking = "0.18"
thiserror = "1"
nom = "7"
# Entity Uuid parsing
uuid = "1.7.0"
# Enable a small amount of optimization in debug mode
[profile.dev]
opt-level = 1
# Enable high optimizations for dependencies (incl. Bevy), but not for our code:
[profile.dev.package."*"]
opt-level = 3
# Prioritize binary size for wasm
[profile.wasm-release] [profile.wasm-release]
inherits = "release" inherits = "release"
opt-level = "z" opt-level = "z"

@ -1,18 +0,0 @@
# Assets
## Custom Assets
### `.entity` files
Files ending in `.entity` store entity information.
Note: This data is not auto-magically de/serialized by Bevy, we write our own parser using `nom`.
This custom reading/writing can be found in `src/save.rs`.
All `.entity` files list one component per line.
Components include:
* `name <string>` Human readable name for entity
* `uuid <UUID string>` Globally unique ID for entity
* `transform translation <f32> <f32> <f32> rotation <f32> <f32> <f32> <f32> scale <f32> <f32> <f32>` 3D Location, Rotation, and Scale for entity
* `model "<path string>" "<string>"` Declares the entity's 3d model
* `camera` Marks the entity as a camera

@ -1,4 +0,0 @@
name camera
uuid 2e45b7e9-6722-4d50-8ea5-67f25b8b0f62
transform translation 2.0 2.0 0.0 rotation 0.0 0.0 0.0 1.0 scale 1.0 1.0 1.0
camera

@ -1,4 +0,0 @@
name van
uuid 5c270e84-814c-4d51-9ccd-ab79d9e01f1d
transform translation 0.0 0.0 0.0 rotation 0.0 0.0 0.0 1.0 scale 1.0 1.0 1.0
model "models/van.glb" "Scene"

Binary file not shown.

Binary file not shown.

@ -1,127 +0,0 @@
use crate::prelude::*;
/// Menu Plugin; empty struct for Plugin impl
pub(crate) struct CameraPlugin;
impl Plugin for CameraPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
Update,
move_editor_fly_camera.run_if(any_with_component::<FlyCamera>),
);
app.add_systems(
Update,
rotate_editor_fly_camera.run_if(any_with_component::<FlyCamera>),
);
}
}
#[derive(Component)]
pub(crate) struct FlyCamera;
/// Fly camera system for moving around like a drone
/// TODO: Only if key is pressed!
fn move_editor_fly_camera(
mut cameras: Query<(&Camera, &mut Transform), With<FlyCamera>>,
windows: Query<&Window>,
primary_window: Query<Entity, With<PrimaryWindow>>,
keys: Res<ButtonInput<KeyCode>>,
time: Res<Time>,
) {
(keys.any_pressed([
KeyCode::KeyW,
KeyCode::KeyS,
KeyCode::KeyA,
KeyCode::KeyD,
KeyCode::KeyQ,
KeyCode::KeyE,
]))
.then(|| {
// Iterate over all cameras
cameras.iter_mut().for_each(|(c, mut t)| {
// Determine which window this camera is attached to
let target_window = match c.target {
RenderTarget::Window(wr) => match wr {
WindowRef::Entity(e) => Some(e),
WindowRef::Primary => Some(primary_window.get_single().unwrap()),
},
_ => None,
};
let window = windows.get(target_window.unwrap()).unwrap();
// If the target window is focused
window.focused.then(|| {
let move_speed = if keys.pressed(KeyCode::ShiftLeft) {
16.0
} else {
4.0
};
let mut delta = Vec3::ZERO;
if keys.pressed(KeyCode::KeyW) {
delta += t.forward() * move_speed * time.delta_seconds()
}
if keys.pressed(KeyCode::KeyS) {
delta += t.back() * move_speed * time.delta_seconds()
}
if keys.pressed(KeyCode::KeyA) {
delta += t.left() * move_speed * time.delta_seconds()
}
if keys.pressed(KeyCode::KeyD) {
delta += t.right() * move_speed * time.delta_seconds()
}
if keys.pressed(KeyCode::KeyE) {
delta += Vec3::Y * move_speed * time.delta_seconds()
}
if keys.pressed(KeyCode::KeyQ) {
delta += Vec3::NEG_Y * move_speed * time.delta_seconds()
}
t.translation += delta;
});
});
});
}
fn rotate_editor_fly_camera(
mut cameras: Query<(&Camera, &mut Transform), With<FlyCamera>>,
windows: Query<&Window>,
primary_window: Query<Entity, With<PrimaryWindow>>,
mouse: Res<ButtonInput<MouseButton>>,
mut cursor_events: EventReader<CursorMoved>,
) {
(!cursor_events.is_empty()).then(|| {
// Iterate over all cameras
cameras.iter_mut().for_each(|(c, mut t)| {
// Determine which window this camera is attached to
let target_window = match c.target {
RenderTarget::Window(wr) => match wr {
WindowRef::Entity(e) => Some(e),
WindowRef::Primary => Some(primary_window.get_single().unwrap()),
},
_ => None,
};
let window = windows.get(target_window.unwrap()).unwrap();
if mouse.pressed(MouseButton::Middle) {
cursor_events
.read()
.filter_map(|CursorMoved { delta, window, .. }| {
(*window == target_window.unwrap()).then_some(delta)
})
.for_each(|delta| {
if let Some(Vec2 { x, y }) = delta {
// Cribbing from bevy_flycam
// Link: https://github.com/sburris0/bevy_flycam/blob/baffe50e0961ad1491d467fa6ab5551f9f21db8f/src/lib.rs#L145-L151
let (mut yaw, mut pitch, _) = t.rotation.to_euler(EulerRot::YXZ);
let window_scale = window.height().min(window.width());
let sensitivity = 0.00012;
pitch -= (sensitivity * y * window_scale).to_radians();
yaw -= (sensitivity * x * window_scale).to_radians();
t.rotation = Quat::from_axis_angle(Vec3::Y, yaw)
* Quat::from_axis_angle(Vec3::X, pitch);
}
});
} else {
cursor_events.clear();
}
});
});
}

@ -1,8 +1,5 @@
use bevy::prelude::*; use bevy::prelude::*;
/// pub(crate) fn any_component_added<T: Component>(q: Query<Entity, Added<T>>) -> bool {
/// ECS scheduler active when an entity has a given component added
///
pub(crate) fn any_component_added<C: Component>(q: Query<Entity, Added<C>>) -> bool {
!q.is_empty() !q.is_empty()
} }

@ -1,5 +1,3 @@
use bevy::render::primitives::Aabb;
use crate::prelude::*; use crate::prelude::*;
pub(crate) struct EditorPlugin; pub(crate) struct EditorPlugin;
@ -14,16 +12,14 @@ impl Plugin for EditorPlugin {
) )
.add_systems( .add_systems(
Update, Update,
( toggle_editor_window.run_if(state_changed::<EditorState>),
toggle_editor_window,
toggle_entity_aabb,
).run_if(state_changed::<EditorState>),
) )
.add_systems( .add_systems(
Update, Update,
( (
handle_window_close.run_if(on_event::<WindowCloseRequested>()), handle_window_close.run_if(on_event::<WindowCloseRequested>()),
plane_gizmos, plane_gizmos,
// fly_camera,
) )
.run_if(in_state(EditorState::Open)), .run_if(in_state(EditorState::Open)),
); );
@ -32,7 +28,7 @@ impl Plugin for EditorPlugin {
/// Tracking the open/closed state of the editor /// Tracking the open/closed state of the editor
#[derive(States, Debug, Clone, PartialEq, Eq, Hash, Default, Component)] #[derive(States, Debug, Clone, PartialEq, Eq, Hash, Default, Component)]
pub(crate) enum EditorState { enum EditorState {
/// The editor is closed => the editor window is disabled /// The editor is closed => the editor window is disabled
#[default] #[default]
Closed, Closed,
@ -82,7 +78,6 @@ fn init_editor(mut commands: Commands) {
let editor_camera = commands let editor_camera = commands
.spawn(( .spawn((
EditorTag, EditorTag,
FlyCamera,
Camera3dBundle { Camera3dBundle {
camera: Camera { camera: Camera {
target: RenderTarget::Window(WindowRef::Entity(editor_window)), target: RenderTarget::Window(WindowRef::Entity(editor_window)),
@ -107,7 +102,7 @@ fn init_editor(mut commands: Commands) {
TextBundle::from_section( TextBundle::from_section(
"Welcome to the editor", "Welcome to the editor",
TextStyle { TextStyle {
color: WHITE.into(), color: Color::WHITE,
..default() ..default()
}, },
), ),
@ -167,61 +162,25 @@ fn plane_gizmos(mut gizmos: Gizmos) {
let r = 20.0; let r = 20.0;
let start = -r as i8; let start = -r as i8;
let end = r as i8; let end = r as i8;
(start..=end).for_each(|n| { (start..=end).into_iter().for_each(|n| {
{ {
let offset = Vec3::Z * (n as f32); let offset = Vec3::Z * (n as f32);
gizmos.line(Vec3::NEG_X * r + offset, Vec3::X * r + offset, GRAY); gizmos.line(Vec3::NEG_X * r + offset, Vec3::X * r + offset, Color::GRAY);
} }
{ {
let offset = Vec3::X * (n as f32); let offset = Vec3::X * (n as f32);
gizmos.line(Vec3::NEG_Z * r + offset, Vec3::Z * r + offset, GRAY); gizmos.line(Vec3::NEG_Z * r + offset, Vec3::Z * r + offset, Color::GRAY);
} }
}); });
// World origin arrows // World origin arrows
{ {
gizmos.arrow(Vec3::ZERO, Vec3::X, RED); gizmos.arrow(Vec3::ZERO, Vec3::X, Color::RED);
gizmos.arrow(Vec3::ZERO, Vec3::Y, DARK_GREEN); gizmos.arrow(Vec3::ZERO, Vec3::Y, Color::DARK_GREEN);
gizmos.arrow(Vec3::ZERO, Vec3::Z, BLUE); gizmos.arrow(Vec3::ZERO, Vec3::Z, Color::BLUE);
}
}
fn toggle_entity_aabb(
state: Res<State<EditorState>>,
query: Query<Entity, With<Handle<SaveEntity>>>,
children: Query<&Children>,
aabbs: Query<(Entity, &Aabb)>,
mut commands: Commands,
) {
let add_component = match state.get() {
EditorState::Open => true,
EditorState::Closed => false,
};
query.iter().for_each(|root| {
if add_component {
commands.entity(root).insert(ShowAabbGizmo { ..default() });
} else {
commands.entity(root).remove::<ShowAabbGizmo>();
}
let mut child_has_bounding_box = false;
for child in children.iter_descendants(root) {
if add_component {
commands.entity(child).insert(ShowAabbGizmo { ..default() });
} else {
commands.entity(child).remove::<ShowAabbGizmo>();
}
if aabbs.contains(child) {
commands.entity(child).insert(Aabb { center: Vec3A::ZERO, half_extents: Vec3A::ONE });
} else {
child_has_bounding_box = true;
} }
} }
if !aabbs.contains(root) && !child_has_bounding_box { fn _fly_camera(mut _camera: Query<&mut Transform, (With<Camera>, With<EditorTag>)>) {
commands.entity(root).insert(Aabb { center: Vec3A::ZERO, half_extents: Vec3A::ONE }); todo!()
} else {
commands.entity(root).remove::<Aabb>();
}
})
} }

@ -1,5 +1,3 @@
/// Camera controller
pub(crate) mod camera;
/// ECS scheduling `run_if` conditions /// ECS scheduling `run_if` conditions
pub(crate) mod conditions; pub(crate) mod conditions;
/// Editor: debugging and run-time modifications to the game /// Editor: debugging and run-time modifications to the game
@ -8,8 +6,6 @@ pub(crate) mod editor;
pub(crate) mod menu; pub(crate) mod menu;
/// Helper module containing common imports across the project /// Helper module containing common imports across the project
pub(crate) mod prelude; pub(crate) mod prelude;
/// Level saving/loading logic
pub(crate) mod save;
/// Window handling /// Window handling
pub(crate) mod window; pub(crate) mod window;
@ -23,10 +19,8 @@ fn main() {
exit_condition: bevy::window::ExitCondition::OnPrimaryClosed, exit_condition: bevy::window::ExitCondition::OnPrimaryClosed,
..default() ..default()
})) }))
.add_plugins(camera::CameraPlugin)
.add_plugins(editor::EditorPlugin)
.add_plugins(menu::MenuPlugin) .add_plugins(menu::MenuPlugin)
.add_plugins(save::SavePlugin) .add_plugins(editor::EditorPlugin)
.add_plugins(window::WindowPlugin) .add_plugins(window::WindowPlugin)
.run(); .run();
} }

@ -9,7 +9,7 @@ impl Plugin for MenuPlugin {
} }
fn init_menu(mut commands: Commands) { fn init_menu(mut commands: Commands) {
// commands.spawn(Camera3dBundle { ..default() }); commands.spawn(Camera3dBundle { ..default() });
commands commands
.spawn(NodeBundle { .spawn(NodeBundle {
style: Style { style: Style {

@ -1,26 +1,9 @@
pub(crate) use bevy::{ pub(crate) use bevy::app::AppExit;
app::AppExit, pub(crate) use bevy::input::common_conditions::input_just_pressed;
asset::{io::Reader, AssetLoader, LoadContext, AsyncReadExt}, pub(crate) use bevy::prelude::*;
color::palettes::css::{WHITE, GRAY, RED, DARK_GREEN, BLUE}, pub(crate) use bevy::render::camera::RenderTarget;
gltf::Gltf, pub(crate) use bevy::window::PrimaryWindow;
input::common_conditions::input_just_pressed, pub(crate) use bevy::window::WindowCloseRequested;
math::Vec3A, pub(crate) use bevy::window::WindowRef;
prelude::*,
render::camera::RenderTarget,
window::{WindowRef, WindowCloseRequested, PrimaryWindow},
};
pub(crate) use nom::{
IResult,
bytes::complete::{tag, take_until1},
character::complete::space1,
number::complete::float,
sequence::tuple,
};
pub(crate) use uuid::Uuid;
pub(crate) use thiserror::Error;
pub(crate) use crate::{ pub(crate) use crate::conditions::*;
camera::*,
conditions::*,
save::*,
};

@ -1,367 +0,0 @@
use crate::prelude::*;
/// Menu Plugin; empty struct for Plugin impl
pub(crate) struct SavePlugin;
impl Plugin for SavePlugin {
fn build(&self, app: &mut App) {
app.init_asset::<SaveEntity>()
.init_asset_loader::<SaveEntityLoader>()
.add_systems(Startup, test_save_entity)
.add_systems(
Update,
check_loaded_entity_assets.run_if(on_event::<AssetEvent<SaveEntity>>()),
);
}
}
#[derive(Asset, TypePath, Debug, Default)]
pub(crate) struct SaveEntity {
transform: Option<Transform>,
name: Option<Name>,
uuid: Option<EntityUuid>,
model: Option<(Handle<Gltf>, String)>,
// TODO: Option<bool> feels like an antipattern as this is either Some(true) or None and never Some(false).
// Either encode more Camera data in this or replace it with Option<()> or just bool.
camera: Option<bool>,
}
impl SaveEntity {
fn parse(
text: &str,
load_context: &mut LoadContext,
) -> Result<SaveEntity, parse::SaveEntityParseError> {
let lines = text.split('\n');
let mut entity = SaveEntity { ..default() };
lines.into_iter().for_each(|line| {
if let Ok(name) = parse::parse_save_name(line) {
entity.name = Some(name);
} else if let Ok(transform) = parse::parse_save_transform(line) {
entity.transform = Some(transform);
} else if let Ok(uuid) = parse::parse_save_uuid(line) {
entity.uuid = Some(EntityUuid(uuid));
} else if let Ok((gltf_path, scene_name)) = parse::parse_save_model(line) {
let handle = load_context.load(gltf_path);
entity.model = Some((handle, scene_name));
} else if let Ok(true) = parse::parse_save_camera(line) {
entity.camera = Some(true);
}
});
Ok(entity)
}
}
#[derive(Component, Clone, Debug)]
struct EntityUuid(Uuid);
mod parse {
use super::*;
#[derive(Error, Debug)]
pub(crate) enum SaveEntityParseError {
#[error("Failed to parse Uuid: {0}")]
Uuid(#[from] uuid::Error),
#[error("Failed to parse name")]
Name,
#[error("Failed to parse Transform")]
Transform,
#[error("Failed to parse Entity")]
Nom(nom::Err<nom::error::Error<Box<str>>>),
}
// Convert Nom error to parse error
// https://stackoverflow.com/a/77974858/3096574
impl From<nom::Err<nom::error::Error<&str>>> for SaveEntityParseError {
fn from(err: nom::Err<nom::error::Error<&str>>) -> Self {
SaveEntityParseError::Nom(err.map_input(|input| input.into()))
}
}
fn parse_word<'a>(w: &'a str) -> impl Fn(&'a str) -> IResult<&'a str, &'a str> {
move |i: &'a str| {
tag(w)(i)
}
}
fn parse_xyz(i: &str) -> IResult<&str, (f32, f32, f32)> {
tuple((float, space1, float, space1, float))(i)
.map(|(s, (x, _, y, _, z))| (s, (x, y, z)))
}
fn parse_wxyz(i: &str) -> IResult<&str, (f32, f32, f32, f32)> {
tuple((
float,
space1,
float,
space1,
float,
space1,
float,
))(i)
.map(|(s, (w, _, x, _, y, _, z))| (s, (w, x, y, z)))
}
fn parse_string(i: &str) -> IResult<&str, &str> {
let (rem, (_, out, _)) = tuple((tag("\""), take_until1("\""), tag("\"")))(i)?;
Ok((rem, out))
}
///
/// ```
/// let parsed = parse_save_transform("transform translation 1.0 2.0 3.0 rotation 1.0 0.0 0.0 0.0 scale 1.0 1.0 1.0").unwrap();
/// let expected = Transform { translation: Vec3::new(1.0, 1.0, 1.0), rotation: Quat::from_xzyw(1.0, 0.0, 0.0, 0.0), scale: Vec3::ONE };
///
/// assert_eq!(parsed, expected);
/// ```
///
pub(crate) fn parse_save_transform(line: &str) -> Result<Transform, SaveEntityParseError> {
let (rem, _) = tag("transform")(line)?;
let mut transform = Transform::default();
let mut curr = rem.trim_start();
for _ in 0..3 {
println!("Curr: {:?}", curr);
if let Ok((rem, (_, _, (x, y, z)))) = tuple((parse_word("translation"), space1, parse_xyz))(curr.trim_start()) {
transform.translation = Vec3::new(x, y, z);
curr = rem.trim_start();
} else if let Ok((rem, (_, _, (x, y, z, w)))) = tuple((parse_word("rotation"), space1, parse_wxyz))(curr.trim_start()) {
transform.rotation = Quat::from_xyzw(x, y, z, w);
curr = rem.trim_start();
} else if let Ok((rem, (_, _, (x, y, z)))) = tuple((parse_word("scale"), space1, parse_xyz))(curr.trim_start()) {
transform.scale = Vec3::new(x, y, z);
curr = rem.trim_start();
} else {
return Err(SaveEntityParseError::Transform);
}
}
// Assert there are no trailing characters on the line
debug_assert_eq!(curr, "");
Ok(transform)
}
#[test]
fn test_parse_transform() {
let line = "transform translation 1.0 2.0 3.0 rotation 0.1 0.2 0.3 1.0 scale 1.1 1.2 1.3";
let parsed = parse_save_transform(line).unwrap();
let expected = Transform {
translation: Vec3::new(1.0, 2.0, 3.0),
rotation: Quat::from_xyzw(0.1, 0.2, 0.3, 1.0),
scale: Vec3::new(1.1, 1.2, 1.3),
};
assert_eq!(parsed, expected);
}
///
/// ```
/// let parsed = parse_save_name("name asfd").unwrap();
/// let expected = Name::new("asdf");
///
/// assert_eq!(parsed, expected);
/// ```
///
pub(crate) fn parse_save_name(line: &str) -> Result<Name, SaveEntityParseError> {
let (remainder, _) = tag("name")(line)?;
let n = remainder.trim().to_string();
if n.is_empty() {
Err(SaveEntityParseError::Name)
} else {
let name = Name::new(n);
Ok(name)
}
}
#[test]
fn test_parse_name() {
{
let line = "name Van";
let parsed = parse_save_name(line).unwrap();
let expected = Name::new("Van");
assert_eq!(parsed, expected);
}
{
let line = "name Big Mike";
let parsed = parse_save_name(line).unwrap();
let expected = Name::new("Big Mike");
assert_eq!(parsed, expected);
}
}
///
/// ```
/// let parsed = parse_save_uuid("uuid 339dd18f-761a-4997-b515-ef57f4dc2447").unwrap();
/// let expected = Uuid::parse_str("339dd18f-761a-4997-b515-ef57f4dc2447").unwrap();
///
/// assert_eq!(parsed, expected);
/// ```
///
pub(crate) fn parse_save_uuid(line: &str) -> Result<Uuid, SaveEntityParseError> {
let (remainder, _) = tag("uuid")(line)?;
let uuid = Uuid::try_parse(remainder.trim())?;
Ok(uuid)
}
#[test]
fn test_parse_uuid() {
let line = "uuid 1c16ab9a-5f79-4340-8469-4086f69c64f2";
let parsed = parse_save_uuid(line).unwrap();
let expected = Uuid::parse_str("1c16ab9a-5f79-4340-8469-4086f69c64f2").unwrap();
assert_eq!(parsed, expected);
}
pub(crate) fn parse_save_model(line: &str) -> Result<(String, String), SaveEntityParseError> {
let (rem, (_, _, gltf_name, _, scene_name)) = tuple((
tag("model"),
space1,
parse_string,
space1,
parse_string,
))(line)?;
debug_assert_eq!(rem, "");
Ok((String::from(gltf_name), String::from(scene_name)))
}
#[test]
fn test_parse_model() {
let line = "model \"models/foo.glb\" \"My Scene\"";
let parsed = parse_save_model(line).unwrap();
let expected = (String::from("models/foo.glb"), String::from("My Scene"));
assert_eq!(parsed, expected);
}
pub(crate) fn parse_save_camera(line: &str) -> Result<bool, SaveEntityParseError> {
let (_rem, cam) = tag("camera")(line)?;
Ok(!cam.is_empty())
}
#[test]
fn test_parse_camera() {
{
let line = "camera";
let parsed = parse_save_camera(line).unwrap();
let expected = true;
assert_eq!(parsed, expected);
}
{
let line = "notcamera";
let parsed = parse_save_camera(line);
assert!(parsed.is_err());
}
}
}
#[derive(Default)]
struct SaveEntityLoader;
#[derive(Error, Debug)]
enum SaveEntityLoaderError {
#[error("Could not load asset: {0}")]
Io(#[from] std::io::Error),
#[error("Could not parse entity: {0}")]
Parse(#[from] parse::SaveEntityParseError),
}
impl AssetLoader for SaveEntityLoader {
type Asset = SaveEntity;
type Settings = ();
type Error = SaveEntityLoaderError;
async fn load<'a>(
&'a self,
reader: &'a mut Reader<'_>,
_settings: &'a (),
load_context: &'a mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let s = std::str::from_utf8(bytes.as_slice()).unwrap();
let save_entity = SaveEntity::parse(s, load_context)?;
Ok(save_entity)
}
fn extensions(&self) -> &[&str] {
&["entity"]
}
}
fn test_save_entity(loader: Res<AssetServer>, mut commands: Commands) {
let handle: Handle<SaveEntity> = loader.load("levels/00/entities/van.entity");
commands.spawn((
SpatialBundle { ..default() },
handle,
));
}
fn check_loaded_entity_assets(
query: Query<(Entity, &Handle<SaveEntity>)>,
mut events: EventReader<AssetEvent<SaveEntity>>,
save_entities: Res<Assets<SaveEntity>>,
mut commands: Commands,
gltfs: Res<Assets<Gltf>>,
) {
events.read().for_each(|event| {
if let AssetEvent::LoadedWithDependencies { id } = event {
query
.iter()
.filter(|(_, handle)| handle.id() == *id)
.for_each(|(entity, _handle)| {
let saved = save_entities.get(*id).unwrap();
debug!(
"Updating entity {:?} ({:?}) because asset changed",
saved.name, saved.uuid
);
let mut e = commands.entity(entity);
// Apply camera
// Should be applied early to avoid clobbering transform
if let Some(is_camera) = &saved.camera {
if *is_camera {
e.insert(Camera3dBundle { ..default() });
}
} else {
e.remove::<(Camera, Camera3d)>();
}
// Apply transform
if let Some(transform) = &saved.transform {
// TODO: Only update if different
e.insert(*transform);
} else {
e.remove::<Transform>();
}
// Apply Name
if let Some(name) = &saved.name {
// TODO: Only update if different
e.insert(name.clone());
} else {
e.remove::<Name>();
}
// Apply Uuid
if let Some(uuid) = &saved.uuid {
// TODO: Only update if different
e.insert(uuid.clone());
} else {
e.remove::<EntityUuid>();
}
// Apply Model
if let Some((gltf_handle, scene_name)) = &saved.model {
// Find scene and update
let gltf = gltfs.get(gltf_handle).unwrap();
let scene_handle = gltf.named_scenes.get(scene_name.as_str().into()).unwrap();
e.insert(scene_handle.clone());
} else {
e.remove::<Handle<Scene>>();
}
});
}
})
}

@ -36,6 +36,6 @@ fn handle_window_close(
.filter_map(|WindowCloseRequested { window }| primary.get(*window).ok()) .filter_map(|WindowCloseRequested { window }| primary.get(*window).ok())
// If this was the primary window, send an AppExit // If this was the primary window, send an AppExit
.for_each(|_| { .for_each(|_| {
exit.send(AppExit::Success); exit.send(AppExit);
}); });
} }

Loading…
Cancel
Save