Earlier this month, we had a hackathon at my current company Hubs. Wanting to escape the usual routine of web apps I took this time to write a small game.
Recently, I found myself growing fond of grid-based dungeon crawlers. People fight over the definition, but usually, it’s games where you explore a dungeon, moving tile by tile on a map and you have real-time or turned-based combat. One of the most typical titles of the genre is Wizardry, inspiring many others games in the West and Japan. Some of the more venerable readers might remember games like Lands of Lore but games of this genre are still released today with titles such as Mary Skelter or Labyrinth of Refrain.
So I decided to check how fast and how far I could go during a limited time. The hackathon was being held for around a day and a half which was very tight.
A style of game that started long ago…
My first step, even before the hackathon started was brainstorming.
- No new technology: Within that time limit, learning a new technology and showing something decent was a bet I didn’t want to take. I opted for programming the game in Godot which I am familiar with already.
- No real 3D: I discarded the idea of working in a 3D space for the same reason. I still strongly wanted a first-person view so I got interested in how old games were doing it and discovered projected 3D.
- Procedural level: I didn’t have time to really design any map or level, so I opted for simple level generation. There is millions of tutorial online, and they always give something interesting to show.
- No combat: There would be no fighting system because I would not have time to implement it.
With those principles, I wrote a little list of features and an order of implementation. Of course, I didn’t implement a quart of stuff I wanted to do, so it’s good that I already limited myself.
…and still strong today on another continent and time!
On the morning of the hackathon, I went to explore tutorials to generate dungeons. Most would be too complex or not adapted (stuff like generating thin paper walls, or very complex algorithms). I finally found one that was very easy to follow and very well written. Following it was a couple hour, and I learned the algorithm behind it and the TileMap system of Godot.
I then implemented the player character, a little arrow at first, and would make it rotate and move on the map, by just one tile at a time. I then added monsters during map generation. Upon reaching the same tile as them, the monster would be removed and you would score one point.
Good! At this point, I have already a working “game”. You can explore some level, and have to find the monsters.
I have something looking like a roguelike.
The implementation was a bit sketchy. To determine if the character would be able to move, I would get his position on the screen, translate that into the position of the tile map, and then check what is the tile at this position.
At this point, I could have stopped and expanded to have more gameplay or more various tiles. But my plan was to use all of this, just as the minimap that you see in games of this genre (when you don’t have to draw it yourself). My objective was now to display fake 3D.
When I am not crawling dungeons, I crawl the internet (which can be as brutal). While not as satisfying, you find some treasures from time to time. Today it was the dungeoncrawler.org website which holds some resources to dev dungeon crawlers. To get the projected 3D, you need to transform a texture of a wall or enemy, so it is stretched accordingly depending on different angles and distances. This seems to imply scary geometry and maths, but the aforementioned website can do it for you. What will result is a big image containing all the textured you need to create your view. This image is called an Atlas.
The final Atlas I used
However, you still needed to write an algorithm to display them on the screen and I hit some major hurdles.
First was that I didn’t really know how to use the image generated, but it came with a JSON, and with some guess and experimentation I could understand that the image coordinates were sorted in according to their depths (z) and how many tiles left (-x) or right (x). From this, I wrote a quick function returning the tiles in front of the hero after a move, in a 5 by 5 square, depending on the orientation (north, west, east, south).
Instinctively I knew that I will have to render them from the farthest of the hero to the closest, so walls in front of the hero would block the view of space in the distance (I could have calculated precisely what the hero can see, as not to render/calculate obstructed space but this was an optimization that I did not need or had time for.) My first experiment to display them on the screen was using the draw_texture_rect_region. This proved a huge mistake that made me lose a lot of time for two reasons.
The first problem was pretty dumb, it’s a draw method, which means that it will draw something on the screen. However, the screen is redrawn in every frame. If I don’t call the method every frame, it would not appear on the screen. It got me scratching my head as to why nothing would appear on my screen when it was drawn for a single frame.
The second problem was more important. I could not draw the right side of the dungeon. Indeed, if you look at the image with the texture, you will notice it only contains images of the left walls and floor.
But you just need to flip it right? Simple! Wrong!
Draw_texture_rect_region would not allow you to flip an image. I would need to flip it first. I struggled a lot with Godot documentation but decided to just duplicate the texture file, flip it on paint and call it day. This did not work as expected, because all the coordinates that you get from the JSON accompanying the file would be wrong.
Then I just need to calculate the coordinate from the left side of the file! However, this did not work as well. I am still unsure why, but at this point, I had lost a lot of time on this, it was the evening and my brain was giving up after working for so long, so intensively. I was very bummed out and the game was looking like this.
After a rough first day!
It was time to go to bed, but I discussed this with my very kind girlfriend about this issue and she suggested using something else in Godot that would flip the image. This would mean rewriting the way I rendered those images, but at this point, I had no other choice. I went to bed and started the next day.
Upon next day, I decided to implement the idea, in a very ugly way:
- First I load the texture using Godot.
- Second I get the image data.
- I flip it.
- I create a new Texture with that image.
- I create a new TextureRect (an object showing a texture on the screen in Godot) with that image.
- I place it at the right place.
This method creates a lot of back and forth, loading a big amount of complex objects in memory for every move. Fortunately, it’s a hackathon so performance is not an issue. There is also only a limited number of objects at the same time on the screen given the nature of the game. This is not the “correct way” but given time constraints, I take the trade-off. This however allowed me to save computation by setting the TextureRect only once and letting Godot handles the drawing (which much be more optimized than my earlier drawing function.) I finally get it done and it works! I have the right side of the dungeon!
At this point, there was not a lot of time left, and my brain was about to burn out, so I didn’t launch myself on anything grandiose, but just little things that make the game feel better.
- Fixed an issue where front walls would not be displayed
- Added a background to make the view less ugly (Actually a dithered screenshot of hubs portal).
- Added a sprite for the monster.
- Added music when you enter the dungeon.
The final result is still quite glitchy but I am still proud of going that far in such a short time.
There is still an issue where it does not display the side of a wall that is not directly next to you, but this is minor compared to my earlier ordeal. All in all, it was great fun! I forgot how thrilling it is to push yourself to do the maximum in a small timeframe. I’m also thankful for Hubs to let us duck around from time to time because it’s the best way to improve yourself and find new passions. Will I ever pick up that project again? Probably not! Next time, I will learn proper 3D and my dungeon will be a collection of cubes. It won’t be looking as retro as games of old, but it will allow for interesting effects and ideas.
See you next time, for my next article about how my Visual Novel transpiler turned out (spoiler: Pretty alright)!