Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Game Development with Rust and WebAssembly
Game Development with Rust and WebAssembly

Game Development with Rust and WebAssembly: Learn how to run Rust on the web while building a game

eBook
₱1256.99 ₱1796.99
Paperback
₱2245.99
Subscription
Free Trial

What do you get with Print?

Product feature icon Instant access to your digital eBook copy whilst your Print order is Shipped
Product feature icon Colour book shipped to your preferred address
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Product feature icon AI Assistant (beta) to help accelerate your learning
Table of content icon View table of contents Preview book icon Preview Book

Game Development with Rust and WebAssembly

Chapter 1: Hello WebAssembly

Let's cut to the chase – if you're holding this book, you probably already know you love Rust, and you think WebAssembly is a great way to deploy your Rust programs to the web. Good news – you're right! Rust and WebAssembly are a match made in programmer heaven, and while WebAssembly is still in its early days, game development is an ideal candidate for WebAssembly. I am excited to be guiding you through building a game for the web in Stack Overflow's "most-loved" language, Rust.

This chapter is all about equipping yourself with the tools for the game development journey. In this chapter, we'll cover the following topics:

  • What is WebAssembly?
  • Creating a Rust and WebAssembly project skeleton
  • Translating JavaScript code into Rust code
  • Drawing to the screen with HTML5 Canvas

Technical requirements

To follow along with the project skeleton, you'll need to install rustup to install the Rust toolchains. This can be found at https://rustup.rs/. While you can install Rust and its various toolchains without using the rustup tool, it's not trivial, and I won't be documenting it here. You'll also need an editor for writing Rust code, and while you can use virtually any editor with rust-analyzer, if you're new to writing Rust, I'd recommend Visual Studio Code and the Rust extension found at https://bit.ly/3tAUyH2. It's easy to set up and works right out of the box.

Finally, you'll need a web browser, and in this chapter, you'll need some familiarity with the terminal and Node.js. If you get stumped, the code for this chapter is available at https://github.com/PacktPublishing/Game-Development-with-Rust-and-WebAssembly/tree/chapter_1. The final code for the entire book is in the main branch at https://github.com/PacktPublishing/Game-Development-with-Rust-and-WebAssembly.

Check out the following video to see the Code in Action: https://bit.ly/3qMV44E

What is WebAssembly?

You picked up this book (thanks!) so in all likelihood, you have some idea of what WebAssembly is, but just in case, let's grab a definition from https://WebAssembly.org:

"WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications."

In other words, Wasm is a binary format that we can compile other languages to so that we can run them in the browser. This is different than transpiling or source-to-source compiling, where languages such as TypeScript are converted into JavaScript for running in JavaScript environments. Those languages are still ultimately running JavaScript, whereas Wasm is bytecode. This makes it a smaller download and parsing and compiling steps are removed when running it, which can lead to significant performance improvements. But let's be honest – you're not using Rust and Wasm for the performance improvements, which aren't guaranteed anyway. You're using it because you like Rust.

And that's okay!

Rust has a great type system, excellent developer tooling, and a fantastic community. While WebAssembly was originally created with C and C++ in mind, Rust is a fantastic language for WebAssembly for all the reasons you love Rust and more. Now, for most of the web's existence, writing applications to run in a browser meant writing JavaScript, and over the years, JavaScript has evolved into a suitably modern language for that purpose. I'm not here to tell you that if you like JavaScript you should stop, but if you love Rust, you should absolutely start compiling to Wasm and running apps in the browser.

Important Note

This book is focused on making web-based games with Rust and Wasm, but you can absolutely run Wasm apps in server-side environments such as Node.js. If you're interested in that, you can check out the book Learn WebAssembly by Mike Rourke, which can be found at https://bit.ly/2N89prp, or the official wasm-bindgen guide at https://bit.ly/39WC63G.

Important Note

This book assumes some familiarity with Rust, although you do not need to be an expert. If at any time you're confused by a Rust concept, I highly encourage you to stop and check "the book", The Rust Programming Language, available for free at https://doc.rust-lang.org/book/.

