/fsharp-wolfenstein

An F# port of the 1992 classic Wolfenstein 3D

Primary LanguageF#OtherNOASSERTION

F# / Fable Wolfenstein

Try it in a browser here: https://www.jamesdrandall.com/wolf3d/

A work in progress F# port of Wolfenstein 3D. Wolfenstein was a formative experience for me! I was big into my PC gaming and the shareware scene when it came out and I remember not knowing anything about it but downloading it from a BBS, installing it, and then excitedly running round the house proclaiming "THIS CHANGES EVERYTHING" over and over again. No one cared.

Screenshot

I had a go at writing a raycaster myself, in C, after I'd played this back in 92/93 and I did get something running but it was both slow and fish eyed. It remained an itch to be scratched and so this F# port seems to have somehow happened.

I've generally tried to keep things fairly functional with performance taking a bit of a hit in some cases as a result.

The engine will run in the browser using Fable or as a native app on Mac, Windows and Linux - I've only got a Mac to hand at the moment so that's all I've tested. In theory should work on other platforms...

The majority of the core code is shared between the two implementations though I didn't set off with multi-targetting in mind. I wrote the Fable version first and so if you look at the desktop code you will see I'm essentially writing some lightweight JS runtime (DataView predominantly) implementations.

Some of the code is rather scruffy and their are some arguments about ints and floats! Was learning as I went... Now its pretty much solved will slowly refactor into something cleaner over time. The Open GL code in the desktop port (I don't use OpenGL to render the game scene, rather I use it to render a single raycast texture) may look excessive - to expedite things I simply picked up some code from a virtual console I've been working on on and off for a year or two and used that.

I aim to at least get the AI working (and you can see the seeds of that in the code) and I will probably add the Wolfenstein health / info bar. Beyond that... don't know. Other fun projects to be built!

Prerequiresites

On Windows install OpenAL SDK from https://www.openal.org/downloads/

Running

Controls in both cases:

Cursor keys - movement Left Ctrl - fire Space bar - action (e.g. open door)

Browser

Compatible with node 16, 17, 18.

To run the game:

  • Navigate to the browser folder
  • Install dependencies: npm ci
  • Start the compiler in watch mode and a development server: npm start
  • After the first compilation is finished, in your browser open: http://localhost:8080/

To build it for distribution

  • npm run build

Desktop

You will need .net 6 installed to run this. To run the game:

  • Navigate to the desktop folder
  • Enter: dotnet run

Implementation / File Format Notes

Things I found...

Orientable Sprite Layouts

Orientable enemies (i.e. those that can face away from the player - e.g. guards, dogs etc.) have the following index layout (using a compass to define things as shown below):

   N            G
W     E
   S            P
Sprite Faces direction
0 S (towards player)
1 SW
2 W
3 NW
4 N
5 NE
6 E
7 SE

Their are then a number of repetitions of this block for various animation frames:

Block Description
0 Stationary - block of 8 sprites as above
1 Running frame 1 - block of 8 sprites as above
2 Running frame 2 - block of 8 sprites as above
3 Running frame 3 - block of 8 sprites as above
4 Running frame 4 - block of 8 sprites as above

The number of running frames can vary by enemy type - the above example is for a guard but a dog is different:

Block Description
0 Stationary - block of 8 sprites as above
1 Running frame 1 - block of 8 sprites as above
2 Running frame 2 - block of 8 sprites as above
3 Running frame 3 - block of 8 sprites as above

So their are 40 sprites for the position and movement state of each enemy.

Their are then an arbitary (per enemy type) number of death sprites and firing sprites.

Sounds

Sound Index Description
0 Huh guards
1 Dog bark
2 Door close
3 Door open
4 Player machine gun
5 Player pistol
6 Or is this the player pistol?
10 Chain gun
11 Machine gun
20 Mein leben
21 Guard pistol
29 Double bark
32 Yeah
33 Shite
34 Aieeee
35 Ohohaw

Player -> Enemy Bullet Hit Detection

Considered a couple of approaches for detecting what, if any, enemy the player will hit if they fire. I could have cast a ray or rays but given the renderer has just walked the object tree and done the depth sorting and clipping I instead take note of the front most sprite that is in the centre of the viewport and apply a tolerance to it.

Asset Extraction

Didn't really want to process WLx files by myself so I used a couple of tools to extract the assets.

Firstly Wolf3dExtract to retrieve the components and then ImageMagick to convert textures from PPM to PNG format.

Screen Layout

Wolfenstein ran at 320x200. The actual 3D viewport max size is 304x152 - the viewport is surrounded by a 1 pixel black border Their is a 3 pixel border teal border at the top of the viewport above the black border and a 7 pixel border to the left and right of that black border.

Sprites

To generate the sprites use Slade to open the VSWAP.WL1 or VSWAP.WL6 file and select all the files beginning with SPR. Right click, graphics, export PNG.

Then with ImageMagick:

mogrify -extent 64x64 -gravity center -background none SPR*.png

License

I've included the Shareware assets in this public repository. And all copyrights of the original game clearly remain with the copyright holders and all props to the geniuses behind the original game (I have no idea if Carmack, Romero, et al. are still copyright holders).

All my efforts (so the F# code and surrounding build systems) are under the MIT license.