React + Rust + Wasm: Play an Animated 3D Model
In this article, we will use Bevy game engine in our React application from our Rust WASM library to play an animated 3D model. We will build on the previous tutorial available here.
Run a Bevy App using Rust
Last time, we used a setup_audio
function with our Bevy app to play an audio file. Now, let's modify our lib.rs
to create a setup_animation
function that will load the model and another play_animation
that actually plays the animation.
Update the setup function
#[wasm_bindgen]
pub fn run_bevy_app() {
App::new()
.add_plugins(DefaultPlugins)
.add_startup_system(setup_animation) # Defined below
.add_system(play_animation) # Define below and called once scene is loaded
.run();
}
Define setup_animation
fn setup_animation(
mut commands: Commands,
asset_server: Res<AssetServer>
) {
# ...
}
Declare a struct to load the animation clips
#[derive(Resource)]
struct Animations(Vec<Handle<AnimationClip>>);
Please note #[derive(Resource)]
that is needed in the loader below.
Read the animations
fn setup_animation(
mut commands: Commands,
asset_server: Res<AssetServer>
) {
# Please ensure that Fox.glb is added to public/assets/models/animated folder
commands.insert_resource(Animations(vec![
asset_server.load("models/animated/Fox.glb#Animation2"),
asset_server.load("models/animated/Fox.glb#Animation1"),
asset_server.load("models/animated/Fox.glb#Animation0"),
]));
}
Setup the camera
fn setup_animation(
mut commands: Commands,
asset_server: Res<AssetServer>
) {
# ...
commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(100.0, 100.0, 150.0)
.looking_at(Vec3::new(0.0, 20.0, 0.0), Vec3::Y),
..default()
});
}
Load the scene
fn setup_animation(
mut commands: Commands,
asset_server: Res<AssetServer>
) {
# ...
# Please ensure that Fox.glb is added to public/assets/models/animated folder
commands.spawn(SceneBundle {
scene: asset_server.load("models/animated/Fox.glb#Scene0"),
..default()
});
}
Define the function to actually play the animation
fn play_animation(
animations: Res<Animations>,
mut player: Query<&mut AnimationPlayer>,
mut done: Local<bool>,
) {
# ...
}
Use Bevy’s AnimationPlayer
fn play_animation(
animations: Res<Animations>,
mut player: Query<&mut AnimationPlayer>,
mut done: Local<bool>,
) {
if !*done {
if let Ok(mut player) = player.get_single_mut() {
player.play(animations.0[0].clone_weak()).repeat();
*done = true;
}
}
}
The code basically triggers a play
command for the first animation in a loop.
Build the new wasm library
Let’s run wasm-pack again to build the updated library
wasm-pack build --target web
Call the function from the demo app
Finally, let’s add a button to load the wasm and call run_bevy_app
function from our App.ts
file like so:
// App.ts
import init, { run_bevy_app } from "rust-wasm-lib";
import './App.css';
function App() {
const runBevyApp = async () => {
await init();
run_bevy_app();
};
return (
<div className="App">
<button onClick={runBevyApp}>Run Bevy App</button>
</div>
);
}
export default App;
Now, if you run the updated app and click on Run Bevy App
, you should see an animated fox on the canvas :)
If you liked this article, subscribe here to get the complete code and updates for the entire collection: