Introduction
If you have seen some of my other blog posts you know I always find an excuse to talk about games made in F#, so once I saw James Randall's talk at fsharpConf about his F# port of Wolfstein 3D I knew I had to talk about it in this blog post.
Overview
This port can be run on desktop or in the browser, you can play it right now in the browser.
Controls in both cases: Cursor keys - movement Left Ctrl - fire Space bar - action (e.g. open door)
According to the creator, the codebase mostly uses a functional style, which seems to have affected the performance in some cases. This is a common scenario we can notice in other games made in F#, that immutability seems to be a hindrance most of the time and mutability is preferred.
Let's take a quick look at some samples of code so we can a better insight on how the project has been built.
Code examples
In the module App.Game
we can see how a record is being created for the initial game state:
let private initialGameState =
{ Map = []
Areas = []
CompositeAreas = []
Level = -1
Player = {
Lives = 3<life>
Score = 0<points>
Health = 100<hp>
Radius = 0.5
Weapons = [ ]
CurrentWeaponIndex = -1
Ammunition = 9<bullets>
CurrentFaceIndex = 0
TimeToFaceChangeMs = 1500.<ms>
}
Camera = {
Position = { vX = 12. ; vY = 6. }
Direction = { vX = -1. ; vY = 0. }
Plane = { vX = 0. ; vY = 1. } }
FieldOfView = 1.
}
ControlState = ControlState.None
GameObjects = []
IsFiring = false
TimeToNextWeaponFrame = None
Doors = []
ViewportFilter = ViewportFilter.None
PixelDissolver = None
ResetLevel = (fun g _ -> g)
}
And it has a type called Game
defined in module Model.fs
type Game =
{ Level: int
Map: Cell list list
Areas: int list list
CompositeAreas: CompositeArea list
GameObjects: GameObject list
Player: Player
Camera: Camera
ControlState: ControlState
IsFiring: bool
TimeToNextWeaponFrame: float<ms> option
Doors: DoorState list
ViewportFilter: ViewportFilter
PixelDissolver: PixelDissolver option
ResetLevel: Game -> Player -> Game
}
In the App.Game
module we can also see the game loop:
let gameLoop (game:Game) (frameTime:float<ms>) =
let updatedGameState =
drawScene statusBarTextures graphics sprites game
|> updateFrame game nextLevel frameTime (fun _ -> Dissolver.create viewportWidth viewportHeight 2)
updatedGameState
let updateControlState game controlState =
{ game with ControlState = game.ControlState ^^^ controlState }
In Audio.Platform
module we can see how audio is sourced and played:
let private soundEffects =
let audioPath filename = sprintf "assets/sounds/%s" (System.Uri.EscapeUriString filename)
SoundEffect.All
|> List.map (fun soundEffectType -> soundEffectType,soundEffectType |> App.Assets.audioFilename "mp3" |> audioPath)
|> List.map (fun (soundEffectType,path) -> soundEffectType,(createOverlayingAudioPlayer path))
|> Map.ofList
let playSoundEffect soundEffect =
let audioObject = soundEffects.[soundEffect]
audioObject 1.0
let playSoundEffectAtVolume (volume:float) soundEffect =
let audioObject = soundEffects.[soundEffect]
audioObject volume
In the App.AI
module we can see how the AI has been implemented:
let applyAi frameTime (game,gameObject,newObjects) =
match gameObject with
| GameObject.Enemy enemy ->
if enemy.IsAlive then
let canSeePlayer = enemy |> isPlayerVisibleToEnemy game
let updatedEnemy, updatedGame =
(enemy,game)
|> preProcess canSeePlayer
|> updateBasedOnCurrentState canSeePlayer frameTime
|> firingOnPlayer canSeePlayer
updatedGame,updatedEnemy |> GameObject.Enemy,newObjects
else
game,gameObject,newObjects
| _ -> game,gameObject,newObjects
Conclusion
In this post we had a quick look at James Randall's port of Wolfestein 3D and some bits of code, I hope you gained some new insights on how a game can be made with F# and let's hope for more F# games in the future 🙂