[GRUEDORF] Kildorf's Devlog
slimehunter84 last edited by
@kildorf The map accordion system sounds like a good time-saver. I also really like the choice of over-the-shoulder battle camera angle and turn order toward the top of the screen.
kildorf last edited by
Gruedorf Post 2022-06-15
- Skipped Gruedorf for 3 months
- Got and/or drew some real art
- Finished most of the core systems
- Lots of refactoring and bug fixes
- Built a demo of the first chunk of the game (sorta)
Hi again. In my last post I closed with the words “If it comes down to it, I’ll always choose putting time into the game over posting a devlog.” That turned out to be more prophetic than I anticipated! There’s a lot to talk about and I’m not sure how much I’ll actually manage to cover but here we go.
How’s Black Mountain?
It’s actually looking pretty good, if I do say so myself. I’ve been working on it close to every day since my last post, and it has changed a lot – looks like I’ve made 390 git commits since then. Obviously, nobody has the time for an exhaustive cataloguing of each thing I’ve done, so instead I’ll just describe where I am now.
At this point I have a demo, which seems to be about 30-60 minutes of playtime (depending on how quickly you zoom around, how much time you take to look at everything, etc etc). It’s essentially the opening of the game plus the game up to the first boss, without any of the optional side areas that will be littered around. I’ve had a few people play it, and it’s been a bit buggy but feedback has been essentially positive.
In the demo, there are no (well, almost) pieces of placeholder sketch art left; everything has been updated to some level of coloured and polished. Our cast is looking much better these days.
The world is also looking better.
A lot of the background painting there is the work of Hyptosis, which should be a familiar name around the Verge community. The small pieces that aren’t from him are from me, looking very carefully at what he painted and trying to match it. It sincerely made me a better painter, and I’m even actually starting to enjoy painting, which has never been my favourite.
The cast also has far less scribbly portraits now.
These are the handiwork of my friend Cil, who I am eternally indebted to now.
I do want to actually talk about one of the new features I added, though! I’ve got a backlog of them to talk about, I suppose, so maybe I’ll manage to work through them over time.
Spread throughout the world, there are a number of campfires. You can kind of think of these like save points, though the game allows you to save anywhere you like. A checkpoint does autosave (along with ANY map change), and it also completely restore your health and energy, and remove all adrenaline (a system in combat that I’ve added), and respawning all monsters that have been defeated. It’s sort of a reset point to let you recover from the last section of the world and prepare for the next.
Growing up (if you’ll permit me to get a bit weirdly personal for a moment) I did a lot of camping and, especially as you get older and the world starts feeling a bit too big and a bit too small at the same time, there’s something liminal about sitting around a campfire with your friends. It’s easier to talk, it’s easier to reminisce and think. This is also true in Black Mountain – the characters are (through mostly but maybe not entirely their own faults) dealing with something much larger than they’ve had to deal with before, and only have each other to lean on. When they stop at a campfire, they might have something to say.
Technically speaking, the way this works is that I have a bank of campfire scenes (built with the same scene definition stuff I described in the last post; the campfire area is actually just its own map, internally), and at any given time I can throw one on a queue of campfire scenes. Every time you rest at a campfire, it checks to see if anything’s queued up, and if there is, it will play it. At the moment, this is mostly used just to provide another scene every few campfires that you encounter, to give a good idea how it works, but the idea is that if anything in particular happens, the next time you rest the characters might have something to say about it. I’ve thought about adding something the first time a character is knocked out in combat, for instance, or it could be used to add some reflection on the part of the characters after they’ve done something big.
The important part here is that it gives a little bit of space for the characters to talk about what’s going on, and give the player a bit more insight into what’s actually going on in their heads. I want to focus on making the characters relatable, and hopefully this will go a long way to doing that.
Back to Talking
I have been in a bit of a self-imposed hermit state for a while now, trying to grind out the main core features of the game (and also on helping with the demo for Alice is Dead, a remake of the classic Flash game, that’s in the Steam Next Fest! You should go check it out!). I’m in a spot now, though, where I need to return a bit to the world of the living, and so I’m going to try to start talking more about my game and what I’m up to, and that sort of thing! At a bare minimum, I’m going to try to get back on that Gruedorf horse and start posting here on a weekly basis again.
kildorf last edited by kildorf
- Tweak movement code to make it a little less slippery and to have less “jittering” at corners.
- Add foot step sounds.
- Add a screenshot button.
- Actually implement status effects in battle.
- Implement a couple of localization-related things.
- A bunch of refactoring.
- Small bug fixes and various tweaks.
The merchant in Shoda’s Brook, aka “Uncle Reg”. Violet’s actual uncle, and just an overall good guy to Kiel and Omen.
Another week of work! Got some new art from Hyptosis to put into the game to make the merchant’s area in town a little more cluttered.
I tweeted about this on Monday, and there’s a fun video to watch there, but to sum up, the player-controlled character now makes footstep sounds as they walk around. This is defined as a default for each map, plus I can define areas within the map that override that default. As an example, in the video in the tweet, the map’s default is the “grass” sound, with extra areas defined for dirt and wood.
I had actually done a little bit of investigation on this a while ago, but had decided to lay it aside for a while. It’s pretty straightforward, really, just play a sound effect every now and then as they’re walking, but my implementation has a few niceties in it that I want to talk about:
Timing: Godot has an AnimationPlayer built-in which lets you define animations on a timeline. One of the things you can add as a “keyframe” on the animation is a simple function call, so rather than trying to keep the steps synchronized correctly with timers or whatever, I just add a function call to play a footstep sound to the frames where the character’s forward foot strikes the ground. There are other ways you could accomplish this with an AnimationPlayer timeline, but I chose this so I have a little more control over which sound plays, exactly.
Override Areas: The areas for the sound effects are pretty straightforward StepEvents which push the new sound into a list when you enter the area, and remove the sound from the list when you exit it. The first pass of the implementation was a simple variable set onto the character’s footstep data, but this caused some problems when crossing from one area to another – it’s not stable whether the area-exit event or the area-enter event will fire first, even if you manage to get the edges of the two areas to line up exactly. If there’s any overlap it’s even harder to know if the area-exit handler should be touching the value or not. Having an array is a little more complex (and slightly less performant) but it is a lot easier to get right.
Sound Selection: The sound that actually plays gets selected based on whatever is at the front of the footstep list; if it’s empty, we use the default. Each type of footstep sound has a bank of 8-12 sounds to provide some variety, and the footstep code remembers the last one it played, and (so long as we have more than one footstep in the current category, because this isn’t my first time at the rodeo) it will ensure that we never play the same sound twice in a row.
An actual conversation I had on Monday with my friend E, who has been helping me a ton with testing the game:
Kildorf: I just implemented status effects in Black Mountain so the giant wasp monsters can poison you now
E: You put poison in your game?
E: Why don’t you just fill it with escort missions where the npcs you have to protect have 3 health too
Status effects are a staple in JRPG combat systems. I had sketched out some of the system (and, truthfully, was already using exactly one status effect for the Defend action in combat) before, but I didn’t yet have the infrastructure for building attacks that set status effects. I started, of course, with the classic “poisoned” status, which damages you each turn. The wasp-looking monsters in the game (which are, by the way, named “Rumble Bees” because they are stinging insects that are ready to fight) use a Bee Sting attack, which have a flat 50% chance of poisoning you when they hit.
I had an idea of something that wasn’t Black Mountain that I wanted to prototype out quickly, just to see how fast I could. Since the idea used a top-down sort of RPG movement, I wanted to just reuse what I already have in Black Mountain. I think it’s pretty good! I started a new Godot project and started copying things over and found that, despite my best intents, a lot of it needed to be reorganized or deleted or whatever. Instead of doing a bunch of refactoring in some throwaway code, I decided I’d go do some refactoring in the Black Mountain codebase instead!
This is actually pretty important to me in the longterm as well, since (and this will be no surprise to anyone who has talked to me about game development for more than five minutes) I have a lot of games I want to make, including some direct sequels to Black Mountain, so it’s something I really should just take a bit of time to do every now and then anyway.
I never did actually get around to prototyping that other idea out.
Localization is something that is relatively straightforward to include from the beginning of your game, and incredibly difficult (or at least, tedious and annoying) to retrofit in. I’m trying to opt for the first approach, even though I don’t actually know whether Black Mountain will ever be localized. It’s just good practice anyway. So I took a bit of time to flesh out some of the stubs I had in place with TODO comments saying “when I want localization to work, do this”. It’s still not quite at the point that I could truly drop in a translation, but it’s definitely closer.
Controllers in Godot
Controllers are a bit of a nuisance.
They’re a great way to control a lot of games, especially if you have vague ambitions to port to a console. Godot does a lot for making it easy to use controllers, but there’s a sticking point that I encountered and I wanted to write up how I dealt with it. I’m not going to claim this is The Best Way or anything (there are certainly some… oddities to it) but Google told me that I’m not the first to encounter this problem, and there wasn’t any solution around that I felt was acceptable.
To sum up the backstory: A long time ago, a playing card company named Nintendo released a video game console known as the Famicom, later dressed up to look like the most boring piece of consumer electronics imaginable and released as the Nintendo Entertainment System for the North American market. Perhaps you’ve heard of it. It had a controller with an A button on the right and a B button to the left. Sega, their competitor, released a system, the SG-1000 on exactly the same day (July 15, 1983), but it only had one button on the controller so who cares – a couple years later, they released the Mark III with a two-button controller, labelled 1 and 2, with 1 on the left and 2 on the right. This is where it all went wrong.
To skip a few things historically-speaking, there are a number of common, popular layouts of controllers now (and none of them are made by Sega): the XBox layout with A on the bottom, the Nintendo layout with A on the right, and the Playstation layout with symbols instead of letters and inconsistent usage of those symbols internationally.
Images clipped from Gamestop’s store pages
Godot does a great job of supporting these different controllers, but it does so at a hardware/physical level. When you set up your input map, you are mapping an action to a physical button, whatever colour/label that button has. This is great if you’re relying on muscle memory that happens to match exactly what you think is right.
The input mapping wizard in Godot.
Thing is, I’d like to, at some point, show buttons and the like that actually match (moreorless) what the controller actually looks like, and I would also like to match what the controller actually implies about what the correct confirm/cancel buttons should be. I also happen to have a Nintendo-layout controller (specifically an 8BitDo SF30 Pro) hooked up to my PC, so I get to see how many other games fail to do this at all. Anyway, seems pretty straightforward though: Godot lets you remap input via code, so just detect if the connected controller is a Nintendo layout controller and remap if necessary, right?
Unfortunately, there are only two pieces of information you get from Godot: the controller’s GUID and its name. This is great for displaying the name, potentially, or differentiating controllers manually… but I don’t really want to have to sit down and figure out the GUID for every possible Nintendo-layout controller that has ever been released (or will be released in the future?) So I decided to dig into where exactly Godot gets its gamepad information. Or, rather, dig BACK in to it, since I had reason to look this up in the past when I was testing someone else’s Godot game and discovered that my controller simply wouldn’t work at all with it.
Turns out, Godot gets its controller information from the SDL_GameControllerDB, a community-sourced database of controller GUIDs, names, and mappings. Godot has a copy of the main TXT file there in its own source, located at (at time of writing, anyway) https://github.com/godotengine/godot/tree/master/core/input. There’s also another file in there for Godot-specific mapping, mostly for browser compatibility, as near as I can tell? In any case, the mappings here are used internally to make the input system work, but does not appear to be exposed to game script at all.
So, I just grabbed my own copy of the TXT file and stuck it in my game source. It’s easy enough to parse, so at load time I now parse my own copy of the controller DB, and create a table in-memory of the mappings. I also, at launch, read the mapping for “all joysticks” out of the input map, store them in an object, and then remove the mappings. For every joystick that is currently connected (and again, any time a new joystick is connected), I re-add specific mapping for that joystick to the input map. Before I do that, though, I check the joystick’s GUID against my in-memory version of the SDL_GameControllerDB, and IF it has a map of
a:b1(which I’m using as my signal that it’s a Nintendo layout) and apply any modifications needed. This also happens to clear up the fact that I had not correctly set up my input mapping before anyway so you could only ever use the first controller plugged in. All of this, of course, falls back to the default XBox layout if they GUID isn’t in the table, so it’s not going to blow up if someone plus in something unrecognized, and to support new controllers I should just have to update my copy of the .txt file.
This also means that when I get around to putting in custom input mapping (which I consider a critical feature still to be done), I can use the same controller DB to determine what labelling should be displayed, and same anywhere in the game I want to display buttons in tutorials or whatever.
There’s still more to be done, of course – I’m not doing any remapping for Playstation controllers (since I don’t have one I can easily test with) and I know if I want to support Switch Joycons they’re a bit of a mess, so I’ll have to figure that out. Regardless, what I have in place should be pretty easily extensible for whatever controllers I need to give a little special care.