James Randall Musings on software development, business and technology.
C# / Blazor Wolfenstein - Part 4 - Loading Assets
Code

Before I can start to render things I need to load a whole bunch of assets:

  • Wall textures
  • Sprites
  • Fonts
  • Miscellaneous images
  • Maps

On my first pass at a Wolfenstein engine I was learning as I went about the various asset files. Unlike Doom with its WAD system Wolfenstein wasn’t built to be so easily moddable and I encountered a variety of scenarios that caused my code to twist around like a twisty thing. However as this is my second go at an engine I’d like to think I’ve got a better handle on things and on an approach - which famous developer said you should always plan to throw your first version away?

On that first pass, for the graphics, I ended up doing a mixture of extracting the assets to PNGs and loading them that way but also, for wall textures, loading them directly from the Wolfenstein VSWAP file. At some point I’d like to revisit things and load everything dynamically but for this C# version I’ve taken the easy route of extracting all the assets as PNG files. There’s a handy cross platform tool called Slade that can be used to do this extraction easily.

The maps are contained within two files MAPHEAD and GAMEMAPS (with extensions of .WL1 and .WL6, the former being the freely available first set of levels and the latter being from the full game - needless to say as the game still sells I’ve only extracted and included assets from the .WL1 files, though the .WL6 files work if you have them). The actual data itself is double compressed using “Carmack encoding” and run length encoding. When you’re downloading things on a 9600bps modem or distributing on floppy discs every byte matters!

The extracted images and the map files can be found in a folder named assets (surprise!) in the wwwroot folder of the Blazor project. They are loaded during initialization into a record called AssetPack. There’s nothing particularly exciting here. I load the bytes through the handily pre-configured HttpClient (thoughtful touch in the Blazor template), use the excellent ImageSharp to decode the PNGs and store the resulting uint arrays (if you recall each pixel is made up of 4 bytes: red, green, blue and alpha) along with the dimensions of the image in a record called Texture. The maps themselves are simply loaded as byte arrays with no further processing for the moment - we’ll get to that in the next part.

As an interlude - although I think Blazor has some significant downsides one of the neat things about the approach Microsoft has taken to getting .NET running in the browser through WASM is that you, as a .NET developer, can continue to use the packages and libraries you know and love. Its painless and remains a familiar experience. I can definitely see the appeal for C# shops - particularly those focused on internal systems. My F# version ran in the browser through Fable which, on the other hand, transpiles F# to JavaScript to run in the browser and so you have to understand the JavaScript ecosystem to a degree - you’re not actually writing .NET code which can be confusing when you first start out with it. Blazor and Fable have almost orthogonal pro’s and con’s.

With the assets loaded a quick test of rendering seemed to be in order - this is pretty basic, we’re basically updating the uint based framebuffer from the Texture records:

private void RenderTexture(Texture texture, int x, int y)
{
    Enumerable.Range(0, texture.Height).Iter(row =>
    {
        var targetY = y + row;
        Enumerable.Range(0, texture.Width).Iter(col =>
            _buffer[targetY,x+col] = texture.Pixels[row,col]
        );
    });
}

public uint[,] UpdateFrameBuffer(AssetPack assetPack)
{
    RenderTexture(assetPack.Walls[0], 0, 0);
    RenderTexture(assetPack.StatusBar.Grin, 70, 0);
    RenderTexture(assetPack.Sprites[0], 140, 0);
    return _buffer;
}

I guess my use of Enumerable.Range rather than a more typical for construct might look strange but as far as possible I’m endeavouring to use side effect free constructs and a functional approach where I can. In any case this gives us the following delicious output (I’ve chopped some whitespace out):

Rendered output

It’s worth noting that we will make very little use of RenderTexture going forwards, I’ve really just popped this in here to test the basic asset loading. Our raycaster will render the walls and is constantly scaling textures based on distance and our sprites will also be scaled based on distance and clipped based on a depth array calculated during the ray casting.

In the next part I expect to be decoding the maps which in the F# version makes use of an unfold. I’m not yet decided if I’ll take the same approach in C# or implement this in a different way. Once we get past this we can start to move on to the bit everyone is interested in: the raycasting renderer. This probably all seems quite methodical so far. Trust me - my original attempt at all this was far less so.

The code for this part can be found here.

If you want to discuss this or have any questions then the best place is on GitHub. I’m only really using Twitter to post updates to my blog these days.