Profiling a Rust and WebAssembly Game

Mục tiêu: Learn to use your browser’s performance tools to profile your Rust and WebAssembly game. You will create a special development build, record a high-stress gameplay session, and analyze the resulting flame graph to identify performance bottlenecks.


Congratulations on building a fully-featured, playable Asteroids game! You’ve masterfully orchestrated a symphony of systems—rendering, input, physics, animation—to create a dynamic and exciting experience. The engine works. The game is fun. Now, we shift our focus from a question of functionality to a question of efficiency. How well does it run? Where does it spend its time? Is it as fast as it could be?

Welcome to the world of performance profiling. Profiling is the science of measuring an application to understand its behavior, identify performance bottlenecks, and guide optimization efforts. Instead of guessing where our code is slow, we will use powerful browser-based tools to get concrete data. This task is not about changing code but about becoming a detective, gathering the clues that will inform our optimization strategy in the tasks to come.

Preparing for Investigation: The Right Build

Before we begin, a crucial point: for our first profiling session, we want clarity over raw speed. We are going to create a special build that includes debugging information. This tells wasm-pack to preserve the names of your Rust functions in the final WebAssembly module, making the profiler’s output infinitely easier to read. While this build will be slower than a release build, it’s the perfect tool for investigation.

Run the following command in your project’s root directory:

wasm-pack build --target web --dev

The --dev flag is key here. It creates a development build with the necessary debug symbols. Once you’ve run this, serve your project and open it in your browser (Google Chrome or Firefox are recommended as they have excellent performance tooling).

Opening the Toolkit: The Performance Tab

All modern browsers come equipped with a suite of developer tools. We are interested in the one designed specifically for performance analysis.

  1. Open the Developer Tools in your browser (usually by pressing F12 or Ctrl+Shift+I / Cmd+Option+I).
  2. Navigate to the Performance tab.

You will see an interface with a few buttons and a blank area. This is our laboratory.

Running the Experiment: Recording a Profile

Our goal is to capture the engine’s behavior during a high-stress moment in the game. An empty screen tells us nothing; we need to see the engine working hard.

  1. In the Performance tab, you will see a “Record” button (a grey circle).
  2. Click Record. The browser will now start meticulously tracking every single function call, memory allocation, and rendering event.
  3. Switch your focus to the game window and play! Specifically, try to create a stressful scenario:
    • Fly the ship around.
    • Shoot constantly.
    • Destroy a few large asteroids to create many smaller fragments and trigger explosion animations. The more objects on screen, the more work your engine has to do.
  4. After about 5-10 seconds of intense gameplay, switch back to the Performance tab and click Stop.

The tab will now fill with a dense, multi-colored graph. This is the “performance profile”—a complete recording of what your application was doing, millisecond by millisecond.

Interpreting the Clues: A Guided Tour of the Profile

At first glance, the performance profile can be intimidating. Let’s break it down into its most important parts.

1. The Timeline Overview

At the very top, you’ll see a chart with FPS (Frames Per Second) and graphs for CPU, NET, etc. The most important one for us is the CPU graph. You’ll likely see a jagged “mountain range” corresponding to the time you were playing. Any significant dips in the FPS chart, especially ones that go below 60fps, are red flags that indicate a “frame drop” or “jank”—a moment where the game visibly stuttered. These are the moments we want to investigate.

2. The Flame Graph (Main Section)

This is the heart of the profiler. It’s a visual representation of the call stack over time. Time moves from left to right. The vertical axis represents the call stack—if function A is on top of function B, it means B called A.

  • The Golden Rule: The width of a block corresponds to the time it took to execute. Wide blocks are your prime suspects for performance bottlenecks.

Drill down into the data. Look for a repeating pattern of tall stacks. Each of these stacks represents one frame of your game, initiated by a call to requestAnimationFrame.

Click and drag to zoom in on one of these frame stacks. As you look from the bottom up, you should see something like this:

  • Animation Frame Fired (the browser starting the process)
  • A JavaScript callback, which in turn calls into your WASM module.
  • A call to something like wasm-function[...]: __wbg_game_tick_... This is your Game::tick method!
  • Inside Game::tick, you will see the individual bars for each of your systems. Because you made a dev build, you should see their names (or something very close), like:
    • ...::player_control_system
    • ...::movement_system
    • ...::collision_system
    • ...::destruction_system
    • ...::animation_system
    • ...::rendering_system

This is the magic moment. You can now visually see how much time your engine is spending in each system. Is the bar for collision_system significantly wider than all the others? If so, you’ve likely found a bottleneck in your collision detection logic. Is the rendering_system taking up a huge chunk of time? Perhaps your drawing calls are less efficient than they could be.

Potential Bottlenecks to Hunt For:

Based on the engine you’ve built, here are some likely suspects to look for in the flame graph:

  • collision_system: This system performs a nested loop (bullets vs. asteroids, player vs. asteroids). When you have many objects on screen, the number of checks grows quadratically. This is a classic source of performance issues. Look for its bar to get wider as more asteroids appear.
  • destruction_system: When an asteroid is destroyed, you despawn entities and spawn new ones. This involves memory allocation and can cause a brief, sharp spike in CPU usage. Look for a frame where this bar is unusually wide, corresponding to the moment an asteroid was hit.
  • rendering_system: The canvas drawing operations (drawImage, translate, rotate) are not free. With dozens of sprites on screen, the time spent in this system can add up.

What You’ve Accomplished

You haven’t changed a single line of code, but you’ve gained invaluable insight. You now have a tool and a methodology to scientifically measure your engine’s performance. You can see, with data, which parts of your architecture are the most computationally expensive. This knowledge is power. It means your future optimization efforts will be targeted and effective, not just blind guesswork.

Next Steps

This investigation has given us our list of suspects. The next step is to perform the first, and often most impactful, optimization: telling the Rust compiler to aggressively optimize our code for size and speed. You will do this by configuring the release profile in Cargo.toml, a simple change that can yield massive performance gains by enabling the compiler’s most powerful features.


Further Reading

  • Chrome DevTools: Analyze Runtime Performance: The official documentation from Google on how to use the Performance tab. It’s a comprehensive resource for everything the tool can do.
  • Firefox Profiler: The official documentation for Firefox’s equally powerful performance analysis tool.
  • Understanding Flame Graphs: A fantastic, in-depth article by Brendan Gregg, the creator of Flame Graphs, explaining the theory behind how they work and how to interpret them effectively.
  • Rust and WebAssembly Profiling: The official wasm-bindgen documentation on profiling, which discusses how to get even more detailed information from your Rust code.

Optimize Rust WASM Build with Cargo Release Profiles

Mục tiêu: Configure the Cargo.toml file to use the release profile for aggressive size and performance optimizations in a Rust WebAssembly project. This involves setting the optimization level to ‘z’ and enabling Link-Time Optimization (LTO).


Excellent work playing the role of a performance detective. In the previous task, you used the browser’s powerful profiling tools to create a detailed “before” picture of your engine’s performance. You now have hard data, not just guesses, about which systems—like collision_system and rendering_system—are the most computationally expensive. You’ve identified the suspects. Now, it’s time to bring in the enforcers.

Our first and most impactful optimization step doesn’t involve rewriting any of our Rust logic. Instead, we are going to change the instructions we give to the Rust compiler itself. By default, when you’ve been building, you’ve likely been using the dev profile, which prioritizes fast compilation times and debuggability. For a final, production-ready application, we need to switch to the release profile and configure it to optimize as aggressively as possible for the web environment.

Understanding Cargo Profiles

Cargo, Rust’s build system and package manager, has a concept of “profiles.” A profile is a pre-configured set of compiler options that you can apply to your build. The two most important profiles are: * dev: Used when you run cargo build or wasm-pack build --dev. It compiles quickly and includes debug information, but the resulting code is not optimized for speed or size. This is what you used for your initial profiling investigation. * release: Used when you run cargo build --release or (by default) wasm-pack build. This profile compiles much more slowly but instructs the compiler to perform a huge number of optimizations to make the final binary as fast and small as possible.

We are going to customize the release profile in our Cargo.toml file to be perfectly tuned for a WebAssembly target.

Configuring the [profile.release] Section

Open your Cargo.toml file. We are going to add a new section to it. If you don’t already have a [profile.release] section, add the following code to the end of the file.

# In Cargo.toml, at the end of the file

# This section configures the settings for our "release" builds.
# `wasm-pack build` uses this profile by default.
[profile.release]
# This is the optimization level. 'z' tells the compiler to optimize
# aggressively for a small binary size. For WebAssembly, a smaller file
# means faster download and startup times for your users, which is often
# more important than squeezing out the last drop of raw CPU speed.
opt-level = 'z'

# Enable Link Time Optimization (LTO). This is a crucial optimization.
# It allows the compiler to perform optimizations across your entire codebase
# at the final "linking" stage, rather than optimizing each file in isolation.
# This can result in significant size reductions and performance improvements
# by inlining functions across crate boundaries and removing unused code.
# The trade-off is a longer compile time, which is perfectly acceptable for
# a final release build.
lto = true

Deconstructing the Optimizations

Let’s look at what these two lines are actually doing for our game engine.

  • opt-level = 'z' (Optimize for Size): When you ship a game on the web, your user has to download the .wasm file. A smaller file means the game loads faster, especially on slower connections. The 'z' level tells the compiler to prioritize this above all else. It will perform all the same speed optimizations as the standard '3' level but will also make additional trade-offs to shrink the binary. For a game engine with many systems and components, this can have a dramatic effect on the final file size.
  • lto = true (Link-Time Optimization): This is one of the most powerful tools in the compiler’s arsenal. Imagine your collision_system calls a small helper function from a different part of your code. Without LTO, the compiler has to generate the code for a full function call. With LTO, the compiler can look at the entire program at once and might decide to “inline” that helper function, essentially copying its code directly into the collision_system. This eliminates the overhead of the function call, making the code faster. It can do this across your entire project and even into the dependency crates you use, like bevy_ecs. For performance-sensitive code like a game engine, LTO is essential.

Building with the New Profile

Now that you’ve given the compiler its new instructions, it’s time to create a fully optimized build. Run the standard wasm-pack build command in your terminal. Because we are not specifying --dev, it will automatically use our newly configured release profile.

wasm-pack build --target web

You will notice that this build takes significantly longer than your previous builds. This is a good sign! It’s the sound of the compiler and linker working much harder, analyzing your entire codebase to apply those powerful LTO optimizations.

The “After” Picture: Re-Profiling

Once the build is complete, serve your project and open it in the browser. Before you even open the profiler, you may feel a difference. The game might seem smoother, especially with many asteroids on screen.

Now, repeat the steps from the previous task:

  1. Open the Performance tab in the developer tools.
  2. Record a 5-10 second profile of intense gameplay.
  3. Stop the recording and analyze the flame graph.

You should notice two major differences:

  1. Shorter Stacks: The overall height of the call stacks for each frame should be noticeably shorter. The wide bars for collision_system and rendering_system that you identified before should be narrower, indicating they are completing their work in less time.
  2. Mangled Names: You will likely see that the clear Rust function names are gone, replaced with mangled or generic wasm-function[...] labels. This is a direct result of LTO and inlining. The compiler has optimized your functions so heavily that their original structure is no longer present in the final binary. This is why profiling a dev build first is so important for identifying which areas to focus on.

You have just taken a massive step in optimizing your engine, leveraging the full power of the Rust compiler to create a smaller, faster, and more professional final product.

Next Steps

The Rust compiler has done its part, producing a highly optimized WebAssembly module. However, there’s one more layer of optimization we can apply. The next task is to use a tool called wasm-opt, which is part of the Binaryen toolchain. This tool is specifically designed to post-process .wasm files, performing WASM-specific optimizations that the Rust compiler might not be aware of, squeezing out the last few kilobytes of size and cycles of performance.


Further Reading

  • The Cargo Book: Profiles: The official documentation on Cargo profiles, explaining all the available settings and how they work.
  • Rust Performance Book: LTO: A section from the official Rust Performance Book dedicated to explaining Link-Time Optimization in more detail.
  • Shrinking .wasm Size: A guide from the official Rust and WebAssembly book with a collection of tips and tricks for reducing the final binary size, many of which you have just implemented.

Optimize WebAssembly with wasm-opt Post-Processing

Mục tiêu: Install the Binaryen toolchain and use its wasm-opt command to perform post-processing optimization on a Rust-generated WebAssembly module. The task involves automating this optimization step by integrating it into the package.json build scripts to reduce the final .wasm file size.


You’ve done a masterful job of leveraging the Rust compiler’s immense power. By configuring the release profile in Cargo.toml, you’ve instructed rustc to perform a suite of powerful optimizations, including Link-Time Optimization (LTO), resulting in a WebAssembly module that is significantly smaller and faster than your initial development builds. You’ve taken the engine from a functional prototype to a lean, efficient machine.

But what if we could make it even better?

The Rust compiler is a master of optimizing Rust code. However, the final .wasm file is a separate language with its own unique structure and quirks. There exists a final layer of optimization that can be applied after the Rust compiler has finished its work. This is where we introduce a new tool to our arsenal: wasm-opt from the Binaryen toolchain. This is the final polish, the last 10% of optimization that separates a great build from a professional one.

The Post-Processor: What is Binaryen and wasm-opt?

Binaryen is a compiler and toolchain infrastructure library specifically for WebAssembly. Think of it as a toolkit full of specialized instruments for manipulating .wasm files. It’s not tied to any specific source language like Rust or C++; it operates directly on the WebAssembly bytecode.

The crown jewel of this toolkit is wasm-opt, a powerful command-line optimizer. Its job is to:

  1. Read a .wasm file that has already been compiled (by wasm-pack, in our case).
  2. Run a series of over 100 WebAssembly-specific optimization passes. These passes can do things like simplify expressions, remove redundant function calls, and reorder code in ways that are more efficient for a WebAssembly virtual machine to execute—optimizations that the original Rust compiler might not have had the context to perform.
  3. Write a new, even smaller and potentially faster .wasm file.

This process is called post-processing, and it’s a standard best practice for shipping production-grade WebAssembly applications.

Step 1: Installing the Binaryen Toolchain

Before you can use wasm-opt, you need to install it. The method depends on your operating system.

  • For macOS (using Homebrew): bash brew install binaryen
  • For Debian/Ubuntu (using apt): bash sudo apt-get install binaryen
  • For Windows (using Chocolatey): bash choco install binaryen
  • Universal Method (Downloading from GitHub): If you don’t use a package manager or the version is outdated, you can always download the latest pre-compiled release directly from the official Binaryen GitHub releases page. Download the archive for your operating system, extract it, and add the bin directory to your system’s PATH.

To verify the installation, open a new terminal and run:

wasm-opt --version

If it prints a version number, you’re ready to go!

Step 2: Running wasm-opt Manually

Let’s start by running the optimizer manually to see its effect. First, ensure you have a release build of your project.

wasm-pack build --target web

This command creates a pkg/ directory. The file we are interested in is your_project_name_bg.wasm. This is the raw WebAssembly module before it’s wrapped in any JavaScript glue code.

Now, let’s run wasm-opt on it. We’ll tell it to optimize aggressively for size (-Oz) and to write the output to a new file so we can compare the results.

# Navigate to your project's root directory
# Replace `your_project_name` with the actual name of your crate from Cargo.toml
wasm-opt pkg/game_engine_2d_bg.wasm -o pkg/game_engine_2d_bg.opt.wasm -Oz

Deconstructing the Command:

  • wasm-opt pkg/game_engine_2d_bg.wasm: The tool and the input file.
  • -o pkg/game_engine_2d_bg.opt.wasm: The -o flag specifies the output file. We’ve named it with .opt to distinguish it.
  • -Oz: This is the optimization flag. Just like opt-level = 'z' in Cargo, -Oz tells wasm-opt to be extremely aggressive in optimizing for small code size. This is the perfect companion to our release profile settings.

Now, compare the file sizes:

# On Linux/macOS
ls -lh pkg/
# On Windows
dir pkg/

You should see that game_engine_2d_bg.opt.wasm is noticeably smaller than the original game_engine_2d_bg.wasm. This size reduction can be anywhere from a few percent to over 15%, depending on the project. That’s a direct improvement to your game’s load time, achieved with a single command!

Step 3: Automating the Polish - Integrating with package.json

Running a command manually is great for learning, but it’s easy to forget. The professional approach is to make this optimization an automatic, guaranteed part of your build process. We can do this easily by adding a post-build script hook in our package.json.

Open your package.json file and find the "scripts" section. You can add a special script named postbuild (or whatever you call your build script + post). Many tools, including npm, will automatically run this script after the build script completes. Let’s create a robust script that optimizes the file and replaces the original.

{
  "name": "your-project-name",
  "version": "0.1.0",
  "scripts": {
    "build": "wasm-pack build --target web",
    "postbuild": "wasm-opt pkg/game_engine_2d_bg.wasm -o pkg/game_engine_2d_bg.opt.wasm -Oz && mv pkg/game_engine_2d_bg.opt.wasm pkg/game_engine_2d_bg.wasm",
    "serve": "serve ."
  },
  "dependencies": {
    "serve": "^14.0.0"
  }
}

Note for Windows users: mv is a Unix command. The Windows equivalent is move. For a cross-platform solution, you might use a package like npm-run-all or shx.

Deconstructing the postbuild script:

  1. wasm-opt pkg/game_engine_2d_bg.wasm -o pkg/game_engine_2d_bg.opt.wasm -Oz: This is the same command we ran manually. It creates the optimized file.
  2. &&: This is a shell operator that means “if the previous command succeeded, then run this next command.”
  3. mv pkg/game_engine_2d_bg.opt.wasm pkg/game_engine_2d_bg.wasm: This command moves (renames) our optimized file, replacing the original, un-optimized version.

Now, you can simply run:

npm run build

This command will first trigger wasm-pack, and immediately after it finishes, npm will execute our postbuild script, leaving you with a fully optimized .wasm file in the pkg directory, ready to be served. Your build pipeline is now complete and professional.

Next Steps

You have now reached the pinnacle of WebAssembly optimization for your project. You are leveraging two distinct layers of tooling—the Rust compiler and the Binaryen toolchain—to produce a final artifact that is as small and fast as possible.

With the build process fully automated and optimized, the next logical step is to ensure your entire project packaging is clean, clear, and ready for others (or your future self) to use. Your next task is to ensure your package.json and build scripts are clean and easy to use, a final piece of housekeeping before you prepare to share your masterpiece with the world.


Further Reading

  • Binaryen Official GitHub Repository: The source of truth for the toolchain, including its full feature set and documentation.
  • wasm-opt Command-Line Options: A detailed explanation of all the flags you can pass to wasm-opt for fine-grained control over its optimization passes.
  • RustWASM Book: Code Size: The official guide from the Rust and WebAssembly Working Group on techniques for shrinking .wasm size, where wasm-opt is a key recommendation.

Refactor npm Scripts for Cross-Platform Compatibility

Mục tiêu: Update the package.json file to create a robust, cross-platform build pipeline. This involves replacing platform-specific shell commands like mv with the shx package to ensure the build process works on Windows, macOS, and Linux, and restructuring the npm scripts for clarity and improved developer experience.


You have assembled an incredibly sophisticated and automated build pipeline. By chaining wasm-pack with a postbuild script that runs wasm-opt, you’ve ensured that every production build of your game engine is as lean and performant as possible. This level of automation is the hallmark of a professional software project. Your package.json file has evolved from a simple project marker into the central control panel for building, optimizing, and serving your application.

Now is the perfect time to step back and refine that control panel. This task is about polishing your package.json and its scripts to make them robust, clear, and universally usable. This isn’t just about tidiness; it’s about improving the Developer Experience (DX) for both your future self and for anyone else who might want to contribute to or learn from your project.

The Problem with Platform-Specific Scripts

