Documentation of the haskell code for Defect Process, a 2d hack n' slash game on Steam. The full source code is available on GitHub.
[Brief Overview] |
[Cross-platform Notes] |
[Additional Links]
gameMain :: World -> IO World gameMain world = do world' <- updateWorld world gameMain world'Since this is a purely functional language the way this works in general is that we use modified copies of data, rather than updating the data in-place.[1]
src/AppEnv/Types.hs
type for the full definition.
updatePlayer :: Player -> IO Player updatePlayer player = do player' <- ... useful things ... return player'This is fine until
updatePlayer
needs to change anything else in addition to Player
, e.g. spawn a particle effect or pull a switch in the level. Adding individual things to the return type gets tedious quickly, e.g. updatePlayer :: Player -> IO (Player, [Particle], Level)
.
updatePlayer :: World -> IO World updatePlayer world = let player = _player world particles = _particles world level = _level world in do player' <- ... useful things ... particles' <- ... useful things ... level' <- ... useful things ... return $ world { _player = player' , _particles = particles' , _level = level' }This also works but now looking at the
updatePlayer
type it's no longer clear what it changes. Any field of World
could be modified so it's much harder to reason about what's happening.
update
functions into two parts, think
/update
:
thinkPlayer :: Player -> [Message] thinkPlayer player = [ ... message to move the player ... , ... message to make a particle effect ... , ... message to flip a level switch ... ] updatePlayer :: [Message] -> Player -> IO Player updatePlayer messages player = do player' <- ... update player according to messages ... return player'which is used as follows:
gameMain :: World -> IO World gameMain world = let player = _player world particles = _particles world level = _level world playerMessages = thinkPlayer player particlesMessages = thinkParticles particles levelMessages = thinkLevel level allMessages = playerMessages ++ particleMessages ++ levelMessages in do player' <- updatePlayer allMessages player particles' <- updateParticles allMessages particles level' <- updateLevel allMessages level return $ world { _player = player' , _particles = particles' , _level = level' }This allows different game entities to communicate with each other while keeping their own
update
functions clean.[2] The full implementation is more complicated but the core idea is as described above, see src/Msg/
and src/World/Main.hs
for the relevant code.
src/Window/
. It's kind of a basic 2d engine, not that much interesting to talk about.
--nonmoving-gc
garbage collector, which avoids stop-the-world behavior that could cause stutters. That flag isn't critical however, the default --copying-gc
behavior is also fine in testing (< 1 ms pauses).