Compare commits
No commits in common. '21e67e04ca7823d23c2f1eb709c729e8d63dd363' and '4bc704aff05a120ed5cf3180ca091647f51f6b1b' have entirely different histories.
21e67e04ca
...
4bc704aff0
File diff suppressed because it is too large
Load Diff
@ -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,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>>();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue