Personal Log »

Gamedev in Haskell?

So at the moment I have a couple of 8-bit projects on hold, or what we could call my TODO list, but I also have a couple of ongoing projects in modern systems. As I mentioned, there are some ideas I would like to implement before I can do it in a 8-bit microcomputer (you learn by doing it wrong).

At first I thought about finishing my little Canvas 2D engine that I wrote refreshing my Javascript. And it all was going well, until I met again an old friend: I’m not sure if what I’m doing is correct, and the performance of Canvas 2D in Linux is just not what I was expecting. It uses too much CPU, and after a bit of profiling my code and comparing with other games online, I got to the conclusion that it wasn’t me.

Rendering with Javascript

Using a CC tileset

I probably should release that engine “as is”, although I don’t know why anyone would use it.

I didn’t like the result –that could be an issue in Linux, or a recent regression–, and it didn’t matter if I was using Firefox or Chromium. I tried PixiJS, that is a nice framework that wraps all in a 2D API, so you can use WebGL instead of Canvas 2D, and it didn’t get better.

And to not make it a rant on the state of the web browsers on Linux –or in Debian, for me it doesn’t make a difference if I doesn’t work well enough on my system that means it may be like that or worse in other systems–, I also tried LÖVE, although my experiences with Lua in the past left me a bit cold.

Turns out LÖVE is great, despite spending with it only a couple of days. Good documentation, the poor performance I was experiencing with the web browsers is gone, and if you fancy going functional you have Fennel –which is a lot of fun!–.

But then –and this is my own fault– I decided to look at some libraries for LÖVE, and that didn’t go well. I should have done all myself, and I would have saved time fighting those libraries to get to something that was not really inspiring me. So I run out of energy, but I think LÖVE and Lua are worth revisiting. After all my time doing gamedev in Python struggling with performance, this felt great!

Then I remembered that I had a code base that I prepared to use C and SDL2, but never used it for anything. “This is the time!”, I thought. And that is one of my “projects to learn”, a simple JRPG that is progressing nicely –I’m currently working on some formulas and you could see my JRPG design notes (not anymore)–. I should write a post on how I’m integrating fe –a tiny Lisp-alike scripting language–.

And as I keep writing good old C I was looking for something else to do in Haskell, because I have done a couple of things, but my last project was too big and I was learning too many new things at the same time. So why not a simple gamedev project? That would limit the scope.

I have nothing interesting to show yet, because I’m still struggling to have some graphics or even an idea of what I want to do, but the SDL2 bindings for Haskell are excellent. The API has changed a bit to make it more high-level and closer to Haskell, but it wasn’t too hard to understand those changes –taking into account that I’m not an SDL expert!–.

The idea is to make something simple, closer to what I would produce in a game jam, and make it public to get some reviews and tips from friends.

So far my main struggle was around parsing JSON files –for tiled maps–, which I wouldn’t say is too gamedev, and it was a good learning exercise. At the end it was easier and cleaner than my C code using cJSON –that is pretty good–.

Anyway, I’m looking forward to have that small game ready. I’m using ReaderT + IORef and so far the functional and no variables bit hasn’t bothered me at all!

At the end this post is not that much about the title. Oh, well. As an example, this is how my map renderer looks at the moment.

-- | Renders a map.
render :: SDL.Renderer -> Map -> IO ()
render renderer (Map mapData tex) = do
  mapM_
    ( \layer ->
        mapM_
          ( \(x, y) ->
              renderTile x y $ tlTiles layer !! (x + (y * mWidth mapData))
          )
          index
    )
    (mlayers mapData)
  where
    mw = mWidth mapData
    mh = mHeight mapData
    index = [(x, y) | x <- [0 .. mw - 1], y <- [0 .. mh - 1]]

    firstgid = tsFirstGid (mTileset mapData)
    cols = tsCols (mTileset mapData)
    tileWidth = tsWidth (mTileset mapData)
    tileHeight = tsHeight (mTileset mapData)

    renderTile :: Int -> Int -> Int -> IO ()
    renderTile x y tile
      | tile < firstgid = pure ()
      | otherwise = do
          let tx = (tile - firstgid) `rem` cols
              ty = (tile - firstgid) `div` cols
              src = U.rect (tx * tileWidth) (ty * tileHeight) tileWidth tileHeight
              dst = U.rect (x * tileWidth) (y * tileHeight) tileWidth tileHeight
          SDL.copy renderer tex (Just src) (Just dst)

Considering that it is two functions –plus another one to get the SDL_Rect, but that’s not strictly necessary–, I’m quite happy with it!

Would you like to discuss the post? You can send me an email!