Your current postbuild script is a fantastic piece of automation, but it has a hidden flaw. Let’s look at a likely version of it:

"postbuild": "wasm-opt ... -o ...opt.wasm && mv pkg/game_engine_2d_bg.opt.wasm pkg/game_engine_2d_bg.wasm"

The mv command is a standard shell command on Linux and macOS. However, on a standard Windows Command Prompt, this command doesn’t exist (its equivalent is move). This means your build script would fail for any developer on a Windows machine.

To create a truly shareable and professional project, our scripts must be cross-platform.

The Solution: shx for Universal Shell Commands

The Node.js ecosystem has a brilliant solution for this problem: the shx package. It provides a cross-platform implementation of common Unix shell commands (mv, rm, cp, etc.) that you can use directly within your npm scripts, and they will work seamlessly on Windows, macOS, and Linux.

Let’s add it to your project as a development dependency. Development dependencies are tools needed for building and developing the project, but not for running the final application.

Open your terminal in the project’s root directory and run:

npm install shx --save-dev

This command adds shx to the devDependencies section of your package.json, making it available to your scripts.

Refactoring for Clarity and Robustness

With shx in our toolkit, we can now rewrite our scripts to be universally compatible and even more readable. We will also take this opportunity to add a few conventional scripts that make development much smoother.

Here is a new, polished version of the scripts section in your package.json.


The Final package.json

Below is a complete, well-structured package.json file. The focus is on the scripts and metadata sections.

{
  "name": "wasm-2d-game-engine",
  "version": "1.0.0",
  "description": "A high-performance 2D game engine built in Rust, compiled to WebAssembly.",
  "main": "index.js",
  "scripts": {
    "build": "wasm-pack build --target web && npm run build:opt",
    "build:opt": "wasm-opt pkg/game_engine_2d_bg.wasm -o pkg/game_engine_2d_bg.wasm.opt -Oz && shx mv pkg/game_engine_2d_bg.wasm.opt pkg/game_engine_2d_bg.wasm",
    "build:dev": "wasm-pack build --target web --dev",
    "serve": "serve .",
    "start": "npm run build && npm run serve",
    "start:dev": "npm run build:dev && npm run serve",
    "clean": "shx rm -rf pkg"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/YOUR_USERNAME/YOUR_REPO.git"
  },
  "author": "Your Name <your.email@example.com>",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/YOUR_USERNAME/YOUR_REPO/issues"
  },
  "homepage": "https://github.com/YOUR_USERNAME/YOUR_REPO#readme",
  "devDependencies": {
    "serve": "^14.0.0",
    "shx": "^0.3.4"
  }
}

Deconstructing the New Scripts

Let’s break down this improved control panel, command by command:

  • "build:opt":

    • This is our core optimization step, now isolated in its own script for clarity.
    • wasm-opt ... -o pkg/game_engine_2d_bg.wasm.opt -Oz: We run wasm-opt as before, but output to a temporary .opt file.
    • && shx mv ...: We use shx mv instead of mv. This command will now work on any operating system. This robustly replaces the original .wasm file with the optimized version.
  • "build":

    • This is now the main command for creating a production-ready build.
    • wasm-pack build --target web: First, it runs the standard release build using your optimized Cargo.toml profile.
    • && npm run build:opt: If the wasm-pack build succeeds, it then chains into our new optimization script, ensuring every production build is fully optimized.
  • "build:dev":

    • A dedicated script for creating a development build. The --dev flag ensures fast compilation and includes debug symbols, which is perfect for profiling or debugging.
  • "clean":

    • A vital housekeeping script. shx rm -rf pkg will completely and safely remove the pkg directory. Running this before a build is a great way to ensure you’re starting from a completely fresh state, with no old artifacts lingering.
  • "serve":

    • This simple script launches a local web server, unchanged from before.
  • "start":

    • The primary command for running the production version of your project locally. It first runs the full build and build:opt chain, then serves the result.
  • "start:dev":

    • Your new best friend during development. This command quickly builds the unoptimized development version and immediately serves it. This provides the fastest possible feedback loop when you’re making changes.

Polishing the Project Metadata

Notice the other fields we’ve fleshed out: description, repository, author, license. These are not just for show. They turn your folder of code into a well-defined software package. When you share this project on a platform like GitHub, this metadata provides crucial context, making it easier for others to understand, use, and contribute to your work.

You have now transformed your project’s packaging from a simple utility into a professional, robust, and developer-friendly control panel. Your build process is clear, repeatable, and will work for anyone, anywhere.

Next Steps

With a clean and professional package structure in place, you are perfectly positioned for the next crucial step in preparing your project for the world: documentation. Your next task is to write a comprehensive README.md file. The clear and simple build scripts you’ve just created will form the core of the “Getting Started” or “How to Run” section of that document, making it incredibly easy for others to get your engine up and running.


Further Reading

  • npm Docs: package.json: The official documentation for every field available in package.json. A fantastic reference.
  • npm Docs: scripts: The definitive guide to using the scripts field in package.json, including special lifecycle scripts like postbuild.
  • shx on npm: The official package page for shx, showing all the cross-platform commands it provides.

Create a Professional README.md for the Game Engine

Mục tiêu: Create a comprehensive README.md file for a Rust and WebAssembly game engine project. Use the provided template to document the project’s features, prerequisites, setup instructions, and available build scripts to create a professional-looking front page for the repository.


You’ve meticulously engineered a powerful, optimized build pipeline. Your package.json is no longer just a file; it’s a sophisticated control panel that allows anyone, on any platform, to reliably build, test, and run your game engine. This automation and professionalism are what separate a personal experiment from a legitimate software project. Now, it’s time to write the user manual for that control panel and the engine it powers.

A project without a README.md is like a powerful machine with no instructions—intimidating, confusing, and likely to be ignored. Your README is the front door to your project. It’s the first thing a potential collaborator, employer, or even your future self will see. A great README clearly and concisely communicates what the project is, why it’s cool, and how to get it running.

This task is about crafting that front door. We will create a comprehensive, professional README.md file that showcases the quality of your work and leverages the clean build scripts you just created to provide a seamless “getting started” experience.

The Anatomy of a Great README

A professional README follows a standard structure that answers a user’s questions in a logical order. We will create a template with the following sections:

  1. Title and Tagline: A clear name and a one-sentence pitch.
  2. Overview: A short paragraph explaining the project’s purpose and the technology used.
  3. Features: A bulleted list showcasing the engine’s capabilities. This is your chance to show off!
  4. Prerequisites: A checklist of tools someone needs to have installed before they can use your project.
  5. Getting Started: The most important section. Simple, copy-pasteable instructions to get the project running locally. This is where your new npm scripts will shine.
  6. Available Scripts: A brief explanation of each npm script, showing the user the different ways they can build and run the project.
  7. License: A declaration of how others are permitted to use your code.

The README.md Template

Create a new file named README.md in the root directory of your project. Copy and paste the following markdown template into it. We will then break down each section.

# Rust + WebAssembly 2D Game Engine

A high-performance, Entity-Component-System (ECS) based 2D game engine built from scratch in Rust, compiled to WebAssembly to run natively in the browser.

## Overview

This project is a complete 2D game engine designed for building fast, memory-safe, and reliable web-based games. It leverages a modern ECS architecture provided by `bevy_ecs` for maximum flexibility and performance. The entire engine is written in Rust and compiled to a highly optimized WebAssembly module, demonstrating advanced systems programming concepts applied to the web platform.

The repository includes two fully playable demo games built with the engine to validate its capabilities:
- **Pong:** A classic implementation to test core rendering, input, and collision systems.
- **Asteroids:** A more complex demo showcasing rotation, dynamic entity spawning (projectiles), sprite sheet animation, and a complete game state loop.

## ✨ Features

- **Modern ECS Architecture:** Built on `bevy_ecs` for clean, data-driven game logic.
- **WASM-Powered Performance:** Runs at near-native speed in the browser, leveraging Rust's performance and memory safety.
- **2D Rendering Pipeline:** Efficiently renders sprites and sprite sheet animations to an HTML `<canvas>`.
- **Transformation System:** Supports position and rotation for dynamic entities.
- **Input Handling:** Robust keyboard input management for player control.
- **Asset Management:** System for loading and storing images and audio.
- **Event-Driven Collision Detection:** A flexible physics system using events to handle interactions.
- **Complete Game Loop:** Full support for managing game state, including scoring and lives.

## Prerequisites

Before you begin, ensure you have the following tools installed on your system:

