Speedy2D lets you build cross-platform games and graphical applications using Rust. Version 1.3 has just been released with support for WebGL, in addition to the previously supported platforms Windows, Mac, and Linux.
This post will demonstrate how to create a WebGL app using Speedy2D. The app will work in any modern web browser, and with some minor configuration changes, you can build the same app for other platforms such as Windows.
To keep things simple, the app will just draw a blue circle at the current mouse position. Click here to see a live demo of the finished result (note: it’ll only work if your browser supports WebGL 2.0).
Note that you will need Rust installed to follow this tutorial – see the instructions here for how to do this.
Step 1: Create the Rust project
Use Cargo to create the project. You can use any name of your choice.
# Create the project
cargo new my-webgl-app
# Go to the project directory
cd my-webgl-app
Open Cargo.toml
in a text editor and add Speedy2D to the dependencies section.
[dependencies]
speedy2d = "1.3.1"
For improved logging, we can also add the following:
log = "0.4"
wasm-logger = "0.2"
console_error_panic_hook = "0.1.6"
Now, run the first build, specifying the WebAssembly target:
# Set up Rust for WebAssembly builds
rustup target add wasm32-unknown-unknown
# Run a build with the WebAssembly target
cargo build --target wasm32-unknown-unknown
This will create a *.wasm
file in the following location:
./target/wasm32-unknown-unknown/debug/my-webgl-app.wasm
We’ll use this file later – once we’ve actually written some code, we’ll need to rebuild it.
Step 2: Create an HTML page with a Canvas
Let’s create the actual web page that will host our app. Save the following HTML code to a file called index.html
in your project directory. The filename my-webgl-app
should be changed in both places to match your project name.
<!DOCTYPE html>
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
<style>
body {
margin: 0px;
padding: 0px;
}
canvas#my_canvas {
position: absolute;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<canvas id="my_canvas"></canvas>
<script type="module" src="./my-webgl-app.js"></script>
<script type="module">
import main from "./my-webgl-app.js";
main();
</script>
</body>
</html>
This web page:
- Contains a
<canvas>
, which is where Speedy2D will draw the graphics.
<canvas id="my_canvas"></canvas>
- Loads our Javascript module – don’t forget to change
my-webgl-app
to the name of your project:
<script type="module" src="./my-webgl-app.js"></script>
- Calls the
main()
function in our module:
<script type="module">
import main from "./my-webgl-app.js";
main();
</script>
It also has some CSS rules to set the size and layout of the canvas.
Step 3: Write the Rust code for our app
Writing a Speedy2D app for any platform looks something like this:
- Create a struct implementing the
WindowHandler
trait. - Add implementations of the callbacks you’d like to receive, for example
on_draw()
,on_mouse_move()
, oron_key_down()
. - In the
main()
function, create aWindow
orWebCanvas
which uses your customWindowHandler
.
In this tutorial, let’s make a very simple app which just draws a blue circle at the current mouse position.
Overwrite the default contents of src/main.rs
with the following Rust code:
use speedy2d::color::Color;
use speedy2d::dimen::Vector2;
use speedy2d::window::{WindowHandler, WindowHelper};
use speedy2d::{Graphics2D, WebCanvas};
struct MyHandler
{
current_mouse_pos: Vector2<f32>
}
impl WindowHandler for MyHandler
{
fn on_mouse_move(&mut self, h: &mut WindowHelper, pos: Vector2<f32>)
{
self.current_mouse_pos = pos;
h.request_redraw();
}
fn on_draw(&mut self, _h: &mut WindowHelper, g: &mut Graphics2D)
{
g.clear_screen(Color::WHITE);
g.draw_circle(self.current_mouse_pos, 50.0, Color::BLUE);
}
}
fn main()
{
wasm_logger::init(wasm_logger::Config::default());
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
let handler = MyHandler { current_mouse_pos: Vector2::ZERO };
WebCanvas::new_for_id("my_canvas", handler).unwrap();
}
Note that:
- We define a new struct called
MyHandler
, which implements theon_mouse_move()
andon_draw()
callbacks. - When we get the
on_mouse_move()
callback, we save the position, and redraw the contents of the screen:
fn on_mouse_move(&mut self, h: &mut WindowHelper, pos: Vector2<f32>)
{
self.current_mouse_pos = pos;
h.request_redraw();
}
- In
on_draw()
, we clear the screen, and draw a circle at the saved mouse position. - In the
main()
function, we set up a logger and panic handler to help with debugging – any errors will be logged to the web console:
wasm_logger::init(wasm_logger::Config::default());
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
- We then create an instance of our custom handler, with the mouse position set to the default of
(0, 0)
:
let handler = MyHandler { current_mouse_pos: Vector2::ZERO };
- And finally, attach our handler to the canvas in our web page:
WebCanvas::new_for_id("my_canvas", handler).unwrap();
Step 4: Building
Time to build the project! First, let’s compile the Rust code into WebAssembly:
cargo build --target wasm32-unknown-unknown
Next, let’s create a directory called generated
to store all the files needed for our web page:
mkdir generated
Copy the web page itself into that directory:
cp index.html generated/
Now we need to generate the Javascript bindings which will let us load the WebAssembly code from our web page.
Install the wasm-bindgen
tool using cargo. If you’re using Linux, you may need to ensure pkg-config
and OpenSSL are installed first (on Ubuntu: sudo apt install pkg-config libssl-dev
).
cargo install wasm-bindgen-cli
Then run the command below, replacing my-webgl-app
with the name of your project. If in the future you do a release build, change debug
to release
.
wasm-bindgen target/wasm32-unknown-unknown/debug/my-webgl-app.wasm --out-dir generated --target web
Your project is now fully built, and the generated
directory now contains the contents of your website.
Step 5: Testing
Due to the way WebAssembly code is loaded, your web page needs to be hosted on a webserver to test it.
Luckily, a crate called devserver makes it easy to run a testing server on our local machine.
First, install devserver
:
cargo install devserver
Then, run it as follows:
devserver --path generated
Now, simply navigate to http://localhost:8080
to see your app in action!
(to use a different port, use the devserver --address
option)
All done!
In a future tutorial I’ll show how to build this same app for Windows, Mac, and Linux, with just a couple of simple modifications. The custom WindowHandler
we made will work without any modifications!
For another WebGL sample, see the following project on GitHub:
- WebGL Hello World example – demonstrates font rendering and input handling.
More information: