-
-
Notifications
You must be signed in to change notification settings - Fork 683
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
projects: example using the bevy 3d game engine and leptos (#2577)
* feat: Added example using the bevy 3d game engine and leptos * fix: moved example to projects * workspace fix
- Loading branch information
Showing
16 changed files
with
396 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
[package] | ||
name = "bevy3d_ui" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[profile.release] | ||
codegen-units = 1 | ||
lto = true | ||
|
||
[dependencies] | ||
leptos = { version = "0.6.11", features = ["csr"] } | ||
leptos_meta = { version = "0.6.11", features = ["csr"] } | ||
leptos_router = { version = "0.6.11", features = ["csr"] } | ||
console_log = "1" | ||
log = "0.4" | ||
console_error_panic_hook = "0.1.7" | ||
bevy = "0.13.2" | ||
crossbeam-channel = "0.5.12" | ||
|
||
[dev-dependencies] | ||
wasm-bindgen = "0.2" | ||
wasm-bindgen-test = "0.3.0" | ||
web-sys = "0.3" | ||
|
||
[workspace] | ||
# The empty workspace here is to keep rust-analyzer satisfied |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Bevy 3D UI Example | ||
|
||
This example combines a leptos UI with a bevy 3D view. | ||
Bevy is a 3D game engine written in rust that can be compiled to web assembly by using the wgpu library. | ||
The wgpu library in turn can target the newer webgpu standard or the older webgl for web browsers. | ||
|
||
In the case of a desktop application, if you wanted to use a styled ui via leptos and a 3d view via bevy | ||
you could also combine this with tauri. | ||
|
||
## Quick Start | ||
|
||
* Run `trunk serve to run the example. | ||
* Browse to http://127.0.0.1:8080/ | ||
|
||
It's best to use a web browser with webgpu capability for best results such as Chrome or Opera. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<link data-trunk rel="rust" data-wasm-opt="z"/> | ||
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico"/> | ||
</head> | ||
<body></body> | ||
</html> |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[toolchain] | ||
channel = "stable" # test change |
38 changes: 38 additions & 0 deletions
38
projects/bevy3d_ui/src/demos/bevydemo1/eventqueue/events.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
use bevy::prelude::*; | ||
|
||
/// Event Processor | ||
#[derive(Resource)] | ||
pub struct EventProcessor<TSender, TReceiver> { | ||
pub sender: crossbeam_channel::Sender<TSender>, | ||
pub receiver: crossbeam_channel::Receiver<TReceiver>, | ||
} | ||
|
||
impl<TSender, TReceiver> Clone for EventProcessor<TSender, TReceiver> { | ||
fn clone(&self) -> Self { | ||
Self { | ||
sender: self.sender.clone(), | ||
receiver: self.receiver.clone(), | ||
} | ||
} | ||
} | ||
|
||
/// Events sent from the client to bevy | ||
#[derive(Debug)] | ||
pub enum ClientInEvents { | ||
/// Update the 3d model position from the client | ||
CounterEvt(CounterEvtData), | ||
} | ||
|
||
/// Events sent out from bevy to the client | ||
#[derive(Debug)] | ||
pub enum PluginOutEvents { | ||
/// TODO Feed back to the client an event from bevy | ||
Click, | ||
} | ||
|
||
/// Input event to update the bevy view from the client | ||
#[derive(Clone, Debug, Event)] | ||
pub struct CounterEvtData { | ||
/// Amount to move on the Y Axis | ||
pub value: f32, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
pub mod events; | ||
pub mod plugin; |
63 changes: 63 additions & 0 deletions
63
projects/bevy3d_ui/src/demos/bevydemo1/eventqueue/plugin.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
use super::events::*; | ||
use bevy::prelude::*; | ||
|
||
/// Events plugin for bevy | ||
#[derive(Clone)] | ||
pub struct DuplexEventsPlugin { | ||
/// Client processor for sending ClientInEvents, receiving PluginOutEvents | ||
client_processor: EventProcessor<ClientInEvents, PluginOutEvents>, | ||
/// Internal processor for sending PluginOutEvents, receiving ClientInEvents | ||
plugin_processor: EventProcessor<PluginOutEvents, ClientInEvents>, | ||
} | ||
|
||
impl DuplexEventsPlugin { | ||
/// Create a new instance | ||
pub fn new() -> DuplexEventsPlugin { | ||
// For sending messages from bevy to the client | ||
let (bevy_sender, client_receiver) = crossbeam_channel::bounded(50); | ||
// For sending message from the client to bevy | ||
let (client_sender, bevy_receiver) = crossbeam_channel::bounded(50); | ||
let instance = DuplexEventsPlugin { | ||
client_processor: EventProcessor { | ||
sender: client_sender, | ||
receiver: client_receiver, | ||
}, | ||
plugin_processor: EventProcessor { | ||
sender: bevy_sender, | ||
receiver: bevy_receiver, | ||
}, | ||
}; | ||
instance | ||
} | ||
|
||
/// Get the client event processor | ||
pub fn get_processor( | ||
&self, | ||
) -> EventProcessor<ClientInEvents, PluginOutEvents> { | ||
self.client_processor.clone() | ||
} | ||
} | ||
|
||
/// Build the bevy plugin and attach | ||
impl Plugin for DuplexEventsPlugin { | ||
fn build(&self, app: &mut App) { | ||
app.insert_resource(self.plugin_processor.clone()) | ||
.init_resource::<Events<CounterEvtData>>() | ||
.add_systems(PreUpdate, input_events_system); | ||
} | ||
} | ||
|
||
/// Send the event to bevy using EventWriter | ||
fn input_events_system( | ||
int_processor: Res<EventProcessor<PluginOutEvents, ClientInEvents>>, | ||
mut counter_event_writer: EventWriter<CounterEvtData>, | ||
) { | ||
for input_event in int_processor.receiver.try_iter() { | ||
match input_event { | ||
ClientInEvents::CounterEvt(event) => { | ||
// Send event through Bevy's event system | ||
counter_event_writer.send(event); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
pub mod eventqueue; | ||
pub mod scene; | ||
pub mod state; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
use super::eventqueue::events::{ | ||
ClientInEvents, CounterEvtData, EventProcessor, PluginOutEvents, | ||
}; | ||
use super::eventqueue::plugin::DuplexEventsPlugin; | ||
use super::state::{Shared, SharedResource, SharedState}; | ||
use bevy::prelude::*; | ||
|
||
/// Represents the Cube in the scene | ||
#[derive(Component, Copy, Clone)] | ||
pub struct Cube; | ||
|
||
/// Represents the 3D Scene | ||
#[derive(Clone)] | ||
pub struct Scene { | ||
is_setup: bool, | ||
canvas_id: String, | ||
evt_plugin: DuplexEventsPlugin, | ||
shared_state: Shared<SharedState>, | ||
processor: EventProcessor<ClientInEvents, PluginOutEvents>, | ||
} | ||
|
||
impl Scene { | ||
/// Create a new instance | ||
pub fn new(canvas_id: String) -> Scene { | ||
let plugin = DuplexEventsPlugin::new(); | ||
let instance = Scene { | ||
is_setup: false, | ||
canvas_id: canvas_id, | ||
evt_plugin: plugin.clone(), | ||
shared_state: SharedState::new(), | ||
processor: plugin.get_processor(), | ||
}; | ||
instance | ||
} | ||
|
||
/// Get the shared state | ||
pub fn get_state(&self) -> Shared<SharedState> { | ||
self.shared_state.clone() | ||
} | ||
|
||
/// Get the event processor | ||
pub fn get_processor( | ||
&self, | ||
) -> EventProcessor<ClientInEvents, PluginOutEvents> { | ||
self.processor.clone() | ||
} | ||
|
||
/// Setup and attach the bevy instance to the html canvas element | ||
pub fn setup(&mut self) { | ||
if self.is_setup == true { | ||
return; | ||
}; | ||
App::new() | ||
.add_plugins(DefaultPlugins.set(WindowPlugin { | ||
primary_window: Some(Window { | ||
canvas: Some(self.canvas_id.clone()), | ||
..default() | ||
}), | ||
..default() | ||
})) | ||
.add_plugins(self.evt_plugin.clone()) | ||
.insert_resource(SharedResource(self.shared_state.clone())) | ||
.add_systems(Startup, setup_scene) | ||
.add_systems(Update, handle_bevy_event) | ||
.run(); | ||
self.is_setup = true; | ||
} | ||
} | ||
|
||
/// Setup the scene | ||
fn setup_scene( | ||
mut commands: Commands, | ||
mut meshes: ResMut<Assets<Mesh>>, | ||
mut materials: ResMut<Assets<StandardMaterial>>, | ||
resource: Res<SharedResource>, | ||
) { | ||
let name = resource.0.lock().unwrap().name.clone(); | ||
// circular base | ||
commands.spawn(PbrBundle { | ||
mesh: meshes.add(Circle::new(4.0)), | ||
material: materials.add(Color::WHITE), | ||
transform: Transform::from_rotation(Quat::from_rotation_x( | ||
-std::f32::consts::FRAC_PI_2, | ||
)), | ||
..default() | ||
}); | ||
// cube | ||
commands.spawn(( | ||
PbrBundle { | ||
mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)), | ||
material: materials.add(Color::rgb_u8(124, 144, 255)), | ||
transform: Transform::from_xyz(0.0, 0.5, 0.0), | ||
..default() | ||
}, | ||
Cube, | ||
)); | ||
// light | ||
commands.spawn(PointLightBundle { | ||
point_light: PointLight { | ||
shadows_enabled: true, | ||
..default() | ||
}, | ||
transform: Transform::from_xyz(4.0, 8.0, 4.0), | ||
..default() | ||
}); | ||
// camera | ||
commands.spawn(Camera3dBundle { | ||
transform: Transform::from_xyz(-2.5, 4.5, 9.0) | ||
.looking_at(Vec3::ZERO, Vec3::Y), | ||
..default() | ||
}); | ||
commands.spawn(TextBundle::from_section(name, TextStyle::default())); | ||
} | ||
|
||
/// Move the Cube on event | ||
fn handle_bevy_event( | ||
mut counter_event_reader: EventReader<CounterEvtData>, | ||
mut cube_query: Query<&mut Transform, With<Cube>>, | ||
) { | ||
let mut cube_transform = cube_query.get_single_mut().expect("no cube :("); | ||
for _ev in counter_event_reader.read() { | ||
cube_transform.translation += Vec3::new(0.0, _ev.value, 0.0); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
use bevy::ecs::system::Resource; | ||
use std::sync::{Arc, Mutex}; | ||
|
||
pub type Shared<T> = Arc<Mutex<T>>; | ||
|
||
/// Shared Resource used for Bevy | ||
#[derive(Resource)] | ||
pub struct SharedResource(pub Shared<SharedState>); | ||
|
||
/// Shared State | ||
pub struct SharedState { | ||
pub name: String, | ||
} | ||
|
||
impl SharedState { | ||
/// Get a new shared state | ||
pub fn new() -> Arc<Mutex<SharedState>> { | ||
let state = SharedState { | ||
name: "This can be used for shared state".to_string(), | ||
}; | ||
let shared = Arc::new(Mutex::new(state)); | ||
shared | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pub mod bevydemo1; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
mod demos; | ||
mod routes; | ||
use leptos::*; | ||
use routes::RootPage; | ||
|
||
pub fn main() { | ||
// Bevy will output a lot of debug info to the console when this is enabled. | ||
//_ = console_log::init_with_level(log::Level::Debug); | ||
console_error_panic_hook::set_once(); | ||
mount_to_body(|| view! { <RootPage/> }) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
use crate::demos::bevydemo1::eventqueue::events::{ | ||
ClientInEvents, CounterEvtData, | ||
}; | ||
use crate::demos::bevydemo1::scene::Scene; | ||
use leptos::*; | ||
|
||
/// 3d view component | ||
#[component] | ||
pub fn Demo1() -> impl IntoView { | ||
// Setup a Counter | ||
let initial_value: i32 = 0; | ||
let step: i32 = 1; | ||
let (value, set_value) = create_signal(initial_value); | ||
|
||
// Setup a bevy 3d scene | ||
let scene = Scene::new("#bevy".to_string()); | ||
let sender = scene.get_processor().sender; | ||
let (sender_sig, _set_sender_sig) = create_signal(sender); | ||
let (scene_sig, _set_scene_sig) = create_signal(scene); | ||
|
||
// We need to add the 3D view onto the canvas post render. | ||
create_effect(move |_| { | ||
request_animation_frame(move || { | ||
scene_sig.get().setup(); | ||
}); | ||
}); | ||
|
||
view! { | ||
<div> | ||
<button on:click=move |_| set_value.set(0)>"Clear"</button> | ||
<button on:click=move |_| { | ||
set_value.update(|value| *value -= step); | ||
let newpos = (step as f32) / 10.0; | ||
sender_sig | ||
.get() | ||
.send(ClientInEvents::CounterEvt(CounterEvtData { value: -newpos })) | ||
.expect("could not send event"); | ||
}>"-1"</button> | ||
<span>"Value: " {value} "!"</span> | ||
<button on:click=move |_| { | ||
set_value.update(|value| *value += step); | ||
let newpos = step as f32 / 10.0; | ||
sender_sig | ||
.get() | ||
.send(ClientInEvents::CounterEvt(CounterEvtData { value: newpos })) | ||
.expect("could not send event"); | ||
}>"+1"</button> | ||
</div> | ||
|
||
<canvas id="bevy" width="800" height="600"></canvas> | ||
} | ||
} |
Oops, something went wrong.