#2d-game-engine #canvas #game #2d-graphics #graphics-engine #graphics

bin+lib twors

A simple Rust 2D game engine based on canvas and WASM

5 releases (3 breaking)

Uses new Rust 2024

new 0.4.0 May 10, 2025
0.3.0 May 1, 2025
0.2.0 Apr 27, 2025
0.1.1 Apr 24, 2025
0.1.0 Apr 19, 2025

#140 in Game dev

Download history 140/week @ 2025-04-16 251/week @ 2025-04-23 133/week @ 2025-04-30

524 downloads per month

MIT license

60KB
1.5K SLoC

TwoRS

crates.io

Easily render 2D graphics in a canvas using WASM - entirely powered by Rust!!! 🦀

🙋 Who is this project for?

This tiny renderer is for people who:

  • Need an example on how setup WASM & interact with the canvas
  • Play around with Rust the fun way - by making a small 2D game

😎 Why Rust/WASM?

WASM's use case is either the reuse of code written in another language or to offload heavy computations to the near-native execution speeds of WASM.

However this is not free - traversing the WASM boundary means going trough some glue code and copying data from JS to the WASM module memory and back. In the case of strings it's even more expensive - because JS strings are UTF-16 and Rust strings use UTF-8 any string passtrough needs to go trough a copy AND a re-encode.

twors doesn't have any complicated physics to offload to WASM at this point in time - neither is it making use of some advanced pre-existing Rust library. So why Rust then?

Simply because Rust is the best!!!

⚡ Quick start

  • To quickly play with twors you can run the examples/playground crate in this repo
    • Move the player via the WASD keys (player can't exit the green area)
    • Spawn items at the players' location via the left mouse button
    • Drag spawned items via the right mouse button
git clone https://github.com/vimlucid/twors
cd twors

cargo install cargo-make
cargo make watch # this assumes you have the `cp` command - e.g. it won't work in Windows CMD

# edit the source code in examples/playground and manually refresh http://localhost:8080

✨ Features

TwoRS's goal is to be simple. It provides:

  • ✅ A main loop with precalculated delta time
  • ✅ Keyboard/mouse
  • ✅ Basic component system (not ECS)

TwoRS does not currently have (and aim) to provide:

  • a physics engine
  • an algorithm library (e.g. pathfinding)
  • a sound library
  • a UI library

🛠️ Installation and build

Compiling a WASM library is a bit more involved than simply executing cargo run, but it's pretty straightforward if you know the steps.

We'll basically:

  • set up our crate for WASM compilation
  • add the bare minimum code to run the twors engine
  • add an index.html with some JavaScript (just enough to run our WASM library)
  • serve all of this goodness via an HTTP server

Once we do this all of the remaining code can be written entirely in Rust!
You are of course free to mix and match as you like.

Step 1 - Create a lib crate

In order to compile Rust into WASM via wasm-pack (an amazing WASM compilation helper) it's necessary to have a lib crate first

cargo new twors-demo --lib

Step 2 - Install twors, wasm_bindgen and console_log

  • A browser-capable logging backend crate like console_log is needed to enable logging in the browser console
    • You will also need a logging frontend crate like log to be able to print messages yourself.
  • wasm_bindgen will generate the necessary JS/Rust glue to enhance what can go trough the WASM/JS boundary.
    • Without it our Rust code that's called from JS (and vice-versa) would be able to only take numbers as arguments and return numbers as a result. With this crate we enable a lot more types in our JS/WASM boundary API.
cargo add twors
cargo add wasm_bindgen
cargo add console_log

Step 3 - Create and run the twors engine.

  • For now we won't add any components, so we'll see a blank canvas when we open our application in the browser.
use twors::{Engine, Result};
use wasm_bindgen::prelude::wasm_bindgen;
use std::collections::HashMap;
use console_log;

// The "wasm_bindgen" attribute will generate glue both on the JS and on the WASM sides.
// Passing Rust types like `&str` from JS is thanks to the magic of `wasm_bindgen`.
#[wasm_bindgen]
pub fn entry(canvas_id: &str) -> Result<()> {
    console_log::init().unwrap(); // Setup logger frontends to use our browser-capable logger.

    // Pass a list of components to render on the canvas.
    // We'll add an index.html file with said canvas later.
    let engine = Engine::new(canvas_id, Vec::default())?;
    engine.run()?;

    Ok(())
}

Step 4 - Change the crate type to a dynamic library intended to be loaded from another language

Add a lib section to your Cargo.toml file (if you don't already have one) and set the crate-type property to cdylib.

The wasm32-unknown-unknown compile target (what wasm-pack makes the Rust compiler use) will detect this and produce a WASM library.

[lib]
crate-type = ["cdylib"]

Step 5 - Install wasm-pack and build your WASM library

The following wasm-pack --build command will produce a pkg folder in your crate's root - this folder will contain the WASM library as well as the JS part of the glue code that's needed for the WASM-JS communication.

cargo install wasm-pack
wasm-pack build --target web

Step 6 - Copy examples/assets/index.html to the pkg folder in your crate's root

We have compiled our WASM library - now we need to call it from JS. The example index.html will:

  • create a full screen canvas
  • run our Rust entry method (that we created earlier) by passing it the canvas ID.
# navigate to your crate root and execute the following
cp ./examples/assets/index.html ./pkg

NOTE: If you don't have the cp command (e.g. if you are using Windows CMD) you can always copy the index.html manually.

Step 7 - Serve the pkg directory at the crate root

You will need to serve the WASM library with the application/wasm MIME type or the browser will refuse to run it. A great server that does this automatically is miniserve

cargo intall miniserve
miniserve ./pkg --index index.html

💻 Development

Experiments and manual testing during development can be done in the examples/playground crate.

# convenience scripts - see "Makefile.toml" for full list of commands
cargo install cargo-make

# run local pre-commit checks - will be run on "build" automatically
cargo make install-git-hooks

cargo make build # build the "playground" crate as a WASM module
cargo make serve # like "build", but will also start a HTTP server
cargo make watch # like "serve", but will restart the server on changes

# other commands
cargo make test
cargo make clean
cargo make format
cargo make licenses # update licenses.html (run after dependency addition/removal)

Notes

  • Make sure to use wasm_assert! instead of assert! in non-test code to see error messages in the browser console.

Dependencies

~7.5–10MB
~181K SLoC