Dark World
What is this game?
Wow, already the third game-jam game where semicongine has been used! Ludum Dare 55 was happening last weekend and, for the sake of it, I even delayed publishing the Steam store page for Cosmic Breakout. This time I was working alone, but the engine is now in much better shape to quickly get something up and running.
Theme
This events theme was Summoning, but this time I only touched the theme slightly. The reason for it is, that this game jam was a great opportunity to write a first prototype for my next game. My personal goal was to explore the concept of light vs. darkness.
Art style
Being not an artist, I tried to go for something simple and chose to use a grayscale art style. While I would have preferred to try colorizing the game in the end, there was simply not enough time. Also, there was quite a bit of good feedback regarding the grayscale art style. That always makes me happy, because “I am only a programmer ;)”.
Gameplay
While thinking a bit about what to write here, I started to recognize that the process of how the mechanics evolved throughout the development was actually an interesting experience on itself. So, here are some thought on that.
I knew from the beginning that the game should contain the following aspects:
- The player somehow has to “light up” a “dark world”.
- There is a controllable player character.
- The perspective should be isometric.
Since I wanted to focus more on mechanics than world/level building, the starting point was a big, flat plane as the “level”. Now, for the “lighting up” part, my inspiration definitely came from Minecraft’s lighting system. Adopting the torch placement and the blocky world style immediately felt right. Now the only thing left was to add some challenge for the player to overcome. One of the most common ways in video games to add player challenge is to add enemies. So I did that and added enemies (black circles) doing a smooth random walk. The “conflict” part of the mechanics should really reflect the light-vs-darkness idea. Therefore, I programmed the enemies to flee from light and to take damage from light. Regarding the threat to the player I did not come up with a great solution, but it worked: The enemies would simply throw little “grenades” in the direction of the player, when in range. Allowing enemies to do melee attacks did not feel right, as I was unsure how to combine chasing the player and avoiding light consistently.
One little extra that increased the experience a lot was adding a small shadow to the grenades. This allows a very good estimation of the trajectory and helps the player avoiding grenades. I am pretty sure the inspiration for this came from Super Mario 64, where they added a small shadow to Mario’s character because playtesters found it difficult to make precise jumps.
Also, adding audio improves any game by miles, so I always do that for my games, no matter what scope.
Lessons learned
- Isolating state or code modules that are independant of each other is always
a good thing. But for some reason I have always been writing the “one, big
gameplay loop”. This always ends up messy. When working on the code for this
game I had a brilliant (i.e. trivial) thought: I can actually have as
many game loops as I want: Have on in this function, have one in the module
over there. It doesn’t matter. So, I ended up with a really easy way to
handle different kinds of screens (here in pseudo code):
The idea here is, that the *Loop functions returnlevel = 1 while mainMenuLoop(level): if levelLoop(level): level = level + 1 creditsLoop()
true
on success andfalse
on failure or quiting. Actually, the return values (might also be enum or something else) and the exact composition of the control structures (while, if, etc) do no matter. What matters is that different kinds of “screens” can be treated in a different loop in a completely different place in the code base. This is very good, because different screen always need different state to track. With a single loop, that means piling up variable declarations in front of the main loop body. Maybe this seems a minor others, but to me it feels like a curse got lifted now. No more clinging to the “one and only main loop” :) Also, it feels very “compositional” and easy to change. - I definitely need to go over the whole sRGB handling in Vulkan (and my engine) again. Somehow I still don’t get how and where values are interpreted as sRGB. My assumption so far was, that that value that the shader writes to the framebuffer is interpreted to be sRGB (because that is what is specified when creating the swapchain renderpass). But this seemed not to work correctly, when I manually set the vertex colors to sRGB values.
- Material types are often shader specific. There should be an easy way to infer the material type from a shader.
Credits
Sam (Diadem Games)
Development status
Released
Release date
Q2 2024
Links
- Linux Download
- Windows Download (Might give a malware alert, but there is no virus, for sure. Don’t trust random heuristics from the internet ;)
- Ludum Dare page