feat(wasm): added working examples for bevy_gltf_components & bevy_gltf_blueprints (#90)
closes #88
This commit is contained in:
parent
af94e49976
commit
d7574a104d
|
@ -821,6 +821,18 @@ dependencies = [
|
||||||
"rand",
|
"rand",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bevy_gltf_blueprints_basic_wasm_example"
|
||||||
|
version = "0.3.0"
|
||||||
|
dependencies = [
|
||||||
|
"bevy",
|
||||||
|
"bevy_asset_loader",
|
||||||
|
"bevy_editor_pls",
|
||||||
|
"bevy_gltf_blueprints",
|
||||||
|
"bevy_rapier3d",
|
||||||
|
"rand",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bevy_gltf_blueprints_basic_xpbd_physics_example"
|
name = "bevy_gltf_blueprints_basic_xpbd_physics_example"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
@ -911,6 +923,16 @@ dependencies = [
|
||||||
"bevy_rapier3d",
|
"bevy_rapier3d",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bevy_gltf_components_basic_wasm_example"
|
||||||
|
version = "0.3.0"
|
||||||
|
dependencies = [
|
||||||
|
"bevy",
|
||||||
|
"bevy_editor_pls",
|
||||||
|
"bevy_gltf_components 0.2.0",
|
||||||
|
"bevy_rapier3d",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bevy_hierarchy"
|
name = "bevy_hierarchy"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
|
|
|
@ -3,7 +3,9 @@ members = [
|
||||||
"crates/bevy_gltf_components",
|
"crates/bevy_gltf_components",
|
||||||
"crates/bevy_gltf_blueprints",
|
"crates/bevy_gltf_blueprints",
|
||||||
"examples/bevy_gltf_components/basic/",
|
"examples/bevy_gltf_components/basic/",
|
||||||
|
"examples/bevy_gltf_components/basic_wasm/",
|
||||||
"examples/bevy_gltf_blueprints/basic/",
|
"examples/bevy_gltf_blueprints/basic/",
|
||||||
|
"examples/bevy_gltf_blueprints/basic_wasm/",
|
||||||
"examples/bevy_gltf_blueprints/basic_scene_components/",
|
"examples/bevy_gltf_blueprints/basic_scene_components/",
|
||||||
"examples/bevy_gltf_blueprints/basic_xpbd_physics/",
|
"examples/bevy_gltf_blueprints/basic_xpbd_physics/",
|
||||||
"examples/bevy_gltf_blueprints/nested_blueprints/",
|
"examples/bevy_gltf_blueprints/nested_blueprints/",
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "bevy_gltf_blueprints_basic_wasm_example"
|
||||||
|
version = "0.3.0"
|
||||||
|
edition = "2021"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bevy="0.12"
|
||||||
|
bevy_gltf_blueprints = { path = "../../../crates/bevy_gltf_blueprints" }
|
||||||
|
bevy_rapier3d = { version = "0.23.0", features = [ "serde-serialize", "debug-render-3d", "enhanced-determinism"] }
|
||||||
|
bevy_asset_loader = { version = "0.18", features = ["standard_dynamic_assets" ]}
|
||||||
|
bevy_editor_pls = { version = "0.6" }
|
||||||
|
rand = "0.8.5"
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Basic wasm example/demo
|
||||||
|
|
||||||
|
This example showcases various components & blueprints extracted from the gltf files, including physics colliders & rigid bodies, in wasm
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
as per the bevy documentation:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
rustup target add wasm32-unknown-unknown
|
||||||
|
cargo install wasm-bindgen-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Building this example
|
||||||
|
|
||||||
|
navigate to the current folder , and then
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cargo build --release --target wasm32-unknown-unknown --target-dir ./target
|
||||||
|
wasm-bindgen --out-name wasm_example \
|
||||||
|
--out-dir ./target/wasm \
|
||||||
|
--target web target/wasm32-unknown-unknown/release/bevy_gltf_blueprints_basic_wasm_example.wasm
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running this example
|
||||||
|
|
||||||
|
run a web server in the current folder, and navigate to the page, you should see the example in your browser
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1 @@
|
||||||
|
({})
|
|
@ -0,0 +1,14 @@
|
||||||
|
({
|
||||||
|
"world":File (path: "models/World.glb"),
|
||||||
|
/*"models": Files (
|
||||||
|
paths: [
|
||||||
|
"models/library/Container.glb",
|
||||||
|
"models/library/Health_Pickup.glb",
|
||||||
|
"models/library/MagicTeapot.glb",
|
||||||
|
"models/library/Pillar.glb",
|
||||||
|
"models/library/Player.glb",
|
||||||
|
"models/library/Unused_in_level_test.glb"
|
||||||
|
],
|
||||||
|
)*/
|
||||||
|
|
||||||
|
})
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,27 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
white 0%,
|
||||||
|
white 49%,
|
||||||
|
black 49%,
|
||||||
|
black 51%,
|
||||||
|
white 51%,
|
||||||
|
white 100%
|
||||||
|
);
|
||||||
|
background-repeat: repeat;
|
||||||
|
background-size: 20px 20px;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<script type="module">
|
||||||
|
import init from './target/wasm/wasm_example.js'
|
||||||
|
init()
|
||||||
|
</script>
|
||||||
|
</html>
|
|
@ -0,0 +1,5 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_asset_loader::prelude::*;
|
||||||
|
|
||||||
|
#[derive(AssetCollection, Resource)]
|
||||||
|
pub struct CoreAssets {}
|
|
@ -0,0 +1,27 @@
|
||||||
|
use bevy::gltf::Gltf;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::utils::HashMap;
|
||||||
|
use bevy_asset_loader::prelude::*;
|
||||||
|
|
||||||
|
#[derive(AssetCollection, Resource)]
|
||||||
|
pub struct GameAssets {
|
||||||
|
#[asset(key = "world")]
|
||||||
|
pub world: Handle<Gltf>,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[asset(paths("models/library/Container.glb", "models/library/Health_Pickup.glb", "models/library/MagicTeapot.glb",
|
||||||
|
"models/library/Pillar.glb",
|
||||||
|
"models/library/Player.glb",
|
||||||
|
"models/library/Unused_in_level_test.glb"), collection(typed))]
|
||||||
|
pub models: Vec<Handle<Gltf>>,
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
#[asset(key = "models", collection(typed, mapped))]
|
||||||
|
pub models: HashMap<String, Handle<Gltf>>,
|
||||||
|
|
||||||
|
#[asset(key = "models", collection(typed))]
|
||||||
|
pub models: Vec<Handle<Gltf>>,*/
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
pub mod assets_core;
|
||||||
|
pub use assets_core::*;
|
||||||
|
|
||||||
|
pub mod assets_game;
|
||||||
|
pub use assets_game::*;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_asset_loader::prelude::*;
|
||||||
|
|
||||||
|
use crate::state::AppState;
|
||||||
|
|
||||||
|
pub struct AssetsPlugin;
|
||||||
|
impl Plugin for AssetsPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app
|
||||||
|
// load core assets (ie assets needed in the main menu, and everywhere else before loading more assets in game)
|
||||||
|
.add_loading_state(
|
||||||
|
LoadingState::new(AppState::CoreLoading).continue_to_state(AppState::MenuRunning),
|
||||||
|
)
|
||||||
|
.add_dynamic_collection_to_loading_state::<_, StandardDynamicAssetCollection>(
|
||||||
|
AppState::CoreLoading,
|
||||||
|
"assets_core.assets.ron",
|
||||||
|
)
|
||||||
|
.add_collection_to_loading_state::<_, CoreAssets>(AppState::CoreLoading)
|
||||||
|
// load game assets
|
||||||
|
.add_loading_state(
|
||||||
|
LoadingState::new(AppState::AppLoading).continue_to_state(AppState::AppRunning),
|
||||||
|
)
|
||||||
|
.add_dynamic_collection_to_loading_state::<_, StandardDynamicAssetCollection>(
|
||||||
|
AppState::AppLoading,
|
||||||
|
"assets_game.assets.ron",
|
||||||
|
)
|
||||||
|
.add_collection_to_loading_state::<_, GameAssets>(AppState::AppLoading);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
use bevy::core_pipeline::bloom::{BloomCompositeMode, BloomSettings};
|
||||||
|
use bevy::core_pipeline::tonemapping::{DebandDither, Tonemapping};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use super::CameraTrackingOffset;
|
||||||
|
|
||||||
|
pub fn camera_replace_proxies(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut added_cameras: Query<(Entity, &mut Camera), (Added<Camera>, With<CameraTrackingOffset>)>,
|
||||||
|
) {
|
||||||
|
for (entity, mut camera) in added_cameras.iter_mut() {
|
||||||
|
info!("detected added camera, updating proxy");
|
||||||
|
camera.hdr = true;
|
||||||
|
commands
|
||||||
|
.entity(entity)
|
||||||
|
.insert(DebandDither::Enabled)
|
||||||
|
.insert(Tonemapping::BlenderFilmic)
|
||||||
|
.insert(BloomSettings {
|
||||||
|
intensity: 0.01,
|
||||||
|
composite_mode: BloomCompositeMode::Additive,
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Component for cameras, with an offset from the Trackable target
|
||||||
|
///
|
||||||
|
pub struct CameraTracking {
|
||||||
|
pub offset: Vec3,
|
||||||
|
}
|
||||||
|
impl Default for CameraTracking {
|
||||||
|
fn default() -> Self {
|
||||||
|
CameraTracking {
|
||||||
|
offset: Vec3::new(0.0, 6.0, 8.0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Debug, Deref, DerefMut)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Component for cameras, with an offset from the Trackable target
|
||||||
|
pub struct CameraTrackingOffset(Vec3);
|
||||||
|
impl Default for CameraTrackingOffset {
|
||||||
|
fn default() -> Self {
|
||||||
|
CameraTrackingOffset(Vec3::new(0.0, 6.0, 8.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CameraTrackingOffset {
|
||||||
|
fn new(input: Vec3) -> Self {
|
||||||
|
CameraTrackingOffset(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Add this component to an entity if you want it to be tracked by a Camera
|
||||||
|
pub struct CameraTrackable;
|
||||||
|
|
||||||
|
pub fn camera_track(
|
||||||
|
mut tracking_cameras: Query<
|
||||||
|
(&mut Transform, &CameraTrackingOffset),
|
||||||
|
(
|
||||||
|
With<Camera>,
|
||||||
|
With<CameraTrackingOffset>,
|
||||||
|
Without<CameraTrackable>,
|
||||||
|
),
|
||||||
|
>,
|
||||||
|
camera_tracked: Query<&Transform, With<CameraTrackable>>,
|
||||||
|
) {
|
||||||
|
for (mut camera_transform, tracking_offset) in tracking_cameras.iter_mut() {
|
||||||
|
for tracked_transform in camera_tracked.iter() {
|
||||||
|
let target_position = tracked_transform.translation + tracking_offset.0;
|
||||||
|
let eased_position = camera_transform.translation.lerp(target_position, 0.1);
|
||||||
|
camera_transform.translation = eased_position; // + tracking.offset;// tracked_transform.translation + tracking.offset;
|
||||||
|
*camera_transform = camera_transform.looking_at(tracked_transform.translation, Vec3::Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
pub mod camera_tracking;
|
||||||
|
pub use camera_tracking::*;
|
||||||
|
|
||||||
|
pub mod camera_replace_proxies;
|
||||||
|
pub use camera_replace_proxies::*;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_gltf_blueprints::GltfBlueprintsSet;
|
||||||
|
|
||||||
|
pub struct CameraPlugin;
|
||||||
|
impl Plugin for CameraPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.register_type::<CameraTrackable>()
|
||||||
|
.register_type::<CameraTracking>()
|
||||||
|
.register_type::<CameraTrackingOffset>()
|
||||||
|
.add_systems(
|
||||||
|
Update,
|
||||||
|
(
|
||||||
|
camera_replace_proxies.after(GltfBlueprintsSet::AfterSpawn),
|
||||||
|
camera_track,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use bevy::pbr::{CascadeShadowConfig, CascadeShadowConfigBuilder};
|
||||||
|
|
||||||
|
// fixme might be too specific to might needs, should it be moved out ? also these are all for lights, not models
|
||||||
|
pub fn lighting_replace_proxies(
|
||||||
|
mut added_dirights: Query<(Entity, &mut DirectionalLight), Added<DirectionalLight>>,
|
||||||
|
mut added_spotlights: Query<&mut SpotLight, Added<SpotLight>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
for (entity, mut light) in added_dirights.iter_mut() {
|
||||||
|
light.illuminance *= 5.0;
|
||||||
|
light.shadows_enabled = true;
|
||||||
|
let shadow_config: CascadeShadowConfig = CascadeShadowConfigBuilder {
|
||||||
|
first_cascade_far_bound: 15.0,
|
||||||
|
maximum_distance: 135.0,
|
||||||
|
..default()
|
||||||
|
}
|
||||||
|
.into();
|
||||||
|
commands.entity(entity).insert(shadow_config);
|
||||||
|
}
|
||||||
|
for mut light in added_spotlights.iter_mut() {
|
||||||
|
light.shadows_enabled = true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
mod lighting_replace_proxies;
|
||||||
|
use lighting_replace_proxies::*;
|
||||||
|
|
||||||
|
use bevy::pbr::{DirectionalLightShadowMap, NotShadowCaster};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub struct LightingPlugin;
|
||||||
|
impl Plugin for LightingPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app
|
||||||
|
.insert_resource(DirectionalLightShadowMap { size: 4096 })
|
||||||
|
// FIXME: adding these since they are missing
|
||||||
|
.register_type::<NotShadowCaster>()
|
||||||
|
|
||||||
|
.add_systems(PreUpdate, lighting_replace_proxies) // FIXME: you should actually run this in a specific state most likely
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
pub mod camera;
|
||||||
|
pub use camera::*;
|
||||||
|
|
||||||
|
pub mod lighting;
|
||||||
|
pub use lighting::*;
|
||||||
|
|
||||||
|
pub mod relationships;
|
||||||
|
pub use relationships::*;
|
||||||
|
|
||||||
|
pub mod physics;
|
||||||
|
pub use physics::*;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_gltf_blueprints::*;
|
||||||
|
|
||||||
|
pub struct CorePlugin;
|
||||||
|
impl Plugin for CorePlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_plugins((
|
||||||
|
LightingPlugin,
|
||||||
|
CameraPlugin,
|
||||||
|
PhysicsPlugin,
|
||||||
|
BlueprintsPlugin {
|
||||||
|
library_folder: "models/library".into(),
|
||||||
|
format: GltfFormat::GLB,
|
||||||
|
aabbs: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
use bevy::prelude::{info, ResMut};
|
||||||
|
use bevy_rapier3d::prelude::RapierConfiguration;
|
||||||
|
|
||||||
|
pub fn pause_physics(mut physics_config: ResMut<RapierConfiguration>) {
|
||||||
|
info!("pausing physics");
|
||||||
|
physics_config.physics_pipeline_active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resume_physics(mut physics_config: ResMut<RapierConfiguration>) {
|
||||||
|
info!("unpausing physics");
|
||||||
|
physics_config.physics_pipeline_active = true;
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
pub mod physics_replace_proxies;
|
||||||
|
use bevy_rapier3d::{
|
||||||
|
prelude::{NoUserData, RapierPhysicsPlugin},
|
||||||
|
render::RapierDebugRenderPlugin,
|
||||||
|
};
|
||||||
|
pub use physics_replace_proxies::*;
|
||||||
|
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
|
pub mod controls;
|
||||||
|
pub use controls::*;
|
||||||
|
|
||||||
|
use crate::state::GameState;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
// use super::blueprints::GltfBlueprintsSet;
|
||||||
|
use bevy_gltf_blueprints::GltfBlueprintsSet;
|
||||||
|
// use crate::Collider;
|
||||||
|
pub struct PhysicsPlugin;
|
||||||
|
impl Plugin for PhysicsPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_plugins((
|
||||||
|
RapierPhysicsPlugin::<NoUserData>::default(),
|
||||||
|
RapierDebugRenderPlugin::default(),
|
||||||
|
))
|
||||||
|
.register_type::<AutoAABBCollider>()
|
||||||
|
.register_type::<physics_replace_proxies::Collider>()
|
||||||
|
// find a way to make serde's stuff serializable
|
||||||
|
// .register_type::<bevy_rapier3d::dynamics::CoefficientCombineRule>()
|
||||||
|
//bevy_rapier3d::dynamics::CoefficientCombineRule
|
||||||
|
.add_systems(
|
||||||
|
Update,
|
||||||
|
physics_replace_proxies.after(GltfBlueprintsSet::AfterSpawn),
|
||||||
|
)
|
||||||
|
.add_systems(OnEnter(GameState::InGame), resume_physics)
|
||||||
|
.add_systems(OnExit(GameState::InGame), pause_physics);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
// use bevy::render::primitives::Aabb;
|
||||||
|
use bevy_rapier3d::geometry::Collider as RapierCollider;
|
||||||
|
use bevy_rapier3d::prelude::{ActiveCollisionTypes, ActiveEvents, ComputedColliderShape};
|
||||||
|
|
||||||
|
use super::utils::*;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub enum Collider {
|
||||||
|
Ball(f32),
|
||||||
|
Cuboid(Vec3),
|
||||||
|
Capsule(Vec3, Vec3, f32),
|
||||||
|
#[default]
|
||||||
|
Mesh,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub enum AutoAABBCollider {
|
||||||
|
#[default]
|
||||||
|
Cuboid,
|
||||||
|
Ball,
|
||||||
|
Capsule,
|
||||||
|
}
|
||||||
|
|
||||||
|
// replaces all physics stand-ins with the actual rapier types
|
||||||
|
pub fn physics_replace_proxies(
|
||||||
|
meshes: Res<Assets<Mesh>>,
|
||||||
|
mesh_handles: Query<&Handle<Mesh>>,
|
||||||
|
mut proxy_colliders: Query<
|
||||||
|
(Entity, &Collider, &Name, &mut Visibility),
|
||||||
|
(Without<RapierCollider>, Added<Collider>),
|
||||||
|
>,
|
||||||
|
// needed for tri meshes
|
||||||
|
children: Query<&Children>,
|
||||||
|
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
for proxy_colider in proxy_colliders.iter_mut() {
|
||||||
|
let (entity, collider_proxy, name, mut visibility) = proxy_colider;
|
||||||
|
// we hide the collider meshes: perhaps they should be removed altogether once processed ?
|
||||||
|
if name.ends_with("_collider") || name.ends_with("_sensor") {
|
||||||
|
*visibility = Visibility::Hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut rapier_collider: RapierCollider;
|
||||||
|
match collider_proxy {
|
||||||
|
Collider::Ball(radius) => {
|
||||||
|
info!("generating collider from proxy: ball");
|
||||||
|
rapier_collider = RapierCollider::ball(*radius);
|
||||||
|
commands.entity(entity)
|
||||||
|
.insert(rapier_collider)
|
||||||
|
.insert(ActiveEvents::COLLISION_EVENTS) // FIXME: this is just for demo purposes !!!
|
||||||
|
;
|
||||||
|
}
|
||||||
|
Collider::Cuboid(size) => {
|
||||||
|
info!("generating collider from proxy: cuboid");
|
||||||
|
rapier_collider = RapierCollider::cuboid(size.x, size.y, size.z);
|
||||||
|
commands.entity(entity)
|
||||||
|
.insert(rapier_collider)
|
||||||
|
.insert(ActiveEvents::COLLISION_EVENTS) // FIXME: this is just for demo purposes !!!
|
||||||
|
;
|
||||||
|
}
|
||||||
|
Collider::Capsule(a, b, radius) => {
|
||||||
|
info!("generating collider from proxy: capsule");
|
||||||
|
rapier_collider = RapierCollider::capsule(*a, *b, *radius);
|
||||||
|
commands.entity(entity)
|
||||||
|
.insert(rapier_collider)
|
||||||
|
.insert(ActiveEvents::COLLISION_EVENTS) // FIXME: this is just for demo purposes !!!
|
||||||
|
;
|
||||||
|
}
|
||||||
|
Collider::Mesh => {
|
||||||
|
info!("generating collider from proxy: mesh");
|
||||||
|
for (_, collider_mesh) in
|
||||||
|
Mesh::search_in_children(entity, &children, &meshes, &mesh_handles)
|
||||||
|
{
|
||||||
|
rapier_collider = RapierCollider::from_bevy_mesh(
|
||||||
|
collider_mesh,
|
||||||
|
&ComputedColliderShape::TriMesh,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
commands
|
||||||
|
.entity(entity)
|
||||||
|
.insert(rapier_collider)
|
||||||
|
// FIXME: this is just for demo purposes !!!
|
||||||
|
.insert(
|
||||||
|
ActiveCollisionTypes::default()
|
||||||
|
| ActiveCollisionTypes::KINEMATIC_STATIC
|
||||||
|
| ActiveCollisionTypes::STATIC_STATIC
|
||||||
|
| ActiveCollisionTypes::DYNAMIC_STATIC,
|
||||||
|
)
|
||||||
|
.insert(ActiveEvents::COLLISION_EVENTS);
|
||||||
|
// .insert(ActiveEvents::COLLISION_EVENTS)
|
||||||
|
// break;
|
||||||
|
// RapierCollider::convex_hull(points)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,175 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::render::mesh::{MeshVertexAttributeId, PrimitiveTopology, VertexAttributeValues};
|
||||||
|
// TAKEN VERBATIB FROM https://github.com/janhohenheim/foxtrot/blob/src/util/trait_extension.rs
|
||||||
|
|
||||||
|
pub(crate) trait Vec3Ext: Copy {
|
||||||
|
fn is_approx_zero(self) -> bool;
|
||||||
|
fn split(self, up: Vec3) -> SplitVec3;
|
||||||
|
}
|
||||||
|
impl Vec3Ext for Vec3 {
|
||||||
|
#[inline]
|
||||||
|
fn is_approx_zero(self) -> bool {
|
||||||
|
self.length_squared() < 1e-5
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn split(self, up: Vec3) -> SplitVec3 {
|
||||||
|
let vertical = up * self.dot(up);
|
||||||
|
let horizontal = self - vertical;
|
||||||
|
SplitVec3 {
|
||||||
|
vertical,
|
||||||
|
horizontal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub(crate) struct SplitVec3 {
|
||||||
|
pub(crate) vertical: Vec3,
|
||||||
|
pub(crate) horizontal: Vec3,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait Vec2Ext: Copy {
|
||||||
|
fn is_approx_zero(self) -> bool;
|
||||||
|
fn x0y(self) -> Vec3;
|
||||||
|
}
|
||||||
|
impl Vec2Ext for Vec2 {
|
||||||
|
#[inline]
|
||||||
|
fn is_approx_zero(self) -> bool {
|
||||||
|
self.length_squared() < 1e-5
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn x0y(self) -> Vec3 {
|
||||||
|
Vec3::new(self.x, 0., self.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait MeshExt {
|
||||||
|
fn transform(&mut self, transform: Transform);
|
||||||
|
fn transformed(&self, transform: Transform) -> Mesh;
|
||||||
|
fn read_coords_mut(&mut self, id: impl Into<MeshVertexAttributeId>) -> &mut Vec<[f32; 3]>;
|
||||||
|
fn search_in_children<'a>(
|
||||||
|
parent: Entity,
|
||||||
|
children: &'a Query<&Children>,
|
||||||
|
meshes: &'a Assets<Mesh>,
|
||||||
|
mesh_handles: &'a Query<&Handle<Mesh>>,
|
||||||
|
) -> Vec<(Entity, &'a Mesh)>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MeshExt for Mesh {
|
||||||
|
fn transform(&mut self, transform: Transform) {
|
||||||
|
for coords in self.read_coords_mut(Mesh::ATTRIBUTE_POSITION.clone()) {
|
||||||
|
let vec3 = (*coords).into();
|
||||||
|
let transformed = transform.transform_point(vec3);
|
||||||
|
*coords = transformed.into();
|
||||||
|
}
|
||||||
|
for normal in self.read_coords_mut(Mesh::ATTRIBUTE_NORMAL.clone()) {
|
||||||
|
let vec3 = (*normal).into();
|
||||||
|
let transformed = transform.rotation.mul_vec3(vec3);
|
||||||
|
*normal = transformed.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transformed(&self, transform: Transform) -> Mesh {
|
||||||
|
let mut mesh = self.clone();
|
||||||
|
mesh.transform(transform);
|
||||||
|
mesh
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_coords_mut(&mut self, id: impl Into<MeshVertexAttributeId>) -> &mut Vec<[f32; 3]> {
|
||||||
|
// Guaranteed by Bevy for the current usage
|
||||||
|
match self
|
||||||
|
.attribute_mut(id)
|
||||||
|
.expect("Failed to read unknown mesh attribute")
|
||||||
|
{
|
||||||
|
VertexAttributeValues::Float32x3(values) => values,
|
||||||
|
// Guaranteed by Bevy for the current usage
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_in_children<'a>(
|
||||||
|
parent: Entity,
|
||||||
|
children_query: &'a Query<&Children>,
|
||||||
|
meshes: &'a Assets<Mesh>,
|
||||||
|
mesh_handles: &'a Query<&Handle<Mesh>>,
|
||||||
|
) -> Vec<(Entity, &'a Mesh)> {
|
||||||
|
if let Ok(children) = children_query.get(parent) {
|
||||||
|
let mut result: Vec<_> = children
|
||||||
|
.iter()
|
||||||
|
.filter_map(|entity| mesh_handles.get(*entity).ok().map(|mesh| (*entity, mesh)))
|
||||||
|
.map(|(entity, mesh_handle)| {
|
||||||
|
(
|
||||||
|
entity,
|
||||||
|
meshes
|
||||||
|
.get(mesh_handle)
|
||||||
|
.expect("Failed to get mesh from handle"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.map(|(entity, mesh)| {
|
||||||
|
assert_eq!(mesh.primitive_topology(), PrimitiveTopology::TriangleList);
|
||||||
|
(entity, mesh)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let mut inner_result = children
|
||||||
|
.iter()
|
||||||
|
.flat_map(|entity| {
|
||||||
|
Self::search_in_children(*entity, children_query, meshes, mesh_handles)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
result.append(&mut inner_result);
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait F32Ext: Copy {
|
||||||
|
fn is_approx_zero(self) -> bool;
|
||||||
|
fn squared(self) -> f32;
|
||||||
|
fn lerp(self, other: f32, ratio: f32) -> f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl F32Ext for f32 {
|
||||||
|
#[inline]
|
||||||
|
fn is_approx_zero(self) -> bool {
|
||||||
|
self.abs() < 1e-5
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn squared(self) -> f32 {
|
||||||
|
self * self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn lerp(self, other: f32, ratio: f32) -> f32 {
|
||||||
|
self.mul_add(1. - ratio, other * ratio)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait TransformExt: Copy {
|
||||||
|
fn horizontally_looking_at(self, target: Vec3, up: Vec3) -> Transform;
|
||||||
|
fn lerp(self, other: Transform, ratio: f32) -> Transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransformExt for Transform {
|
||||||
|
fn horizontally_looking_at(self, target: Vec3, up: Vec3) -> Transform {
|
||||||
|
let direction = target - self.translation;
|
||||||
|
let horizontal_direction = direction - up * direction.dot(up);
|
||||||
|
let look_target = self.translation + horizontal_direction;
|
||||||
|
self.looking_at(look_target, up)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lerp(self, other: Transform, ratio: f32) -> Transform {
|
||||||
|
let translation = self.translation.lerp(other.translation, ratio);
|
||||||
|
let rotation = self.rotation.slerp(other.rotation, ratio);
|
||||||
|
let scale = self.scale.lerp(other.scale, ratio);
|
||||||
|
Transform {
|
||||||
|
translation,
|
||||||
|
rotation,
|
||||||
|
scale,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
pub mod relationships_insert_dependant_components;
|
||||||
|
pub use relationships_insert_dependant_components::*;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub struct EcsRelationshipsPlugin;
|
||||||
|
impl Plugin for EcsRelationshipsPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub fn insert_dependant_component<
|
||||||
|
Dependant: Component,
|
||||||
|
Dependency: Component + std::default::Default,
|
||||||
|
>(
|
||||||
|
mut commands: Commands,
|
||||||
|
entities_without_depency: Query<(Entity, &Name), (With<Dependant>, Without<Dependency>)>,
|
||||||
|
) {
|
||||||
|
for (entity, name) in entities_without_depency.iter() {
|
||||||
|
let name = name.clone().to_string();
|
||||||
|
commands.entity(entity).insert(Dependency::default());
|
||||||
|
warn!("found an entity called {} with a {} component but without an {}, please check your assets", name.clone(), std::any::type_name::<Dependant>(), std::any::type_name::<Dependency>());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
assets::GameAssets,
|
||||||
|
state::{GameState, InAppRunning},
|
||||||
|
};
|
||||||
|
use bevy_gltf_blueprints::{BluePrintBundle, BlueprintName, GameWorldTag};
|
||||||
|
|
||||||
|
use bevy_rapier3d::prelude::Velocity;
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
pub fn setup_game(
|
||||||
|
mut commands: Commands,
|
||||||
|
game_assets: Res<GameAssets>,
|
||||||
|
models: Res<Assets<bevy::gltf::Gltf>>,
|
||||||
|
mut next_game_state: ResMut<NextState<GameState>>,
|
||||||
|
) {
|
||||||
|
println!("setting up all stuff");
|
||||||
|
commands.insert_resource(AmbientLight {
|
||||||
|
color: Color::WHITE,
|
||||||
|
brightness: 0.2,
|
||||||
|
});
|
||||||
|
// here we actually spawn our game world/level
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
SceneBundle {
|
||||||
|
// note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene
|
||||||
|
scene: models
|
||||||
|
.get(game_assets.world.id())
|
||||||
|
.expect("main level should have been loaded")
|
||||||
|
.scenes[0]
|
||||||
|
.clone(),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
bevy::prelude::Name::from("world"),
|
||||||
|
GameWorldTag,
|
||||||
|
InAppRunning,
|
||||||
|
));
|
||||||
|
|
||||||
|
next_game_state.set(GameState::InGame)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct UnregisteredComponent;
|
||||||
|
|
||||||
|
pub fn spawn_test(
|
||||||
|
keycode: Res<Input<KeyCode>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
|
||||||
|
mut game_world: Query<(Entity, &Children), With<GameWorldTag>>,
|
||||||
|
) {
|
||||||
|
if keycode.just_pressed(KeyCode::T) {
|
||||||
|
let world = game_world.single_mut();
|
||||||
|
let world = world.1[0];
|
||||||
|
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let range = 5.5;
|
||||||
|
let x: f32 = rng.gen_range(-range..range);
|
||||||
|
let y: f32 = rng.gen_range(-range..range);
|
||||||
|
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let range = 0.8;
|
||||||
|
let vel_x: f32 = rng.gen_range(-range..range);
|
||||||
|
let vel_y: f32 = rng.gen_range(2.0..2.5);
|
||||||
|
let vel_z: f32 = rng.gen_range(-range..range);
|
||||||
|
|
||||||
|
let name_index: u64 = rng.gen();
|
||||||
|
|
||||||
|
let new_entity = commands
|
||||||
|
.spawn((
|
||||||
|
BluePrintBundle {
|
||||||
|
blueprint: BlueprintName("Health_Pickup".to_string()),
|
||||||
|
transform: TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
bevy::prelude::Name::from(format!("test{}", name_index)),
|
||||||
|
// BlueprintName("Health_Pickup".to_string()),
|
||||||
|
// SpawnHere,
|
||||||
|
// TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)),
|
||||||
|
Velocity {
|
||||||
|
linvel: Vec3::new(vel_x, vel_y, vel_z),
|
||||||
|
angvel: Vec3::new(0.0, 0.0, 0.0),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.id();
|
||||||
|
commands.entity(world).add_child(new_entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_test_failing(
|
||||||
|
keycode: Res<Input<KeyCode>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
|
||||||
|
mut game_world: Query<(Entity, &Children), With<GameWorldTag>>,
|
||||||
|
) {
|
||||||
|
if keycode.just_pressed(KeyCode::U) {
|
||||||
|
let world = game_world.single_mut();
|
||||||
|
let world = world.1[0];
|
||||||
|
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let range = 5.5;
|
||||||
|
let x: f32 = rng.gen_range(-range..range);
|
||||||
|
let y: f32 = rng.gen_range(-range..range);
|
||||||
|
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let range = 0.8;
|
||||||
|
let vel_x: f32 = rng.gen_range(-range..range);
|
||||||
|
let vel_y: f32 = rng.gen_range(2.0..2.5);
|
||||||
|
let vel_z: f32 = rng.gen_range(-range..range);
|
||||||
|
|
||||||
|
let name_index: u64 = rng.gen();
|
||||||
|
|
||||||
|
let new_entity = commands
|
||||||
|
.spawn((
|
||||||
|
BluePrintBundle {
|
||||||
|
blueprint: BlueprintName("Health_Pickup".to_string()),
|
||||||
|
transform: TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
bevy::prelude::Name::from(format!("test{}", name_index)),
|
||||||
|
// BlueprintName("Health_Pickup".to_string()),
|
||||||
|
// SpawnHere,
|
||||||
|
// TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)),
|
||||||
|
Velocity {
|
||||||
|
linvel: Vec3::new(vel_x, vel_y, vel_z),
|
||||||
|
angvel: Vec3::new(0.0, 0.0, 0.0),
|
||||||
|
},
|
||||||
|
UnregisteredComponent,
|
||||||
|
))
|
||||||
|
.id();
|
||||||
|
commands.entity(world).add_child(new_entity);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::state::{AppState, GameState, InMainMenu};
|
||||||
|
|
||||||
|
pub fn setup_main_menu(mut commands: Commands) {
|
||||||
|
commands.spawn((Camera2dBundle::default(), InMainMenu));
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
TextBundle::from_section(
|
||||||
|
"SOME GAME TITLE !!",
|
||||||
|
TextStyle {
|
||||||
|
//font: asset_server.load("fonts/FiraMono-Medium.ttf"),
|
||||||
|
font_size: 18.0,
|
||||||
|
color: Color::WHITE,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_style(Style {
|
||||||
|
position_type: PositionType::Absolute,
|
||||||
|
top: Val::Px(100.0),
|
||||||
|
left: Val::Px(200.0),
|
||||||
|
..default()
|
||||||
|
}),
|
||||||
|
InMainMenu,
|
||||||
|
));
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
TextBundle::from_section(
|
||||||
|
"New Game (press Enter to start, press T once the game is started for demo spawning)",
|
||||||
|
TextStyle {
|
||||||
|
//font: asset_server.load("fonts/FiraMono-Medium.ttf"),
|
||||||
|
font_size: 18.0,
|
||||||
|
color: Color::WHITE,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_style(Style {
|
||||||
|
position_type: PositionType::Absolute,
|
||||||
|
top: Val::Px(200.0),
|
||||||
|
left: Val::Px(200.0),
|
||||||
|
..default()
|
||||||
|
}),
|
||||||
|
InMainMenu,
|
||||||
|
));
|
||||||
|
|
||||||
|
/*
|
||||||
|
commands.spawn((
|
||||||
|
TextBundle::from_section(
|
||||||
|
"Load Game",
|
||||||
|
TextStyle {
|
||||||
|
//font: asset_server.load("fonts/FiraMono-Medium.ttf"),
|
||||||
|
font_size: 18.0,
|
||||||
|
color: Color::WHITE,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_style(Style {
|
||||||
|
position_type: PositionType::Absolute,
|
||||||
|
top: Val::Px(250.0),
|
||||||
|
left: Val::Px(200.0),
|
||||||
|
..default()
|
||||||
|
}),
|
||||||
|
InMainMenu
|
||||||
|
));
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
TextBundle::from_section(
|
||||||
|
"Exit Game",
|
||||||
|
TextStyle {
|
||||||
|
//font: asset_server.load("fonts/FiraMono-Medium.ttf"),
|
||||||
|
font_size: 18.0,
|
||||||
|
color: Color::WHITE,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_style(Style {
|
||||||
|
position_type: PositionType::Absolute,
|
||||||
|
top: Val::Px(300.0),
|
||||||
|
left: Val::Px(200.0),
|
||||||
|
..default()
|
||||||
|
}),
|
||||||
|
InMainMenu
|
||||||
|
));*/
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn teardown_main_menu(bla: Query<Entity, With<InMainMenu>>, mut commands: Commands) {
|
||||||
|
for bli in bla.iter() {
|
||||||
|
commands.entity(bli).despawn_recursive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main_menu(
|
||||||
|
keycode: Res<Input<KeyCode>>,
|
||||||
|
|
||||||
|
mut next_app_state: ResMut<NextState<AppState>>,
|
||||||
|
// mut next_game_state: ResMut<NextState<GameState>>,
|
||||||
|
// mut save_requested_events: EventWriter<SaveRequest>,
|
||||||
|
// mut load_requested_events: EventWriter<LoadRequest>,
|
||||||
|
) {
|
||||||
|
if keycode.just_pressed(KeyCode::Return) {
|
||||||
|
next_app_state.set(AppState::AppLoading);
|
||||||
|
// next_game_state.set(GameState::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
if keycode.just_pressed(KeyCode::L) {
|
||||||
|
next_app_state.set(AppState::AppLoading);
|
||||||
|
// load_requested_events.send(LoadRequest { path: "toto".into() })
|
||||||
|
}
|
||||||
|
|
||||||
|
if keycode.just_pressed(KeyCode::S) {
|
||||||
|
// save_requested_events.send(SaveRequest { path: "toto".into() })
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
pub mod in_game;
|
||||||
|
pub use in_game::*;
|
||||||
|
|
||||||
|
pub mod in_main_menu;
|
||||||
|
pub use in_main_menu::*;
|
||||||
|
|
||||||
|
pub mod picking;
|
||||||
|
pub use picking::*;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
insert_dependant_component,
|
||||||
|
state::{AppState, GameState},
|
||||||
|
};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_rapier3d::prelude::*;
|
||||||
|
|
||||||
|
// this file is just for demo purposes, contains various types of components, systems etc
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub enum SoundMaterial {
|
||||||
|
Metal,
|
||||||
|
Wood,
|
||||||
|
Rock,
|
||||||
|
Cloth,
|
||||||
|
Squishy,
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Demo marker component
|
||||||
|
pub struct Player;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Demo component showing auto injection of components
|
||||||
|
pub struct ShouldBeWithPlayer;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Demo marker component
|
||||||
|
pub struct Interactible;
|
||||||
|
|
||||||
|
fn player_move_demo(
|
||||||
|
keycode: Res<Input<KeyCode>>,
|
||||||
|
mut players: Query<&mut Transform, With<Player>>,
|
||||||
|
) {
|
||||||
|
let speed = 0.2;
|
||||||
|
if let Ok(mut player) = players.get_single_mut() {
|
||||||
|
if keycode.pressed(KeyCode::Left) {
|
||||||
|
player.translation.x += speed;
|
||||||
|
}
|
||||||
|
if keycode.pressed(KeyCode::Right) {
|
||||||
|
player.translation.x -= speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if keycode.pressed(KeyCode::Up) {
|
||||||
|
player.translation.z += speed;
|
||||||
|
}
|
||||||
|
if keycode.pressed(KeyCode::Down) {
|
||||||
|
player.translation.z -= speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// collision tests/debug
|
||||||
|
pub fn test_collision_events(
|
||||||
|
mut collision_events: EventReader<CollisionEvent>,
|
||||||
|
mut contact_force_events: EventReader<ContactForceEvent>,
|
||||||
|
) {
|
||||||
|
for collision_event in collision_events.read() {
|
||||||
|
println!("collision");
|
||||||
|
match collision_event {
|
||||||
|
CollisionEvent::Started(_entity1, _entity2, _) => {
|
||||||
|
println!("collision started")
|
||||||
|
}
|
||||||
|
CollisionEvent::Stopped(_entity1, _entity2, _) => {
|
||||||
|
println!("collision ended")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for contact_force_event in contact_force_events.read() {
|
||||||
|
println!("Received contact force event: {:?}", contact_force_event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GamePlugin;
|
||||||
|
impl Plugin for GamePlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_plugins(PickingPlugin)
|
||||||
|
.register_type::<Interactible>()
|
||||||
|
.register_type::<SoundMaterial>()
|
||||||
|
.register_type::<Player>()
|
||||||
|
// little helper utility, to automatically inject components that are dependant on an other component
|
||||||
|
// ie, here an Entity with a Player component should also always have a ShouldBeWithPlayer component
|
||||||
|
// you get a warning if you use this, as I consider this to be stop-gap solution (usually you should have either a bundle, or directly define all needed components)
|
||||||
|
.add_systems(
|
||||||
|
Update,
|
||||||
|
(
|
||||||
|
// insert_dependant_component::<Player, ShouldBeWithPlayer>,
|
||||||
|
player_move_demo, //.run_if(in_state(AppState::Running)),
|
||||||
|
// test_collision_events
|
||||||
|
spawn_test,
|
||||||
|
spawn_test_failing,
|
||||||
|
)
|
||||||
|
.run_if(in_state(GameState::InGame)),
|
||||||
|
)
|
||||||
|
.add_systems(OnEnter(AppState::MenuRunning), setup_main_menu)
|
||||||
|
.add_systems(OnExit(AppState::MenuRunning), teardown_main_menu)
|
||||||
|
.add_systems(Update, main_menu.run_if(in_state(AppState::MenuRunning)))
|
||||||
|
.add_systems(OnEnter(AppState::AppRunning), setup_game);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
use super::Player;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_gltf_blueprints::GltfBlueprintsSet;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct Pickable;
|
||||||
|
|
||||||
|
// very simple, crude picking (as in picking up objects) implementation
|
||||||
|
|
||||||
|
pub fn picking(
|
||||||
|
players: Query<&GlobalTransform, With<Player>>,
|
||||||
|
pickables: Query<(Entity, &GlobalTransform), With<Pickable>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
for player_transforms in players.iter() {
|
||||||
|
for (pickable, pickable_transforms) in pickables.iter() {
|
||||||
|
let distance = player_transforms
|
||||||
|
.translation()
|
||||||
|
.distance(pickable_transforms.translation());
|
||||||
|
if distance < 2.5 {
|
||||||
|
commands.entity(pickable).despawn_recursive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PickingPlugin;
|
||||||
|
impl Plugin for PickingPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.register_type::<Pickable>()
|
||||||
|
.add_systems(Update, (picking.after(GltfBlueprintsSet::AfterSpawn),));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_editor_pls::prelude::*;
|
||||||
|
|
||||||
|
mod core;
|
||||||
|
use crate::core::*;
|
||||||
|
|
||||||
|
pub mod assets;
|
||||||
|
use assets::*;
|
||||||
|
|
||||||
|
pub mod state;
|
||||||
|
use state::*;
|
||||||
|
|
||||||
|
mod game;
|
||||||
|
use game::*;
|
||||||
|
|
||||||
|
mod test_components;
|
||||||
|
use test_components::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins((
|
||||||
|
DefaultPlugins.set(AssetPlugin::default()),
|
||||||
|
// editor
|
||||||
|
EditorPlugin::default(),
|
||||||
|
// our custom plugins
|
||||||
|
StatePlugin,
|
||||||
|
AssetsPlugin,
|
||||||
|
CorePlugin, // reusable plugins
|
||||||
|
GamePlugin, // specific to our game
|
||||||
|
ComponentsTestPlugin, // Showcases different type of components /structs
|
||||||
|
))
|
||||||
|
.run();
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
use bevy::app::AppExit;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default, States)]
|
||||||
|
pub enum AppState {
|
||||||
|
#[default]
|
||||||
|
CoreLoading,
|
||||||
|
MenuRunning,
|
||||||
|
AppLoading,
|
||||||
|
AppRunning,
|
||||||
|
AppEnding,
|
||||||
|
|
||||||
|
// FIXME: not sure
|
||||||
|
LoadingGame,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default, States)]
|
||||||
|
pub enum GameState {
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
|
||||||
|
InMenu,
|
||||||
|
InGame,
|
||||||
|
|
||||||
|
InGameOver,
|
||||||
|
|
||||||
|
InSaving,
|
||||||
|
InLoading,
|
||||||
|
}
|
||||||
|
|
||||||
|
// tag components for all entities within a certain state (for despawning them if needed) , FIXME: seems kinda hack-ish
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct InCoreLoading;
|
||||||
|
#[derive(Component, Default)]
|
||||||
|
pub struct InMenuRunning;
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct InAppLoading;
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct InAppRunning;
|
||||||
|
|
||||||
|
// components for tagging in game vs in game menu stuff
|
||||||
|
#[derive(Component, Default)]
|
||||||
|
pub struct InMainMenu;
|
||||||
|
#[derive(Component, Default)]
|
||||||
|
pub struct InMenu;
|
||||||
|
#[derive(Component, Default)]
|
||||||
|
pub struct InGame;
|
||||||
|
|
||||||
|
pub struct StatePlugin;
|
||||||
|
impl Plugin for StatePlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_state::<AppState>().add_state::<GameState>();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct UnitTest;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct TuppleTestF32(f32);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct TuppleTestU64(u64);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct TuppleTestStr(String);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct TuppleTest2(f32, u64, String);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct TuppleTestBool(bool);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct TuppleVec2(Vec2);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct TuppleVec3(Vec3);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct TuppleVec(Vec<String>);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct TuppleTestColor(Color);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct BasicTest {
|
||||||
|
a: f32,
|
||||||
|
b: u64,
|
||||||
|
c: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub enum EnumTest {
|
||||||
|
Metal,
|
||||||
|
Wood,
|
||||||
|
Rock,
|
||||||
|
Cloth,
|
||||||
|
Squishy,
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ComponentsTestPlugin;
|
||||||
|
impl Plugin for ComponentsTestPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.register_type::<BasicTest>()
|
||||||
|
.register_type::<UnitTest>()
|
||||||
|
.register_type::<TuppleTestF32>()
|
||||||
|
.register_type::<TuppleTestU64>()
|
||||||
|
.register_type::<TuppleTestStr>()
|
||||||
|
.register_type::<TuppleTestBool>()
|
||||||
|
.register_type::<TuppleTest2>()
|
||||||
|
.register_type::<TuppleVec2>()
|
||||||
|
.register_type::<TuppleVec3>()
|
||||||
|
.register_type::<EnumTest>()
|
||||||
|
.register_type::<TuppleTestColor>()
|
||||||
|
.register_type::<TuppleVec>()
|
||||||
|
.register_type::<Vec<String>>();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
{"rustc_fingerprint":14672523188090023269,"outputs":{"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.70.0 (90c541806 2023-05-31)\nbinary: rustc\ncommit-hash: 90c541806f23a127002de5b4038be731ba1458ca\ncommit-date: 2023-05-31\nhost: x86_64-unknown-linux-gnu\nrelease: 1.70.0\nLLVM version: 16.0.2\n","stderr":""}},"successes":{}}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,27 @@
|
||||||
|
[package]
|
||||||
|
name = "bevy_gltf_components_basic_wasm_example"
|
||||||
|
version = "0.3.0"
|
||||||
|
edition = "2021"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bevy="0.12"
|
||||||
|
bevy_gltf_components = { path = "../../../crates/bevy_gltf_components" }
|
||||||
|
bevy_rapier3d = { version = "0.23.0", features = [ "serde-serialize", "debug-render-3d", "enhanced-determinism"] }
|
||||||
|
bevy_editor_pls = { version = "0.6" }
|
||||||
|
|
||||||
|
[profile.wasm-release]
|
||||||
|
# Use release profile as default values
|
||||||
|
inherits = "release"
|
||||||
|
|
||||||
|
# Optimize with size in mind, also try "s", sometimes it is better.
|
||||||
|
# This doesn't increase compilation times compared to -O3, great improvements
|
||||||
|
opt-level = "z"
|
||||||
|
|
||||||
|
# Do a second optimization pass removing duplicate or unused code from dependencies.
|
||||||
|
# Slows compile times, marginal improvements
|
||||||
|
lto = "fat"
|
||||||
|
|
||||||
|
# When building crates, optimize larger chunks at a time
|
||||||
|
# Slows compile times, marginal improvements
|
||||||
|
codegen-units = 1
|
|
@ -0,0 +1,37 @@
|
||||||
|
|
||||||
|
# Basic bevy_gltf_components wasm demo
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
as per the bevy documentation:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
rustup target add wasm32-unknown-unknown
|
||||||
|
cargo install wasm-bindgen-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Building this example
|
||||||
|
|
||||||
|
navigate to the current folder , and then
|
||||||
|
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cargo build --release --target wasm32-unknown-unknown --target-dir ./target
|
||||||
|
wasm-bindgen --out-name wasm_example \
|
||||||
|
--out-dir ./target/wasm \
|
||||||
|
--target web target/wasm32-unknown-unknown/release/bevy_gltf_components_basic_wasm_example.wasm
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running this example
|
||||||
|
|
||||||
|
run a web server in the current folder, and navigate to the page, you should see the example in your browser
|
||||||
|
|
||||||
|
### Additional notes
|
||||||
|
|
||||||
|
|
||||||
|
## Information
|
||||||
|
- the Bevy/ Rust code is [here](./src/main.rs)
|
||||||
|
- the Blender file is [here](./assets/basic.blend)
|
||||||
|
- I added [bevy_editor_pls](https://github.com/jakobhellermann/bevy_editor_pls) as a dependency for convenience so you can inspect your level/components
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,27 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
white 0%,
|
||||||
|
white 49%,
|
||||||
|
black 49%,
|
||||||
|
black 51%,
|
||||||
|
white 51%,
|
||||||
|
white 100%
|
||||||
|
);
|
||||||
|
background-repeat: repeat;
|
||||||
|
background-size: 20px 20px;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<script type="module">
|
||||||
|
import init from './target/wasm/wasm_example.js'
|
||||||
|
init()
|
||||||
|
</script>
|
||||||
|
</html>
|
|
@ -0,0 +1,24 @@
|
||||||
|
use bevy::core_pipeline::bloom::{BloomCompositeMode, BloomSettings};
|
||||||
|
use bevy::core_pipeline::tonemapping::{DebandDither, Tonemapping};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use super::CameraTrackingOffset;
|
||||||
|
|
||||||
|
pub fn camera_replace_proxies(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut added_cameras: Query<(Entity, &mut Camera), (Added<Camera>, With<CameraTrackingOffset>)>,
|
||||||
|
) {
|
||||||
|
for (entity, mut camera) in added_cameras.iter_mut() {
|
||||||
|
info!("detected added camera, updating proxy");
|
||||||
|
camera.hdr = true;
|
||||||
|
commands
|
||||||
|
.entity(entity)
|
||||||
|
.insert(DebandDither::Enabled)
|
||||||
|
.insert(Tonemapping::BlenderFilmic)
|
||||||
|
.insert(BloomSettings {
|
||||||
|
intensity: 0.01,
|
||||||
|
composite_mode: BloomCompositeMode::Additive,
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Component for cameras, with an offset from the Trackable target
|
||||||
|
///
|
||||||
|
pub struct CameraTracking {
|
||||||
|
pub offset: Vec3,
|
||||||
|
}
|
||||||
|
impl Default for CameraTracking {
|
||||||
|
fn default() -> Self {
|
||||||
|
CameraTracking {
|
||||||
|
offset: Vec3::new(0.0, 6.0, 8.0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Debug, Deref, DerefMut)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Component for cameras, with an offset from the Trackable target
|
||||||
|
pub struct CameraTrackingOffset(Vec3);
|
||||||
|
impl Default for CameraTrackingOffset {
|
||||||
|
fn default() -> Self {
|
||||||
|
CameraTrackingOffset(Vec3::new(0.0, 6.0, 8.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CameraTrackingOffset {
|
||||||
|
fn new(input: Vec3) -> Self {
|
||||||
|
CameraTrackingOffset(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Add this component to an entity if you want it to be tracked by a Camera
|
||||||
|
pub struct CameraTrackable;
|
||||||
|
|
||||||
|
pub fn camera_track(
|
||||||
|
mut tracking_cameras: Query<
|
||||||
|
(&mut Transform, &CameraTrackingOffset),
|
||||||
|
(
|
||||||
|
With<Camera>,
|
||||||
|
With<CameraTrackingOffset>,
|
||||||
|
Without<CameraTrackable>,
|
||||||
|
),
|
||||||
|
>,
|
||||||
|
camera_tracked: Query<&Transform, With<CameraTrackable>>,
|
||||||
|
) {
|
||||||
|
for (mut camera_transform, tracking_offset) in tracking_cameras.iter_mut() {
|
||||||
|
for tracked_transform in camera_tracked.iter() {
|
||||||
|
let target_position = tracked_transform.translation + tracking_offset.0;
|
||||||
|
let eased_position = camera_transform.translation.lerp(target_position, 0.1);
|
||||||
|
camera_transform.translation = eased_position; // + tracking.offset;// tracked_transform.translation + tracking.offset;
|
||||||
|
*camera_transform = camera_transform.looking_at(tracked_transform.translation, Vec3::Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
pub mod camera_tracking;
|
||||||
|
pub use camera_tracking::*;
|
||||||
|
|
||||||
|
pub mod camera_replace_proxies;
|
||||||
|
pub use camera_replace_proxies::*;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub struct CameraPlugin;
|
||||||
|
impl Plugin for CameraPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.register_type::<CameraTrackable>()
|
||||||
|
.register_type::<CameraTracking>()
|
||||||
|
.register_type::<CameraTrackingOffset>()
|
||||||
|
.add_systems(Update, (camera_replace_proxies, camera_track));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use bevy::pbr::{CascadeShadowConfig, CascadeShadowConfigBuilder};
|
||||||
|
|
||||||
|
// fixme might be too specific to might needs, should it be moved out ? also these are all for lights, not models
|
||||||
|
pub fn lighting_replace_proxies(
|
||||||
|
mut added_dirights: Query<(Entity, &mut DirectionalLight), Added<DirectionalLight>>,
|
||||||
|
mut added_spotlights: Query<&mut SpotLight, Added<SpotLight>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
for (entity, mut light) in added_dirights.iter_mut() {
|
||||||
|
light.illuminance *= 5.0;
|
||||||
|
light.shadows_enabled = true;
|
||||||
|
let shadow_config: CascadeShadowConfig = CascadeShadowConfigBuilder {
|
||||||
|
first_cascade_far_bound: 15.0,
|
||||||
|
maximum_distance: 135.0,
|
||||||
|
..default()
|
||||||
|
}
|
||||||
|
.into();
|
||||||
|
commands.entity(entity).insert(shadow_config);
|
||||||
|
}
|
||||||
|
for mut light in added_spotlights.iter_mut() {
|
||||||
|
light.shadows_enabled = true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
mod lighting_replace_proxies;
|
||||||
|
use lighting_replace_proxies::*;
|
||||||
|
|
||||||
|
use bevy::pbr::{DirectionalLightShadowMap, NotShadowCaster};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub struct LightingPlugin;
|
||||||
|
impl Plugin for LightingPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app
|
||||||
|
.insert_resource(DirectionalLightShadowMap { size: 4096 })
|
||||||
|
// FIXME: adding these since they are missing
|
||||||
|
.register_type::<NotShadowCaster>()
|
||||||
|
|
||||||
|
.add_systems(PreUpdate, lighting_replace_proxies) // FIXME: you should actually run this in a specific state most likely
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
pub mod camera;
|
||||||
|
pub use camera::*;
|
||||||
|
|
||||||
|
pub mod lighting;
|
||||||
|
pub use lighting::*;
|
||||||
|
|
||||||
|
pub mod relationships;
|
||||||
|
pub use relationships::*;
|
||||||
|
|
||||||
|
pub mod physics;
|
||||||
|
pub use physics::*;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
pub struct CorePlugin;
|
||||||
|
impl Plugin for CorePlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_plugins((LightingPlugin, CameraPlugin, PhysicsPlugin));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
use bevy::prelude::ResMut;
|
||||||
|
use bevy_rapier3d::prelude::RapierConfiguration;
|
||||||
|
|
||||||
|
pub fn pause_physics(mut physics_config: ResMut<RapierConfiguration>) {
|
||||||
|
physics_config.physics_pipeline_active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resume_physics(mut physics_config: ResMut<RapierConfiguration>) {
|
||||||
|
physics_config.physics_pipeline_active = true;
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
pub mod physics_replace_proxies;
|
||||||
|
pub use physics_replace_proxies::*;
|
||||||
|
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
|
pub mod controls;
|
||||||
|
pub use controls::*;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
// use crate::Collider;
|
||||||
|
pub struct PhysicsPlugin;
|
||||||
|
impl Plugin for PhysicsPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app
|
||||||
|
.register_type::<AutoAABBCollider>()
|
||||||
|
.register_type::<physics_replace_proxies::Collider>()
|
||||||
|
|
||||||
|
// find a way to make serde's stuff serializable
|
||||||
|
// .register_type::<bevy_rapier3d::dynamics::CoefficientCombineRule>()
|
||||||
|
//bevy_rapier3d::dynamics::CoefficientCombineRule
|
||||||
|
|
||||||
|
.add_systems(Update, physics_replace_proxies)
|
||||||
|
//.add_system(pause_physics.in_schedule(OnEnter(GameState::InMenu)))
|
||||||
|
//.add_system(resume_physics.in_schedule(OnEnter(GameState::InGame)))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
// use bevy::render::primitives::Aabb;
|
||||||
|
use bevy_rapier3d::geometry::Collider as RapierCollider;
|
||||||
|
use bevy_rapier3d::prelude::{ActiveCollisionTypes, ActiveEvents, ComputedColliderShape};
|
||||||
|
|
||||||
|
use super::utils::*;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub enum Collider {
|
||||||
|
Ball(f32),
|
||||||
|
Cuboid(Vec3),
|
||||||
|
Capsule(Vec3, Vec3, f32),
|
||||||
|
#[default]
|
||||||
|
Mesh,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub enum AutoAABBCollider {
|
||||||
|
#[default]
|
||||||
|
Cuboid,
|
||||||
|
Ball,
|
||||||
|
Capsule,
|
||||||
|
}
|
||||||
|
|
||||||
|
// replaces all physics stand-ins with the actual rapier types
|
||||||
|
pub fn physics_replace_proxies(
|
||||||
|
meshes: Res<Assets<Mesh>>,
|
||||||
|
mesh_handles: Query<&Handle<Mesh>>,
|
||||||
|
mut proxy_colliders: Query<
|
||||||
|
(Entity, &Collider, &Name, &mut Visibility),
|
||||||
|
(Without<RapierCollider>, Added<Collider>),
|
||||||
|
>,
|
||||||
|
// needed for tri meshes
|
||||||
|
children: Query<&Children>,
|
||||||
|
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
for proxy_colider in proxy_colliders.iter_mut() {
|
||||||
|
let (entity, collider_proxy, name, mut visibility) = proxy_colider;
|
||||||
|
// we hide the collider meshes: perhaps they should be removed altogether once processed ?
|
||||||
|
if name.ends_with("_collider") || name.ends_with("_sensor") {
|
||||||
|
*visibility = Visibility::Hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut rapier_collider: RapierCollider;
|
||||||
|
match collider_proxy {
|
||||||
|
Collider::Ball(radius) => {
|
||||||
|
println!("proxy: ball");
|
||||||
|
rapier_collider = RapierCollider::ball(*radius);
|
||||||
|
commands.entity(entity)
|
||||||
|
.insert(rapier_collider)
|
||||||
|
.insert(ActiveEvents::COLLISION_EVENTS) // FIXME: this is just for demo purposes !!!
|
||||||
|
;
|
||||||
|
}
|
||||||
|
Collider::Cuboid(size) => {
|
||||||
|
println!("proxy: cuboid");
|
||||||
|
rapier_collider = RapierCollider::cuboid(size.x, size.y, size.z);
|
||||||
|
commands.entity(entity)
|
||||||
|
.insert(rapier_collider)
|
||||||
|
.insert(ActiveEvents::COLLISION_EVENTS) // FIXME: this is just for demo purposes !!!
|
||||||
|
;
|
||||||
|
}
|
||||||
|
Collider::Capsule(a, b, radius) => {
|
||||||
|
println!("proxy: capsule");
|
||||||
|
rapier_collider = RapierCollider::capsule(*a, *b, *radius);
|
||||||
|
commands.entity(entity)
|
||||||
|
.insert(rapier_collider)
|
||||||
|
.insert(ActiveEvents::COLLISION_EVENTS) // FIXME: this is just for demo purposes !!!
|
||||||
|
;
|
||||||
|
}
|
||||||
|
Collider::Mesh => {
|
||||||
|
println!("proxy: mesh");
|
||||||
|
for (_, collider_mesh) in
|
||||||
|
Mesh::search_in_children(entity, &children, &meshes, &mesh_handles)
|
||||||
|
{
|
||||||
|
rapier_collider = RapierCollider::from_bevy_mesh(
|
||||||
|
collider_mesh,
|
||||||
|
&ComputedColliderShape::TriMesh,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
commands
|
||||||
|
.entity(entity)
|
||||||
|
.insert(rapier_collider)
|
||||||
|
// FIXME: this is just for demo purposes !!!
|
||||||
|
.insert(
|
||||||
|
ActiveCollisionTypes::default()
|
||||||
|
| ActiveCollisionTypes::KINEMATIC_STATIC
|
||||||
|
| ActiveCollisionTypes::STATIC_STATIC
|
||||||
|
| ActiveCollisionTypes::DYNAMIC_STATIC,
|
||||||
|
)
|
||||||
|
.insert(ActiveEvents::COLLISION_EVENTS);
|
||||||
|
// .insert(ActiveEvents::COLLISION_EVENTS)
|
||||||
|
// break;
|
||||||
|
// RapierCollider::convex_hull(points)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,175 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::render::mesh::{MeshVertexAttributeId, PrimitiveTopology, VertexAttributeValues};
|
||||||
|
// TAKEN VERBATIB FROM https://github.com/janhohenheim/foxtrot/blob/src/util/trait_extension.rs
|
||||||
|
|
||||||
|
pub(crate) trait Vec3Ext: Copy {
|
||||||
|
fn is_approx_zero(self) -> bool;
|
||||||
|
fn split(self, up: Vec3) -> SplitVec3;
|
||||||
|
}
|
||||||
|
impl Vec3Ext for Vec3 {
|
||||||
|
#[inline]
|
||||||
|
fn is_approx_zero(self) -> bool {
|
||||||
|
self.length_squared() < 1e-5
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn split(self, up: Vec3) -> SplitVec3 {
|
||||||
|
let vertical = up * self.dot(up);
|
||||||
|
let horizontal = self - vertical;
|
||||||
|
SplitVec3 {
|
||||||
|
vertical,
|
||||||
|
horizontal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub(crate) struct SplitVec3 {
|
||||||
|
pub(crate) vertical: Vec3,
|
||||||
|
pub(crate) horizontal: Vec3,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait Vec2Ext: Copy {
|
||||||
|
fn is_approx_zero(self) -> bool;
|
||||||
|
fn x0y(self) -> Vec3;
|
||||||
|
}
|
||||||
|
impl Vec2Ext for Vec2 {
|
||||||
|
#[inline]
|
||||||
|
fn is_approx_zero(self) -> bool {
|
||||||
|
self.length_squared() < 1e-5
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn x0y(self) -> Vec3 {
|
||||||
|
Vec3::new(self.x, 0., self.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait MeshExt {
|
||||||
|
fn transform(&mut self, transform: Transform);
|
||||||
|
fn transformed(&self, transform: Transform) -> Mesh;
|
||||||
|
fn read_coords_mut(&mut self, id: impl Into<MeshVertexAttributeId>) -> &mut Vec<[f32; 3]>;
|
||||||
|
fn search_in_children<'a>(
|
||||||
|
parent: Entity,
|
||||||
|
children: &'a Query<&Children>,
|
||||||
|
meshes: &'a Assets<Mesh>,
|
||||||
|
mesh_handles: &'a Query<&Handle<Mesh>>,
|
||||||
|
) -> Vec<(Entity, &'a Mesh)>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MeshExt for Mesh {
|
||||||
|
fn transform(&mut self, transform: Transform) {
|
||||||
|
for coords in self.read_coords_mut(Mesh::ATTRIBUTE_POSITION.clone()) {
|
||||||
|
let vec3 = (*coords).into();
|
||||||
|
let transformed = transform.transform_point(vec3);
|
||||||
|
*coords = transformed.into();
|
||||||
|
}
|
||||||
|
for normal in self.read_coords_mut(Mesh::ATTRIBUTE_NORMAL.clone()) {
|
||||||
|
let vec3 = (*normal).into();
|
||||||
|
let transformed = transform.rotation.mul_vec3(vec3);
|
||||||
|
*normal = transformed.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transformed(&self, transform: Transform) -> Mesh {
|
||||||
|
let mut mesh = self.clone();
|
||||||
|
mesh.transform(transform);
|
||||||
|
mesh
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_coords_mut(&mut self, id: impl Into<MeshVertexAttributeId>) -> &mut Vec<[f32; 3]> {
|
||||||
|
// Guaranteed by Bevy for the current usage
|
||||||
|
match self
|
||||||
|
.attribute_mut(id)
|
||||||
|
.expect("Failed to read unknown mesh attribute")
|
||||||
|
{
|
||||||
|
VertexAttributeValues::Float32x3(values) => values,
|
||||||
|
// Guaranteed by Bevy for the current usage
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_in_children<'a>(
|
||||||
|
parent: Entity,
|
||||||
|
children_query: &'a Query<&Children>,
|
||||||
|
meshes: &'a Assets<Mesh>,
|
||||||
|
mesh_handles: &'a Query<&Handle<Mesh>>,
|
||||||
|
) -> Vec<(Entity, &'a Mesh)> {
|
||||||
|
if let Ok(children) = children_query.get(parent) {
|
||||||
|
let mut result: Vec<_> = children
|
||||||
|
.iter()
|
||||||
|
.filter_map(|entity| mesh_handles.get(*entity).ok().map(|mesh| (*entity, mesh)))
|
||||||
|
.map(|(entity, mesh_handle)| {
|
||||||
|
(
|
||||||
|
entity,
|
||||||
|
meshes
|
||||||
|
.get(mesh_handle)
|
||||||
|
.expect("Failed to get mesh from handle"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.map(|(entity, mesh)| {
|
||||||
|
assert_eq!(mesh.primitive_topology(), PrimitiveTopology::TriangleList);
|
||||||
|
(entity, mesh)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let mut inner_result = children
|
||||||
|
.iter()
|
||||||
|
.flat_map(|entity| {
|
||||||
|
Self::search_in_children(*entity, children_query, meshes, mesh_handles)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
result.append(&mut inner_result);
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait F32Ext: Copy {
|
||||||
|
fn is_approx_zero(self) -> bool;
|
||||||
|
fn squared(self) -> f32;
|
||||||
|
fn lerp(self, other: f32, ratio: f32) -> f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl F32Ext for f32 {
|
||||||
|
#[inline]
|
||||||
|
fn is_approx_zero(self) -> bool {
|
||||||
|
self.abs() < 1e-5
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn squared(self) -> f32 {
|
||||||
|
self * self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn lerp(self, other: f32, ratio: f32) -> f32 {
|
||||||
|
self.mul_add(1. - ratio, other * ratio)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait TransformExt: Copy {
|
||||||
|
fn horizontally_looking_at(self, target: Vec3, up: Vec3) -> Transform;
|
||||||
|
fn lerp(self, other: Transform, ratio: f32) -> Transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransformExt for Transform {
|
||||||
|
fn horizontally_looking_at(self, target: Vec3, up: Vec3) -> Transform {
|
||||||
|
let direction = target - self.translation;
|
||||||
|
let horizontal_direction = direction - up * direction.dot(up);
|
||||||
|
let look_target = self.translation + horizontal_direction;
|
||||||
|
self.looking_at(look_target, up)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lerp(self, other: Transform, ratio: f32) -> Transform {
|
||||||
|
let translation = self.translation.lerp(other.translation, ratio);
|
||||||
|
let rotation = self.rotation.slerp(other.rotation, ratio);
|
||||||
|
let scale = self.scale.lerp(other.scale, ratio);
|
||||||
|
Transform {
|
||||||
|
translation,
|
||||||
|
rotation,
|
||||||
|
scale,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
pub mod relationships_insert_dependant_components;
|
||||||
|
pub use relationships_insert_dependant_components::*;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub struct EcsRelationshipsPlugin;
|
||||||
|
impl Plugin for EcsRelationshipsPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub fn insert_dependant_component<
|
||||||
|
Dependant: Component,
|
||||||
|
Dependency: Component + std::default::Default,
|
||||||
|
>(
|
||||||
|
mut commands: Commands,
|
||||||
|
entities_without_depency: Query<(Entity, &Name), (With<Dependant>, Without<Dependency>)>,
|
||||||
|
) {
|
||||||
|
for (entity, name) in entities_without_depency.iter() {
|
||||||
|
let name = name.clone().to_string();
|
||||||
|
commands.entity(entity).insert(Dependency::default());
|
||||||
|
warn!("found an entity called {} with a {} component but without an {}, please check your assets", name.clone(), std::any::type_name::<Dependant>(), std::any::type_name::<Dependency>());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
use crate::insert_dependant_component;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_rapier3d::prelude::*;
|
||||||
|
|
||||||
|
// this file is just for demo purposes, contains various types of components, systems etc
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub enum SoundMaterial {
|
||||||
|
Metal,
|
||||||
|
Wood,
|
||||||
|
Rock,
|
||||||
|
Cloth,
|
||||||
|
Squishy,
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Demo marker component
|
||||||
|
pub struct Player;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Demo component showing auto injection of components
|
||||||
|
pub struct ShouldBeWithPlayer;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Demo marker component
|
||||||
|
pub struct Interactible;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Demo marker component
|
||||||
|
pub struct Pickable;
|
||||||
|
|
||||||
|
fn player_move_demo(
|
||||||
|
keycode: Res<Input<KeyCode>>,
|
||||||
|
mut players: Query<&mut Transform, With<Player>>,
|
||||||
|
) {
|
||||||
|
let speed = 0.2;
|
||||||
|
if let Ok(mut player) = players.get_single_mut() {
|
||||||
|
if keycode.pressed(KeyCode::Left) {
|
||||||
|
player.translation.x += speed;
|
||||||
|
}
|
||||||
|
if keycode.pressed(KeyCode::Right) {
|
||||||
|
player.translation.x -= speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if keycode.pressed(KeyCode::Up) {
|
||||||
|
player.translation.z += speed;
|
||||||
|
}
|
||||||
|
if keycode.pressed(KeyCode::Down) {
|
||||||
|
player.translation.z -= speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// collision tests/debug
|
||||||
|
pub fn test_collision_events(
|
||||||
|
mut collision_events: EventReader<CollisionEvent>,
|
||||||
|
mut contact_force_events: EventReader<ContactForceEvent>,
|
||||||
|
) {
|
||||||
|
for collision_event in collision_events.read() {
|
||||||
|
println!("collision");
|
||||||
|
match collision_event {
|
||||||
|
CollisionEvent::Started(_entity1, _entity2, _) => {
|
||||||
|
println!("collision started")
|
||||||
|
}
|
||||||
|
CollisionEvent::Stopped(_entity1, _entity2, _) => {
|
||||||
|
println!("collision ended")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for contact_force_event in contact_force_events.read() {
|
||||||
|
println!("Received contact force event: {:?}", contact_force_event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DemoPlugin;
|
||||||
|
impl Plugin for DemoPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.register_type::<Interactible>()
|
||||||
|
.register_type::<Pickable>()
|
||||||
|
.register_type::<SoundMaterial>()
|
||||||
|
.register_type::<Player>()
|
||||||
|
// little helper utility, to automatically inject components that are dependant on an other component
|
||||||
|
// ie, here an Entity with a Player component should also always have a ShouldBeWithPlayer component
|
||||||
|
// you get a warning if you use this, as I consider this to be stop-gap solution (usually you should have either a bundle, or directly define all needed components)
|
||||||
|
.add_systems(
|
||||||
|
Update,
|
||||||
|
(
|
||||||
|
insert_dependant_component::<Player, ShouldBeWithPlayer>,
|
||||||
|
player_move_demo, //.run_if(in_state(AppState::Running)),
|
||||||
|
test_collision_events,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
use bevy::{gltf::Gltf, prelude::*};
|
||||||
|
use bevy_editor_pls::prelude::*;
|
||||||
|
use bevy_gltf_components::ComponentsFromGltfPlugin;
|
||||||
|
use bevy_rapier3d::prelude::*;
|
||||||
|
|
||||||
|
mod core;
|
||||||
|
use crate::core::*;
|
||||||
|
|
||||||
|
mod game;
|
||||||
|
use game::*;
|
||||||
|
|
||||||
|
mod test_components;
|
||||||
|
use test_components::*;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// helper marker component
|
||||||
|
pub struct LoadedMarker;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, States)]
|
||||||
|
enum AppState {
|
||||||
|
#[default]
|
||||||
|
Loading,
|
||||||
|
Running,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins((
|
||||||
|
DefaultPlugins.set(AssetPlugin::default()),
|
||||||
|
// editor
|
||||||
|
EditorPlugin::default(),
|
||||||
|
// physics
|
||||||
|
RapierPhysicsPlugin::<NoUserData>::default(),
|
||||||
|
RapierDebugRenderPlugin::default(),
|
||||||
|
// our custom plugins
|
||||||
|
ComponentsFromGltfPlugin,
|
||||||
|
CorePlugin, // reusable plugins
|
||||||
|
DemoPlugin, // specific to our game
|
||||||
|
ComponentsTestPlugin, // Showcases different type of components /structs
|
||||||
|
))
|
||||||
|
.add_state::<AppState>()
|
||||||
|
.add_systems(Startup, setup)
|
||||||
|
.add_systems(Update, (spawn_level.run_if(in_state(AppState::Loading)),))
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub struct MyGltf(pub Handle<Gltf>);
|
||||||
|
|
||||||
|
// we preload the data here, but this is for DEMO PURPOSES ONLY !! Please use https://github.com/NiklasEi/bevy_asset_loader or a similar logic to seperate loading / pre processing
|
||||||
|
// of assets from the spawning
|
||||||
|
// MyGltf is also just for the same purpose, you do not need it in a real scenario
|
||||||
|
// the states here are also for demo purposes only,
|
||||||
|
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
|
commands.insert_resource(MyGltf(asset_server.load("models/level1.glb")));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_level(
|
||||||
|
mut commands: Commands,
|
||||||
|
scene_markers: Query<&LoadedMarker>,
|
||||||
|
mut asset_event_reader: EventReader<AssetEvent<Gltf>>,
|
||||||
|
mut next_state: ResMut<NextState<AppState>>,
|
||||||
|
models: Res<Assets<bevy::gltf::Gltf>>,
|
||||||
|
) {
|
||||||
|
if let Some(asset_event) = asset_event_reader.read().next() {
|
||||||
|
match asset_event {
|
||||||
|
AssetEvent::Added { id } => {
|
||||||
|
info!("GLTF loaded/ added {:?}", asset_event);
|
||||||
|
let my_gltf = models.get(*id).unwrap();
|
||||||
|
if scene_markers.is_empty() {
|
||||||
|
info!("spawning scene");
|
||||||
|
commands.spawn((
|
||||||
|
SceneBundle {
|
||||||
|
scene: my_gltf.scenes[0].clone(),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
LoadedMarker,
|
||||||
|
Name::new("Level1"),
|
||||||
|
));
|
||||||
|
next_state.set(AppState::Running);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct UnitTest;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct TuppleTestF32(f32);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct TuppleTestU64(u64);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct TuppleTestStr(String);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct TuppleTest2(f32, u64, String);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct TuppleTestBool(bool);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct TuppleVec2(Vec2);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct TuppleVec3(Vec3);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct TuppleVec(Vec<String>);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct TuppleTestColor(Color);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct BasicTest {
|
||||||
|
a: f32,
|
||||||
|
b: u64,
|
||||||
|
c: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub enum EnumTest {
|
||||||
|
Metal,
|
||||||
|
Wood,
|
||||||
|
Rock,
|
||||||
|
Cloth,
|
||||||
|
Squishy,
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ComponentsTestPlugin;
|
||||||
|
impl Plugin for ComponentsTestPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.register_type::<BasicTest>()
|
||||||
|
.register_type::<UnitTest>()
|
||||||
|
.register_type::<TuppleTestF32>()
|
||||||
|
.register_type::<TuppleTestU64>()
|
||||||
|
.register_type::<TuppleTestStr>()
|
||||||
|
.register_type::<TuppleTestBool>()
|
||||||
|
.register_type::<TuppleTest2>()
|
||||||
|
.register_type::<TuppleVec2>()
|
||||||
|
.register_type::<TuppleVec3>()
|
||||||
|
.register_type::<EnumTest>()
|
||||||
|
.register_type::<TuppleTestColor>()
|
||||||
|
.register_type::<TuppleVec>()
|
||||||
|
.register_type::<Vec<String>>();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue