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.
I released a game I wrote years ago on Itch. It’s called “The House”.
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 xn--The Wizards Castle-qq9j (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.
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.
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
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:
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.
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.
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.