Hello world! I’m taking a vacation and working on a 7 day rogue like game. Well, not strictly 7 days. I did do some pre-work getting a basic console-like engine going in Game Maker Studio after miserably failing at getting any console goodness to work on windows. Yesterday (Day 1), I started working after lunch, from my pre-work I had a player moving around a room with walls, line of sight field of view, colored lighting, and an object following the player around.
The first thing I worked on was level generation. I went with a simple method I’ve been calling “Carve and Paint”. Starting with a level full of un-carved blocks and carving out rectangular rooms, then carving out linear hallways between each new room and every pre-existing room. Then I paint in details such as lights and creatures. Super standard no flairs pseudo cave room thing generation. However it did uncover quite a large problem.
My debug room was only 10×10(100 blocks); the full level is around 60×40(2400 blocks). My lighting and visibility checks were causing multiple minutes per frame. So I had to spend quite a lot of time optimizing. My first thought was – eh, lets get this out of script and compile down to LLVM, but getting Game Maker’s YYC compiler working on Windows 10 isn’t well documented yet and I didn’t want to spend to much time on that route.
I looked for waste, and found plenty. I moved the light blending into the simulation update (IE only when the user interacts), that helped quite a bit, got the user turn update to multiple minutes, instead of every frame (small victories). Then I reduced the lighting visibility check from 17 ray-casts (all corners to all corners (16) + center to center), down to 5 (viewer center to target center and target all corners). This helped some too, but it was still more than 10 seconds per update. I then face-palmed when I looked at my basic AI – to tell if it could see the player I was updating the entire room’s visibility based on the creature, then checking if the player was visible. Instead of just checking, the 5 ray-casts between the player and the creature like a sane person. That got the player update down to about 2 seconds. Playable, but terrible. With the game in a pseudo-working state profiling came in to save the day.
Intuitively I expected the ray-casts to be eating most of my compute time – NOPE! Never trust your intuition when optimizing, you are not a computer. I found that my general cover solution was not efficient enough because I assumed I was checking visibility one at a time. Disabling non-vision blocking objects and enabling them once again for each objects visibility check. This was eating up 90% of my time. Only 2% was ray-casting. I duplicated my visibility script and took out all of the object filtering, moving it up to the calling function doing visibility for all objects in the scene. So by fixing up that terrible algorithmic issue, I eliminated 2399 of 2400 passes and everything ran great and I could start making game play.
First order of business was to get combat working. I created a Fighter component and slapped it on the player and all of the enemy creatures. Before, getting to far I added UI to show player health so I can see things working outside of the debugger. Then I created a basic “thing hits other thing” function and started messaging out to the debug console. Realizing that players usually see those sorts of messages I hooked up a basic logging console in the UI, messages now go to both places. Add in death and XP gain and I almost had a game. XP UI and leveling up restoring player health came in next. Everything was working great until I started balancing combat to be challenging instead of creature slaughter prime time. When the player died the game crashed spectacularly.
Technical design theme for the ages – never assume you will have anything available. Treating the Player object as a permanent singleton was great for rapid prototyping, but I had to go back and shore up a few dozen places that assumed the player was in the scene. After that it was time to explore my world and slay some monsters, but I kept getting lost in the ever shifting field of view.
Remember that hallway on the other side of the door your near? Easy right? Well, now expand that to 2400 tiles you may not be able to see. Navigation was terribly difficult. Fortunately tracking where you have seen before fog of war style is technically simple. In a few minutes I had a new ‘fow_memerable’ flag on objects and if you had seen them before they would stick around forever in a gray unlit state to act as your memory for you.
All in all, day 1 went extremely well – I got farther along than I had hoped for and I am very excited for day 2’s progress. First thing on the menu for day 2 is: Breakfast!