[GRUEDORF] Kildorf's Devlog
-
Gruedorf Post 2021-08-22
- (SQ2) Fixed boats
- (SQ2) Started dungeon interior
Slow Week
I actually didn’t get much done this week. I did finally get a bit done today, though, and figure out how to have multiple persistent copies of vehicles, and started on the interior of the first dungeon. Trying to figure out how to get switches to animate correctly now.
Here’s a picture of Lakeside, the town where you will get your first boat! That is way past the dungeon I’m now working on, I’m not really sure why I did things in that order.
-
Gruedorf Post 2021-08-29
- (SQ2) Made a switch work
- Back to Black Mountain!
- Moved on to the next map, got it mostly roughed in
Nearly Forgot!
Whoops, nearly forgot to post. No pictures this time, just hastily written words.
SimpleQuest 2 Switches
On the SimpleQuest 2 front, I got a wall-switch to open a door to work. At least, it switches back and forth, it doesn’t open the door yet. This shouldn’t have been as difficult as it was but it took some time for me to realize that the asset pack I was using has the switches laid out in a way that doesn’t work for RPGMaker. There is not (as near as I can tell) a way to just play an arbitrary sequence of frames; in fact, sprites are generally packed into a sheet with seven other sprites, and the actual animation they play is pretty baked in. I’m sure it can be changed with plugins etc etc but anyway…
Point is, the switches in the (absolutely great) Mighty Pack are presented as just a series of three frames for different colours. To make it work I had to copy them around and turn it into a full sprite with the three frames (plus an extra that I made just because) are mapped to the switch sprite’s 4 directions. Then, to play the switch flipping you just animate the sprite “rotating” in place. This is how chests and doors in the built-in assets are animated as well!
Back to Black Mountain
And then I decided I really wanted to work on Black Mountain again. I’d kind of churned out of drawing on one of the cave levels, because it’s a big map and it’s got a lot of details. I gave myself permission to leave the rest of the details… un-detailed, and move on to the next map (the next floor up, which is an abandoned/ruined town that was carved into the middle of Black Mountain). It’s not quite as big (the interior floors of the Black Mountain cave system get smaller the higher up you get, since the mountain is roughly conical) and has gone pretty quickly. It’s got a bunch of interior maps (since there are “buildings”) but those should mostly be a screen or two, so no biggie – and I’m going to be skipping the unimportant ones for now. (Unimportant is defined as “you don’t need to go there to complete the story”.)
It feels good to be back on the game. Hopefully I can keep making decent progress! Have a good week!
-
Gruedorf Post 2021-09-06
- Roughed in all of the big maps
A Holiday
Whoops. Normally I post on Sunday but Labour Day made me kind of miss that I needed to post yesterday. But anyway… working on more maps! I’ve got rough drafts of most of the “big” maps — the exteriors and the big dungeon levels. There’s are still interiors to draw but the end of the marathon is in sight. Then I need to do more marathons!
Anyway, yeah, have a good week!
-
Gruedorf Post 2021-10-17
- Spent several weeks in a useless funk
- Roughed in the final map (of the game, not the final one to rough in)
- Rebuilt an old one-hour compo game
Da Funk
I’ve been pretty burned out on just about everything lately. It hasn’t been fun, and it’s been even less productive, so I haven’t really had anything to say on here for a while. I can’t really say it’s “over” either, but I’m hoping that I’m starting to see the end of it – or at least maybe I can find a way to be productive anyway. I know that sounds like I’m just a workaholic, but truth is that this stuff is what I do to feel good about things. If I’m not working on some creative project for too long, things get dark, so I’m trying to avoid going and further down that road.
Sorry! I know that’s vague and depressing, so I’m hoping I can talk about more positive things now!
The Climax
I actually did manage, last weekend, to do some more rough map drafting. I drew out the final area of the game, the peak of Black Mountain itself. Those of you who played the original Journey to Black Mountain might remember this as the place where Kiel finally meets the Phoenix! I don’t really want to completely spoil things at this point, but that is still true in this version of the game, but the specifics of it are very different.
I’ll need to review notes and such to be sure, but I think I have one more required dungeon area to draw out, a number of interiors for the abandoned town area of Black Mountain, and then a handful of miscellaneous things to finish or fix up for various maps, and then I can actually build out the beginning-to-end maps for the draft version game. After that will be starting to fill in the story and actual stuff in the maps.
A(nother) Detour
A couple months ago I talked about how things were wearing me down, and how I was working on SimpleQuest 2 as a way to deal with some of the burn out on my large game. Perhaps it escaped your notice, but I decided to take a break from building the content out for a large game by building content out for a different large game. This is extremely on-brand for me, but also not very useful.
I was thinking back to the Crappy Games Xplosion of yesteryear, and realized that 16 years ago I could knock out a (bad) game in an hour. I have learned a lot about making software since then but I don’t think I’ve ever felt as comfortable with a game engine since Verge 3 – comfort defined as “being able to knock out prototypes very quickly” – not even the engine I built with my own two hands.
So this time, in an attempt to shake myself out of the rut, I instead opted to go in the other direction. I’m going to challenge myself to make crappy games, but quickly, and hopefully in great quantity, and I’m using Godot to do it. It’s not exactly “making progress” on Black Mountain but as you may have noticed I haven’t been making any progress anyway so I may as well try to hone my skill and familiarity with the tool that I’m using.
To that end, I rebuilt one of the games I made for the Crappy Games Xplosion: Tank Attack. I started it… a little over 12 hours ago at the time of this writing, but I also got groceries and cooked dinner and various other things today, so I think in total I spent 4-5 hours on it. I tried not to stress about making it good, but it is pretty similar in scope to Super Tank Attack, the version I released after spending a few extra hours on it. It’s a lot harder, but “balance” wasn’t really what I was aiming for.
You can play Tank Attack (2021) online. Keyboard and mouse required.
So far the highest score I’m aware of is 730. Join us on Discord to let me know how you did! Or don’t, either way is fine.
Not sure what I’ll have to talk about next week, if anything. I’m aiming to have another tiny game to play, but we’ll see. Have a good one!
-
Gruedorf Post 2021-10-24
- Built a Breakout clone
- Built a platformer in the style of old DOS games
Breaking Out
I built a clone of Breakout. You can play it online.
This one actually has a win condition! If you beat three levels you are done! I feel like this game is a little more even in terms of difficulty than Tank Attack but honestly I’ve never been especially good at Breakout so what do I know?
In terms of features, it’s pretty standard barebones Breakout. Each brick is worth 100 points and takes one hit to break; you get a new life at certain score thresholds, etc.
I actually finished this one earlier in the week, and decided to just get started on the next. So uh…
Zombies, Bullets, and Platforms
I also built a platformer! You can play it online, too.
It features a hero wearing a bright magenta shirt, so I think we can all agree it is an instant classic. This one also has a win condition – it’s got four levels, and if you beat all four, you win! If you feel like it has no particular theming to hang the narrative together, you are correct. Thanks for noticing.
Feature-wise, it’s got your standard jumping and shooting, limited health and ammo, and collectible score/money. There are five different enemy types, all with different behaviour. There’s an overworld map for level selection, including gating different areas of the map behind completing a particular level. Dying in a level just shunts you back to the overworld map (and resets your ammo and money to what it was before you entered).
I had a bunch of ideas for other things to add, and I’ll probably come back to it some day, but I felt like I’d reached the right point to stop with this one.
Thoughts, Feelings, and Lessons Learned
As hoped, making these small games is both helping me understand how I like to build stuff in Godot, and also giving me small drips of dopamine, so that’s nice. I’m coming to realize that, while after trying a bunch of different engines, Godot ended up being the one I hate the least, I’ve still been fighting it and trying to stick with building things how I want to instead of what is easy and natural with the engine. I’m also finding out that I may actually be faster with Godot now than I ever was with Verge, if I actually co-operate with the engine.
I wanted to collect some notes and thoughts about the things I learned this week. Mostly, I’m writing them down to solidify them in my own head, but you never know, maybe this will help other people out too. Almost all of these come from building the platformer because I didn’t bother to write any notes down while building the Breakout game because I was kind of operating in a fugue state.
AnimationTrees Will Break Your Heart
I use AnimationTrees for some of the animation handling in the game, and that may be a complete mistake – multiple times, Godot went behind my back and messed up some of my entity scenes in a way that I needed to dig into the .tscn files directly to figure out what was what. It was like, the first time you added an instance of a given enemy to a level, it would be fine, but the second one would freak out in weird and unpredictable ways, and cause the first to glitch as well?
In any case, I eventually figured out that it was because (for some reason), the enemy scene was being saved with a relative path for some of its animation stuff which went to its parent and back in –
../Zombie/Sprite:frame
for instance, when justSprite:frame
would be correct. This meant it seemed to work as long as it retained its name of Zombie, but since Godot cannot have siblings with the same name, the second Zombie would get namedZombie2
and it would break, both because the second one wouldn’t animate correctly and it would be trying to animate the first as well.To fix this bug I ended up manually modifying the .tscn files. Thankfully, Godot’s PackedScene file format is dead simple.
Physics
A generic physics engine is great for getting going quickly, but it can be kind of a pain, even when it’s specifically got special handling for games like Godot’s. I’m not sure it’s more of a pain than writing it all myself, but it does mean you have to sneak around behind its back sometimes. I lost a couple of hours of development time to trying to get my moving platforms to stop being pushed around by the player and bullets.
Think About Testing
Even if it’s a pain, it’s worth putting a little extra time into making it so you can run a level scene directly (instead of using F5 to run, you use F6). If you have autoloads that set things up, this will be a little annoying, but ultimately it will be worth it – especially in a game like this where levels are quite discrete and some levels act as “gates” to the others.
Tool Scripts
Tool scripts in Godot are when you mark a script (with the
tool
keyword) as running in the editor as well as in the game. Since Godot’s IDE is actually written in Godot itself, this means you can do whatever you want within the editor, with the full runtime engine available – including the current scene tree and the editor UI.They’re something I’ve dabbled in a little with Godot, but not as much as I’d like. They seem incredibly powerful for custom ad hoc stuff. In this case I added a couple of small things like making sure (some of) the enemies would face in the right direction to make it easier to see how they would interact with the player, and drawing a bounding box in the level based on where its camera bounds were set up. I need to try to do more with them!
Miscellaneous
- Cheat codes: you’re going to want them for testing. Maybe I left them in my games when I shipped them?
- Consider using separate tilesets for your foreground and background tilemaps. Maybe then I’d stop drawing them into the wrong frigging layers.
- Draw all your sprites facing the same direction instead of picking a random one each time, maybe?
Next Week
That’s it for this week! I’m not sure exactly what I’ll tackle next, but I’ve been adding ideas to a list whenever I think of another game I’d like to remake or a problem I’d like to tackle or a style I’d like to try, and it’s getting pretty long already!
I probably WON’T try to do two games next week. I was having a lot of fun (… most of the time) working on the games this week, but I don’t want to burn myself out even further – if I only get one done next week (or if I take two weeks to build the next one!) I’m not going to be too hard on myself.
-
Gruedorf Post 2021-10-31
- Made a spaceship game
Space
I made a game that I call Gravity Ships, and as per usual, you can play it online.
It’s something like if you took Asteroids and made it with a Spacewar! engine instead. There are planets, asteroids (which spawn over time), enemy ships (which are suicidally stupid), and a black hole. Everything is affected by gravity (except the black hole) – for convenience’s sake I don’t bother to calculate the pull from anything other than planets, asteroids, and the black hole, but the only truly stationary thing in the field is the black hole at the middle of it. It also has a mini-map, which isn’t particularly exciting other than it served as a way for me to learn the way custom drawing for nodes works in Godot.
There are plenty of things that could be added to the game: a score, shields, smarter enemies, etc. I kind of had a vague idea about needing to do something at the planets, either blow up things on the surface or what-have-you, but honestly I got pretty sick of working on this game. The numbers involved in gravitational equations aren’t intuitive in any way, so it was a lot of guess-and-try-and-guess-again development. The playing field is (by design) large and mostly empty, and it’s actually not especially fun to fly a space ship with “realistic” controls, and really this isn’t even especially realistic – there’s no fuel and the mass changes that result from it, rotation isn’t force based, etc etc. All in all, surprise surprise: more realism isn’t necessarily more fun!
As harsh as I am on it, I do think it could be fun with some more work. I’m not really interested in putting more time into it now, but the future is unknown! Perhaps I’ll come back to it, or scavenge it for parts later on.
Happy Halloween, and see you next week!
-
Gruedorf Post 2021-11-07
- Messed around with tool scripts and plugins to get a better feel for them
- Built a blocky first person shooter
Castle Escape 3D!
I have harnessed what was previously only hypothetical: a THIRD dimension for video games. You can play the result, Castle Escape 3D, online.
Tooling
I actually wasn’t going to make a game at all this week. I figured since I’d made 4 in the last two weeks, I could take a breather for the week and instead delve into looking at building tools for Godot. I messed around a bit, figured out how to add novel UI to the editor, and then felt the itch to just make a game instead. I still learned quite a bit, and realized that I had been using plugins/addons incorrectly for Black Mountain. So, toss that on the pile of things I need to clean up when I get back to it.
Escape the Castle
I was a kid at just the right time for Wolfenstein 3d and Doom to be a huge deal. We also had the shareware version of a lesser-known cousin to those games, Catacomb Abyss, which captured my imagination – look, I’ve been a Tolkien nerd since before I could read – in a way that nazis and sci-fi demons didn’t quite manage. It certainly isn’t a better game, but I do love fantasy shooters, and I feel like they’ve always been an underserved market.
Anyway, so I had the 3d Catacomb games in mind a lot while I was building this game. I decided that, for the sake of speed, I would build the levels (which, in the end, was only one level) out of blocks, just like that era of game. I actually re-used some of the tiles I drew for the platformer a few updates ago, but drew lots of new art as well, probably more than I should have. The hand (based on a photo ref on my own hand outstretched toward my computer monitor) is probably some of the best work I’ve done for these games so far.
I ran into a few hiccups. In no particular order…
The HTML export didn’t work out as well with this game. It’s been great with the 2d games, but this one seems to have a bit of hitching that isn’t present when running on the desktop, and there were some texture glitches. Nothing game-breaking, but unfortunate. I also didn’t actually spend any time trying to troubleshoot those, so maybe it would be easy to fix.
Importing textures in 3d defaults to importing them in “Video RAM” format, and filtered. This probably makes a lot more sense when you’re not using pixel art for textures. The real lesson here is, if you’re going to be importing a lot of art that matches in style, use the “Set As Default for X” feature (under the Preset menu in the Import tab). Don’t just keep redoing it manually like a dumbass when you’re trying to build a game in two days.
GridMap is the “3d tile map” primitive that Godot offers. It’s actually really cool in a lot of ways but in a few specific ways it is not so cool. Its UI is kinda bad; in particular, its tile selector does not show you tile IDs anywhere, even though their integer ID is the only way that the rest of your game is going to be able to refer to them.
Similarly, it’s really cool that you can set up a scene with a bunch of meshes and some collision data, etc, and then import that as your MeshLibrary (“tileset”) for use with GridMap. What is less cool is that it centers every one of those meshes, even if they are not supposed to centered in their grid space. If you double-click the MeshLibrary .tres file after the conversion, you can manually modify the offset for the each mesh – but next time you create the MeshLibrary by converting your scene, it will throw that away. Also, if you rename one of the meshes in the source scene, it will not get rid of the one from before and will instead just add a duplicate with a different name. (It’s possible you could leverage this second bad behaviour to work around the first? It’s annoying that you have to, though.)
I use billboarded sprites a fair bit in this game. It took a while to get the rendering just right, and it turns out that you probably want bill boarding turned on (I used y-locked bill boarding throughout this game, I just prefer it) and you also want to set the Depth Draw Method to “Opaque Pre-Pass”.
And So On
Not sure what I’ll work on next. Maybe I’ll actually take a week off from making games? Maybe I’ll actually make something smaller and easier like I keep intending to, rather that making something more complex each week? Who knows. Have a good one!
-
Gruedorf Post 2021-11-21
- Built a 2d block-pushing game with mediocre puzzles
Grab the Gems
You can play Grab the Gems online. – at least in theory. As I’m writing this my webhost seems to be having issues so if it doesn’t seem to be loading very fast, try again later I guess!
Grab the Gems is the name of a number of games I’ve made in the past. I come back to it and make “a new version” every now and then, usually as a way to try out a new programming environment or tool. I might come back and revisit this one some day because there is actually a certain amount of (very dumb) “lore” that I did not include in this game at all.
This particular version of the game is based heavily around simply Sokoban-style block pushing puzzles. Every level has a door that needs to be opened by pressing down a switch, usually with a block. Blocks that fall in the lava turn into platforms you can walk on. There are, in fact, gems to grab, but there isn’t… actually any particular point to getting them.
But Why?
So if I didn’t get around to adding in that stuff, why did I make it? Especially when it took two weeks!?
Well, first of all, you’re not the boss of me. I’ve been kind of exhausted and needed to pull back a bit on this stuff to keep from burning myself out too much.
Second of all, I specifically chose it to learn more about two areas of Godot that I want to get better at: controlling asynchronous behaviour and building editor tools, so I focused on those instead.
Asynchronous Behaviour
The original Grab the Gems was “turn-based” in the sense that it was a QuickBasic game where nothing happened at all until you pressed a key. So far, most of the Godot work I’ve done has been with real-time stuff, and I wanted a simple-ish game to dig into to try and nail a turn-based loop. It SEEMS like it should be easy, but it turns out that the standard way of doing things in Godot leans very heavily into these real-time systems. Even in a more fluid movement/real time game, I want to have a better understanding of event loops and such because I’ll need them for things like cutscenes and textboxes.
I already have some of this stuff kind of worked out for Black Mountain but it’s always felt a bit hacky and hand-wavey. I was hoping to figure out a more solid way of doing it! I had perhaps moderate success. I feel like I’m coming to a better understanding of it, mostly, but I still don’t feel like I know how to update the systems in Black Mountain appropriately to make things more controlled and predictable. I’m sure I’ll figure it out, though.
Editor Tools
Since I kept talking about it, I decided to use this as an excuse to try building some tools and then actually use them.
One of the problems that I’ve had a number of times in making these games is getting things in the right place in the scene hierarchy. In this case, all of the “entities” in the level (the player, blocks, switches, gems, etc) all live in a YSort node (called “Entities”) sandwiched between under and over tilemaps.
Standard Godot UI gives you a file explorer. As you can see I’ve got all of my Entity .tscn files tucked into a scene/entity directory. I can grab any of those .tscn files and drag them into the currently edited scene to place one of those entities. That’s all well and good, but it will create the entity as a child of whatever node you happen to have currently selected. I want them all to land under Entities, but if I happened to just be dragging around “Gem” to reposition it and forget to click on Entities in the scene tree again, if I drag another Gem.tscn onto the map, it’ll become a child of “Gem”. That will look correct, and might even work most of the time, but I don’t want to just have my nodes strewn all over the place willy-nilly.
It’s also a bit of a pain, because I want to grab the .tscn files, not the .gd files. Yeah, I could put the script files in a different location than the scene files, but I prefer this layout.
So I set out to make myself a palette of entities that will just always drop them into the right place.
And I did. Getting it working probably took half of the total development time, in the end, but it DID actually make creating levels faster. It was 100% not actually worth it for this project, since I only bothered to make three levels, but it’s definitely something I’m going to be cribbing in the future for other projects. The code isn’t quite just drop-in and it works, but it shouldn’t be hard to rework as needed. So that’s cool!
The basic parts of it are:
- Create a new plugin
- Plugins are very cool but plugin development is a pain in the ass. I had to frequently reload the editor, since an error in some code might lead to having UI panels left over that I can’t ever clean up.
- Another annoying thing is that when you change the code of a loaded plugin, it appears to reinitialize all your member variables but it doesn’t re-run _ready() and so on. If you have a saved reference to something, it just throws it away, and now you get a bunch of errors.
- It is incredibly obnoxious that Godot’s editor doesn’t save your window size and position. I am using a single large 4k monitor, and Godot really wants you to use it maximized. There are apparently workarounds you can set up with a plugin, so I’m going to look more into that, but it’s offensive that I have to.
- Instance some UI and add it to the docks
- Add a refresh button
- This saved me from SOME reloading at least.
- Get a list of all the .tscn files in the scene/entity directory
- Generate a preview of each one
- Previews are built into Godot! Accessing them is awful. You must have access to the (global but NOT singleton) EditorInterface object to get access to the (global but NOT singleton) ResourcePreviewer object, but you can ONLY get the EditorInterface in the actual EditorPlugin class, NOT in the UI scenes that it instances.
- I used a disgusting hack to get around this – since the EditorInterface is global for all EditorPlugin objects, I just instantiate a new EditorPlugin and get it from there. This trick is documented in Godot Q&A but it is another thing that is offensive to me.
- Also worth noting that once you DO get the ResourcePreviewer, you can call queue_resource_preview() on it – this takes a path, an object and function to callback to once it has the preview ready, and a “userdata” for you to pass info through. It turns out that queue_resource_preview() will not actually call that callback, if you pass null as the userdata. Pass 0 instead.
- Add drag and drop to preview buttons
- This was surprisingly not too bad, but I didn’t bother to use the built-in drag interface. I just used button down and button up signals on TextureButton controls.
- Make UndoRedo work
- This was kind of annoying to get right. You’ve got to supply both “do” and “undo” functions, and in this case since I was instancing a new version of the packed scene, I needed to make sure the lifetime on the instance was right. Instead of calling queue_free() on it for undo, you just want to remove it. Instead, instance the scene before you set up the do/undo methods, and use add_do_reference(instanced_scene); that way it’ll be around for redo to add it back to the scene, but it’ll get cleaned up it falls out of the UndoRedo history.
I’m probably forgetting some pieces. Like I said, it was probably the hardest part of this whole project. It’s very nice to have it working, though, so it may just be some pain I have to put up with, in order to have a nicer time of building the rest of the game.
Have a good week!
-
Gruedorf Post 2021-11-28
- Got back to work on Black Mountain
- Imported all of the maps I’d drawn but not gotten into the game yet
- Fixed up some stuff I just hadn’t finished
- Some code refactoring
- Started drawing another cave
I had a week off so I got quite a lot done this week, and it wasn’t JUST playing Heretic.
Importing Maps
I had a big backlog of maps that were drawn but weren’t functional in the game yet. I sat down and got those all in. It’s semi-rewarding to do but still just kind of monotonous. You can now (I think) walk from the beginning of the game to the end! There’s no story in there yet though, just the maps, and some of the bits which are going to be light puzzles just let you walk through instead.
A Second Look
It also let me see that I just kind of… had stopped halfway through some of the maps. It also let me take another look at the flow of the maps – I’ve corrected one pretty layout issue and have some thoughts about fixing some others. I’m not super proficient at map design, so it’s something I’m trying to keep an eye out for. It also gave me the opportunity to try out the flow of re-organizing a map in this “drafting” format. It’s… okay. It’s certainly not as easy as changing around a traditional tile map. I don’t think I’ll change my mind about how I’m building the maps for this game, but it’s interesting to see what works well and what doesn’t.
I also came up with some ideas for how to make building the levels a little less onerous. I’ll talk about that more when/if I get around to doing it.
Code Refactoring
I’m trying to apply bits and pieces of what I learned from making six games in the last month to clean up some of the code. All things considered, I’m still relatively new at using Godot, and still firmly in that awkward phase where it takes way longer to make anything (big) than it takes to get better enough that you develop a pretty hardcore disdain for the code.
Happily (?) I intend to leverage this code for other games so it’s worth spending some time cleaning it up. The biggest thing I did was just small housekeeping things.
- I was inheriting from
Node
for a lot of helper objects that should have just inherited fromObject
. If it doesn’t need to go in the scene tree, it doesn’t need to inherit from Node. - I was registering a lot of things as custom types unnecessarily, with the primary goal of being able to refer to them by class name. You can just use
class_name
for that –class_name Foo
will makeFoo
available globally as a reference to that class.
I’d like to take a crack at making my movement system better, maybe. First I need to figure out whether I had a good reason for some of the decisions that I don’t like now. I also want to try to iron out some wrinkles in some of my basic map primitives, especially cliffs and climbable areas.
A New Cave
I have one cave that I need (you have to move through it to get through an area) to draw, that I just hadn’t yet, so I finally got started on that.
Some Pictures
It’s been a while since I’ve posted any screenshots from Black Mountain so here we go. The maps are not “finished”, they’re missing bits and pieces like trees, etc.
-
@kildorf this is sexy and I love it (and by extension: you)
-
Gruedorf Post 2021-12-05
- Finished building a multilevel cave
- Added a “scatter” type
I couldn’t devote quite as much time to Black Mountain this week, but I still made some decent progress.
A New Cave
I have drawn most of the maps in roughly “chronological order” in terms of the order that the player encounters them. There is one particular cave (which you use to bypass a roadblock) which I just kind of… skipped when I was drawing maps earlier. Not entirely sure why, but it’s wired up now.
Scatter
And in making ths cave I just… could not bring myself to draw millions of rocks again. One thing that is a drawback of the way I’m doing maps is that it’s not like I can just play around with tiles until a cave looks as messy as I want; I have to draw each thing individually or go through some extra effort to set them up as entities. Entities are, internally, KinematicBody2Ds with their own collision information, they can move around, they have the potential to react to being “activated” by the player, etc, and I don’t really want to have hundreds of them in a map (even if, really, at least on PCs it probably won’t make that much difference).
Worse, since most rocks are tall enough to occlude the player character, I often have to draw each of those rocks in two pieces – behind and in front of the player. They also must be at least a whole tile wide or else you just can’t do that split correctly. This is also why I can’t really just toss them into the tileset. Trees also have this problem, but they’re big enough that they naturally split into a “stump” and the trunk/foliage. It’s kind of annoying to place two tiles per tree, but hey, it’s way better than the days when a tree was like 25 individual tiles on different layers!
Anyway, I decided to go ahead and add a new kind of thing; I only really need the art, they don’t need to move or have their own built-in collision or anything, but they do need some offset information and I want them to be easy to reuse. I called them “scatter”, which is a term I cribbed from tabletop miniatures. They’re the random crates and barrels and other junk that you can just kinda toss around the map to make a room look lived in. I had previously updated my entity palette (which I wrote about earlier) to allow for multiple sections based on subdirectories of my entities directory, so I just threw in special casing for a “scatter” subdirectory.
When you drag a scatter piece into the map, it finds (or creates, if necessary) a YSort node within the existing sprites YSort. I didn’t have to put them in their own subnode, but it keeps my scene tree a lot more manageable, since I can collapse that section.
I’m actually really happy with it. I’ve dressed up the cave pretty well with some boulders and some stalagmites, and it was significantly less work than drawing that all bespoke.
Miscellaneous Notes
- Remember that if a YSort node is a child to another YSort node, they will sort their children together.
- Godot allows you to enter a formula in its UI edit boxes – if you have a rect and you just want to move it to the right by 3 tiles, each 24 pixels wide, you can just edit its X member and write, e.g.
240+3*24
and it will convert to312
for you.
That’s it for now. Next I’m going to be rearranging some of the Black Mountain maps a bit. I realized that I need to reorder the flow of the 2nd and 3rd level of the mountain to introduce doors and keys in a better order. Along with that I’ll need to actually draw some interiors (which I feel like I’ve mentioned every post for the last three months). See you next week!
-
Gruedorf Post 2022-01-02
- Neglected to post a devlog for a month
- Completed an interior area
- Reworked some level flows
Happy 2022, everyone!
The last month has been kind of hectic, and I was sick for some of it (no, not with that). I’ve managed to make some progress but haven’t gotten around to writing about it.
The Second Floor
The second floor of Black Mountain (which I mentioned a few months ago) is an abandoned town carved out of the rock. When pilgrims journeyed to visit the phoenix that lives atop the mountain, they would stay here. Unfortunately, like the rest of the interior, it has fallen into ruin and is now empty.
Or is it!? (It wouldn’t be a very interesting area if it was empty so you can probably guess that it is not.)
Some Miscellaneous Code Fixing
Since I am, fundamentally, much more of a programmer than an artist, I sometimes try to sort of side-step into getting some work done by tackling a small code issue or whatever. I’ve done that a few times, so my Entity Palette works a lot better now. I was having a lot of trouble getting Godot to actually generate preview thumbnails for the placeable scenes, so I read a lot about Viewports and the like and I’m now simply instantiating a little copy of the scene into the toolbar. It then also does some scanning of the scene to figure out how to automatically scale things so they fit into the “button” properly.
Getting the math right on the scaling was surprisingly challenging! Not because it was especially difficult, but my brain had temporarily stopped working, as it does.
Scatter Was Worth It!
Adding the “scatter” objects that I talked about in my last post was completely worth it. Much of the “dressing” of the rooms in the screenshot are, in fact, scatter, and while the bits themselves need some work, the workflow with them has been great, and it’s freeing me from having to hand-draw a ton of rocks, which I’ve gotten pretty sick of drawing.
Unfortunately, there’s not a ton more to talk about despite the long period between posts. Working on these maps is time-consuming, but I don’t want to show everything off. I’ll have to work even harder to figure this out when I get to actually filling things in with story. That sounds like a problem for later-Kildorf, though!
Anyway, hopefully I’ll be back in a week. Have a good one!
-
Gruedorf Post 2022-01-09
- Drew a bunch more interiors
- Did a bit of a style test on a sprite
The last week has been pretty productive in terms of “work done” but honestly not a lot interesting to talk about.
Interiors
I finished the remainder of the story-important interior maps (assuming I don’t realize I missed some).
As you can probably see, these little homes are mostly built out of reusable pieces. This is because I got tired of drawing maps in the least efficient way possible! I’ve been experimenting with my scatter system to see how far I can push it and, in a pleasing turn of events, the answer seems to be “pretty far”. As an example, the smaller chairs visible in the various houses are all actually a single type of “scatter” as far as my tools are concerned, but you’re able to set the frame you want and flip the sprite, so they’re pretty convenient for bashing together a map quickly.
A Sprite
Quite some time ago I was trying out a sprite style for Geas and I drew a sprite of Kiel; just a single frame, sadly. I happened to run across it the other day and remembered how much I liked it.
Unfortunately this is made for a different resolution than I’m targeting with Black Mountain; it’s built for a 32-pixel tile (which would be about right for a 480p resolution). For Black Mountain I’m using a 24 pixel tile (because that fits a 360p resolution, which I’m using because it upscales to actual widescreen HD resolutions properly). The design is also wrong for Black Mountain; it’s an older Kiel who has gotten somewhat more confident and past her awkward teenage years.
So anyway I sat down to try to recreate a similar style, but for the smaller tile size and the right design.
I’ll probably still go back in and mess with it some, and then of course there’s the whole problem of it still only being a single still frame, but I’m glad I did it. It’s nice to remind myself occasionally that the game isn’t just going to be a sketchy red mess forever.
So What Now?
With the maps above complete, I am done the bulk of the (story-necessary) map making for the game. I’ve got two big projects left on this version of the game: Fill in the “game” bits, like puzzles and combats, and fill in the story. (After that, of course, I have the general “clean up and make sure everything’s in that I need in”.) I’m sure that I’ll need to tweak the maps as I go somewhat, but I’m hoping that won’t be too difficult. The danger with the way I’ve been building these maps, of course, is that if I need to substantially change a map’s structure I have to go back to Clip Studio and start moving things around – as I’m sure I’ve rambled about in the past, this is why I’m doing the whole “sketch” version of the game. Especially as I’ve been trying to switch to building more maps out of reusable pieces, this should hopefully not be too big a deal. So this time next week I will probably be griping about how my cutscene system has bitrot or something.
Have a good week!
-
Gruedorf Post 2022-03-16
- Made most of an entirely unrelated game.
- Released a game I wrote years ago, finally.
- Tried a couple of things in Black Mountain that weren’t definitely wins, and put them on the back burner instead.
- Made myself some handy debugging UI in-game.
- Updated and added functionality to my cutscene framework.
- Wrote and implemented (real, but draft) cutscenes for the beginning of the game.
- Investigated how to handle localization in the future, and left some proof-of-concept stuff in place.
- Reworked parts of the combat system and redrew the art as necessary.
- Added a timer that keeps track of how long you’ve played this save.
- Added the first pass on new “wander within an area” behavior for entities.
- Fixed a bunch of bugs.
There’s a lot to talk about, so this is a long one; it’s been a while. I left my full time job (which I’d been at for more than a decade) in mid February, and while I’m not exactly sure what my time looks like in the long term, for now I’m taking a break and focusing on my own stuff for a little while. This means the last couple of weeks has been particularly full of development, I just hadn’t gotten around to writing anything.
Other Games
I released a game I wrote years ago on Itch. It’s called “The House”.
You can play it in your browser.
It’s a “Halloween game” – it’s not particularly scary or anything but it is, I guess, “mildly unsettling”? I started this back in 2015, and had most of it done some time around Christmas. Since I had missed any useful window for releasing it, I think I got my mom to play it and didn’t bother to release it more widely than that. (That’s not a joke.) It’s also built in probably the dumbest possible way to build an IF game in a browser.
I also spent a week or so building (in a somewhat less stupid way?) a nostalgic love letter to a 1979 game called The Wizard’s Castle (and a handful of other names over the years) – I call my version “Zot”.
I played a lot of an MBASIC port of the game when I was a kid, on a computer slightly older than me. I’ve actually remade it before, as some hard drive spelunking recently reminded me, but I figured it’d be quick to remake and the old version was for DOS.
Anyway, it’s not done, and I decided to stop using it as a reason to procrastinate on getting back into Black Mountain. I might still pick at it every now and then, but it’s not my focus. When/if I finish it, I’ll probably release it for free on Itch like The House.
Debugging UI
The game is pretty big now, and I’m not just working linearly through a bunch of maps, so I decided I needed some more debugging/“cheat” controls within the game. It’s pretty easy to bash together a UI in Godot as long as you don’t actually care much about how it looks, so I just did that. It even has a tabbed interface and everything.
The first tab is the flag manager. Internally, the game just has a big table of key-value pairs that can get set and checked (in code or in cutscenes), which automatically gets persisted through save game files. Not exactly ground-breaking stuff, but it works. All this panel does is give me a way to edit/create flags so I can bump around where exactly I am in the story or whatever, so I can re-trigger an event that I just ran or test different branches easily, that sort of thing.
The second tab is for jumping to a different map. One thing I’ve always despised in building a 2d RPG is having to carefully figure out an exact coordinate to drop the player on when they switch maps, and then put that coordinate in the map switch call. If you change the structure of a map, you have to go and find all the references to it in all the other maps’ code (or wherever those live), and change them. Instead I opted to put simple invisible nodes, “entrances”, and when you send a player to a map, you send them to an entrance instead – the player’s entity inherits the position, facing direction, and layer parentage of the entrance. It also makes it convenient to query and list all of the possible entrances to a map, which is what this list gives me. I can open the “accordion” for any map in the game and click an entrance, and it triggers a map switch to that entrance. Handy for hopping around to test different things.
(I should say that TECHNICALLY you can still send the player to a specific coordinate on a map rather than an entrance, but it’s a lot less convenient and I mostly only use it for internal stuff like loading games.)
I’m thinking about adding another tab with all available cutscenes and letting me trigger them, but that’s a little trickier. Not technically, but being able to trigger a cutscene anywhere you want would be a bit wonky since they will, by their nature, be written to work on a particular map and at a particular location. Might be worth it anyway; it’s just debugging stuff, after all.
Cutscenes
Black Mountain is a fundamentally story-driven game. One thing that I have always found both interesting and frustrating to work out is a good way to define a cutscene. In this case, I’m using “cutscene” in a pretty general way – anything where the player is not engaged in moment-to-moment decisions or direct control. In particular, I’m always striving for some particular things:
- I want a good way to be able to have as little boilerplate as possible in my cutscene definitions. I find it hard to write interesting scenes and program at the same time. Yeah, I won’t be able to completely get away without any “code” whatsoever, but I want it to fit as naturally into the writing as possible.
- I want to easily be able to set up things that do not complete immediately and wait for them. Something as simple as making a sprite walk a couple of tiles before popping up the next textbox can be surprisingly annoying to do, and often requires a lot of boilerplate (which I don’t want).
- I want to be able to do multiple things at once. Even worse than waiting for one thing is waiting for multiple things, especially when those aren’t trivial “things”.
I don’t know if I’ve necessarily built the best thing to handle all of this, but I do like what I’ve made. (I do wish I had encountered Dialogic earlier – if you yourself want to solve these problems, I suggest checking it out instead. It might work for you, and be a lot less work than what I did!) What I’ve ended up with is essentially a Domain Specific Language (DSL), which lets me load any number of .txt files, parse them at load time into an array of objects (that are essentially opcodes with arguments), and then trigger them whenever I want, by name.
The .txt files look something like this:
# Lines that start with a # sign are comments. I haven't bothered to implement comments that # start some time later in the line, largely so I don't have to worry about escaping #s if I # decide to use them in dialog or something. # Note that whitespace at the beginning of a line is ignored in all cases, so indenting # comments, or any other piece of the script to help with readability, is fine. Similarly, # blank lines are simply ignored, so you can space things out if you need to. # A line that starts with a $ starts a scene; I'll use this name in an event to run this # cutscene. $ Scene Name - A dash shows a text box with no "speaker" defined, for narration or whatever. <Kiel> This is a dialog box, using Kiel's name and her portrait (if available.) > This would be a continuation of the previous dialog box, after the player hit a button. <Violet:sad> I can have variants of portraits; in this case the ":sad" will get stripped off and Violet's name displayed. > If there is a "sad" variant of Violet's portrait, it will get used. > If there is no "sad" variant, it will fall back on Violet's default portrait. <Some_Guy> For technical reasons, names can't have spaces, so I convert _ in a name to a space for display. > Also if there is no portrait for someone, it'll still show a name for the speaker but no portrait. # A single file can contain as many scenes as you want; being able to load multiple files is just # for convenience and organization. $ Some Other Scene # ...
In addition to showing text boxes, I can…
- move entities (including the player and the camera) around the map (including walking around, changing movement speed, facing different directions, teleporting them to another location, switching maps, etc.)
- add temporary “puppet” entities to the map, with a specific name, that get removed at the end of the cutscene
- add and remove characters from the party
- play sound effects or change the music
- give items to the player and change character equipment
- start a combat
- set story flags
- branch based on flags (as described above in the debug UI stuff)
- run multiple chunks of script in parallel, waiting on all of them to finish before moving on
- etc.
There are some bits of functionality I know I’ll need that I haven’t thrown in there yet, but it’s pretty robust at this point. A couple of future things I want to do is be able to load different cutscene files based on the current language/locale being used, and also be able to compile the .txt files into .gd files that simply define the cutscenes directly in the object/opcode form – it’s very convenient right NOW to be loading text files but I don’t really want to parse those unnecessarily every single time the game launches. You can see a lot of this in action in this video of the first 5 minutes (moreorless) of the game:
(Note that the music is currently placeholder – it’s public domain stuff found on OpenGameArt by the fantastic Juhani Junkala/Subspace Audio.)
Let’s Fight
With the beginning cutscenes blocked out, it was time to start tackling the next thing – fixing up the combat system. You can see a bit of it in the video above, but it’s rougher than the rest of the game, and so I started working on that. I decided I wanted more space on the screen, so I needed to make some things smaller, and there’s a lot of cruft internally from the various iterations I’d gone through. Also, as funny as that monster graphic is (to me), I was getting awfully sick of looking at it.
I was originally going to have a “heart” based health system rather than a more traditional HP system, but I decided to ditch that. The energy system was also going to be somewhat different but I realized some systemic issues with it that led some of the overall game and dungeon design in directions I didn’t want, so it’s a little more standard now as well. The bar (still to be implemented) is an adrenaline gauge system similar to a fighting game, where more adrenaline can empower special attacks and/or change how the character fights at different levels. I also replaced the constantly-ticking active time battle system with a visible queue of actions – it’s actually pretty much the same system but instead of filling up meters as time passes, it just pre-calculates the next X turns, so you can see when each character will be acting.
There’s still lots to do, but it’s coming together.
Wandering Monsters
I have a class of objects that I call “behaviors”. They’re nodes, or areas, or whatever, that you just drop into an entity on the map. Then, when the entity is being processed each frame, those behaviors get a chance to control the entity (in the order that they’re in the scene tree as children to the entity). Previously, I had three available behaviours: “look around”, “chase”, and “fight touch”.
- Look around simply stands in place and randomly changes direction. This is actually changing the facing of the sprite, so the entity is visibly “looking around”. It also doesn’t just pick a new direction and snap there, it will interpolate to the new direction over time.
- Chase adds a vision “cone” to the entity, and if the player enters that cone then the entity starts chasing the player. Almost, anyway – the behavior actually checks to see if there are any walls or other obstructions between the two, and will ignore the player if the sight is blocked. If the player gets out of sight (either by escaping the vision cone or ducking behind a wall), the entity will continue to the location it last saw the player, and then look around. If it has lost sight of the player completely, it gives up and stops chasing them.
- Fight touch just means that if the entity touches the player, it launches a combat. The behavior has the set up of which monsters at what position on teh battle screen.
Using these, and the fact that the behaviors will attempt to execute in a particular order, means that I can naturally express “fight the player if I’m touching them, chase them if I see them, and just look around if I don’t” just by dropping nodes into the entity. Also, changing the children (or just the order) in response to some other event means they could change what they’re doing based on whatever.
Anyway, so I also added a new behaviour, wander. The wander behavior needs to have a reference to a special “territory” polygon, and then the creature will simply pick a random location in the territory and, assuming it’s actually able to stand there, it will beeline straight for it. Once it arrives, it will immediately pick a new target location. This leads to my monsters sprinting around their territory in a frenzy, and also there’s no path finding or accounting for complex shapes, so it definitely needs work, but! This means that by dropping in a wander behavior instead of the “look around” behavior, I have monsters that will wander around, chase you if they see you, and attack. It’s another system that I’m pretty happy with how it’s turning out to be flexible and sensible as I work with it.
And then…
Anyway, so I’m spending a lot more time on Black Mountain these days than I was. Hopefully that keeps up! I’ll try to get back into this but we all know that the surest way to kill a blog of any sort is end every post with “I’ll post again soon!” If it comes down to it, I’ll always choose putting time into the game over posting a devlog… but maybe I can find a bit more of a balance, at least?
Have a good week.
-
@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.
-
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.
The Art
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.
Campfires
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.
Cheers!
-
Devlog 2022-06-22
- 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.
Footsteps
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.
Status Effects
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: Goddamit
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 tooStatus 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.
Refactoring
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
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 pagesGodot 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.
-
Devlog 2022-07-06
- made enemy “AI” a little better
- add a display of the enemy name during targeting
- tweak a few places to change how a selection is highlighted to make it more obvious
- some boring (to talk about) refactoring and trying things out that haven’t gotten anywhere yet
- started on building out the village of the fae
I missed last week for personal reasons, which also led to me not getting as much done since then as I’d normally like.
Enemies
Previously, enemies had only a half-implemented partial idea for deciding what action to use, exactly. Unless they had a special script for their combat (which I use exactly once), they would always use exactly one attack; the last one they had defined. Now, though, each monster has a table of attacks they can choose from, with a weight. Here are the actions for the Gremlin monster:
"actions": [ { "chance": 2, "skill": "m_slam", "skill_level": 1, "target": "random" }, { "chance": 1, "skill": "stone_spike", "skill_level": 1, "target": "random" } ],
I do a weighted selection from the table based on the “chance” field, and then look at target. At this point, I believe every monster has all its attacks target “random”, which just means a random target. I could have it target a particular character by name and, more usefully, I intend to give myself the ability to restrict targets based on things like “has Energy left”, “the most HP”, etc. Anyway, once a skill and target is chosen, I just apply the skill (at the given skill level; any skill can scale based skill level) to the chosen target.
Weighted Selections
Actually, I want to talk a bit more about this since it’s something I’ve implemented a bunch of times. I often find myself wanting to have a table of options with different likelihoods for each thing. What I do not want to do, though, is define a bunch of percentages that all add up exactly to 100% that I then have to manually tweak each time I add something to the list. What I definitely do not want to do is wrap my head around a random selection based on iterative “rolls”, even though that would be very easy to code – this would look like “start at A and flip a coin; if it’s heads choose A, but if it’s tails then flip another coin; if it’s heads, choose B, but if it’s tails then flip another coin…”.
So instead I do this weighted choice: I just give each thing in the table a “chance”, which can be an arbitrary integer. Two entries with the same chance have the same chance of being chosen, while an entry with twice the “chance” has… twice the chance of being chosen, compared to the other. It’s very easy to reason about, and the code isn’t that hard to write. It looks like this:
func weighted_rand_from(arr, weight_key="chance"): var chance_sum = 0 # start out by summing the "chance" values for the whole table for a in arr: chance_sum += a[weight_key] # notice that we use the [weight_key] parameter so it can be called whatever you need var n = randi_range(chance_sum) # generate a single random number for a in arr: # step through the array, n -= a[weight_key] # subtracting the chance of each entry from the "roll" if n < 0: # once we drop below 0, we've found the one we want! return a return arr[arr.size() - 1] # return the last thing in the array as a safe default
Yes, this does involved iterating over the whole array (potentially) twice, but unless you’re using very large arrays this shouldn’t be an issue. If it ever did, you’d be farther ahead to recalculate your table into something else for your export. I doubt I’ll end up needing to worry about this for Black Mountain.
Enemy Name Display and Highlighting
I mentioned the name of one of my monsters was named a Rumble Bee internally but had to just refer to it as “the wasp monster”, and I got a bit of feedback from a playtester that it’s nice to know what the monsters are called. I tend to agree, so I added a display for them when picking a target.
Twig Sprites are one of the first enemies you encounter and probably the easiest to defeat in the game.
Along with that you can see I’ve added an extra cursor to the list of monsters. I’ve added one to the list of party members as well.
Kiel is probably going to use First Aid on Omen or something, but who knows for sure.
I’ve also added a small tweak to the character selection in the menu, for viewing status, using items, etc etc. I bump their portrait up a bit just to make it a little more obvious who you have selected. This one was suggested by Hyptosis, and it’s small but I do think it helps with clarity.
The only way Violet will ever be taller than the other two.
Village of the Fae
The next area I’m working on filling out is the Village of the Fae.
The fairies are definitely friendly and definitely won’t feed your baby to a goat.
This is a very quick paint job (and not even finished) like the rest of my map painting. I just want it to be clear and playable for the time being – it will get much prettier in the future.
-
- packed up everything I owned except the stuff I sold or threw out
- moved, really far
- made a decision that I haven’t decided whether it’s good or bad yet
Some time has passed since my last post. I have a reasonably good excuse, I think – I moved me, my wife, my cat, and most of our stuff (what we could justify keeping anyway) thousands of miles across the country, and got a new job. Things have been a bit hectic (and, really, still are!)
Not Unproductive
That said, I haven’t gotten NOTHING done on Black Mountain. After driving across the country (and my stuff, separately, being shipped across) I discovered my desktop computer no longer boots. It doesn’t even really try to power on, so it’s probably the motherboard. I got a new laptop right away (since I didn’t have an alternative that was up to the task of what my new job was going to demand of it), but the files (other than what was committed to git) were locked away on the hard drive in the desktop. I got the new Godot 4 beta and started messing around with that (trying very hard not to think too hard about “what if all my original art files are gone”), and didn’t make anything particularly interesting but I got a feel for how stable the beta is (quite stable!) and what had changed (quite a bit!)
Eventually we got settled in a new apartment and I got myself an external enclosure for my hard drive. The hard drive was totally fine, all my files were there, and I decided that what I should do is just bite the bullet and migrate the game to Godot 4!
A Mistake… Maybe?
I have gone back and forth a lot on just how much of a mistake this was. I’m still in the middle of the process, though I’m making progress at least. Nearly every change from Godot 3 -> 4 seems to be good (the rest seem pretty neutral, honestly) but I have wrestled a lot with what the automatic upgrade process gave me.
To be clear: I went into this fully aware that I was doing something that wasn’t recommended, I have a backup of the original project so I can always go back to Godot 3 if I want, etc. I did the automatic migration with beta 2, and beta 3 is already out (and beta 4 should be out soon). The migration process is part of what they’re improving as they go, so in the future this should be a lot smoother
That said, it’s been rough. Thanks to Godot 3’s wide-spread usage of strings for things like deferred function calls and properties etc, it means that if they changed a name for something, the migration process has to update all instances of that name to the new one… even if that occurs inside of a string (and, apparently, inside of a comment). This meant things like having my “Fight On Touch” behaviour being renamed to “Fight Checked Behaviour”.
Another thing that ended up being surprisingly rough was the new asynchronous stuff – being able to use
await
was one of the changes I was most excited for, but it turns out I had made use of the old yield-based asynchronicity in ways that weren’t compatible with the new way of doing things! There were a number of places where I would hold onto a GDScriptFunctionState object from a call on a stack, for instance, and check each frame to see if it was completed. Turns out you can’t really do that anymore, though I have figured out how to rework things for the new system (at least, so far). I’ll talk more about that below.Overall, though, I do like Godot 4. I’ve said it’s rough, but I want to stress again that it’s the migration process that has been. Godot 4 itself is still a beta, it’s quite solid, and if I were starting a new project I would absolutely start it in Godot 4.
Many Async Calls at Once
In Godot 3, coroutines are done with
yield
, which is actually used to wait for a signal.def f(): print("A") yield(get_tree(), "idle_frame") print("B")
Calling
f()
here will print “A”, then wait for the “idle_frame” signal from the root tree (which is fired every frame), and then will print “B” – specificallyidle_frame
is triggered right before_process
is called one each of your objects.What yield actually does is pack up the function’s execution state into a
GDScriptFunctionState
object, and returns immediately, thus ending execution of the function. Whenever the signal you’re waiting on fires, it unpacks thatGDScriptFunctionState
object and resumes executing. Since it jumps right back into the execution, you can stick that yield wherever you need in the function and start it right back up there…def wait(time): var done = OS.get_ticks_msec() + (time * 1000.0) while OS.get_ticks_msec() < done: var t = OS.get_ticks_msec() yield(get_tree(), "idle_frame") return (OS.get_ticks_msec() - t) / 1000.0
This wait() function will keep
yield
ing out of the while loop every frame, and going right back in to check how much time has passed. Once it passes the requested threshold (time
in seconds) it will exit out of the loop and be done.The next important part is that async functions (aka coroutines) fire a signal called
completed
when they finish. (Technically this is probably not quite correct; I don’t thinkcompleted
is really a signal but it works like one, so I think it’s relatively safe to think of it that way.)Using that wait function above would look like…
def f(): print("A") yield(wait(5), "completed") print("B")
This version of
f()
would print A, wait 5 whole seconds (without locking up the engine, so rendering and input can still happen just fine) and then print B.This is an aside, but another cool thing with this is that you can get a value returned from an async call.
func wait_for_next_frame(): var t = OS.get_ticks_msec() yield(get_tree(), "idle_frame") return (OS.get_ticks_msec() - t) / 1000.0 func f(): print("A") var dt = yield(wait_for_next_frame(), "completed") print("B %s" % [dt])
Here,
wait_for_next_frame()
is a slightly fancier version ofyield(get_tree(), "idle_frame")
which will not only wait until the next frame, but will tell you how many seconds have passed since (which hopefully for you is a very small fraction of a second!)What happens, though, if you call a coroutine without yield?
func f(): print("A") var what = wait_for_next_frame() print("B %s" % [what])
In this case, you will print “A” and “B” immediately. Since there’s no
yield
, you’re not pausing execution. What you’ll see printed next to “B”, though, is aGDScriptFunctionState
object – in fact, it’s the one that represents the state of the coroutine when it calledyield
! That object has a function you can call to see if the function state is still valid; that is, can it still re-enter. You can hold onto that state object, and keep checkingis_valid()
, in fact, until it becomes false… which means the function has finally completed.So that means, say we have three coroutines we all want to run, but we don’t know how long they’ll all take. Say they’re part of a cutscene system – you want to have two characters walk around independently, and you also want to have a textbox show up that waits for the player’s input. You then want to end the cutscene once all three things have finished. You don’t know if the player will hit the button first or after the characters have finished moving, and you’re not sure exactly how long the characters will take to move (let’s say they’re doing some path finding or whatever).
Well… to cut to the chase, you write this function (pulled from Black Mountain’s Godot 3 source):
func multiyield(fs): yield(wait_for_next_frame(), "completed") var done = false while not done: done = true var s = "" for f in fs: if f is GDScriptFunctionState and f.is_valid(): done = false yield(wait_for_next_frame(), "completed") func f(): var async_things = [ move_first_character(), move_second_character(), show_textbox() ] yield(multiyield(async_things, "completed")
multiyield()
takes in a list ofGDScriptFunctionState
objects (at least, theoretically that’s what they are, but just in case we check to make sure). We start up a loop and in each iteration of the loop, we check every one of those objects. If any of them are still valid, we keep the loop going (but wait for the next frame to give things a chance to process). If every single one is no longer valid, we exit. In other words, we’ve created an async call that gloms all of those coroutines together and doesn’t complete until they’re all done!I will note that I am yielding for a single frame at the beginning because of a really cool quirk in the Godot 3 coroutines. Functions that don’t yield? They don’t have a “completed” signal. So if you have a normal, non-coroutine function, and you call
yield(foo(), "completed")
with it, it just blows up. If you look at my multiyield, you’ll notice that iffs
is empty, or if none of them are valid in the very first iteration, it’ll just fall through and return without needing to wait for a frame – meaning it would never yield at all. I throw in one frame of delay right at the beginning so I know that, no matter what, multiyield will always yield at least once. (This sucks; it’s a good thing I’m making a fairly slow JRPG and not a tight action game.)(At this point I should note that if you’re familiar with Promises in Javascript, this
multiyield()
is essentiallyPromise.all()
– I haven’t needed to write any of the rest of the various “multi-Promise” functions, but I’m reasonably sure you could write them all with a similar approach.)And Now in Godot 4…
So then what happens if you call this in Godot 4? Well, there is no
yield()
in Godot 4, so you’ll get that error. You might also see this error:Parser Error: Function "foo()" is a coroutine, so it must be called with "await".
Godot 4 is a lot more smart about what is and isn’t a coroutine, and it has introduced a new statement,
await foo()
which functions kind of likeyield(foo(), "completed")
but is a lot less kludgy. You no longeryield
in a way that returns a function state object – I am pretty sure Godot still uses those sameGDScriptFunctionState
objects internally but they don’t just hand them back to you when you call the coroutine in a particular way, which means you don’t shoot yourself in the foot by forgetting to yield on a coroutine’s completed “signal” so often! You canawait
on a normal non-coroutine function just fine, it simply continues on immediately! It’s great!It also means my exceedingly clever code to make a multiyield doesn’t work. Which was a bit disappointing. But I did find a work around, and it’s even kind of not-gross. Here’s what I’ve figured out:
func async_all(fs:Array): var id = 0 var rtnvalues = [] var completed = [] # use an array instead of an int because arrays are reference types rtnvalues.resize(fs.size()) var complete_one = func(rtnvalue, i): rtnvalues[i] = rtnvalue completed.push_back(i) for f in fs: var AO = AsyncWrapper.new(f[0], f.slice(1)) AO.async_done.connect(complete_one.bind(id)) AO.run() id += 1 while completed.size() < fs.size(): await get_tree().process_frame return rtnvalues class AsyncWrapper extends Object: signal async_done(returnValue) var _callable:Callable var _args:Array func _init(callable:Callable, args:Array): _callable = callable _args = args func run(): var r = await _callable.callv(_args) async_done.emit(r)
async_all()
here is my newmultiyield()
.AsyncWrapper
is a cool new Godot 4 internal class. You pass in a list of functions, just like with multiyield, but instead of calling those functions you pass a list of arrays likeasync_all([ [foo, "first_param", 2], [bar, "bars_param"] ])
which isn’t great but it works. (Note that callables in Godot 4 are first-class values, so no more passing string names around! I could probably replace these with .bind() calls instead, now that I think about it… hmm.) It sets up a few values, and an inline function
complete_one
(another new feature!), then sets about calling each one. It calls them with the AsyncWrapper class, whichawait
s the result of the call, and then triggers a signal – a signal we’ve connected to ourcomplete_one
function.complete_one
adds a value to thecompleted
array, and once thecompleted
array has grown in size to match the number of functions pass in… we’re done! As the comment notes, I’m using an array forcompleted
instead of just incrementing a number because (as near as I can tell) the anonymous inline function captures the value of variables at their declaration time, meaning every invocation ofcomplete_one
would think it was 0. Since arrays are reference values, every call is adding to the same array.Okay Time to Put the Keyboard Away
Okay I need to sleep and it is not making my explanations more coherent. Hopefully someone out there gleaned SOME useful information from this. Have a good week!
-
Devlog 2022-11-13
- continued Godot 4 migration
- got cutscenes playing again
- fixed game saving/loading
- fixed shadows
- discovered that _ready() works subtly differently now
- lots of fighting with UI
- various other fixes
You can actually get out of the house and talk to people again! The game crashes moments later when the shop interface attempts to load.Not a super exciting week to report on; just continued slogging through the update process. If nothing else, this process is helping me reacquaint myself with the code after not looking at it for a few months!
Just a few miscellaneous notes this week…
Godot 4 Changed _ready()
In Godot,
_ready()
is called when the Node is, well, ready to start its set up (after all its children in the scene tree have completed their_ready()
work). It’s where you do all your upfront initialization (usually), so it’s pretty important. In Godot 3, the_ready()
defined in every class in your inheritance hierarchy gets called, in order, with your “local”_ready()
being called last. This is different from how most function calls work, since they will call the “local” function and you have to explicitly call the super-class’s version by using.whatever()
.In Godot 4,
_ready()
is no longer special (and instead of just using a bare.
to call your super-class’s methods, you usesuper.
) so you have to explicitly callsuper._ready()
in your_ready()
method. I actually think this is good! Consistency is great! It was just a surprise since that’s not how it worked before, and I only figured it out after some trial and error.… And Some Other Things
There’s one other weird change that I’ve found, which I’m not as fond of. Classes in GDScript, especially in Godot 3, are a bit weird to work with as a data type themselves. It’s customary to override two particular functions,
get_class()
andis_class()
, so that you can query what something is. They’d look something likeextends Node class_name FooBar func get_class(): return "FooBar" func is_class(class_name): return class_name == get_class() or .is_class(class_name)
which lets you write code like
if thing.is_class(TheOneIWant):
, allowing for inheritance etc etc.Well, anyway, in Godot 4 you can’t do that because
get_class
appears to be special now and you can’t actually override it. I’m not sure if this is intentional or a current bug! Anyway, turns out that theis
keyword does what you need now anyway so there’s not really any real reason for doing all of this anymore anyway. You can just writeif thing is TheOneIWant
and it handles inheritance correctly.(I actually just popped open Godot 3.5 to see if I could figure out exactly how
is
works in Godot 3, but for some reason I couldn’t even get my test project to run and print anything at all to the console so if it’s worked this way all along, then I guess this more a note to myself than useful to anyone else.)UI Continues to Baffle Me
I come from a web development background, primarily in frontend development, so it’s not that UI in general is mysterious to me but it always takes me several tries to get Godot’s UI system to do what I want. I’m glad the UI system is there, it’s quite capable! I just always have to fight it. Some combination of me using it in some bone-headed ways and some interesting decisions on the part of the auto-migration has led me to having to fix a lot of my menus and such to get them looking right again. I’m using the process to try to understand the theme system a bit better and actually, you know, use that instead of having a bunch of manual overrides everywhere. So again, probably for the better! It’s just work.
The Show Goes On
I’m getting impatient to get back to actually pushing the game forward. I don’t know I’ll manage that by next Sunday, but progress is progress.
Have a great week!