A Space-Shooter Game on the Web with C#, WASM, and Uno Platform

Growing up as a 90s kid, I played many space shooter games, which gave me a profound love for arcade-style games. This prompted the idea of building my own game, only this time; I wanted my game to run on the web! The challenge was, how? This article shares some insights on my choice of framework and how I made a 2D space shooter game with C#, WebAssembly, and Uno Platform.

To keep this article short, I can’t cover every detail of how I built this game so here’s the link to my GitHub repository if you wish to dig a little deeper:

Why Not Unity?

We all know that these 2D games are easily made on Unity, but they aren’t responsive on the web. You get a WebGL player on your browser screen, a fixed-sized area where you can play the game. It can go Fullscreen, but that’s not genuinely responsive.

Also, Unity WebGL content is not supported correctly on mobile devices. It may still work, especially on high-end devices, but many mobile devices are not powerful enough and don’t have enough memory to support Unity WebGL content well. It’s excellent for native game development but maybe not for our particular scenario. So instead of using Unity, I built a little game engine targeting WebAssembly with Uno Platform, which renders the game on any device and is responsive to any screen size.

For a person who writes a lot of C#, I found that Blazor and Uno Platform are the most suitable ones to fuel it on WebAssembly. Microsoft backs both open source projects, each has excellent tooling support in Visual Studio and VS Code, and is professionally supported.

So Why Uno Platform?

Uno Platform isn’t technically a game development platform, nor is it trying to be. But in its essence, it’s a great performing UI framework for WASM, and here’s why:

  • I use a great-looking set of UI components that I do not have to build from the ground up. The control library is vastly rich. You can use Fluent, Material, and Cupertino visual styles, which makes Uno Platform a unique control library. I’m talking about how much investment Microsoft has made to build the UI components for WinUI3. You get to use them on the web now.

  • Uno Platform UI controls are theme aware, so no additional work is needed for light and dark mode implementation. If you have set dark mode on your computer, the app will automatically adapt to that in real time. This also works flawlessly, even in mobile browsers.

  • I get to use pure HTML5 elements where I want and utilize the power of CSS. I can make any XAML framework element an HTML element with a single attribute. This gives me a hybrid UI component that lets me use all the HTML and XAML properties of a single control. This is simply something unique.

  • Good documentation is available on UI controls, with some examples and uses cases on the Uno Platform website. The controls are technically WinUI3, so you can use the WinUI3 controls Library app from Microsoft Store as a reference.

  • I get to use C#, and Uno WASM targets .NET 6, giving you the goodness it brings with blazing fast execution times, parallel thread handling and thread safety, LINQ, and almost everything that the .NET stack brings to the table. I am talking about executing functions summing up to thousands of lines of code in milliseconds in the browser!

  • Great JS interop. You can write custom JavaScript functions that you can invoke from your C# code and vice versa. These functions can be run in a single UI component or a global context.

  • Works great in Visual Studio Code and Visual Studio 2022. You get good Uno Platform Solution Templates to start with. Great build times and debugging capability. You can even debug your JavaScript code directly in Visual Studio, not just the C# bits. And pretty good logging support is provided too.

You can AOT compile, which significantly increases the app’s performance by at least three times while increasing the size of the app a little bit. But compensates you with the performance it brings. I am talking about ~60 FPS here!

About Uno Platform

For those new to Uno Platform, it allows for creating pixel-perfect, single-source C# and XAML apps that run natively on Windows, iOS, Android, macOS, Linux, and Web via WebAssembly. It offers Figma integration for design-development handoff and a set of extensions to bootstrap your projects. Uno Platform is free, open-source (Apache 2.0), and available on GitHub.

What Challenges Await

It sounds great, but every framework and project has challenges and complications. Here are some of the more notables obstacles I had to contend with:

  • Wasm apps can be hosted on IIS or Linux. You can build CI/CD pipelines leveraging GitHub Actions and GitHub Pages. However, a proper guide on how to write the GitHub workflow for an Uno Wasm app is not readily available. Fortunately, I managed to write one myself that completes the job perfectly. You can check it out here: main.yml.

  • The Visual Studio IntelliSense, auto-complete feature, and code completion are a hit or miss. While writing code in XAML, you will find that Visual Studio is popping up errors while generating the code-behind code, for example, a button click event. Even if it sometimes creates the code, the generated code has missing function parameters, which you must fix manually.

  • If you run your app in debug mode or run without debugging, when you make changes to your code and hit rebuild, you will see that Uno Platform is failing to build as it can not move the DIST package. The workaround is that you have to clean the solution and build again.

  • Visual Studio will show warnings in your XAML code that a particular control doesn’t exist, especially when you build your custom controls. This is always a false positive.

  • Unlike the Uno WinUI target, there is no XAML designer support for Uno WASM yet. So for beginners, it might be a little tricky. So get WinUI 3 Gallery first and know how these controls look, work, and how to write them.

How to Play Audio?

Since this is a space shooter game, a lot of sound effects were needed to make the game feel more immersive. Some sounds, such as the lasers being fired, will be played too frequently, and I had to ensure that it didn’t eat up memory and come up with a reusable solution. Unfortunately, Uno WASM has no out-of-box media player support yet, but I managed to develop a great workaround to work with audio files.

Remember when I mentioned being able to use HTML tags? That comes into play here. So what I did is simply create a class called AudioPlayer, and added an HtmlElement attribute on top of it and now I have an actual HTML audio element.

				
					[HtmlElement("audio")]
public sealed class AudioPlayer : FrameworkElement
{

}
				
			

Now we should be able to use this to play audio files, right? Let’s write a constructor to initialize this component with some parameters which let us control the properties of the HTML audio tag.

				
					public AudioPlayer(string source, double volume, bool loop = false)
{
    var audio = "element.style.display = \"none\"; " +
                "element.controls = false; " +
                $"element.src = \"{source}\"; " +
                $"element.volume = {volume}; " +
                $"element.loop = {loop.ToString().ToLower()}; ";


    this.ExecuteJavascript(audio);
    this.RegisterHtmlEventHandler("onended", OnCompleted);
}
				
			

See how I used the “element.” It gives the “this.ExecuteJavascript(audio);” the context of execution. Now that we have an audio element in hand, we need methods to set source, play, and pause. So here we go.

				
					public void SetSource(string source)
{
    this.ExecuteJavascript($"element.src = \"{source}\"; ");
}


public void Play()
{
    this.ExecuteJavascript("element.currentTime = 0; element.play();");
}


public void Pause()
{
    this.ExecuteJavascript("element.pause(); element.currentTime = 0;");
} 
				
			

And now, we have a perfect lightweight and reusable audio-playing solution. I created a simple enum that lets me use this element without writing a lot of code.

				
					public enum SoundType
{
   BACKGROUND_MUSIC,
   PLAYER_ROUNDS_FIRE,

   // some more sound types ...
 
   GAME_START,
   GAME_OVER
}
				
			

I created an AudioHelper static class that lets me use some AudioPlayer instances on demand and play some preset audio files that I added to the Assets folder.

				
					public static class AudioHelper
{
   private static AudioPlayer BACKGROUND_MUSIC = null;
   private static AudioPlayer PLAYER_ROUNDS_FIRE = null;

   // some more audio players here...      

   private static AudioPlayer GAME_START = null;
   private static AudioPlayer GAME_OVER = null;  

   public static void PlaySound(string baseUrl, SoundType soundType)
   {
      switch (soundType)
      {         
          case SoundType.PLAYER_ROUNDS_FIRE:
          {
             if (PLAYER_ROUNDS_FIRE is null)
             {
               PLAYER_ROUNDS_FIRE = new AudioPlayer(
               source: 
               string.Concat(baseUrl, "/", "Assets/Sounds/<AN-AUDIO-FILE>.mp3"),
               volume: 0.1);
             }


             PLAYER_ROUNDS_FIRE.Play();
           }
           break; 

           // some more cases for all the sound types...   
           
           default:                    
             break;
      }
   }    

}
				
			

Now that we have this helper class we can simply use it like this:

				
					AudioHelper.PlaySound(baseUrl, SoundType.PLAYER_ROUNDS_FIRE);
				
			

You might also be thinking about the baseUrl parameter and how I got that. Well, here’s how:

				
					private void GetBaseUrl()
{
 
  var indexUrl = Uno.Foundation.WebAssemblyRuntime.InvokeJS("window.location.href;");

  var appPackage = Environment.GetEnvironmentVariable("UNO_BOOTSTRAP_APP_BASE");
 
  baseUrl = $"{indexUrl}{appPackage}";
}
				
			

The “UNO_BOOTSTRAP_APP_BASE” environment variable gives you the package id of the Uno wasm app that gets generated when you publish the app. This is needed to create a workable Url for the Audio files as we are not using a conventional resource for a XAML UI element. So we can not use the “ms-appx://” URI scheme here.

How's the gameplay experience?

If you run the game on your computer, you’ll notice that it looks like a game that was made for PC, you’ll be able to control your spacecraft with arrow keys, and the game will run Fullscreen utilizing your full-screen size. But if you run it on your mobile phone, you will see the game responds to touches on your screen’s left and right edges. You will notice a little slide effect if you look closely and feel your key press while moving your ship left and right. The nifty cool bit is that I was able to program the game to give a different gameplay experience depending on the device form factor. You will notice that the game objects on a computer will appear more prominent than the ones on a mobile device. And you get a landscape orientation on a computer and portrait orientation on mobile. I also developed some cool effects which mimic Nvidia PhysX and explosion.

I had a blast (no pun intended) building this game and implementing various game elements, such as the player’s ship firing endlessly, enemy ships firing, blasters colliding with objects, generating power-ups, and health pickups on a random time interval. Even with the game scaling difficulty and developing enemies with various attack patterns as players level up and include a boss at the end of each level, I could get all these calculations executed every 19 milliseconds inside your browser. To build something like this using C# almost entirely makes me think that a new era of web app development is approaching us, where we will be free to choose programming languages and a combination of them to create unique experiences on the web.

Follow Asadullah Rifat on LinkedIn for more interesting Uno Platform builds. Thanks for reading.

Tags:

Share this post:
Tune in Today at 12 PM EST for our free Uno Platform 5.0 Live Webinar
Watch Here