Games using Blazor Webassembly on itch.io
During GMTK 2022 Game Jam I decided to try to make a game in Blazor Webassembly. I've been using this alot lately for work and wanted to stay in C# mode ... I like Dart/Flutter a lot and it would have worked, but I've been trying to do things in Blazor lately to see how they really compare.
Of course I could have used any other random game engine, and would have been alot more successful in the game portion of this challenge. But that wouldn't have taught me very much about making real things with Blazor,
Last year I used Flutter (with more success) although after the game jam I quickly ran into performance issues with rendering more complex scenes that made me less interested in using Flutter/Dart for games.
TL;DR
How to get a blazor webassembly game to run on itch.io
- In the index.html headers use <base href="./" />
- Hardcode the router in app.razor to always load the Index page in the Found and NotFound cases
- Unresolved - SkiaSharp.Views.Blazor doesn't run on web browser except on Windows
Day One - Friday
I didn't have anything resembling a game to start with for Blazor, so I found something that looked reasonable on GitHub https://github.com/aventius-software/BlazorGE
It had at least 2d graphics, keyboard input and a game loop. Also as a bonus it had Entity-Component-System which I've been reading about but never used in practice. In retrospect, that's probably the most successful part of this game jam for me. Not this specific ECS implementation, but just using one to make a game ... I liked almost everything about that process.
Looked at the sample games included and started a new project ... no title yet ... spoiler alert ... actually turns out there will never an interesting title, or story, or characters, or anything approaching the artistic portion of game making.
I think my first mistake was right at the beginning. On the project template I picked "ASP.NET Core hosted" and "Progressive Web Application" I never use either of these with Blazor normally and don't know why I decided to do both this time.
I guess I was thinking of multiplayer ideas I would never get to and thought I could put some AI and networking in the ASP.NET API controller. Although I knew fully I had nothing resembling that much time. In retrospect, not selecting the ASP.NET core hosting would have forced me to recommend a Python standalone server to host the files (which I eventually suggest)
During development there really is no difference between the ASP.NET core hosted experience and the regular one.
Selecting PWA I think was a big mistake. It seems to trigger a lot of weirdness around caching and Chrome. Normally Chrome is the flagship browser for me, but something about the PWA service worker is weird and getting Chrome to not cache things was exceptionally difficult. Often it will just not try loading anything at all and end up with a partially loaded application that cannot interact. Actually using the offline mode and installing the program does work and ran way better under Chrome, but that is insane thing to expect anyone to do. I don't think I will ever use the PWA setting again.
BlazorGE does what pretty much everyone is doing right now with custom graphics in Blazor. Marshal thru the Javascript introp to run canvas commands in batches. For a simple custom user interface this works fine and the Blazor Javascript interop is pretty nice and keeps the JS development to a minimum .
However, Blazor graphics via interop is not really viable for a game. I ran into framerate issues very quickly and decided we abandon that sinking ship ... in the morning though because by the time I found a viable alternative it was getting late.
Day Two - Saturday
SkiaSharp had a recent build that includes Blazor View and the sample application worked right away. So I throw away BlazorGE.Graphics2D and swap in SkiaSharp. Actually probably just should have thrown away BlazorGE at this point in the process but instead I kept most of it and hacked the GameLoop to shreads to make it work in place.
Oh fun fact for unknown reasons editing the javascript files used by project libraries wasn't working. I gave up understanding why and ripped them out and put them top level in my game project. Even then I sometimes had issues with changes being seen by Chrome. Very weird and frustrating and I blame PWA somehow :) really I fought with this the whole time but couldn't find a root cause and too many real problems to chase down.
SkiaSharp is really nice running in Blazor Webassembly. I never got close to its performance limits ... I'm sure I will in the future but for this it was full screen graphics rendering at 70+ fps even in debug mode. Also it feels like a real graphics library. The Javascript interop ones always feel lame and half done (because they are and because HTML Canvas API is not that great to begin with)
Day Three - Sunday
Enjoying learning how to use Entity Component System. Even without knowing anything about it, its pretty easy to see how to use it to put the pieces of a game together. Also SkiaSharp continues to be low friction way of doing game graphics. I did not get very far into the game portion, didn't push Skia very hard at all. So this experiment really didn't provide any insight into how viable this is for something bigger. I basically only proved what I already suspected that Blazor + javascript interop wasn't going to work ... its actually way worse that Dart/Flutter
But I am running out of time. I just barely get the main game mechanic working and realize I have zero chance to implement the game I was trying to make and I start to see how what I have could be playable. However, using someone else's partially finished game engine that I know nothing about ends up being a bad decision when there is no time left to figure things out. Just getting screen changes and end of game detection working takes too long and there was no time for adding scoring ... so basically there is no point to the game :)
I stop working on the game when it seemingly and a welcome screen, main screen, pause mode, end of game and restart. No scoring but its still the minimum viable thing we can call a game. And I have 30 minutes left to upload ... I knew from the previous year's Flutter experiment that I was going to have difficulty getting it to run on itch.io.
Of course it doesn't work. Not even close. It's always the way the Base HREF works in these iframe, random CDN file hosting environments. Blazor is really dependent on the base href being just right. I switch to no base and retry. Files load but the application doesn't and with the stress of the time crunch I don't see the reason why. I abandoned the web version and just upload the windows server which includes the web version files. Its really a terrible way to submit the game but there was no time left and wanted to submit something.
Testing what I uploaded afterward I realize the full impact of the PWA mode and how inconsistently it reloads. Switching to the Python server works so much better. Should have just included the web files and those instructions as my original last ditch effort.
Itch.io starts crashing almost completely at the 25-30 minutes before deadline, so I call that the end of the Game Jam.
Day Four - Monday
There is of course no way I'm going to let this failure be the end. Today I am going to get a Blazor Webassembly game to upload and run correctly ... at least that is what I thought when I woke up.
Step one fix the file loading. The problem is definitely what to use as the Base HREF. On servers I control I always leave this as "/" or something static, or if it needs to be dynamic then I just rewrite it as I'm serving the index file. On itch.io we have the issue of not being able to predict what it will be ahead of time and not being able to change it.
The solution that works in Flutter is to remove it completely and use <base href="" /> which of course I tried but didn't see why this wasn't working. I didn't trust the web assembly loading and the PWA caching in Chrome was giving me fits.
I copied all of the code into a new Blazor Webassembly project without the server or PWA and really got the exact same result when uploading to a new itch.io project. The files get loaded to what presumably is a global incremented file batch id ... I tried to time it a couple dozen times, change the index.html to have a base href to the CDN with the right batch number (blindly guessing how many batches itch.io will use between my upload attempts) The closest I got in about 10 minutes of trying was within 2 and I'm still really curious if this would have worked if I guessed it perfectly.
I went back to the blank <base href="" /> and found a clue. When I copied the code to a new project I didn't set the body background to black ... this left the blazor loading tags visible and a real error message. Or more precisely the default content when the Blazor routing fails, Its something ridiculously vague like "File could not found." Which incidentally I had noticed on Sunday before the deadline but didn't associate it with my code (which of course I hadn't written this part since its in the default Blazor template) and thought it was related to the application files not loading correctly.
So the first issue was the Blazor Router ... which in its documentation states that Base HREF has to be included and end with a "/". I didn't really need the router or multiple pages for my game, so I just hardcoded it to a static route in both the Found and NotFound case. There is probably a way cleaner way of doing this.
<Router AppAssembly="@typeof(App).Assembly"> <Found Context="routeData"> <RouteView RouteData="staticRoute" DefaultLayout="@typeof(MainLayout)" /> </Found> <NotFound> <RouteView RouteData="staticRoute" DefaultLayout="@typeof(MainLayout)" /> </NotFound> </Router> @code { private RouteData staticRoute = new RouteData(typeof(RollTheDice.Pages.Index), new Dictionary<string,object>()); }
This solution almost works ... however, there are still problems with how dynamic resources are loading. The final solution needs to also include a <base href="./" />
Such a simple solution
However, this is only the beginning of the real problems. Now that the game is running from a web browser, I try to run it on my phone and from my chromebook. Both fail to run even though it works on my windows desktop.
I think it is something with SkiaSharp.Views.Blazor but other than proving there are definitely some platforms the webassembly runs on and some that it doesn't, I don't have any clue yet where to start looking for the cause. I thought web assembly was by its nature platform independent so don't understand what is causing this to be "seemingly"
I'm a little paranoid that posting the web project publicly will be an issue for the GMTK so I'm waiting until the voting is over ... unless you've read this far and really want the link and password I'll give it to you :)
Leave a comment
Log in with itch.io to leave a comment.