- [Rust and Cargo](https://www.rust-lang.org/tools/install)
- [`wasm-pack`](https://rustwasm.github.io/wasm-pack/installer/)
- [Node.js and npm](https://nodejs.org/en/download/)
- [Binaryen (`wasm-opt`)](https://github.com/WebAssembly/binaryen#installing)

## 🚀 Getting Started

To get a local copy up and running, follow these simple steps.

1.  **Clone the repository:**
    ```bash
    git clone https://github.com/YOUR_USERNAME/YOUR_REPO_NAME.git
    cd YOUR_REPO_NAME
    ```

2.  **Install NPM dependencies:**
    This will install the local web server (`serve`) and the cross-platform shell tools (`shx`).
    ```bash
    npm install
    ```

3.  **Run in Development Mode:**
    This command compiles the Rust code in debug mode (for faster compilation) and starts a local server. The page will be available at `http://localhost:3000`.
    ```bash
    npm run start:dev
    ```

## 🛠️ Available Scripts

This project includes a set of pre-configured `npm` scripts to streamline development and build processes:

- `npm run start`: Builds the project in **release** mode (fully optimized) and serves it.
- `npm run start:dev`: Builds the project in **development** mode (fast compilation) and serves it.
- `npm run build`: Compiles the project into a fully optimized release build in the `/pkg` directory.
- `npm run build:dev`: Compiles the project in development mode.
- `npm run serve`: Starts a local web server for the project root.
- `npm run clean`: Removes the `/pkg` build directory.

## 📄 License

This project is distributed under the MIT License. See `LICENSE` for more information.

Deconstructing the Template

  • Header (#, ##): The title and section headers use markdown headings. They structure the document, making it easy to scan.
  • Overview: This is your “elevator pitch.” It quickly tells someone what the project is and why it’s interesting. Mentioning the specific technologies (Rust, bevy_ecs, WebAssembly) is crucial for context.
  • Features: This is a high-impact section. By listing the concrete capabilities of your engine, you are demonstrating the scope and success of your project at a glance. It’s a fantastic summary for anyone reviewing your portfolio.
  • Prerequisites: This section prevents user frustration. By clearly stating the required tools, you ensure that anyone who tries to run your project is set up for success.
  • Getting Started: This is the payoff from your previous task. The instructions are incredibly simple and reliable because of the robust scripts you created. Using fenced code blocks (```bash) makes the commands easy to copy and paste. Remember to replace YOUR_USERNAME/YOUR_REPO_NAME with your actual GitHub details once you create the repository.
  • Available Scripts: This section adds another layer of professionalism. It shows that you’ve thought about the development lifecycle and provided convenient tools for different scenarios (e.g., the difference between a quick dev build and a final production build).
  • License: Stating a license (like MIT) is a critical open-source best practice. It tells others how they are legally allowed to use, modify, and distribute your work. You can create a LICENSE file in your root directory and copy the standard MIT License text into it.

You have now documented your project with the same level of care and professionalism that you used to build it. This README.md file transforms your codebase from a personal folder into a shareable, understandable, and impressive portfolio piece.

Next Steps

Your code is optimized, your build process is automated, and your documentation is pristine. The project is ready to be shared with the world. The next logical step is to put it under version control and host it on a public platform. Your next task is to create a GitHub repository for your project and push your code to it.


Further Reading

  • GitHub Docs: About READMEs: The official documentation from GitHub on the purpose and power of a good README.
  • Awesome README: A curated list of fantastic README.md examples to draw inspiration from.
  • Choose a License: A non-technical guide to help you choose an appropriate open-source license for your projects.
  • Markdown Guide: A free and open-source reference guide that explains how to use Markdown.

Publish a Local Project to GitHub

Mục tiêu: A step-by-step guide to initializing a local Git repository, creating a remote repository on GitHub, and pushing your project’s code for the first time.


Of course! Let’s get this task done. Here is the detailed solution structured as a blog article.


Your project is now polished to a professional sheen. The code is highly optimized, the build process is a model of cross-platform automation, and your comprehensive README.md file serves as a perfect introduction for any visitor. You’ve built a magnificent house and written a detailed welcome guide for the front door. Now, it’s time to place that house on the most popular street in the developer world: GitHub.

This task is about taking your local project and giving it a public home. By creating a GitHub repository, you’re not just backing up your code; you’re enabling version control, opening the door to future collaboration, and creating a powerful, living entry in your professional portfolio.

The Power of Version Control with Git

Before we begin, it’s worth understanding the tool we’re about to use. Git is a distributed version control system. It’s like an infinite “undo” button for your entire project, allowing you to track every change, experiment with new features on separate timelines (called “branches”), and rewind to any point in your project’s history. GitHub is a web platform built around Git that adds a powerful social and collaborative layer, making it easy to share, discuss, and manage your code.

Step 1: Initialize a Local Git Repository

There’s a good chance you’ve already done this, but it’s the foundational step. If you haven’t, open a terminal in your project’s root directory and run this command:

git init

This command initializes a new Git repository in your current folder. It creates a hidden .git subdirectory where Git stores all of its tracking information, history, and configuration for your project.

Step 2: The Art of Ignoring: Creating a .gitignore File

A Git repository should only track your source code—the essential files needed to build the project. It should not track generated files, build artifacts, or dependency folders. Committing these files bloats your repository, slows down cloning, and can cause issues for other collaborators.

The .gitignore file is a simple text file that lists all the files and patterns that Git should intentionally ignore. Create a file named .gitignore in your project’s root directory and add the following content. This is a robust template specifically for a Rust, WebAssembly, and Node.js project like yours.

# .gitignore

# Rust / Cargo
# Ignore the main build directory, which can be very large.
/target/

# Ignore Cargo's lock file if this were a library, but for a binary/application,
# it's good practice to commit it to ensure reproducible builds. So we'll keep it.
# Cargo.lock

# Ignore local editor and OS files
.DS_Store
*.rs.bk
*.swp

# Node.js / npm
# Ignore the massive folder of downloaded dependencies.
/node_modules/

# Ignore log files and other generated artifacts.
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# wasm-pack
# Ignore the build output directory from wasm-pack.
/pkg/

This file tells Git to disregard the target/ directory (where Rust compiles its artifacts), the node_modules/ directory (where npm installs dependencies), and the pkg/ directory (where wasm-pack places its output). These can all be regenerated from your source code by running cargo build or npm install, so they don’t belong in your history.

Step 3: Create the Remote Repository on GitHub

Now, let’s create the public home for your project.

  1. Navigate to GitHub.com and log in to your account.
  2. In the top-right corner, click the + icon and select “New repository”.
  3. Repository name: Give your project a clear, descriptive name (e.g., rust-wasm-game-engine).
  4. Description: This is the perfect place to put your one-sentence tagline from your README.md. “A high-performance, ECS-based 2D game engine built in Rust and WebAssembly.”
  5. Public/Private: Select Public to make it a visible part of your portfolio.
  6. IMPORTANT: Do not initialize the repository with a README, license, or .gitignore file. You have already created these files locally, and checking these boxes would create a repository with a separate history, making it more difficult to connect.
  7. Click “Create repository”.

GitHub will now present you with a page containing a URL and some command-line instructions. We’re interested in the section titled “…or push an existing repository from the command line.”

Step 4: Staging, Committing, and Pushing Your Work

This is the core Git workflow that you will use hundreds of times. We’ll perform it now to upload your initial project state.

Run these commands one by one in your terminal from your project’s root directory.

  1. Stage all your files: bash git add . The git add . command takes all the files in your current directory (that aren’t ignored by .gitignore) and stages them. “Staging” is like putting files into a box to prepare a snapshot of your project. It doesn’t save the changes yet; it just prepares them for the next step.
  2. Commit the staged files: bash git commit -m "Initial commit: Add full game engine and demo projects" The git commit command takes the snapshot you prepared in the staging area and permanently saves it to your local project history. The -m flag allows you to provide a commit message. A good commit message is a concise summary of the changes made in that snapshot.
  3. Connect your local repository to the remote on GitHub: Copy the line from the GitHub page that looks like this (your URL will be different): bash git remote add origin https://github.com/YOUR_USERNAME/YOUR_REPO_NAME.git This command tells your local Git repository about a “remote” location. By convention, the primary remote repository is named origin.
  4. (Optional but recommended) Rename your default branch to main: Historically, the default branch was named master. The industry standard is now main. If your local branch is not already named main, run this command: bash git branch -M main
  5. Push your commit to GitHub: bash git push -u origin main The git push command is what finally uploads your local commits to the remote repository (origin). The -u flag (short for --set-upstream) sets up a tracking relationship between your local main branch and the origin/main branch, so in the future, you can simply run git push.

The Big Reveal

That’s it! Refresh the GitHub page for your repository. You will see all your project files, and your beautifully crafted README.md will be rendered as the project’s homepage. You can click on “Commits” to see your “Initial commit” in the history. You now have a professional, version-controlled, public home for your game engine.

Next Steps

Your project is now live on GitHub, perfectly poised for the next and most exciting step in deployment. Having your code in a GitHub repository is the essential prerequisite for automating your builds and deployments. Your next task is to set up GitHub Actions to automatically build and deploy your project to GitHub Pages, creating a live, playable version of your game that you can share with anyone in the world.


Further Reading

  • Pro Git Book: The definitive book on Git, available for free online. It covers everything from the basics to advanced topics.
  • GitHub Docs: Hello World: GitHub’s own friendly, interactive introduction to the basic workflow.
  • How to Write a Git Commit Message: A classic blog post outlining the best practices for writing clear, effective commit messages—a crucial skill for any developer.

Automate Deployment with GitHub Actions and GitHub Pages

Mục tiêu: A guide on setting up a Continuous Integration and Continuous Deployment (CI/CD) pipeline to automatically build, optimize, and deploy a Rust/WebAssembly project to a live URL using GitHub Actions and GitHub Pages.


You have reached a pivotal moment in your journey as a developer. Your game engine, complete with two fantastic demos, is now version-controlled and hosted in a public GitHub repository. This is an immense achievement, transforming your local project into a shareable, professional portfolio piece. Now, we will take the final step to bring it to life for the entire world to see. We will turn your repository into a self-deploying application that automatically builds, optimizes, and publishes itself to a live URL every time you push a change.

This is the magic of Continuous Integration and Continuous Deployment (CI/CD), and we will achieve it using two integrated GitHub features: GitHub Actions and GitHub Pages.

  • GitHub Actions is an automation platform built directly into GitHub. It allows you to run a sequence of commands (a “workflow”) on a virtual machine in response to events in your repository, such as pushing new code.
  • GitHub Pages is a free static site hosting service. It can take the files from a branch in your repository (like your index.html, JavaScript, and .wasm files) and publish them as a live website.

Our goal is to create a GitHub Action that, upon every push to your main branch, automatically runs the complete build and optimization process you’ve so carefully crafted and then deploys the resulting game to GitHub Pages.

Creating the Workflow Definition File

GitHub Actions are defined by special files written in a format called YAML (YAML Ain’t Markup Language), which is a human-readable way to structure data. This file must be placed in a specific directory in your project: .github/workflows/.

First, create the necessary directories in your project’s root:

mkdir -p .github/workflows

Now, create a new file inside that directory named deploy.yml (the name can be anything, but deploy.yml or main.yml are common conventions).

Copy the following workflow definition into your deploy.yml file. This is the complete recipe for your automated deployment pipeline.

# .github/workflows/deploy.yml

# 1. Give the workflow a name that will appear in the "Actions" tab of your GitHub repository.
name: Build and Deploy to GitHub Pages

# 2. Define the event that triggers this workflow.
on:
  # We want this to run every time code is pushed to the `main` branch.
  push:
    branches: [ "main" ]

# 3. Define the jobs that will be run. A workflow can have one or more jobs.
jobs:
  # We'll define a single job called `build-and-deploy`.
  build-and-deploy:
    # 4. Specify the virtual machine environment to run the job on.
    # `ubuntu-latest` is a reliable and common choice.
    runs-on: ubuntu-latest

    # 5. Define the sequence of steps that make up the job.
    steps:
      # Step 5.1: Check out the repository's code
      # This uses a pre-built action to download your repository's code into the runner.
      - name: Checkout 🛎️
        uses: actions/checkout@v3

      # Step 5.2: Install the Rust toolchain
      # We need Rust to compile our code. This action sets up `rustup` and `cargo`.
      - name: Install Rust Toolchain 🦀
        uses: dtolnay/rust-toolchain@stable

      # Step 5.3: Install wasm-pack
      # This is the primary tool for building our Rust-WASM package.
      - name: Install wasm-pack 📦
        run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh

      # Step 5.4: Install Binaryen (for wasm-opt)
      # Our optimization pipeline requires `wasm-opt`. We install it via Ubuntu's package manager.
      - name: Install wasm-opt 🚀
        run: sudo apt-get update && sudo apt-get install -y binaryen

      # Step 5.5: Install Node.js
      # We need Node.js and npm to install dependencies and run our build scripts.
      - name: Install Node.js 🟩
        uses: actions/setup-node@v3
        with:
          node-version: '18' # Specify a recent LTS version of Node.js

      # Step 5.6: Install npm dependencies
      # This runs `npm install` to get `serve` and `shx`.
      - name: Install Dependencies 🔧
        run: npm install

      # Step 5.7: Build the project
      # This is the magic step! We run the exact same command you would run locally.
      # It will trigger `wasm-pack` with the release profile, followed by our `build:opt`
      # script which runs `wasm-opt`.
      - name: Build Project 🏗️
        run: npm run build

      # Step 5.8: Deploy to GitHub Pages
      # This step uses a popular community action to handle the deployment.
      - name: Deploy to GitHub Pages 🚀
        uses: peaceiris/actions-gh-pages@v3
        with:
          # The action needs a token to be able to push to your repository.
          # GITHUB_TOKEN is a special secret automatically provided by GitHub Actions.
          github_token: $
          # This specifies the directory that contains the files to be deployed.
          # Our `index.html`, `assets`, and the generated `pkg` folder are all in the root.
          publish_dir: .

Deconstructing the Workflow

This YAML file is the automated version of you sitting at your computer. Let’s break down the steps:

  1. Checkout: The virtual machine starts empty. This step gets your code.
  2. Install Rust Toolchain: Sets up cargo and the Rust compiler.
  3. Install wasm-pack: Installs the essential build tool.
  4. Install wasm-opt: Installs the Binaryen toolchain to perform our crucial post-processing optimization step.
  5. Install Node.js: Sets up npm.
  6. Install Dependencies: Runs npm install, exactly as you would.
  7. Build Project: Runs npm run build. This is a beautiful moment of payoff for the work you did in the previous tasks. This single command encapsulates your entire complex, multi-stage, cross-platform build and optimization pipeline.
  8. Deploy to GitHub Pages: This is the final step. The peaceiris/actions-gh-pages@v3 action is a community-built tool that simplifies deployment. It will take the contents of the publish_dir (in our case, the entire project directory containing index.html, assets/, pkg/, etc.), create a new commit with them, and push that commit to a special branch, which is by default named gh-pages.

Activating Your Deployment

With the deploy.yml file created, follow these steps to trigger your first automated deployment:

  1. Commit and Push the Workflow File: Add the new workflow file to Git and push it to your main branch. bash git add .github/workflows/deploy.yml git commit -m "feat: Add GitHub Action for automatic deployment" git push origin main This push is the event that will trigger your workflow for the very first time!
  2. Watch the Action Run: Go to your repository on GitHub and click on the “Actions” tab. You will see your “Build and Deploy to GitHub Pages” workflow listed, with a yellow circle indicating it’s in progress. You can click on it to see a live log of each step as it executes. If all goes well, it will finish with a green checkmark.
  3. Configure GitHub Pages: Now that the action has successfully created a gh-pages branch with your built game, you need to tell GitHub to serve a website from it.

    • In your GitHub repository, go to “Settings”.
    • In the left sidebar, click on “Pages”.
    • Under “Build and deployment”, change the “Source” from “Deploy from a branch” to “GitHub Actions”. This is the modern and recommended approach. The workflow you’ve just created will automatically handle the deployment process.
    • Click “Save”.
  4. Visit Your Live Game! GitHub will now display a URL at the top of the Pages settings, usually in the format: https://YOUR_USERNAME.github.io/YOUR_REPO_NAME/. It may take a minute or two for the site to become live after the first deployment.

Congratulations! Your game engine is now live on the internet. Every time you push a change to your main branch, this entire process will repeat automatically, and your live site will be updated within minutes. You have built a complete, professional CI/CD pipeline.

Next Steps

Your engine is built, optimized, documented, and now, automatically deployed. There is only one thing left to do: Verify that the deployed version of your game engine and demos work correctly. This is the final quality assurance step to ensure your masterpiece is ready to be shared.


Further Reading

  • GitHub Actions Documentation: The official and comprehensive guide to all things GitHub Actions.
  • GitHub Pages Documentation: The official guide to setting up a GitHub Pages site.
  • YAML Specification: A quick reference for the YAML syntax.
  • peaceiris/actions-gh-pages: The documentation for the deployment action we used, showing its other powerful configuration options.

Live Deployment and QA Verification

Mục tiêu: Perform the final Quality Assurance (QA) on your live game engine. Find your deployed application’s URL on GitHub Pages, meticulously test the ‘Asteroids’ demo, then verify the CI/CD pipeline by pushing a change to activate and test the ‘Pong’ demo.


An extraordinary moment has arrived. You stand at the finish line of a marathon development journey. In the previous task, you assembled and launched a sophisticated CI/CD pipeline using GitHub Actions, transforming your repository from a simple code backup into a self-deploying application. Your game engine is now, in theory, live on the web.

This final task is the moment of truth. It is the crucial step of Quality Assurance (QA) where we transition from “it works on my machine” to “it works for the world.” We will meticulously verify that every feature you’ve painstakingly built—from rendering and input to collision and animation—functions perfectly in the live production environment. It’s time to play your own game and confirm your masterpiece is ready for its debut.

Your Live URL: Finding the Front Door

Your GitHub Actions workflow has already built and deployed your project. Now, let’s find the live URL where it’s hosted.

  1. Navigate to your repository on GitHub.
  2. Click on the “Settings” tab.
  3. In the left sidebar, click on “Pages”.
  4. At the top of this page, you will see a banner that says, “Your site is live at:” followed by a URL. It will look something like this:

    https://YOUR_USERNAME.github.io/YOUR_REPO_NAME/

Click the “Visit site” button or copy this URL into a new browser tab. You should be greeted by your running game!

The Ultimate Verification Checklist

Now, put on your tester’s hat. We will systematically go through every feature of your engine, starting with the currently active ‘Asteroids’ demo. For each item, check for both correct functionality and any errors in the browser’s developer console (press F12 or Ctrl+Shift+I).

General Health Check

  • Page Loads: The page loads successfully without a 404 error.
  • Canvas Appears: The black <canvas> element is visible.
  • No Console Errors: The developer console is free of red error messages, especially any related to WASM initialization or asset loading.

Testing the ‘Asteroids’ Demo

  • Initial Scene: The player’s ship appears in the center, and two large asteroids are visible, drifting across the screen.
  • Player Control - Rotation: Pressing the Left and Right arrow keys makes the ship rotate smoothly.
  • Player Control - Thrust: Pressing the Up arrow key applies forward thrust, and the ship moves in the direction it’s facing. Releasing the key should demonstrate inertia as the ship continues to drift.
  • Shooting: Tapping the Space Bar spawns a projectile at the nose of the ship that travels forward.
  • Bullet-Asteroid Collision: When a bullet overlaps with an asteroid, the collision is detected.
  • Destruction & Animation: Upon collision, the bullet and asteroid disappear, an explosion animation plays at the impact site, and smaller asteroid fragments are spawned.
  • Player-Asteroid Collision: Flying the ship into an asteroid triggers a collision.
  • Player Death: Upon colliding with an asteroid, the player’s ship is destroyed (despawned), an explosion animation plays, and a life is lost.
  • Game State & UI: The score in the top-left corner increases when asteroids are destroyed. The life icons correctly reflect the number of lives remaining.
  • Game Over: After losing the final life, the “GAME OVER” message appears in the center of the screen, and player controls are effectively disabled (since the ship is gone).

Testing Your CI/CD Pipeline and the ‘Pong’ Demo

A true test of your pipeline is to see it in action. Let’s verify the ‘Pong’ demo by making a small change locally and pushing it to trigger a new deployment.

  1. Switch to the Pong Setup: In your local src/lib.rs file, find the Game::new function. Comment out the Asteroids setup and uncomment the Pong setup.

    // src/lib.rs -> in Game::new
    
    // --- HIGHLIGHTED CHANGE ---\
    // We are now commenting out the Asteroids setup and calling our Pong setup.\
    setup\_pong(&mut world, canvas.width() as f32, canvas.height() as f32);
    // setup\_asteroids(&mut world, canvas.width() as f32, canvas.height() as f32);\
    // --- END HIGHLIGHTED CHANGE ---\
    
  2. Commit and Push: Commit this change with a clear message and push it to your main branch.

    bash git add src/lib.rs git commit -m "chore: Switch to Pong demo for live testing" git push origin main

  3. Monitor the Action: Go to the “Actions” tab of your GitHub repository. You will see a new workflow run has started. Wait for it to complete (it should take a few minutes).
  4. Verify the ‘Pong’ Demo: Once the action is complete, go back to your live URL and do a hard refresh (Ctrl+Shift+R or Cmd+Shift+R). You should now see the Pong game. Run through this checklist:

    • Initial Scene: Two paddles and a ball are correctly positioned.
    • Ball Physics: The ball moves and correctly bounces off the top and bottom walls.
    • Player Control: The Up and Down arrow keys move the left paddle.
    • Paddle Collision: The ball correctly bounces off both paddles.
    • Scoring and Reset: When the ball goes past a paddle, the score updates, and the ball resets to the center.
    • UI: The score is displayed correctly at the top of the screen.

Troubleshooting Common Deployment Issues

If something isn’t working, don’t panic! This is a normal part of deployment. Here are the most common culprits:

  • Problem: Assets (images, sounds) are not loading (404 errors in the console’s Network tab).

    • Cause: This often happens if asset paths are incorrect for the GitHub Pages subdirectory structure. For example, an absolute path like "/assets/ship.png" will fail.
    • Solution: Ensure all asset paths in your Rust code are relative, like "assets/ship.png". Your current setup should already do this, but it’s the first thing to check.
  • Problem: A blank screen appears, and the browser console shows errors about the WASM module failing to load.

    • Cause: The path to your JavaScript bootstrap file (bootstrap.js) or the generated files in the /pkg directory might be incorrect in your index.html.
    • Solution: Make sure your index.html script tag uses a relative path: <script src="./bootstrap.js" type="module"></script>. Verify in the Network tab that bootstrap.js, the _bg.wasm file, and the glue .js file are all being loaded successfully.
  • Problem: The GitHub Action fails.

    • Cause: This is usually due to a typo in the deploy.yml file or an issue with one of the commands.
    • Solution: Go to the “Actions” tab, click on the failed run, and inspect the logs. The output is very detailed and will show you exactly which step (Install wasm-opt, Build Project, etc.) failed and provide the error message.

A Project Completed

Congratulations! You have successfully built, tested, optimized, documented, deployed, and now verified a complete 2D game engine from the ground up. This is a monumental accomplishment that showcases a deep understanding of Rust, WebAssembly, software architecture, and modern development practices. You have not just built a game; you have built the factory that makes games.

This project is a significant portfolio piece. Share the live URL with pride!

The Journey Ahead: Possible Enhancements

Your engine is a powerful and stable foundation. Should you wish to continue building upon it, consider these exciting challenges:

  • Player Respawn Logic: For Asteroids, implement a system to respawn the player after a few seconds, provided they have lives left.
  • Level Progression: Add a resource to track the current level. When all asteroids are cleared, spawn a new, more difficult wave.
  • A Main Menu: Implement a new game state (PlayState::MainMenu) and a UI system to display a start screen.
  • Sound Effects: Trigger explosion and thrust sound effects from your systems using the AudioManager.
  • Advanced Physics: Upgrade your AABB collision detection to circle-based collision for more accurate asteroid physics.

Further Reading

  • Debugging WebAssembly: A collection of tools and techniques for debugging WASM code, which can be different from debugging standard JavaScript.
  • Automated Testing with wasm-bindgen-test: For future projects, learn how to write automated unit and integration tests for your Rust-WASM code that can run in a headless browser as part of your CI pipeline.
  • Advanced GitHub Actions: Explore features like caching dependencies to dramatically speed up your build times or setting up separate workflows for running tests.