So, now that I've convinced you to do what you were already going to do anyway, let's go over some of the tools you'll need to write a game for the web in Rust:

  • rustup: Most likely you're already using rustup if you're writing Rust code. If you're not, you should, as it's the standard way to install Rust. It allows for easy installations of toolchains, Rust compilers, and even launches the Rust documentation. You'll need it to install the Wasm toolchain, and you can install it from the previous link. The code in this book has been tested on Rust version 1.57.0.
  • Node.js: I know – I promised you that we'd be writing in Rust! We will, but this is still a web application and you'll be using Node.js to run the application. I recommend installing the current long-term support version (16.13.0 at the time of writing). Older versions of Node.js may not work with the package creation tools as expected. If you're using Ubuntu Linux, be especially cautious when using the Debian distribution, which installs a very old version at this time. When in doubt, use tools for managing multiple versions, such as the Node Version Manager (nvm) tool for Linux/Mac or the corresponding nvm-windows tool for Windows, to ensure that you're using the long-term release version. I use the asdf tool (https://asdf-vm.com/) for managing multiple versions myself, although I don't usually recommend it to people that haven't used a version management tool before.
  • webpack: We'll use webpack to bundle our application for release and run a development server. Most of the time, you won't have to worry about it, but it's there.

    Important Note

    The current template uses webpack 4. Make sure to check that when looking up documentation.

  • wasm-pack: This is a Rust tool for building Rust-generated WebAssembly code. Like webpack, most of the time you won't know it's there, as it's managed by webpack, and your Rust application will largely be managed by Rust build tools.
  • wasm-bindgen: This is one of the crates you'll need to get to know to write Rust-generated WebAssembly code. One limitation of WebAssembly is that you cannot access the Document Object Model (DOM) that represents a web page directly. Instead, WebAssembly programs need to call JavaScript functions to do that, requiring bindings and serializing data back and forth. What wasm-bindgen does is create those bindings and the boilerplate needed to call JavaScript functions from your Rust code, as well as provide tools to create bindings in the other direction so that JavaScript code can call back into the Rust code. We'll cover the details of how wasm-bindgen works as we go through the book, but to avoid getting bogged down in details right now, you can just think of it as a library to call JavaScript from your Rust code.
  • web-sys: This is a crate made up of many pre-generated bindings, using wasm-bindgen, for the web. We'll use web-sys to call browser APIs such as the canvas and requestAnimationFrame. This book assumes at least a passing familiarity with web development but doesn't require expertise in this area, and in fact, one of the advantages of game development in Rust is that we can just treat the browser as a platform library that we call functions on. The web-sys crate means we don't have to create all those bindings ourselves.
  • Canvas: HTML Canvas is a <canvas> browser element, such as headers or paragraphs, only it allows you to draw directly to it. This is how we can make a video game! There are many ways to draw to the canvas, including WebGL and WebGPU, but we're going to use the built-in Canvas API for most of this project. While this isn't the absolute fastest way of making a game, it's fast enough for learning purposes and avoids adding more technologies to our stack.

Finally, while googling web-sys, web-bindgen, or other Rust packages for WebAssembly, you are likely to come across references to cargo-web and stdweb. While both of those projects were important to the development of Rust as a WebAssembly source, neither has been updated since 2019 and can be safely ignored. Now that we know the tools we'll be using, let's start building our first Rust project.

A Rust project skeleton

Important Note

These directions are based on the status of rust-webpack-template at the time of writing. It's likely to have changed at the time of reading this, so pay close attention to the changes we are making. If they don't make sense, check the documents for wasm-pack and use your best judgment.

At this point, I'm going to assume you've installed rustup and Node.js. If you haven't, go ahead and follow the instructions for your platform to install them, and then follow these steps:

  1. Initialize the project

Let's start by creating a project skeleton for your application, which will be the Rust webpack Template from the Rust Wasm group. It's found on GitHub at https://github.com/rustwasm/rust-webpack-template, but you don't want to download it. Instead, use npm init to create it, like this:

mkdir walk-the-dog
cd walk-the-dog
npm init rust-webpack

You should see something like this:

npx: installed 17 in 1.941s
 🦀 Rust + 🕸 WebAssembly + Webpack = ❤
Installed dependencies ✅

Congratulations! You have created your project.

  1. Install dependencies

You can install the dependencies with npm:

npm install

Important Note

If you prefer to use yarn, you can, with the exception of the npm init command. I'll be using npm for this book.

  1. Run the server

After the installation completes, you can now run a development server with npm run start. You may see an error, like this:

ℹ  Installing wasm-pack
Error: Rust compilation.
at ChildProcess.<anonymous> (/walk-the-dog/node_modules/@wasm-tool/wasm-pack-plugin/plugin.js:221:16)
at ChildProcess.emit (events.js:315:20)
at maybeClose (internal/child_process.js:1048:16)
at Socket.<anonymous> (internal/child_process.js:439:11)
at Socket.emit (events.js:315:20)
at Pipe.<anonymous> (net.js:673:12)

If that happens, you'll need to install wasm-pack manually.

  1. Install wasm-pack

On Linux and macOS systems wasm-pack is installed with a simple cURL script:

curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh

Windows users have a separate installer that can be found at https://rustwasm.github.io.

  1. Run the server – take two

Now that wasm-pack is installed, webpack can use it, and you should be able to run the app:

npm run start

When you see wdm: Compiled successfully. , you can browse your app at http://localhost:8080. Okay, yes, it's a blank page, but if you open the developer tools console, you should see the following:

Figure 1.1 – Hello WebAssembly!

Figure 1.1 – Hello WebAssembly!

You've got the application running in the browser, but the Rust ecosystem updates faster than the template you used can keep up.

  1. Update the Rust edition

The latest Rust edition, with the most recent Rust idioms and conventions, is 2021. This is changed in the generated Cargo.toml file in the package section, as shown here:

# You must change these to your own details.
[package]
name = "rust-webpack-template"
description = "Walk the Dog - the game for the Rust Games with WebAssembly book"
version = "0.1.0"
authors = ["Eric Smith <paytonrules@gmail.com>"]
categories = ["wasm"]
readme = "README.md"
edition = "2021"

It is only the edition field that is changed here.

  1. Update the dependencies

The dependencies in the generated Cargo.toml file are not going to be the latest and greatest unless you happened to pull the template down the moment it was updated. Since neither of us is that lucky, you're going to want to open up that file and modify the dependencies to the following. Please note that the ellipses are just there to mark a gap in the file and are not meant to be typed in:

wasm-bindgen = "0.2.78"
...
[dependencies.web-sys]
version = "0.3.55"
...
[dev-dependencies]
wasm-bindgen-test = "0.3.28"
futures = "0.3.18"
js-sys = "0.3.55"
wasm-bindgen-futures = "0.4.28"

Those are the versions I used while writing this book. If you're feeling adventurous, you can go to http://crates.io and find the most recent version of each dependency, which is what I would do, but I am a glutton for punishment. You're probably smarter than me and will use the versions specified here so that the sample code works.

  1. Update console_error_panic_hook

console_error_panic_hook is a very useful crate during the development of a WebAssembly application. It takes panics in Rust code and forwards them to the console so that you can debug them. The current template attempts to hide it behind a feature flag, but unfortunately, there's a bug and it doesn't work. Remember to double-check your generated code; if it doesn't look like what I've reproduced here, the bug may have been fixed, but in the meantime, delete the following code (still in Cargo.toml).

[target."cfg(debug_assertions)".dependencies]
console_error_panic_hook = "0.1.5"

Then add the to the [dependencies] section, under wasm-bindgen is a good spot:

console_error_panic_hook = "0.1.7"

Later, we'll make this a conditional dependency so that you don't deploy it during release builds, but for now, this is enough progress. Who wants to continue messing with config files anyway? I want to draw stuff to the screen!

Tip

While this application uses an npm init template to create itself, you can use its output to create a cargo generate template so that you don't have to redo these changes every time you create an application, simply by creating a git repository. Of course, if you do that, you'll fall behind changes to the rust-webpack template, so it's a trade-off. If you're curious about using cargo generate to create your own templates, you can find more information here: https://bit.ly/3hCFWTs.

Drawing to the canvas

To write our game in Rust, we're going to need to draw to the screen, and for that, we'll use the HTML Canvas element using the 2D context. What the canvas provides is an API for drawing directly to the screen, without knowledge of WebGL or using an external tool. It's not the fastest technology in the world but it's perfectly suitable for our small game. Let's start converting our Rust app from "Hello World" to an application that draws a Sierpiński triangle.

Important Note

The Sierpiński triangle is a fractal image that is created by drawing a triangle, then subdividing that triangle into four triangles, and then subdividing those triangles into four triangles, and so on. It sounds complicated but, as with many fractals, is created from only a few lines of math:

  1. Add the canvas

Canvas is an HTML element that lets us draw to it freely, making it an ideal candidate for games. Indeed, at the time of writing, Adobe Flash is officially dead, and if you see a game on the internet, be it 2D or 3D, it's running in a canvas element. Canvas can use WebGL or WebGPU for games, and WebAssembly will work quite well with those technologies, but they are out of the scope of this book. We'll be using the built-in Canvas 2D API and its 2D context. This means you won't have to learn a shading language, and we'll be able to get images on the screen very quickly. It also means that if you need to, you can find excellent documentation on the Mozilla Developer Network (MDN) Web Docs website: https://mzl.la/3tX5qPC.

To draw to the canvas, we'll need to add it to the web page. Open up static/index.html and add underneath <body> tag <canvas id="canvas" tabindex="0" height="600" width="600">Your browser does not support the canvas.</canvas>. The width and height are pretty arbitrary but seem appropriate for now. The "Your browser does not support the canvas." message will show up on browsers that don't support HTML Canvas, but there aren't many of those anymore.

Important Note

Make sure you don't delete the <script> tag. That's running the JavaScript and WebAssembly you're building in this project!

  1. Clean up errors

Finally, we get to write some Rust code! Well, we get to delete some Rust code anyway. In the src/lib.rs file, you'll see a function named main_js() with the following code:

// This provides better error messages in debug mode.
// It's disabled in release mode so it doesn't bloat 
   up the file size.
    #[cfg(debug_assertions)]
    console_error_panic_hook::set_once();

You can go ahead and remove the comments and the [cfg(debug_annotations)] annotation. For the time being, we'll leave that running in our build and will remove it when preparing for production with a feature flag.

Important Note

If you're seeing an error in your editor that says the console::log_1(&JsValue::from_str("Hello world!")) code is missing an unsafe block, don't worry – that error is wrong. Unfortunately, it's a bug in rust-analyzer that's been addressed in this issue: https://bit.ly/3BbQ39m. You'll see this error with anything that uses procedural macros under the hood. If you're using an editor that supports experimental settings, you may be able to fix the problem; check the rust-analyzer.experimental.procAttrMacros setting. When in doubt, check the output from npm run start, as that is the more accurate source for compiler errors.

Tip

If you diverge from this book and decide to deploy, go to Chapter 10, Continuous Deployment, and learn how to hide that feature behind a feature flag in release mode, so you don't deploy code you don't need into production.

Removing that code will remove the warning: Found 'debug_assertions' in 'target.'cfg(...)'.dependencies'. message on startup of the app. At this point, you may have noticed that I'm not telling you to restart the server after changes, and that's because npm start runs the webpack-dev-server, which automatically detects changes and then rebuilds and refreshes the app. Unless you're changing the webpack config, you shouldn't have to restart.

The current code

Up to now, I've been telling you what to do, and you've been blindly doing it because you're following along like a good reader. That's very diligent of you, if a little trusting, and it's time to take a look at the current source and see just what we have in our WebAssembly library. First, let's start with the use directives.

use wasm_bindgen::prelude::*;
use web_sys::console;

The first import is the prelude for wasm_bindgen. This brings in the macros you'll see shortly, and a couple of types that are pretty necessary for writing Rust for the web. Fortunately, it's not a lot, and shouldn't pollute the namespace too much.

Important Note

"Pollute the namespace" refers to what can happen when you use the '*' syntax and import everything from a given module. If the module has a lot of exported names, you have now those same names in your project, and they aren't obvious when you're coding. If, for instance, wasm_bindgen::prelude had a function named add in it and you also had a function named add in your namespace, they would collide. You can work around this by using explicit namespaces when you call the functions, but then why use * in the first place? By convention, many Rust packages have a module named prelude, which can be imported via * for ease of use; other modules should be imported with their full name.

The other import is web_sys::console, which brings in the console namespace from web_sys, which in turn mimics the console namespace in JavaScript. This is a good time to talk a little more in detail about what these two modules do. I've said it before but it probably bears repeating – wasm_bindgen provides the capability to bind JavaScript functions so you can call them in WebAssembly and to expose your WebAssembly functions to JavaScript. There's that language again, the one we're trying to avoid by writing Rust, but it can't be avoided because we're working in a browser.

In fact, one of the limitations of WebAssembly is that it cannot manipulate the DOM, which is a fancy way of saying that it can't change the web page. What it can do is call functions in JavaScript, which in turn do that work. In addition, JavaScript knows nothing about your WebAssembly types, so any data that is passed to a JavaScript object is marshaled into shared memory and then pulled back out by JavaScript so that it can turn it into something it understands. This is a LOT of code to write over and over again, and that is what the wasm-bindgen crate does for you. Later, we'll use it to bind our own custom bindings to third-party JavaScript code, but what about all the functions already built into the browser, such as console.log? That's where web-sys comes in. It uses wasm-bindgen to bind to all the functions in the browser environment so that you don't have to manually specify them. Think of it as a helper crate that says, "Yeah, I know you'll need all these functions so I created them for you."

So, to sum up, wasm-bindgen gives you the capability to communicate between WebAssembly and JavaScript, and web-sys contains a large number of pre-created bindings. If you're particularly interested in how the calls between WebAssembly and JavaScript work, check out this article by Lin Clark, which explains it in great detail, and with pictures: https://hacks.mozilla.org/2018/10/calls-between-javascript-and-webassembly-are-finally-fast-%F0%9F%8E%89/.

The wee allocator

After the use statements you'll see a comment block referring to the `wee_alloc` feature, which is a WebAssembly allocator that uses much less memory than the default Rust allocator. We're not using it, and it was disabled in the Cargo.toml file, so you can delete it from both the source code and Cargo.toml.

The main

Finally, we get to the main part of our program:

#[wasm_bindgen(start)]
pub fn main_js() -> Result<(), JsValue> {

The wasm_bindgen(start) annotation exports main_js so that it can be called by JavaScript, and the start parameter identifies it as the starting point of the program. If you're curious, you can take a look at pkg/index_bg.wasm.d.ts to see what was generated by it. You'll also want to take note of the return value, Result, where the error type can be JsValue, which represents an object owned by JavaScript and not Rust.

At this point, you may start to wonder how you'll keep track of what's JavaScript and what's Rust, and I'd advise you to not worry too much about it right now. There's a lot of jargon popping up and there's no way you'll keep it all in your head; just let it swim around in there and when it comes up again, I'll explain it again. JsValue is just a representative JavaScript object in your Rust code.

Finally, let's look at the contents:

console_error_panic_hook::set_once();
// Your code goes here!
console::log_1(&JsValue::from_str("Hello world!"));
Ok(())

The first line sets the panic hook, which just means that any panics will be redirected to the web browser's console. You'll need it for debugging, and it's best to keep it at the beginning of the program. Our one line, our Hello World, is console::log_1(&JsValue::from_str("Hello world!"));. That calls the JavaScript console.log function, but it's using the version that's log_1 because the JavaScript version takes varying parameters. This is something that's going to come up again and again when using web-sys, which is that JavaScript supports varargs and Rust doesn't. So instead, many variations are created in the web-sys module to match the alternatives. If a JavaScript function you expect doesn't exist, then take a look at the Rust documents for web-sys (https://bit.ly/2NlRmOI) and see whether there are versions that are similar but built to account for multiple parameters.

Tip

A series of macros for several of the more commonly used functions (such as log) could solve this problem, but that's an exercise for the reader.

Finally, the function returns Ok(()), as is typical of Rust programs. Now that we've seen the generated code, let's break it down with our own.

Drawing a triangle

We've spent a lot of time digging into the code we currently have, and it's a lot to just write "Hello World" to the console. Why don't we have some fun and actually draw to the canvas?

What we're going to do is mimic the following JavaScript code in Rust:

canvas = window.document.getElementById("canvas")
context = canvas.getContext("2d")
context.moveTo(300, 0)
context.beginPath()
context.lineTo(0, 600)
context.lineTo(600, 600)
context.lineTo(300, 0)
context.closePath()
context.stroke()
context.fill()

This code grabs the canvas element we put in index.html, grabs its 2D context, and then draws a black triangle. One way to draw a shape on the context is to draw a line path, then stroke, and, in this case, fill it. You can actually see this in the browser using the web developer tools built into most browsers. This screenshot is from Firefox:

Figure 1.2 – A simple canvas triangle

Figure 1.2 – A simple canvas triangle

Let's do the same thing in our Rust program. You'll see that it's a little…different. Start with the quick addition of a use statement at the top:

use wasm_bindgen::JsCast;

Then, replace the existing main_js function with the following:

console_error_panic_hook::set_once();
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let canvas = document
    .get_element_by_id("canvas")
    .unwrap()
    .dyn_into::<web_sys::HtmlCanvasElement>()
    .unwrap();
let context = canvas
    .get_context("2d")
    .unwrap()
    .unwrap()
    .dyn_into::<web_sys::CanvasRenderingContext2d>()
    .unwrap();
context.move_to(300.0, 0.0); // top of triangle
context.begin_path();
context.line_to(0.0, 600.0); // bottom left of triangle
context.line_to(600.0, 600.0); // bottom right of triangle
context.line_to(300.0, 0.0); // back to top of triangle
context.close_path();
context.stroke();
context.fill();
Ok(())

There are a few differences that stand out, but at a glance, you may just feel like Rust code is a lot noisier than JavaScript code, and that's true. You might be inclined to say that it's less elegant or isn't as clean, but I'd say that's in the eye of the beholder. JavaScript is a dynamically typed language and it shows. It ignores undefined and null, and can just crash if any of the values are not present. It uses duck typing to call all the functions on the context, which means that if the function is present, it simply calls it; otherwise, it throws exceptions.

Rust code takes a very different approach, one that favors explicitness and safety but at the cost of the code having extra noise. In Rust, you have to be more explicit when calling methods on structs, hence the casting, and you have to acknowledge null or failed Result types, hence all the unwraps. I've spent years using dynamic languages, including JavaScript, and I like them a lot. I certainly liked them a lot better than writing in C++, which I find overly verbose without really granting some of the safety advantages, but I think that with some tweaks, we can make Rust code nearly as elegant as JavaScript without glossing over exceptions and results.

My rant aside, if you're still running the program, you'll notice one minor detail – the Rust code doesn't compile! This leads me to the first thing we'll need to cover when translating JavaScript code to Rust code.

web-sys and feature flags

The web-sys crate makes heavy use of feature flags to keep its size down. This means that every time you want to use a function and it doesn't exist, you'll need to check which feature flag it's tied to, which is in its documentation, and add it to Cargo.toml. Fortunately, this is well documented and easy enough to do; we don't even need to restart the server!

Looking at our errors, we should see the following:

error[E0425]: cannot find function 'window' in crate 'web_sys'
--> src/lib.rs:18:27
|
18 | let window = web_sys::window().unwrap();
|                           ^^^^^^ not found in 'web_sys'

There are a few more errors of the same kind, but what you see here is that window is not in the web_sys module. Now, if you check the documentation for the window function in web-sys at https://bit.ly/3ak3sAR, you'll see that, yes, it does exist, but there is the This API requires the following crate features to be activated: Window message.

Open the cargo.toml file and look for dependencies.web-sys. You'll see that it has a features entry with just ["console"] in it; go ahead and add "Window", "Document", "HtmlCanvasElement", "CanvasRenderingContext2d", and "Element" to that list. To be clear, you don't need all those feature flags for just the window function; that's all of the functions we're using.

You'll notice the project will rebuild automatically and should build successfully. If you look in the browser, you'll see your own black triangle! Let's extend it and learn a bit more about how we did it.

Tip

When a function you expect to exist on web-sys doesn't, go and check the feature flags in the documents.

DOM interaction

You'll notice that the method for drawing the triangle after you get the context looks essentially the same as the method in JavaScript – draw a line path, stroke, and fill it. The code at the top that interacted with the DOM looks…different. Let's break down what's going on here:

  • Unwrapping option

Getting the Window is just a function in the web-sys crate, one you enabled when you added the Window feature to Cargo.toml. However, you'll notice it's got unwrap at the end:

let window = web_sys::window().unwrap();

In JavaScript, window can be null or undefined, at least theoretically, and in Rust, this gets translated into Option<Window>. You can see that unwrap is applied to the result of window(), document(), and get_element_by_id() because all of them return Option<T>.

  • dyn_into

What the heck is dyn_into? Well, this oddity accounts for the difference between the way JavaScript does typing and the way Rust does. When we retrieve the canvas with get_element_by_id, it returns Option<Element>, and Element does not have any functions relating to the canvas. In JavaScript, you can use dynamic typing to assume the element has the get_context method, and if you're wrong, the program will throw an exception. This is anathema to Rust; indeed, this is a case where one developer's convenience is another developer's potential bug in hiding, so in order to use Element, we have to call the dyn_into function to cast it into HtmlCanvasElement. This method was brought into scope with the `use wasm_bindgen::JsCast` declaration.

Important Note

Note that HtmlCanvasElement, Document, and Element were all also feature flags you had to add in web-sys.

  • Two unwraps?

After calling get_context("2d"), we actually call unwrap twice; that's not a typo. What's going on is that get_context returns a Result<Option<Object>>, so we unwrap it twice. This is another case where the game can't recover if this fails, so unwrap is okay, but I wouldn't complain if you replaced those with expect so that you can give a clearer error message.

A Sierpiński triangle

Now let's have some real fun, and draw a Sierpiński triangle a few levels deep. If you're up for a challenge, you can try and write the code yourself before following along with the solution presented here. The way the algorithm works is to draw the first triangle (the one you are already drawing) and then draw another three triangles, where the first triangle has the same top point but its other two points are at the halfway point on each side of the original triangle. Then, draw a second triangle on the lower left, with its top at the halfway point of the left side, its lower-right point at the halfway point of the bottom of the original triangle, and its lower-left point at the lower-left point of the original triangle. Finally, you create a third triangle in the lower-right corner of the original triangle. This leaves a "hole" in the middle shaped like an upside-down triangle. This is much easier to visualize than it is to explain, so how about a picture?

Figure 1.3 – A one-level Sierpiński triangle

Figure 1.3 – A one-level Sierpiński triangle

Each of the numbered triangles was one that was drawn. The upside-down blue triangle is what's left of the original triangle because we didn't draw over it.

So that's one triangle subdivided into four. Now, the algorithm works recursively, taking each triangle and subdividing again. So, two levels deep, it looks like this:

Figure 1.4 – A two-level Sierpiński triangle

Figure 1.4 – A two-level Sierpiński triangle

Note that it doesn't subdivide the upside-down triangle in the center, just the three purple ones that you created. Indeed, all the triangles with their points down are just "happy accidents" that make the shape look cool. You now know enough at this point to draw your own Sierpiński triangle, with one catch – you should remove the fill statement on context. Otherwise, all the triangles will be filled black and you won't be able to see them. Go ahead and give it a try.

Drawing the Sierpiński triangle

So, did you give it a try? No, I wouldn't either; I guess we have a lot in common. To get started with creating a Sierpiński triangle, let's replace the hardcoded triangle with a triangle function. Here's the first pass at draw_triangle:

fn draw_triangle(context: &web_sys::CanvasRenderingContext2d,     points: [(f64, f64); 3]) {
        let [top, left, right] = points;
        context.move_to(top.0, top.1);
        context.begin_path();
        context.line_to(left.0, left.1);
        context.line_to(right.0, right.1);
        context.line_to(top.0, top.1);
        context.close_path();
        context.stroke();
}

There are a couple of small changes from the hard-coded version that we started with. The function takes a reference to the context and a list of three points. Points themselves are represented by tuples. We've also gotten rid of the fill function, so we only have an empty triangle. Replace the inline draw_triangle with the function call, which should look like this:

let context = canvas
    .get_context("2d")
    .unwrap()
    .unwrap()
    .dyn_into::<web_sys::CanvasRenderingContext2d>()
    .unwrap();
draw_triangle(&context, [(300.0, 0.0), (0.0, 600.0), (600.0, 600.0)]);

Now that you're drawing one empty triangle, you're ready to start drawing the recursive triangles. Rather than starting with recursion, let's draw the first subdivision by drawing three more triangles. The first will have the same top point and two side points:

draw_triangle(&context, [(300.0, 0.0), (150.00, 300.0), (450.0, 300.0)]);

Note that the third tuple has an x halfway between 300.0 and 600.0, not between 0 and 600.0, because the top point of the triangle is halfway between the other two points. Also note that y gets larger as you go down, which is upside-down compared to many 3D systems. Now, let's add the lower-left and lower-right triangles:

draw_triangle(&context, [(150.0, 300.0), (0.0, 600.0), (300.0, 600.0)]);
draw_triangle(&context, [(450.0, 300.0), (300.0, 600.0), (600.0, 600.0)]);

Your triangles should look like this:

Figure 1.5 – Your triangles

Figure 1.5 – Your triangles

You will start to see a pattern at this point, and we can begin to turn our hardcoded triangles into an algorithm. We'll create a function called sierpinski that takes the context, the triangle dimensions, and a depth function so that we only draw as many triangles as we want, instead of drawing them to infinity and crashing the browser. Then, we'll move those functions we called into that function:

fn sierpinski(context: &web_sys::CanvasRenderingContext2d, points: [(f64, f64); 3], depth: u8) {
    draw_triangle(&context, [(300.0, 0.0), (0.0, 600.0), 
     (600.0, 600.0)]);
    draw_triangle(&context, [(300.0, 0.0), (150.00, 300.0), 
     (450.0, 300.0)]);
    draw_triangle(&context, [(150.0, 300.0), (0.0, 600.0), 
     (300.0, 600.0)]);
    draw_triangle(&context, [(450.0, 300.0), (300.0, 
     600.0), (600.0, 600.0)]);
}

This function currently ignores everything except the context, but you can replace those four draw_triangle calls from main_js and replace them with a call to sierpinski:

sierpinski(&context, [(300.0, 0.0), (0.0, 600.0), (600.0, 600.0)], 2);

It's important that you only send a depth of 2 for now so that the image will continue to look the same as we progress. Think of this call as a proto-unit test, guaranteeing our behavior doesn't change while we refactor. Now, in sierpinski, take the first triangle and have it use the passed-in points:

fn sierpinski(context: &web_sys::CanvasRenderingContext2d, points: [(f64, f64); 3], depth: u8) {
    draw_triangle(&context, points);
    ...

Then, after drawing the triangle, reduce the depth by one and see if it is still greater than 0. Then, draw the rest of the triangles:

...
let depth = depth - 1;
if depth > 0 {
    draw_triangle(&context, [(300.0, 0.0), (150.00, 300.0), 
     (450.0, 300.0)]);
    draw_triangle(&context, [(150.0, 300.0), (0.0, 600.0), 
     (300.0, 600.0)]);
    draw_triangle(&context, [(450.0, 300.0), (300.0, 
     600.0), (600.0, 600.0)]);
}

Now, to complete the recursion, you can replace all those draw_triangle calls with calls into sierpinski:

if depth > 0 {
    sierpinski(
        &context,
        [(300.0, 0.0), (150.00, 300.0), (450.0, 300.0)],
        depth,
    );
    sierpinski(
        &context,
        [(150.0, 300.0), (0.0, 600.0), (300.0, 600.0)],
        depth,
    );
    sierpinski(
        &context,
        [(450.0, 300.0), (300.0, 600.0), (600.0, 600.0)],
        depth,
    );
    }

So far so good – you should still see a triangle subdivided into four triangles. Finally, we can actually calculate the midpoints of each line on the original triangle and use those to create the recursive triangles, instead of hardcoding them:

let [top, left, right] = points;
if depth > 0 {
    let left_middle = ((top.0 + left.0) / 2.0, (top.1 + 
     left.1) / 2.0);
    let right_middle = ((top.0 + right.0) / 2.0, (top.1 + 
     right.1) / 2.0);
    let bottom_middle = (top.0, right.1);
    sierpinski(&context, [top, left_middle, right_middle], 
     depth);
    sierpinski(&context, [left_middle, left, 
     bottom_middle], depth);
    sierpinski(&context, [right_middle, bottom_middle, 
     right], depth);
}

Calculating the midpoint of a line segment is done by taking the x and y coordinates of each end, adding those together, and then dividing them by two. While the preceding code works, let's make it clearer by writing a new function, as shown here:

fn midpoint(point_1: (f64, f64), point_2: (f64, f64)) -> (f64, f64) {
    ((point_1.0 + point_2.0) / 2.0, (point_1.1 + point_2.1) 
    / 2.0)
}

Now, we can use that in the preceding function, for clarity:

if depth > 0 {
    let left_middle = midpoint(top, left);
    let right_middle = midpoint(top, right);
    let bottom_middle = midpoint(left, right);
    sierpinski(&context, [top, left_middle, right_middle], 
     depth);
    sierpinski(&context, [left_middle, left, 
     bottom_middle], depth);
    sierpinski(&context, [right_middle, bottom_middle, 
     right], depth);
}

If you've been following along, you should make sure you're still showing a triangle with four inside to ensure you haven't made any mistakes. Now for the big reveal – go ahead and change the depth to 5 in the original Sierpinski call:

sierpinski(&context, [(300.0, 0.0), (0.0, 600.0), (600.0, 600.0)], 5);

You should see a recursive drawing of triangles, like this:

Figure 1.6 – A recursive drawing of triangles

Figure 1.6 – A recursive drawing of triangles

Looking good! But what about those colors we saw in the original diagrams? They make it much more interesting.

When libraries aren't compatible

The earlier examples of this image had the triangles filled in with a different random color at each recursive layer. So, the first triangle was one color, three and four were another, the next nine another, and so on. It makes for a more interesting image and it provides a good example of what to do when a library isn't completely WebAssembly-compatible.

To create a random color, we'll need a random number generator, and that is not part of the standard library but instead found in a crate. You can add that crate by changing the Cargo.toml file to include it as a dependency:

console_error_panic_hook = "0.1.7"
rand = "0.8.4"

When you do this, you'll get a compiler error that looks like the following (although your message may differ slightly):

error: target is not supported, for more information see: https://docs.rs/getrandom/#unsupported-targets
   --> /usr/local/cargo/registry/src/github.com-
   1ecc6299db9ec823/getrandom-0.2.2/src/lib.rs:213:9
    |
213 | /         compile_error!("target is not supported, for more information see: \
214 | |                         https://docs.rs/getrandom/#unsupported-targets");

This is a case where a transitive dependency, in this case getrandom, does not compile on the WebAssembly target. In this case, it's an extremely helpful error message, and if you follow the link, you'll get the solution in the documentation. Specifically, you need to enable js in the feature flags for getrandom. Go back to your Cargo.toml file and add the following:

getrandom = { version = "0.2.3", features = ["js"] }

This adds the getrandom dependency with the js feature enabled, and your code will begin compiling again. The lesson to take away from this is that not every Rust crate will compile on the WebAssembly target, and when that happens, you'll need to check the documents.

Tip

When a crate won't compile slowly, read the error message and follow the instructions. It's very easy to skim right over the reason the build is breaking, especially when you're frustrated.

Random colors

Now that we've got the random create building with our project, let's change the color of the triangles as we draw them to a random color. To do that, we'll set fillStyle with a color before we draw the triangle, and we'll add a fill command. This is, generally, how the Context2D API works. You set up the state of the context and then execute commands with that state set. It takes a little getting used to but you'll get the hang of it. Let's add color as a parameter of the three u8 tuples to draw_triangle:

fn draw_triangle(
    context: &web_sys::CanvasRenderingContext2d,
    points: [(f64, f64); 3],
    color: (u8, u8, u8),
) {

Important Note

Colors are represented here as three components, red, green, and blue, where each value can go from 0 to 255. We're using tuples in this chapter because we can make progress quickly, but if it's starting to bother you, you're welcome to make proper structs.

Now that draw_triangle needs a color, our application doesn't compile. Let's move to the sierpinski function and pass a color to it as well. We're going to send the color to the sierpinski function, instead of generating it there, so that we can get one color at every level. The first generation will be one solid color, then the second will all be one color, and then the third a third color, and so on. So let's add that:

fn sierpinski(
    context: &web_sys::CanvasRenderingContext2d,
    points: [(f64, f64); 3],
    color: (u8, u8, u8),
    depth: u8,
) {
    draw_triangle(&context, points, color);
    let depth = depth - 1;
    let [top, left, right] = points;
    if depth > 0 {
        let left_middle = midpoint(top, left);
        let right_middle = midpoint(top, right);
        let bottom_middle = midpoint(left, right);
        sierpinski(&context, [top, left_middle, 
         right_middle], color, depth);
        sierpinski(&context, [left_middle, left, 
         bottom_middle], color, depth);
        sierpinski(&context, [right_middle, bottom_middle, 
         right], color, depth);
    }
}

I put color as the third parameter and not the fourth because I think it looks better that way. Remember to pass color to the other calls. Finally, so that we can compile, we'll send a color to the initial sierpinski call:

sierpinski(
    &context,
    [(300.0, 0.0), (0.0, 600.0), (600.0, 600.0)],
    (0, 255, 0),
    5,
);

Since this is an RGB color, (0, 255, 0) represents green. Now, we've made our code compile, but it doesn't do anything, so let's work back downward from the original call and into the sierpinski function again. Instead of just passing the color through, let's create a new tuple that has a random number for each component. You'll need to add use rand::prelude::*; to the use declarations at the top. Then, add the following code to the sierpinski function, after the if depth > 0 check:

let mut rng = thread_rng();
let next_color = (
    rng.gen_range(0..255),
    rng.gen_range(0..255),
    rng.gen_range(0..255),
);
...
sierpinski(
    &context,
    top, left_middle, right_middle],
    next_color,
    depth,
);
sierpinski(
    &context,
    [left_middle, left, bottom_middle],
    next_color,
    depth,
);
sierpinski(
    &context,
    [right_middle, bottom_middle, right],
    next_color,
    depth,
);

Inside the depth check, we randomly generate next_color and then pass it along to all the recursive sierpinski calls. But of course, our output still doesn't look any different. We never changed draw_triangle to change the color! This is going to be a little weird because the context.fillStyle property takes DOMString in JavaScript, so we'll need to do a conversion. At the top of draw_triangle, add two lines:

let color_str = format!("rgb({}, {}, {})", color.0, color.1, color.2);
context.set_fill_style(&wasm_bindgen::JsValue::from_str(&color_str));

On line one, we convert our tuple of three unsigned integers to a string reading "rgb(255, 0, 255)", which is what the fillStyle property expects. On the second line, we use set_fill_style to set it, doing that funky conversion. There are two things that you need to understand with this function. The first is that, generally, JavaScript properties are just public and you set them, but web-sys generates getter and setter functions. The second is that these generated functions frequently take JsValue objects, which represent an object owned by JavaScript. Fortunately, wasm_bindgen has factory functions for these, so we can create them easily and use the compiler as our guide.

Tip

Whenever you translate from JavaScript code to Rust, make sure that you check the documentation of the corresponding functions to see what types are needed. Passing a string to JavaScript isn't always as simple as you might think.

Finally, we actually need to fill the triangles to see those colors, so after context.stroke(), you need to restore that context.fill() method you deleted earlier, and ta-da!

Figure 1.7 – Filled triangles

Figure 1.7 – Filled triangles

You've done it, and you're ready to start creating a real game.

Summary

In this chapter, we've done a lot. We've written our first WebAssembly app using Rust, moving from "Hello World" to drawing in the browser with HTML Canvas. You've added crates, run a development server, and interacted with the DOM. You've learned a lot about interacting with the browser, including the following:

  • Creating the main entry point with #[wasm_bindgen(start)]
  • Translating JavaScript code to Rust code
  • Dealing with crates that compile to JavaScript

You've also been introduced to HTML Canvas. Frankly, it's been a bit of a whirlwind, so don't worry if some information flew over your head, as we'll cover many of these topics again – including in the next chapter, where we'll start drawing sprites.

Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Build and deploy an endless runner game for the web from scratch through this helpful guide with key images printed in color
  • Learn how to use Rust for web development with WebAssembly
  • Explore modern game development and programming techniques to build 2D games using Rust

Description

The Rust programming language has held the most-loved technology ranking on Stack Overflow for 6 years running, while JavaScript has been the most-used programming language for 9 years straight as it runs on every web browser. Now, thanks to WebAssembly (or Wasm), you can use the language you love on the platform that's everywhere. This book is an easy-to-follow reference to help you develop your own games, teaching you all about game development and how to create an endless runner from scratch. You'll begin by drawing simple graphics in the browser window, and then learn how to move the main character across the screen. You'll also create a game loop, a renderer, and more, all written entirely in Rust. After getting simple shapes onto the screen, you'll scale the challenge by adding sprites, sounds, and user input. As you advance, you'll discover how to implement a procedurally generated world. Finally, you'll learn how to keep your Rust code clean and organized so you can continue to implement new features and deploy your app on the web. By the end of this Rust programming book, you'll build a 2D game in Rust, deploy it to the web, and be confident enough to start building your own games.

Who is this book for?

This game development book is for developers interested in Rust who want to create and deploy 2D games to the web. Game developers looking to build a game on the web platform using WebAssembly without C++ programming or web developers who want to explore WebAssembly along with JavaScript web will also find this book useful. The book will also help Rust developers who want to move from the server side to the client side by familiarizing them with the WebAssembly toolchain. Basic knowledge of Rust programming is assumed.

What you will learn

  • Build and deploy a Rust application to the web using WebAssembly
  • Use wasm-bindgen and the Canvas API to draw real-time graphics
  • Write a game loop and take keyboard input for dynamic action
  • Explore collision detection and create a dynamic character that can jump on and off platforms and fall down holes
  • Manage animations using state machines
  • Generate levels procedurally for an endless runner
  • Load and display sprites and sprite sheets for animations
  • Test, refactor, and keep your code clean and maintainable
Estimated delivery fee Deliver to Philippines

Standard delivery 10 - 13 business days

₱492.95

Premium delivery 5 - 8 business days

₱2548.95
(Includes tracking information)

Product Details

Country selected
Publication date, Length, Edition, Language, ISBN-13
Publication date : Apr 29, 2022
Length: 476 pages
Edition : 1st
Language : English
ISBN-13 : 9781801070973
Languages :

What do you get with Print?

Product feature icon Instant access to your digital eBook copy whilst your Print order is Shipped
Product feature icon Colour book shipped to your preferred address
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Product feature icon AI Assistant (beta) to help accelerate your learning
Estimated delivery fee Deliver to Philippines

Standard delivery 10 - 13 business days

₱492.95

Premium delivery 5 - 8 business days

₱2548.95
(Includes tracking information)

Product Details

Publication date : Apr 29, 2022
Length: 476 pages
Edition : 1st
Language : English
ISBN-13 : 9781801070973
Languages :

Packt Subscriptions

See our plans and pricing
Modal Close icon
$19.99 billed monthly
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Simple pricing, no contract
$199.99 billed annually
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just ₱260 each
Feature tick icon Exclusive print discounts
$279.99 billed in 18 months
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just ₱260 each
Feature tick icon Exclusive print discounts

Frequently bought together


Stars icon
Total 6,481.97
Practical WebAssembly
₱2653.99
Game Development with Rust and WebAssembly
₱2245.99
Rust Web Development with Rocket
₱1581.99
Total 6,481.97 Stars icon

Table of Contents

15 Chapters
Part 1: Getting Started with Rust, WebAssembly, and Game Development Chevron down icon Chevron up icon
Chapter 1: Hello WebAssembly Chevron down icon Chevron up icon
Chapter 2: Drawing Sprites Chevron down icon Chevron up icon
Part 2: Writing Your Endless Runner Chevron down icon Chevron up icon
Chapter 3: Creating a Game Loop Chevron down icon Chevron up icon
Chapter 4: Managing Animations with State Machines Chevron down icon Chevron up icon
Chapter 5: Collision Detection Chevron down icon Chevron up icon
Chapter 6: Creating an Endless Runner Chevron down icon Chevron up icon
Chapter 7: Sound Effects and Music Chevron down icon Chevron up icon
Chapter 8: Adding a UI Chevron down icon Chevron up icon
Part 3: Testing and Advanced Tricks Chevron down icon Chevron up icon
Chapter 9: Testing, Debugging, and Performance Chevron down icon Chevron up icon
Chapter 10: Continuous Deployment Chevron down icon Chevron up icon
Chapter 11: Further Resources and What's Next? Chevron down icon Chevron up icon
Other Books You May Enjoy Chevron down icon Chevron up icon

Customer reviews

Most Recent
Rating distribution
Full star icon Full star icon Full star icon Half star icon Empty star icon 3.3
(6 Ratings)
5 star 33.3%
4 star 33.3%
3 star 0%
2 star 0%
1 star 33.3%
Filter icon Filter
Most Recent

Filter reviews by




Bryan Jul 02, 2023
Full star icon Empty star icon Empty star icon Empty star icon Empty star icon 1
This book starts at chapter 3. There is no introduction, no table of contents … nothing; literally the first page of the book is the introduction to chapter 3; which references the foundations laid in previous chapters.I don’t know if this is a printing error, but the book is, quite literally, incomplete.
Amazon Verified review Amazon
Amazon Customer Jan 10, 2023
Full star icon Empty star icon Empty star icon Empty star icon Empty star icon 1
There are no instructions on installing the required toolchain - which leads to immediate failures running the npm commands. I am not confident that the rest of the book is worth investing time in if this is how it STARTS.
Amazon Verified review Amazon
jorjun Jul 29, 2022
Full star icon Full star icon Full star icon Full star icon Empty star icon 4
Javascript is not rigorous with respect to types and function return results, Rust certainly is. Therefore in interface between the two - needed until major native API work is completed - much of the labour in producing web assembly with Rust is being explicit when javascript is using magic. And to to this requires deep system knowledge that detracts from the game development objective. But super pleased that this book exists and it is a pioneering work that deserves celebration.
Amazon Verified review Amazon
Tiny May 04, 2022
Full star icon Full star icon Full star icon Full star icon Full star icon 5
Have you ever played a game offered through a website, and thought, I could do better? Have you looked at the mechanics and wondered how everything was accomplished? In either case, this is the book for you. Erik Smith does a great job of offering a step-by-step solution to use Rust and Web Assembly to write your own 2D platformers. This is for those who have a basic knowledge of Rust, and web design, and looking for a chance to improve those skills. He steps from basic knowledge, through implementing graphics and endless runners, to considering those special skills one needs to move to continuous deployment and have millions play your game. Millions might be a stretch but this text certainly offers all the basic tools for success. The first section deals with implementing your own Rust instantiation, using WebAssembly to convert and compile files. He works through the basics of drawing through constructing your own images and then using libraries to sequence those images in the game. After all, you don’t want a static construct but one that alternates between various steps as movement occurs. The drawing tools occur at the basic level but teach you how to integrate those into sheets and use them multiple times. The next section is where the magic happens. Smith details the easy steps to building collision, and collision avoidance through using rectangles so game objects can interact with each other and create results. The basic steps of object and character then build into integrating backgrounds, making systems work in the background, and building the endless runner so your game doesn’t have to stop. Step by step rust code is included, with clear GitHub links to check your code. In addition, he steps through the early process and identifies potential errors to help remove smells from your code. Finally, the text builds into those advanced steps, using Rust to test and debug the game so overall performance doesn’t suffer. He offers tips on how to make your personal code in a continuous deployment manner so all those new improvements make it direct to your customer. Finally, he offers some advanced steps to move beyond the code in the book, and improve the sample game with clear challenges. This was a great book, clearly written and well demonstrated at every step. The code was usable, the awareness of error messages helped alleviate common problems, and the GitHub solutions offer a sneak peek for when you get stuck. I recommend the book to anyone interested in creating their own 2D platformer, or in simply learning more about Rust applications.
Amazon Verified review Amazon
POE Apr 29, 2022
Full star icon Full star icon Full star icon Full star icon Full star icon 5
As a seasoned game developer, this title intrigued me. After reading the book, my skepticism waned. The author does a great job of showing readers how to create 2D games with Rust and WebAssembly. The book does assume readers are at least somewhat familiar with Rust. If you are not, I recommend reviewing the rust-lang.org site before reading the book.The book is well organized and starts with project setup. This entails using the WebAssembly toolchain. This setup serves as a foundation for later chapters. Typical 2D objects and game mechanics are explored this well-written book that include drawing and animating sprites, game loops, state machines, collisions, and audio. More advanced coverage includes implementing a game canvas, performance optimization, and testing and debugging.Perhaps, the best characteristic of this book is that all code is thoroughly explained. So, this is not just another book with mostly source code. You can download the source code and following along with the book. The author provides clear explanations of the featured development concepts and resultant code.
Amazon Verified review Amazon
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

What is the delivery time and cost of print book? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela
What is custom duty/charge? Chevron down icon Chevron up icon

Customs duty are charges levied on goods when they cross international borders. It is a tax that is imposed on imported goods. These duties are charged by special authorities and bodies created by local governments and are meant to protect local industries, economies, and businesses.

Do I have to pay customs charges for the print book order? Chevron down icon Chevron up icon

The orders shipped to the countries that are listed under EU27 will not bear custom charges. They are paid by Packt as part of the order.

List of EU27 countries: www.gov.uk/eu-eea:

A custom duty or localized taxes may be applicable on the shipment and would be charged by the recipient country outside of the EU27 which should be paid by the customer and these duties are not included in the shipping charges been charged on the order.

How do I know my custom duty charges? Chevron down icon Chevron up icon

The amount of duty payable varies greatly depending on the imported goods, the country of origin and several other factors like the total invoice amount or dimensions like weight, and other such criteria applicable in your country.

For example:

  • If you live in Mexico, and the declared value of your ordered items is over $ 50, for you to receive a package, you will have to pay additional import tax of 19% which will be $ 9.50 to the courier service.
  • Whereas if you live in Turkey, and the declared value of your ordered items is over € 22, for you to receive a package, you will have to pay additional import tax of 18% which will be € 3.96 to the courier service.
How can I cancel my order? Chevron down icon Chevron up icon

Cancellation Policy for Published Printed Books:

You can cancel any order within 1 hour of placing the order. Simply contact customercare@packt.com with your order details or payment transaction id. If your order has already started the shipment process, we will do our best to stop it. However, if it is already on the way to you then when you receive it, you can contact us at customercare@packt.com using the returns and refund process.

Please understand that Packt Publishing cannot provide refunds or cancel any order except for the cases described in our Return Policy (i.e. Packt Publishing agrees to replace your printed book because it arrives damaged or material defect in book), Packt Publishing will not accept returns.

What is your returns and refunds policy? Chevron down icon Chevron up icon

Return Policy:

We want you to be happy with your purchase from Packtpub.com. We will not hassle you with returning print books to us. If the print book you receive from us is incorrect, damaged, doesn't work or is unacceptably late, please contact Customer Relations Team on customercare@packt.com with the order number and issue details as explained below:

  1. If you ordered (eBook, Video or Print Book) incorrectly or accidentally, please contact Customer Relations Team on customercare@packt.com within one hour of placing the order and we will replace/refund you the item cost.
  2. Sadly, if your eBook or Video file is faulty or a fault occurs during the eBook or Video being made available to you, i.e. during download then you should contact Customer Relations Team within 14 days of purchase on customercare@packt.com who will be able to resolve this issue for you.
  3. You will have a choice of replacement or refund of the problem items.(damaged, defective or incorrect)
  4. Once Customer Care Team confirms that you will be refunded, you should receive the refund within 10 to 12 working days.
  5. If you are only requesting a refund of one book from a multiple order, then we will refund you the appropriate single item.
  6. Where the items were shipped under a free shipping offer, there will be no shipping costs to refund.

On the off chance your printed book arrives damaged, with book material defect, contact our Customer Relation Team on customercare@packt.com within 14 days of receipt of the book with appropriate evidence of damage and we will work with you to secure a replacement copy, if necessary. Please note that each printed book you order from us is individually made by Packt's professional book-printing partner which is on a print-on-demand basis.

What tax is charged? Chevron down icon Chevron up icon

Currently, no tax is charged on the purchase of any print book (subject to change based on the laws and regulations). A localized VAT fee is charged only to our European and UK customers on eBooks, Video and subscriptions that they buy. GST is charged to Indian customers for eBooks and video purchases.

What payment methods can I use? Chevron down icon Chevron up icon

You can pay with the following card types:

  1. Visa Debit
  2. Visa Credit
  3. MasterCard
  4. PayPal
What is the delivery time and cost of print books? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela