<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Hyperlink Your Heart - Game Development</title><link href="https://blog.hyperlinkyourheart.com/" rel="alternate"></link><link href="https://blog.hyperlinkyourheart.com/feeds/category.game-development.atom.xml" rel="self"></link><id>https://blog.hyperlinkyourheart.com/</id><updated>2025-08-08T14:10:00+02:00</updated><subtitle>Until there's nothing left.</subtitle><entry><title>The Power of Breaks</title><link href="https://blog.hyperlinkyourheart.com/breaks.html" rel="alternate"></link><published>2025-08-08T14:10:00+02:00</published><updated>2025-08-08T14:10:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2025-08-08:/breaks.html</id><summary type="html">&lt;p&gt;Sometimes taking a break makes the solution to a problem obvious when you return to&amp;nbsp;it.&lt;/p&gt;</summary><content type="html">&lt;p&gt;A few months ago I was stuck on a particularly frustrating problem in the development of &lt;a href="https://github.com/khoulihan/digression" title="Digression repository on GitHub."&gt;Digression&lt;/a&gt;, my dialogue graph editor for &lt;a href="https://godotengine.org/" title="The game engine you waited for."&gt;Godot&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I had recently switched it to a main screen editor, as discussed in my &lt;a href="https://blog.hyperlinkyourheart.com/lovely.html" title="Post-mortem of my Ludum Dare entry Lovely, Dark and Deep."&gt;post-mortem of my last Ludum Dare entry&lt;/a&gt;, and added a list of open graphs, and another of anchor points in the currently edited graph, similar to the scripts and methods list in the built-in script editor. Basically I have decided to reproduce the &lt;span class="caps"&gt;UI&lt;/span&gt; conventions of the script editor where they are at all&amp;nbsp;applicable.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/khoulihan/digression" title="Digression repository on GitHub."&gt;&lt;img alt="Screenshot of the graph editor main screen described above." src="https://blog.hyperlinkyourheart.com/images/breaks/digression.png" title="It's nearly looking like a built-in part of the editor now."&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This seemed fine initally, but as I got stuck into a big refactor of how the underlying resources are updated by the &lt;span class="caps"&gt;GUI&lt;/span&gt;, and how and when those are saved, I noticed something really weird: sometimes when I pressed ctrl-s to save the scene and current graph, the editor would switch to one of the other open&amp;nbsp;graphs!&lt;/p&gt;
&lt;p&gt;I think I must have spent two weeks on and off trying to figure out this problem. The code was absolutely littered with debug log lines trying to trace the sequence of events triggered by the save, but there didn&amp;#8217;t seem to be any connection between the code doing the saving, or the code handling key input, and the code responsible for switching between graphs. Eventually I gave up in frustration, and decided to &lt;a href="https://blog.hyperlinkyourheart.com/coming-down.html" title="Coming Down art post."&gt;do some art&lt;/a&gt; in my spare time instead. Then I played some video&amp;nbsp;games.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Marketing image for the game &amp;quot;The Alters&amp;quot;, showing the main character and his clones in a huddle on the right, with the name of the game on the left." src="https://blog.hyperlinkyourheart.com/images/breaks/alters.jpg" title="It's really good."&gt;&lt;/p&gt;
&lt;p&gt;Well, when I finally came back to it, I figured out what was going on within five minutes. First I noticed that the graph that was being switched to was always one with a name starting with the letter s, and if there wasn&amp;#8217;t one starting with an s, then nothing happened. I tried other ctrl key combos and those switched to graphs starting with the corresponding letter, if there was one. It wasn&amp;#8217;t anything to do with saving, or how I&amp;#8217;d implemented it, or anything to do with my code at all, it was just a &lt;a href="https://github.com/godotengine/godot/issues/109274" title="Issue on the Godot issue tracker for the described bug."&gt;bug in the search feature of the ItemList node&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;When I was deep into the development I had a very blinkered view of what was going on: I&amp;#8217;m working on saving, it happens when I save, it must have something to do with that, and it must be due to changes I&amp;#8217;ve made. A little break from it gave me enough perspective to see that none of that was the case, and there was ample evidence demonstrating&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;This of course isn&amp;#8217;t the first time in my life, in programming or other endeavours, that taking a break has helped me solve a problem or even develop skills. I often find that taking a break from art results in a seemingly huge improvement in my skills when I return to it. In that case I think it is down to being more relaxed and putting less pressure on myself when starting a new piece after a break than when I have recently finished a piece that I feel I need to improve&amp;nbsp;upon.&lt;/p&gt;
&lt;p&gt;In conclusion: breaks good. Take a step back&amp;nbsp;sometimes.&lt;/p&gt;</content><category term="Game Development"></category><category term="godot"></category><category term="life"></category><category term="self-care"></category></entry><entry><title>Lovely, Dark and Deep</title><link href="https://blog.hyperlinkyourheart.com/lovely.html" rel="alternate"></link><published>2025-06-14T18:28:00+02:00</published><updated>2025-06-14T18:28:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2025-06-14:/lovely.html</id><summary type="html">&lt;p&gt;Post-mortem and results for my Ludum Dare 57&amp;nbsp;entry.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://hyperlinkyourheart.itch.io/lovely-dark-and-deep" title="itch.io page for the game"&gt;&lt;img alt="Pixel art of a forest with a man on a bicycle in the foreground, and the text &amp;quot;Lovely, Dark and Deep&amp;quot;" src="https://blog.hyperlinkyourheart.com/images/lovely-dark-and-deep/cover_image.png" title="Lovely, Dark and Deep"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Back in April I took part in Ludum Dare 57, which had the theme &amp;#8220;Depths&amp;#8221;. As usual I was eager to take &lt;a href="https://github.com/khoulihan/digression" title="Digression repository on GitHub"&gt;Digression&lt;/a&gt; for a spin and see how much it had improved since its last outing for &lt;a href="https://hyperlinkyourheart.itch.io/out-of-gas" title="Out of Gas on itch.io"&gt;Ludum Dare 48&lt;/a&gt;, and this time I managed to come up with something that allowed me to do that. Coincidentally, the theme for &lt;span class="caps"&gt;LD48&lt;/span&gt; was almost the same (&amp;#8220;Deeper and Deeper&amp;#8221;), and I ended up making a somewhat similar game in some&amp;nbsp;respects!&lt;/p&gt;
&lt;h2&gt;The&amp;nbsp;Concept&lt;/h2&gt;
&lt;p&gt;The initial concept was one I&amp;#8217;ve had kicking around for a while, of a post-apocalyptic trading game with two phases to the gameplay - one where you travel between outposts by car, possibly having to engage in combat or other encounters, and another at outposts where you have to trade and maintain your car. I think of it as terrestrial &lt;a href="https://en.wikipedia.org/wiki/Escape_Velocity_(video_game)" title="Wikipedia entry for the game Escape Velocity"&gt;Escape Velocity&lt;/a&gt;, though the travel would undoubtedly be much simplified for a jam&amp;nbsp;game.&lt;/p&gt;
&lt;p&gt;Of course none of the elements of that idea are set in stone. I usually think of it as being set in a desert (the ol&amp;#8217; Mad Max influence I suppose), but in this case I wasn&amp;#8217;t sure if deserts &lt;em&gt;have&lt;/em&gt; depths? So that was out. I considered setting it in deep space instead (some sort of &lt;em&gt;non-terrestrial&lt;/em&gt; Escape Velocity, I suppose? What a notion), but that seemed too close to the &lt;a href="https://hyperlinkyourheart.itch.io/out-of-gas" title="Out of Gas on itch.io"&gt;game I made for Ludum Dare 48&lt;/a&gt; already. And so it became deep forest, and then the car became a bicycle because that seemed more&amp;nbsp;foresty.&lt;/p&gt;
&lt;p&gt;Most of the rest of the originally envisioned gameplay mechanics fell away as I tried to scope it to the 72 hours available. The travelling became just an opportunity for a bit of dialogue, the vehicle maintenance became a simple stat alongside others for the player, and the trading disappeared entirely. In an effort to introduce some gameplay beyond just engaging in dialogue, I added in a hand of randomly drawn cards at each location and a limited number of action points for their use. I think I had in mind the dice rolls in &lt;a href="https://citizensleeper.com/" title="Website of the Citizen Sleeper series of games"&gt;Citizen Sleeper&lt;/a&gt; when I came up with this, but I couldn&amp;#8217;t really remember how they worked in that&amp;nbsp;game.&lt;/p&gt;
&lt;p&gt;Considering it has been several months since the jam I don&amp;#8217;t think I could give you a blow by blow even if I wanted to, so I&amp;#8217;m just going to talk a bit about what worked and what&amp;nbsp;didn&amp;#8217;t.&lt;/p&gt;
&lt;h2&gt;What&amp;nbsp;Worked&lt;/h2&gt;
&lt;h3&gt;Digression&lt;/h3&gt;
&lt;p&gt;Digression worked really well for the most part. I think I encountered one bug, but I was able to work around it. I was able to churn out dialogue really quickly for the encounters and travel, and it was relatively easy to implement changes to the game state during dialogue as well (giving items and buffs and the like) - though that aspect of it could have been better, as I discuss&amp;nbsp;below.&lt;/p&gt;
&lt;h3&gt;Use of&amp;nbsp;Resources&lt;/h3&gt;
&lt;p&gt;I used &lt;code&gt;Resource&lt;/code&gt; types for all the items, buffs, curses, character classes etc. Although they were a bit of a mess and included some stuff that didn&amp;#8217;t end up getting used, it really paid off to work with resources right from the beginning. Often in the past I would implement something like the player&amp;#8217;s gun, for example, as just a scene or part of a scene, with the intention to rework it later to allow different guns defined by resources. And of course in a jam that never happens as time runs out and I get fatigued - so there ends up being only one gun, one pickup&amp;nbsp;etc.&lt;/p&gt;
&lt;p&gt;Probably the most obvious effect this had in this game is the character class selection. It doesn&amp;#8217;t add all that much, but I think it is a neat thing to have in a jam game and it gives the impression of&amp;nbsp;depth.&lt;/p&gt;
&lt;p&gt;So, big thumbs up to doing things the right way from the&amp;nbsp;beginning.&lt;/p&gt;
&lt;h3&gt;Audio&lt;/h3&gt;
&lt;p&gt;I used old reliable &lt;a href="https://lmms.io/" title="Website of the LMMS DAW"&gt;&lt;span class="caps"&gt;LMMS&lt;/span&gt;&lt;/a&gt; for the music this time around. Last time I tried some new tools and had a really bad time. I&amp;#8217;m really proud of what I made for this game, it had the exact spooky, mysterious quality I was going for. I don&amp;#8217;t think &lt;a href="https://www.beepbox.co" title="Beepbox - an online chiptune music tool"&gt;beepbox&lt;/a&gt; would have cut it this&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;The game didn&amp;#8217;t call for many sound effects, but the ones that are there, aside from the &lt;span class="caps"&gt;UI&lt;/span&gt;, are foley - always fun to do that and I think they work pretty&amp;nbsp;well.&lt;/p&gt;
&lt;h3&gt;Art&lt;/h3&gt;
&lt;p&gt;What can I say, I think I did a good job on the art. That&amp;#8217;s always been my strength anyway so big&amp;nbsp;woop.&lt;/p&gt;
&lt;p&gt;I am still using &lt;a href="https://github.com/Orama-Interactive/Pixelorama" title="Pixelorama on GitHub"&gt;Pixelorama&lt;/a&gt; and there are still a few things about it that I find grating, but I have gotten used to it enough that it doesn&amp;#8217;t get in my way. It was better suited to this game than my last one because most of the art was large background pieces rather than&amp;nbsp;tiles.&lt;/p&gt;
&lt;p&gt;Something that&amp;#8217;s kinda new is that I did lot of animation using tweens. Little things like the the back and forth motion of the bicycle and how it enters and exits the screen, and the distribution and flipping of the cards, were entirely done with tweens. They took only minutes to implement and I think they look great, and add so much to the aesthetic of the game. I only had to actually animate one thing by hand, the&amp;nbsp;pedalling.&lt;/p&gt;
&lt;h3&gt;The Humour,&amp;nbsp;apparently&lt;/h3&gt;
&lt;p&gt;That Sully, what a&amp;nbsp;card.&lt;/p&gt;
&lt;h2&gt;What&amp;nbsp;Didn&amp;#8217;t&lt;/h2&gt;
&lt;h3&gt;Digression&lt;/h3&gt;
&lt;p&gt;Though it certainly didn&amp;#8217;t let me down, I definitely noticed a few pain points in actually using it for a&amp;nbsp;game.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It was originally a main-screen editor, and at some point I decided it should be in the bottom dock - probably because Godot didn&amp;#8217;t provide an official way to add a main screen plugin and you had to sort of hack it in. However it requires a lot of space to use properly, and having it in the bottom dock was an impediment to that. I have moved it back to the main screen since the&amp;nbsp;jam.&lt;/li&gt;
&lt;li&gt;Having to manually manage the size of dialogue nodes was a hassle. I have since updated them to be resizeable width-wise, but to grow with the text length-wise. Much nicer to&amp;nbsp;use.&lt;/li&gt;
&lt;li&gt;Similarly, it was kind of a pain scrolling up and down a node when there was a long section of text. I have since partially implemented a mechanism to allow the dialogue node to be &amp;#8220;maximised&amp;#8221; when you want to focus on editing, and I will be doing the same for the choice&amp;nbsp;node.&lt;/li&gt;
&lt;li&gt;The lack of an open graphs list made it a bit awkward to move back and forth between graphs, as I had to find them in a scene or the filesystem every time. I have since added an open graphs list, as well as an anchors list for navigating around a&amp;nbsp;graph.&lt;/li&gt;
&lt;li&gt;The two different signals for dialogue and choice dialogue struck me as a little awkward in retrospect. I will be consolidating them I&amp;nbsp;think.&lt;/li&gt;
&lt;li&gt;There are a few problems with the variable management system. One is that they lacked defaults - I have added these since. Another is that the variables didn&amp;#8217;t mesh well with the overall game, i.e. game state and dialogue state felt like two separate things that I had to bridge. I&amp;#8217;m not sure how I am going to address that&amp;nbsp;yet.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Gameplay&lt;/h3&gt;
&lt;p&gt;The gameplay was pretty awful. I clearly do not really know how card games work. Limiting the player&amp;#8217;s actions to a random selection of cards just resulted in frustration, especially when it was not obvious that some actions required items, and when their use of the cards was also constrained by action&amp;nbsp;points.&lt;/p&gt;
&lt;p&gt;The stats generally degraded too slowly as well and there were unclear interdependencies between them - your health doesn&amp;#8217;t degrade until you exhaust your food or water stats&amp;nbsp;completely.&lt;/p&gt;
&lt;p&gt;The encounters were fun, I thought, but there were too few of them and they were too random - you would see the same ones over and over sometimes. Too many of them had disappointing outcomes, and if you didn&amp;#8217;t get the right cards there was nothing to&amp;nbsp;do.&lt;/p&gt;
&lt;p&gt;I think the random encounters worked a bit better in &lt;a href="https://hyperlinkyourheart.itch.io/out-of-gas" title="Out of Gas on itch.io"&gt;Out of Gas&lt;/a&gt; for two reasons: most encounters involved combat, so that is just inherently more exciting - and the fact that you were fleeing something, and could be killed, was slightly more compelling than a set of depleting&amp;nbsp;stats.&lt;/p&gt;
&lt;p&gt;Overall, the randomness just left the player with little sense of agency. I would have been better off dropping the gameplay altogether and just making it a short visual novel, with agency expressed only through dialogue choices. Though I&amp;#8217;m not sure I could write a compelling story for a visual novel in such a short timespan&amp;nbsp;either&amp;#8230;&lt;/p&gt;
&lt;h3&gt;Bugs&lt;/h3&gt;
&lt;p&gt;I introduced a bug at the last minute that prevented the game from ending when your health or vehicle state reached zero lol,&amp;nbsp;lmao.&lt;/p&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Results table - 257th overall, 600th in Fun, 256th in Innovation, 446th in Theme, 80th in Graphics, 49th in Audio, 48th in Humour, 70th in Mood" src="https://blog.hyperlinkyourheart.com/images/lovely-dark-and-deep/results.png" title="Not too bad. Could be better."&gt;&lt;/p&gt;
&lt;p&gt;Much better than my last two outings, and some good ratings in several categories, but definitely let down overall by the gameplay. I&amp;#8217;m a bit disappointed by the mood rating as I felt I nailed that, but it is a very subjective one of course - and it&amp;#8217;s still pretty&amp;nbsp;good!&lt;/p&gt;
&lt;p&gt;Some&amp;nbsp;notables:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;My highest ever rating, and placing, in humour (please&amp;nbsp;clap).&lt;/li&gt;
&lt;li&gt;Highest ever audio rating (by a hair), and&amp;nbsp;placing.&lt;/li&gt;
&lt;li&gt;First time being in the top 100 in four categories as a solo&amp;nbsp;dev.&lt;/li&gt;
&lt;li&gt;First time being in the top 50 in two categories. Third time being in the top 50 in any&amp;nbsp;category.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Second lowest&lt;/em&gt; rating in fun - only my Ludum Dare 38 entry, which was only the opening scene of an adventure game and had no gameplay, was&amp;nbsp;lower!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="Graph of audio placings" src="https://blog.hyperlinkyourheart.com/images/lovely-dark-and-deep/audioplacings.png"&gt;
&lt;img alt="Graph of humour ratings" src="https://blog.hyperlinkyourheart.com/images/lovely-dark-and-deep/humourratings.png"&gt;&lt;/p&gt;
&lt;h2&gt;Post-Jam&lt;/h2&gt;
&lt;p&gt;Nah.&lt;/p&gt;
&lt;h2&gt;Future&amp;nbsp;Developments&lt;/h2&gt;
&lt;p&gt;I was inspired to work on some sweeping improvements to Digression and have made a lot of progress, but there is also a lot of stuff broken and partially completed now. I will post about the changes in more depth when they are&amp;nbsp;done.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of dialogue and choice nodes in Digression" src="https://blog.hyperlinkyourheart.com/images/lovely-dark-and-deep/digressioncurrentstate.png" title="An example of some of the changes I've made"&gt;&lt;/p&gt;
&lt;p&gt;I also created a set of placeholder graphics to use in future jams. I used the default Godot robot sprite in this game, but it is not ideal in some ways because everything ends up looking the same, and when you put in the actual art you have to undo any scaling that was done to make the placeholder the correct&amp;nbsp;size.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of the game in early development, with the Godot logo everywhere" src="https://blog.hyperlinkyourheart.com/images/lovely-dark-and-deep/placeholders.png" title="Looks like Godot is already here"&gt;&lt;/p&gt;</content><category term="Game Development"></category><category term="ludum-dare"></category><category term="godot"></category><category term="godot4"></category><category term="pixelart"></category><category term="music"></category></entry><entry><title>From Hell’s Heart</title><link href="https://blog.hyperlinkyourheart.com/from-hells-heart.html" rel="alternate"></link><published>2024-05-14T22:46:00+02:00</published><updated>2024-05-14T22:46:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2024-05-14:/from-hells-heart.html</id><summary type="html">&lt;p&gt;A post-mortem of my Ludum Dare 55 entry, From Hell&amp;#8217;s Heart, a twin-stick&amp;nbsp;shooter.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="Demon character from my game bound in a circle of protection" src="https://blog.hyperlinkyourheart.com/images/from-hells-heart/my_boy_scaled.png" title="What have they done to my boy??"&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://hyperlinkyourheart.itch.io/from-hells-heart" title="From Hell's Heart Itch Page"&gt;From Hell&amp;#8217;s Heart&lt;/a&gt; is my entry for Ludum Dare 55, the first Ludum Dare I have participated in since I made &lt;a href="https://hyperlinkyourheart.itch.io/out-of-gas" title="Out of Gas Itch Page"&gt;Out of Gas&lt;/a&gt; for Ludum Dare 48 in&amp;nbsp;2021.&lt;/p&gt;
&lt;p&gt;I was not thrilled with it overall (and neither was anybody else - but we&amp;#8217;ll get to that), but it was good to get into working on actual gameplay programming again after several years of only working on tooling, and it has inspired me to do a lot more gamedev work&amp;nbsp;since.&lt;/p&gt;
&lt;h2&gt;The&amp;nbsp;Concept&lt;/h2&gt;
&lt;p&gt;I was quite disappointed by the theme. I had an idea for the theme &amp;#8220;It Spreads&amp;#8221;, which was one of the final round themes, that I was very invested in. I was going to make a zombie shooter where the weapons were all spreadables like jam and nutella and so on, and I had a lot of fun art and gameplay ideas for&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;So when the theme of &amp;#8220;Summoning&amp;#8221; was announced I was disappointed. But, the theme is rarely the one I would prefer, so I got to brainstorming and came up with the idea of &amp;#8220;reverse-Doom&amp;#8221;, which evolved into something not explicitly Doom related, but where you play as a demon fighting soldiers nonetheless. For a brief moment I was even considering calling it &amp;#8220;What if Doom but you&amp;#8217;re the&amp;nbsp;Demon&amp;#8221;.&lt;/p&gt;
&lt;p&gt;The thing I liked about this concept was that it allowed me to address the theme in two ways - you are a demon who has been summoned, and you can summon other demons to help you. However, the fact that it is perfectly playable and beatable without ever summoning your demon friends diminishes this&amp;nbsp;somewhat!&lt;/p&gt;
&lt;p&gt;One of my hopes for this jam was to try out my dialogue graph editor, &lt;a href="https://github.com/khoulihan/digression" title="Digression GitHub Page"&gt;Digression&lt;/a&gt;, in another game, and unfortunately there was no place for it with this concept. I used it heavily in &lt;a href="https://hyperlinkyourheart.itch.io/out-of-gas" title="Out of Gas Itch Page"&gt;Out of Gas&lt;/a&gt; and &lt;a href="https://hyperlinkyourheart.itch.io/gophers" title="Gophers Itch Page"&gt;Gophers&lt;/a&gt; in the past, but it has been fleshed out significantly since then and is ready to be put through its paces. Oh&amp;nbsp;well.&lt;/p&gt;
&lt;h2&gt;Art&lt;/h2&gt;
&lt;p&gt;I couldn&amp;#8217;t get Pyxel Edit to run on my new computer when setting up for the jam (it is Windows only, and I have run it using Wine in the past), so I decided to branch out and try &lt;a href="https://github.com/Orama-Interactive/Pixelorama" title="Pixelorama GitHub Page"&gt;Pixelorama&lt;/a&gt; - a pixel art editor built in Godot. Although my muscle memory from Pyxel edit tripped me up constantly in minor ways it was a good experience overall and I think I will continue using it going forward, as I always prefer open-source and native applications when they are available. The only thing I really found lacking was the way it handles the grid/tiles. There are no tools for quickly copying and manipulating tiles, and the grid is specified in the global settings instead of being per-file, even though one grid is unlikely to suit different files even in the same&amp;nbsp;project.&lt;/p&gt;
&lt;p&gt;I also had to adapt the way I managed spritesheets, as Pyxel edit allows you to define multiple animations in a single file, with the frames being tiles, while Pixelorama will only animate the entire file as a single unit. As such, character sprites are spread across multiple files each. In Godot this means swapping the sprite texture for each animation, but that didn&amp;#8217;t seem to cause any&amp;nbsp;problems.&lt;/p&gt;
&lt;p&gt;I made some big mistakes with the level art early on, initially designing it at twice the appropriate resolution for the characters I had in mind. I was able to take the basic design ideas and downscale them into the final wall and floor tiles easily enough, but it still wasted a lot of time. I designed them for an auto-tiling approach which I developed a while ago, where a tool script populates the floor tiles when you update the&amp;nbsp;walls.&lt;/p&gt;
&lt;p&gt;&lt;img alt="The initial oversized art I designed. The walls are about 5 times the height of the characters." src="https://blog.hyperlinkyourheart.com/images/from-hells-heart/humongowalls.png" title="Humongous walls, and some variants of the player character."&gt;&lt;/p&gt;
&lt;p&gt;I based the character shapes roughly on the design I came up with for &lt;a href="https://hyperlinkyourheart.itch.io/guerrilla-gardening" title="Guerrilla Gardening Itch Page"&gt;Guerrilla Gardening&lt;/a&gt; for Ludum Dare 41, which is very round and fun, and I&amp;#8217;ve found works really well for shooter games. It also features outlines for the characters which means there&amp;#8217;s less risk of ending up with characters that don&amp;#8217;t show up well against the&amp;nbsp;background!&lt;/p&gt;
&lt;p&gt;I was quite pleased with the art overall, though when I designed the wall and floor variants for the opening &amp;#8220;cutscene&amp;#8221; I realised that the game is far too red overall, as the characters looked much better against the bluer&amp;nbsp;background!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of part of the opening scene, with blue-grey crates against blue-grey walls and floor" src="https://blog.hyperlinkyourheart.com/images/from-hells-heart/crates.png" title="The crates had much less contrast though."&gt;&lt;/p&gt;
&lt;p&gt;I am determined next time I do a jam solo to not do full animations for each character or game element, and instead use the animation features of Godot to bounce, squash and spin things to bring them to life. This will give me much more time to create a variety of characters and objects, which I think would contribute more to a jam game than full run-cycle animations. The only place I used this kind of technique in this game was for the spinning and pulsing of the portal, and I think it was quite effective. People do some great things with this kind of animation and I am missing&amp;nbsp;out.&lt;/p&gt;
&lt;h2&gt;Music&lt;/h2&gt;
&lt;p&gt;Music is an element of jam games that I am always trying to find a new and better approach to, as it is something that I enjoy a lot but am not particularly skilled at. In the past I have used &lt;a href="https://www.beepbox.co" title="Beepbox"&gt;beepbox&lt;/a&gt;, &lt;a href="https://lmms.io/" title="LMMS Website"&gt;&lt;span class="caps"&gt;LMMS&lt;/span&gt;&lt;/a&gt; and &lt;a href="https://boscaceoil.net/" title="Bosca Ceoil Website"&gt;Bosca Ceoil&lt;/a&gt; with different degrees of&amp;nbsp;success.&lt;/p&gt;
&lt;p&gt;In the week prior to the jam I came across an application called &lt;a href="https://helio.fm/" title="Helio Website"&gt;Helio&lt;/a&gt; which I thought looked incredibly promising. It looked like it might be as easy to use as Bosca Ceoil but with a wider variety of possible instruments, so I was excited to try it&amp;nbsp;out.&lt;/p&gt;
&lt;p&gt;Another type of tool that I am always looking to bring into the fold is modular synth emulators. I have tried &lt;a href="https://vcvrack.com/" title="VCV Rack Website"&gt;&lt;span class="caps"&gt;VCV&lt;/span&gt; rack&lt;/a&gt; in the past but never managed to use it for a game, and I recently came across a fork of it called &lt;a href="https://cardinal.kx.studio/" title="Cardinal Website"&gt;Cardinal&lt;/a&gt; and had been practicing with that. I discovered that I could use Cardinal as a plugin for Helio and create instruments in it to control using Helio, so I planned to either do that or create a patch to generate all the music for the game, depending on what idea I was working on. The concept for From Hell&amp;#8217;s Heart didn&amp;#8217;t really seem to call for techno or ambient music, so I ended up trying the&amp;nbsp;latter.&lt;/p&gt;
&lt;p&gt;Unfortunately, things did not go smoothly at all with creating the music. I found Helio to be lacking in some essential features, and with a confusing menu system. It crashed constantly, forgetting instrument settings each time and sometimes changing the volume of notes or moving them around arbitrarily. I think the interface with Cardinal may have been responsible for some of this, and ultimately abandoned trying to use it for any instruments in favor of a selection of sound fonts. There was no way to preview the sound fonts in Helio, and I ended up previewing them on the command line using &lt;code&gt;fluidsynth&lt;/code&gt;, but not all of the ones I found that I liked worked when loaded into instruments in Helio. It was just a frustrating mess all&amp;nbsp;around.&lt;/p&gt;
&lt;p&gt;Most bafflingly in terms of missing features in Helio, I couldn&amp;#8217;t find a way of creating more than one pattern for an instrument, so I ended up having to use multiple tracks of the same instrument when I wanted a different&amp;nbsp;pattern.&lt;/p&gt;
&lt;p&gt;It also lacks a central mixer where effects can be applied. Instead you can add effects on a per-instrument basis. I tried to do this in Cardinal as well, but again abandoned that as time dragged on and the application kept&amp;nbsp;crashing.&lt;/p&gt;
&lt;p&gt;So overall it was a bit of a nightmare, took five precious hours, and the track ended up being overly repetitive, with just one melody repeated over and over with the only variation being different instruments coming in and out. I hated it when I was done, but it grew on me a bit when I put it in the game. It did kind of have the mood I was aiming&amp;nbsp;for.&lt;/p&gt;
&lt;p&gt;Every time I try to make music in Linux I feel like I am missing out on some brilliant workflow that ties a variety of different applications together with their different specialities, but I am just failing to grasp how it all works, and this time was no different. Back to &lt;span class="caps"&gt;LMMS&lt;/span&gt; next time I&amp;nbsp;think&amp;#8230;&lt;/p&gt;
&lt;h2&gt;Sound&amp;nbsp;Effects&lt;/h2&gt;
&lt;p&gt;Somewhere that I did get to use Cardinal was for the sound effects! I mostly used the Audible Instruments synth modules with a variety of different setting, and used &lt;a href="https://www.audacityteam.org/" title="Audacity Website"&gt;Audacity&lt;/a&gt; to record different notes being played for variety. Some of the results I was really pleased with (the demon snarls), others much less so (the enemy voices, which sounded like robots saying random words), but overall it worked pretty well. I would have preferred to do some foley stuff but there wasn&amp;#8217;t time, and it was a step up from &lt;a href="http://www.drpetter.se/project_sfxr.html" title="SFXR Website"&gt;&lt;span class="caps"&gt;SFXR&lt;/span&gt;&lt;/a&gt; at&amp;nbsp;least.&lt;/p&gt;
&lt;h2&gt;Code&lt;/h2&gt;
&lt;p&gt;Since my long-time project, &lt;a href="https://gamejolt.com/games/just-a-robot/185852" title="Just a Robot GameJolt Page"&gt;Just a Robot&lt;/a&gt;, is a shooter, I had a fair bit of base code to plunder for this jam. I don&amp;#8217;t think I did or learned anything particularly interesting this time around, but the base code did include a technique for showing a character silhouette when they are obscured by a wall which is interesting enough, and some automatic configuration of floor and mask tilemaps to allow rooms to be banged out quickly with little possibility of&amp;nbsp;error.&lt;/p&gt;
&lt;p&gt;The trick to the silhouettes is to create a mask of the parts of the walls that should obscure game entities using a &lt;code&gt;BackBufferCopy&lt;/code&gt; node, and then check that mask in a shader on any sprite that should be obscured. Objects in the game do not need to be children of the &lt;code&gt;TileMap&lt;/code&gt;, and in fact there is a different &lt;code&gt;TileMap&lt;/code&gt; for walls and&amp;nbsp;floors.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of Godot editor scene tree showing mask TileMap and BackBufferCopy" src="https://blog.hyperlinkyourheart.com/images/from-hells-heart/backbuffer.png" title="Yes I used nodes as &amp;quot;folders&amp;quot; here, bad very bad."&gt;&lt;/p&gt;
&lt;p&gt;The mask &lt;code&gt;TileMap&lt;/code&gt; is populated automatically in the editor using a script like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;tool&lt;/span&gt;
&lt;span class="k"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TileMap&lt;/span&gt;

&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;copy_map&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;NodePath&lt;/span&gt;
&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;copy_from_layer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;copy_to_layer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;refresh_frequency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;

&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;onready&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_copy_map_node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;copy_map&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_editor_hint&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_tool_process&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_tool_process&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;copy_map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;refresh_frequency&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_copy_map_node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;copy_map&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current_ticks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_ticks_msec&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current_ticks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;refresh_frequency&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current_ticks&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;_copy_map&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_copy_map&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cells&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_copy_map_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_used_cells&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;copy_from_layer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clear_layer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;copy_to_layer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_copy_map_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_cell_source_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;copy_from_layer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;copy_to_layer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;_copy_map_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_cell_source_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;copy_from_layer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;_copy_map_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_cell_atlas_coords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;copy_from_layer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;_copy_map_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_cell_alternative_tile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;copy_from_layer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fix_invalid_tiles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The mask tileset just has 100% red everywhere that should be obscured, so the shader just checks for red in the screen texture and displays a grey colour instead of the sprite&amp;#8217;s texture anywhere it finds it, leaving the alpha&amp;nbsp;intact:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;shader_type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;canvas_item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;uniform&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hide_when_occluded&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;uniform&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sampler2D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SCREEN_TEXTURE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hint_screen_texture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;filter_linear_mipmap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fragment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;vec4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mask&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;textureLod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SCREEN_TEXTURE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SCREEN_UV&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hide_when_occluded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;COLOR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;COLOR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rgb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;vec3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m m-Double"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It turned out that people really hated the tight corridors and enemies being obscured though, so I probably won&amp;#8217;t be using this technique&amp;nbsp;again!&lt;/p&gt;
&lt;p&gt;A similar script to the one that populates the mask also populates a &lt;code&gt;TileMap&lt;/code&gt; with floor tiles based on the wall tiles. The floor tiles have a different size than the wall tiles however, so it is a bit longer and more involved. I think I would prefer to just put floor and wall tiles on different layers of the same &lt;code&gt;TileMap&lt;/code&gt; in the future, or as &lt;code&gt;TileMapLayers&lt;/code&gt; or whatever is being introduced in Godot&amp;nbsp;4.3&amp;#8230;&lt;/p&gt;
&lt;p&gt;Another neat thing I did in the editor was to have spawn points for portals and enemies draw a line to the thing they are associated with, or an obvious warning if they are not configured correctly. This helped me ensure that everything was set up properly when rushing through the room designs in the last few hours of the jam! I am trying to get into the habit of adding this sort of tooling to everything that might benefit from&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of Godot editor showing lines from spawn points to associated entities" src="https://blog.hyperlinkyourheart.com/images/from-hells-heart/spawnlines.png" title="Lines, lines, everywhere are lines"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;tool&lt;/span&gt;
&lt;span class="k"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Marker2D&lt;/span&gt;


&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GameRoomSpawnPoint&lt;/span&gt;


&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_editor_hint&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_tool_process&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_tool_process&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;queue_redraw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_draw&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_editor_hint&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;null&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;_draw_to_destination&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;_draw_warning&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_draw_warning&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw_circle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb nb-Type"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;15.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_draw_to_destination&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw_circle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb nb-Type"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;15.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GREEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw_line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb nb-Type"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;global_position&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb nb-Type"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GREEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I also took from the base code a node-based state machine for enemy &lt;span class="caps"&gt;AI&lt;/span&gt;. This turned out to be a bit confusing and inflexible, and when I tried to introduce some new behaviour on the final day I ended up making the game crash constantly, and had to roll back. I&amp;#8217;m still unclear on exactly what went wrong there - I was getting null references due to the summoned allies despawning, even with null checks before referencing them. It was probably really obvious but I was exhausted by then. In any case there were other problems with the state machine I developed and I have started investigating the Godot plugin &lt;a href="https://github.com/bitbrain/beehave" title="Beehave GitHub Page"&gt;Beehave&lt;/a&gt; as an alternative for the&amp;nbsp;future.&lt;/p&gt;
&lt;p&gt;Something that did not work very well was the enemy navigation and avoidance. I completely misunderstood how the avoidance system was supposed to be used, so it was actually not in play at all. In experiments I&amp;#8217;ve done since the jam I got it working to some extent, but it seems like it does not work very well anyway, with agents just grinding to a halt in many circumstances even after avoiding an obstacle! The navigation itself would have worked much better with one small settings change of &lt;code&gt;path_postprocessing&lt;/code&gt; in the &lt;code&gt;NavigationAgent2D&lt;/code&gt; to &amp;#8220;edgecentered&amp;#8221;, as I discovered later. As I had it for the jam, navigation agents are always getting stuck on wall&amp;nbsp;corners&amp;#8230;&lt;/p&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;My game got lots of nice commments, and also a lot of complaints about out-of-bounds glitches, enemies being obscured by the walls, and being too&amp;nbsp;easy.&lt;/p&gt;
&lt;p&gt;I was uninspired by the theme this time around and my skills were somewhat rusty in every area, and the game was generic and buggy as a result, so I was unsurprised by the negative comments. I did think it looked good and was reasonably juicy and sounded alright. I wasn&amp;#8217;t expecting great overall ratings, but I did think it would do ok in the graphics category at least. However, the ratings were quite bad across the board, at least compared to previous&amp;nbsp;jams!&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left;"&gt;Category&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Rating&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Placing&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Percentile&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Overall&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.571&lt;/td&gt;
&lt;td style="text-align: right;"&gt;574&lt;/td&gt;
&lt;td style="text-align: right;"&gt;65th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Fun&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.63&lt;/td&gt;
&lt;td style="text-align: right;"&gt;418&lt;/td&gt;
&lt;td style="text-align: right;"&gt;75th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Theme&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.413&lt;/td&gt;
&lt;td style="text-align: right;"&gt;792&lt;/td&gt;
&lt;td style="text-align: right;"&gt;52nd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Innovation&lt;/td&gt;
&lt;td style="text-align: right;"&gt;2.739&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1080&lt;/td&gt;
&lt;td style="text-align: right;"&gt;35th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Humor&lt;/td&gt;
&lt;td style="text-align: right;"&gt;2.609&lt;/td&gt;
&lt;td style="text-align: right;"&gt;823&lt;/td&gt;
&lt;td style="text-align: right;"&gt;50th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Graphics&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.935&lt;/td&gt;
&lt;td style="text-align: right;"&gt;428&lt;/td&gt;
&lt;td style="text-align: right;"&gt;74th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Audio&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.619&lt;/td&gt;
&lt;td style="text-align: right;"&gt;343&lt;/td&gt;
&lt;td style="text-align: right;"&gt;79th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Mood&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.457&lt;/td&gt;
&lt;td style="text-align: right;"&gt;686&lt;/td&gt;
&lt;td style="text-align: right;"&gt;59th&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This game did worse in almost every category in percentile terms than my Ludum Dare 38 entry which I didn&amp;#8217;t even complete - it was just an opening scene with a short dialog and no gameplay. Quite a drop from my &amp;#8220;Gophers&amp;#8221;&amp;nbsp;peak!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ratings Graph" src="https://blog.hyperlinkyourheart.com/images/from-hells-heart/ratingsgraph.png"&gt;
&lt;img alt="Percentile Graph" src="https://blog.hyperlinkyourheart.com/images/from-hells-heart/percentilegraph.png"&gt;&lt;/p&gt;
&lt;h2&gt;Post-jam and&amp;nbsp;Take-away&lt;/h2&gt;
&lt;p&gt;I won&amp;#8217;t make my usual promise to work on a post-jam version of this game, both because it&amp;#8217;s not interesting enough to be worth it, and because I never keep that promise&amp;nbsp;anyway!&lt;/p&gt;
&lt;p&gt;Instead I have been inspired to start working on &lt;a href="https://gamejolt.com/games/just-a-robot/185852" title="Just a Robot GameJolt Page"&gt;Just a Robot&lt;/a&gt; again since the jam, trying to get combat and enemy behaviour working the way I have always envisioned them, to see if it would actually be fun. The idea is to make it a cover-based shooter where the enemies abilities are close to the player&amp;#8217;s, more or less, and combat feels weighty and dangerous in a way that is distinct from bullet-hell&amp;nbsp;shooters.&lt;/p&gt;
&lt;p&gt;I successfully implemented a cover system for the player, but when I went to enable the enemies to use it and switch to using Beehave for their &lt;span class="caps"&gt;AI&lt;/span&gt; I quickly discovered that the way I had built them so far was too centralised and inheritance based, so after watching a few tutorials and considering things a bit I decided to start again mostly from scratch with a more composition-based approach. This is working really well for the player so far and I&amp;#8217;m nearly back at the same point as I was&amp;nbsp;previously.&lt;/p&gt;
&lt;p&gt;I also thought about and experimented with tiling approaches a bit, creating both a very small scale autotiling tileset with multiple terrains, and a mockup of a more organic looking tileset with a wide variety of designs, angled walls and floor sections and the like. I am tired of creating boxy, uninteresting levels. I was initially inspired in my gamedev journey by the art and design of Hyper Light Drifter, and I want to get back to achieving something of that look and sense of verticality - maybe not in jam games, but in Just a&amp;nbsp;Robot.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Complex tiles mockup" src="https://blog.hyperlinkyourheart.com/images/from-hells-heart/HLD-like Room Experiment.png" title="Too bright for this game, but moving in the right direction I think"&gt;&lt;/p&gt;
&lt;p&gt;I started on a greybox version of the above to actually use in designing the game, but it&amp;#8217;s not quite finished&amp;nbsp;yet.&lt;/p&gt;</content><category term="Game Development"></category><category term="godot"></category><category term="godot4"></category><category term="gdscript"></category><category term="ludum-dare"></category><category term="music"></category><category term="pixelart"></category></entry><entry><title>I Accidentally Visual Scripting</title><link href="https://blog.hyperlinkyourheart.com/accidental-visual-scripting.html" rel="alternate"></link><published>2023-11-30T22:31:00+01:00</published><updated>2023-11-30T22:31:00+01:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2023-11-30:/accidental-visual-scripting.html</id><summary type="html">&lt;p&gt;I seem to be accidentally implementing a visual scripting system as part of my&amp;nbsp;tooling&amp;#8230;&lt;/p&gt;</summary><content type="html">&lt;p&gt;For some months now I&amp;#8217;ve been trying to make some progress on the game I&amp;#8217;ve been &amp;#8220;working on&amp;#8221; for probably the better part of a decade, &lt;a href="https://gamejolt.com/games/just-a-robot/185852" title="Just a Robot on GameJolt"&gt;Just a Robot&lt;/a&gt; (yep, 7 years ago was the first post there, and it was already in progress for a few years before that!). I haven&amp;#8217;t really been working on it for most of that time, though it has always been in the back of my&amp;nbsp;mind.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Variations on the character art over the years, including sprites and a portrait" src="https://blog.hyperlinkyourheart.com/images/accidental-visual-scripting/art_changes.png" title="All I actually seem to do is redesign the art..."&gt;&lt;/p&gt;
&lt;p&gt;While I have re-implemented some basic &lt;em&gt;gunplay&lt;/em&gt; mechanics, and experimented with auto-tiling and the like, my main task has been improving the editor for cutscene graphs that I designed previously (and which I&amp;#8217;ve &lt;a href="https://blog.hyperlinkyourheart.com/coroutine-callbacks.html" title="Coroutine Callbacks"&gt;mentioned&lt;/a&gt; a &lt;a href="https://blog.hyperlinkyourheart.com/all-my-yields.html" title="All My Yields post"&gt;few times before&lt;/a&gt;) based on my experience of using it in a &lt;a href="https://blog.hyperlinkyourheart.com/gophers-post-mortem.html" title="Gophers Post-mortem"&gt;couple&lt;/a&gt; of &lt;a href="https://blog.hyperlinkyourheart.com/out-of-gas-post-mortem.html" title="Out of Gas Post Mortem"&gt;jam games&lt;/a&gt;, and the anticipated requirements for this&amp;nbsp;game.&lt;/p&gt;
&lt;p&gt;I am also hoping that it will be something that other people might find useful, and that I can release in the Godot asset library. As such I try to do things in a generalised and user-friendly way, with nothing that would tie it specifically to my game, or rough edges that I would just ignore myself but be embarrassed if other people had to deal with&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;As a result, it&amp;#8217;s taking quite a&amp;nbsp;while!&lt;/p&gt;
&lt;p&gt;One thing I&amp;#8217;ve noticed is that as I try to maintain flexibility I seem to be implementing tiny haphazard visual scripting systems for calculating values and defining conditions. Of course the graph editing is itself a type of visual scripting, but that that was both what I was expecting to be designing, and has well established &lt;span class="caps"&gt;UI&lt;/span&gt; conventions in the engine. I don&amp;#8217;t think there are any conventions or controls for defining values and conditions in the way that I&amp;nbsp;am.&lt;/p&gt;
&lt;h2&gt;Variable&amp;nbsp;Changes&lt;/h2&gt;
&lt;p&gt;In the initial incarnation of the graph editor, it was possible to set the value of variables, and branch based on the value of variables, but the way it was implemented was&amp;#8230; not&amp;nbsp;good.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Variable names were entered as strings. This seemed like a problem to me - as the scope of a project grew I anticipated that it would become harder and harder to keep track of what variables were being used, and if they were entered correctly&amp;nbsp;everywhere.&lt;/li&gt;
&lt;li&gt;Values were also always strings - if you wanted a boolean, just enter &amp;#8220;true&amp;#8221; or&amp;nbsp;&amp;#8220;false&amp;#8221;!&lt;/li&gt;
&lt;li&gt;There was no scoping, just one global pool of variables for all&amp;nbsp;graphs.&lt;/li&gt;
&lt;li&gt;You could only assign constant values to variables. No incrementing or decrementing, arbitrary calculations, or using the values of other&amp;nbsp;variables.&lt;/li&gt;
&lt;li&gt;You could only compare to constant values for&amp;nbsp;branching.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Screenshot of early version of the editor, showing string variable name and value fields" src="https://blog.hyperlinkyourheart.com/images/accidental-visual-scripting/dialogue_editor_example.png" title="This Naomi be Wolf"&gt;&lt;/p&gt;
&lt;p&gt;My initial changes to improve this situation were to introduce scoping and type definitions for variables. Variables could be scoped to the graph, to the area (i.e. &amp;#8220;level&amp;#8221; or &amp;#8220;room&amp;#8221; or however the game wanted to define it), or be global. They could also be &lt;code&gt;boolean&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;float&lt;/code&gt;, or &lt;code&gt;string&lt;/code&gt;. The &lt;span class="caps"&gt;UI&lt;/span&gt; would change to reflect the type, so you would get a checkbox for booleans and a numbox for&amp;nbsp;numbers.&lt;/p&gt;
&lt;p&gt;This was better, but still kind of awful, because everywhere a variable was used the scope and type would have to be selected again! Unlike in code there was no way to declare a variable once and then use it elsewhere with its type and scope already&amp;nbsp;known.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of one of the graphs from my game &amp;quot;Out of Gas&amp;quot;, showing scoped boolean variables with values set by checkboxes" src="https://blog.hyperlinkyourheart.com/images/accidental-visual-scripting/teenagers_dialogue.png" title="Teenagers up to no good as usual"&gt;&lt;/p&gt;
&lt;p&gt;So next&amp;#8230; I implemented variable declarations, more or less! These are defined in the project, and anywhere that a variable is required in a graph it can be selected from a searchable&amp;nbsp;dialog.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Dialog for defining a variable" src="https://blog.hyperlinkyourheart.com/images/accidental-visual-scripting/variable_definition.png" title="Defining a variable"&gt;
&lt;img alt="Variable set node with the variable selection control and a boolean value" src="https://blog.hyperlinkyourheart.com/images/accidental-visual-scripting/predefined_variable_set.png" title="Variable Set node"&gt;
&lt;img alt="Dialog for finding a variable" src="https://blog.hyperlinkyourheart.com/images/accidental-visual-scripting/variable_selection.png" title="Selecting a variable"&gt;&lt;/p&gt;
&lt;p&gt;Now I felt like I was getting places, though the problem of only being able to set and compare constants remains, and is what I&amp;#8217;m currently tackling. But more about that in a&amp;nbsp;minute.&lt;/p&gt;
&lt;h2&gt;Choice&amp;nbsp;Conditions&lt;/h2&gt;
&lt;p&gt;Another feature of the initial version of the graph editor was the ability to make dialogue choices only conditionally available to the player - for example making a choice dependent on having encountered a particular character or completed a particular quest. This was of course based on the comparison of a variable to a string constant, so it had all the same deficiencies as everything else about the variables, as well as a few of its&amp;nbsp;own:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Only one variable could be used for each&amp;nbsp;choice.&lt;/li&gt;
&lt;li&gt;The only comparison available was equality, no greater or less than or anything like&amp;nbsp;that.&lt;/li&gt;
&lt;li&gt;There was no possibility of&amp;nbsp;negation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The minimal improvement would be to allow a comparison of a single variable with a selectable operator, and a constant value. But what if the choice depends on the value of multiple variables? What if you want to make different choices available if the player has encountered a character but not completed a quest, than if they have done both? This could maybe be achieved using branching nodes to set a third variable, but that seemed like jumping through a lot of unnecessary hoops. I wanted to be able to define complex conditions directly on the&amp;nbsp;choices.&lt;/p&gt;
&lt;p&gt;My solution was to add a dialog where an arbitrarily complex condition can be defined - though currently only with constants on the right side of any&amp;nbsp;operator.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A screenshot of the condition definition dialog alongside the node that it was invoked from" src="https://blog.hyperlinkyourheart.com/images/accidental-visual-scripting/farnsworth.png" title="The Farnsworth Condition"&gt;&lt;/p&gt;
&lt;p&gt;As you can see above, the condition is structured as a tree with boolean operators grouping the results of comparison operators. The whole condition is summarised as its (frankly much more understandable) equivalent&amp;nbsp;GDScript.&lt;/p&gt;
&lt;p&gt;So mission mostly accomplished - it was now possible to define conditions on choices with an arbitrary number of clauses. However, it was starting to seem like a lot of &lt;span class="caps"&gt;UI&lt;/span&gt; complexity to achieve something that is quite simple to do in code - almost like a type of visual&amp;nbsp;scripting&amp;#8230;&lt;/p&gt;
&lt;h2&gt;Visualising&amp;nbsp;Values&lt;/h2&gt;
&lt;p&gt;Now it&amp;#8217;s time to tackle the other side of the equation - the values to set or compare the chosen variables&amp;nbsp;to.&lt;/p&gt;
&lt;p&gt;For setting variables, I wanted it to be possible to increment or decrement values as well as setting static ones&amp;#8230; But why not also allow values to be multiplied or divided? What if you want to set a variable to 2x another variable, or the result of a more complex calculation? These don&amp;#8217;t seem like particularly likely requirements for my game, but why reduce the flexibility by only allowing a handful of fixed&amp;nbsp;operations?&lt;/p&gt;
&lt;p&gt;For comparisons, I initially only considered allowing a choice between a constant or another variable. But would these not also benefit from more flexibility? What if you want to check if a variable is greater than half another variable? If setting variables was as flexible as described above, this could be achieved by setting a temporary variable, but that seems unnecessarily round-about and annoying to have to do. Since the task is the same for both situations (obtain a value for the right side of an operator), it seemed like it would be prudent to create one control that would cover&amp;nbsp;both.&lt;/p&gt;
&lt;p&gt;Another factor is that most of the above concerns only apply to integer and float variables. Booleans have another set of operators that might be applied to them. Strings have a more restricted set of operators, but there are a variety of functions that you might want to apply, or methods that you might want to call on them - &lt;code&gt;to_lower&lt;/code&gt;, &lt;code&gt;rstrip&lt;/code&gt;, &lt;code&gt;replacen&lt;/code&gt;, etc. In fact, the same might be true of integers and&amp;nbsp;floats&amp;#8230;&lt;/p&gt;
&lt;p&gt;With all that in mind the requirements have become quite complex - I&amp;#8217;m faced with implementing a small but significant subset of GDScript in a &lt;span class="caps"&gt;GUI&lt;/span&gt;!&lt;/p&gt;
&lt;p&gt;The design I have so far allows for multiple variables or constants to have operators to be applied between them, grouped by brackets if necessary, and for a selection of appropriate (for the type) functions to be&amp;nbsp;called:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of the design of the proposed value calculation control in the designer" src="https://blog.hyperlinkyourheart.com/images/accidental-visual-scripting/value_calculation_mockup.png" title="That's a really long and weird way to write 1..."&gt;&lt;/p&gt;
&lt;p&gt;One thing that annoys me about this is that it&amp;#8217;s much the same structure as is involved in creating the conditions (a tree), but uses completely different controls and looks and works completely differently. It&amp;#8217;s going to look quite strange when they are side by side in the conditions dialog&amp;#8230; However, the tree control used for the conditions probably suits that better because the elements need to be selectable, and the &amp;#8220;prefix notation&amp;#8221; also suits it better I think, while it would be unfamiliar for most people for mathematical&amp;nbsp;operators.&lt;/p&gt;
&lt;p&gt;I can&amp;#8217;t really tell at this point if this is at all intuitive or if there&amp;#8217;s an obviously much simpler solution that I&amp;#8217;ve overlooked. But it certainly makes me long to be able to just enter the values as code, regardless of their&amp;nbsp;composition.&lt;/p&gt;
&lt;h2&gt;Could I Uuuh&amp;#8230; Do&amp;nbsp;That?&lt;/h2&gt;
&lt;p&gt;I have been thinking about what would be involved in allowing the user to enter conditions and values as the GDScript code they would likely already be familiar&amp;nbsp;with.&lt;/p&gt;
&lt;p&gt;Godot includes an &lt;code&gt;Expression&lt;/code&gt; class which can be used to parse and execute arbitrary expressions, so conceivably that could be used to run whatever the user input, and make the entire GDScript language available to them. It looks like it can even parse the text for errors before running it, which would be all that I would want to do in the editor anyway. One likely difficulty is that the referenced variables would not actually be GDScript variables at runtime, but entries in one of several dictionaries, so I might have to write my own parser anyway to pick those out and replace them. I&amp;#8217;m not sure I really want to do&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;If that didn&amp;#8217;t work out, the alternative would be to parse the input myself (even more parsing!) and convert it into the same resource structures that I&amp;#8217;m intending to have the &lt;span class="caps"&gt;UI&lt;/span&gt; create. This would likely be much more limited in which language constructs and operations it could support - and that might cause confusion or frustration for the&amp;nbsp;user.&lt;/p&gt;
&lt;p&gt;And, there are reasons why allowing these things to be defined as code might not be a good idea&amp;nbsp;anyway:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I don&amp;#8217;t really like the idea of having to switch from a &lt;span class="caps"&gt;GUI&lt;/span&gt; way of doing things to a code way of doing things when the rest of the plugin is very much &lt;span class="caps"&gt;GUI&lt;/span&gt;&amp;nbsp;driven.&lt;/li&gt;
&lt;li&gt;I don&amp;#8217;t like the fact that the predefined variables would not be selectable, negating some of their utility. Or at least, to make them selectable I would have to implement some sort of auto-completion on top of everything else, which might be beyond&amp;nbsp;me! &lt;/li&gt;
&lt;li&gt;Some people use C# with Godot, and it&amp;#8217;s unclear if the &lt;code&gt;Expression&lt;/code&gt; class can parse C# - I suspect it&amp;nbsp;can&amp;#8217;t.&lt;/li&gt;
&lt;li&gt;Using the &lt;code&gt;Expression&lt;/code&gt; class would likely allow arbitrary GDScript to be entered and executed during graph processing. That might be too much flexibility! If I want to allow that it will be its own node&amp;nbsp;type.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One other &lt;span class="caps"&gt;GUI&lt;/span&gt; option I can think of would be to allow value calculations and conditions to be defined using their own type of graphs. This might have the advantage of using well established &lt;span class="caps"&gt;UI&lt;/span&gt; conventions and existing controls. On the other hand, a graph editor is not ideal for defining a tree, and it would probably appear even more unnecessarily complex and confusing for the undoubtedly most common use cases of setting variables to constant values and defining simple conditions based on single variables. It would also not be any less work for&amp;nbsp;me!&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;&lt;span class="caps"&gt;UI&lt;/span&gt; is hard &lt;i class="fas fa-sad-cry body-icon" /&gt; &lt;/p&gt;</content><category term="Game Development"></category><category term="godot"></category><category term="godot4"></category><category term="gdscript"></category><category term="cutscenegrapheditor"></category></entry><entry><title>All My Yield()s, Gone!</title><link href="https://blog.hyperlinkyourheart.com/all-my-yields.html" rel="alternate"></link><published>2023-06-23T17:10:00+02:00</published><updated>2023-06-23T17:10:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2023-06-23:/all-my-yields.html</id><summary type="html">&lt;p&gt;The removal of yield() in Godot 4 means a new approach is required for some of its use&amp;nbsp;cases.&lt;/p&gt;</summary><content type="html">&lt;p&gt;In a &lt;a href="https://blog.hyperlinkyourheart.com/coroutine-callbacks.html" title="Coroutine Callbacks post"&gt;previous post&lt;/a&gt; I described a way to use the state object returned by a &lt;code&gt;yield()&lt;/code&gt; call to control the traversal of a graph - specifically, a graph describing a cutscene or dialogue - where some nodes in the graph require waiting on input from the user or some other event before&amp;nbsp;proceeding.&lt;/p&gt;
&lt;p&gt;In &lt;a href="https://godotengine.org/" title="The game engine you waited for."&gt;Godot 4&lt;/a&gt; the &lt;code&gt;yield&lt;/code&gt; function was replaced with the &lt;code&gt;await&lt;/code&gt; keyword. This has the same basic purpose: to suspend execution of the current function and return to the caller, to be resumed at a later time. However, it does not return the state object that &lt;code&gt;yield&lt;/code&gt; did, so there is no built-in way to resume the function from the caller (that I can see,&amp;nbsp;anyway).&lt;/p&gt;
&lt;p&gt;Fortunately, it is not difficult to recreate the functionality. The first thing we need to do is define a very simple class that we can include instances of in the signals from the graph&amp;nbsp;controller:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;ProceedSignal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;signal&lt;/span&gt; &lt;span class="n"&gt;ready_to_proceed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;proceed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;ready_to_proceed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This class includes a signal and a method that the consumer of the graph can call to tell the graph controller that it can proceed to the next node - similar to the &lt;code&gt;resume()&lt;/code&gt; method on the coroutine state object in Godot&amp;nbsp;3.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;_await_response&lt;/code&gt; function which previously yielded to create a resumable coroutine state, now just returns a new instance of this class. It could alternatively just be created directly where this function is&amp;nbsp;called:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_await_response&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ProceedSignal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In &lt;code&gt;process_cutscene()&lt;/code&gt; we now &lt;code&gt;await&lt;/code&gt; calls to process node types that require waiting on the consumer, rather than &lt;code&gt;yield&lt;/code&gt;ing&amp;nbsp;them:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;process_cutscene&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cutscene&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;_graph_stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;_local_store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;_current_graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cutscene&lt;/span&gt;
    &lt;span class="n"&gt;_current_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_current_graph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;root_node&lt;/span&gt;

    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;DialogueTextNode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_process_dialogue_node&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;BranchNode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;_process_branch_node&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And when processing such a node, we just create the &lt;code&gt;ProceedSignal&lt;/code&gt; object, emit the relevant signal with it, and then await the &lt;code&gt;ready_to_proceed&lt;/code&gt; signal from&amp;nbsp;it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_process_dialogue_node&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;

    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;

    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;character_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;variant_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;character&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;character_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;character_name&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;character_variant&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;variant_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;character_variant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;variant_name&lt;/span&gt;

    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_await_response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;call_deferred&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;_emit_dialogue_signal&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;character_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;variant_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;process&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ready_to_proceed&lt;/span&gt;

    &lt;span class="n"&gt;_current_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_get_node_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_current_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Nothing much changes from the consumer&amp;#8217;s point of view, it just needs to store the object and then call the &lt;code&gt;proceed()&lt;/code&gt; method when it&amp;#8217;s&amp;nbsp;ready:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_on_cutscene_controller_dialogue_display_requested&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;character_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;character_variant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;process&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Hang on to the process object so we can tell the cutscene controller&lt;/span&gt;
    &lt;span class="c1"&gt;# to continue when we&amp;#39;re ready to proceed&lt;/span&gt;
    &lt;span class="n"&gt;_current_process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;

    &lt;span class="o"&gt;...&lt;/span&gt;


&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_on_dialogue_display_continue_clicked&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;DialogueDisplay&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;_current_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;proceed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That&amp;#8217;s all the changes required for this project! Of course, the coroutine state object also had a property indicating if it was resumable or not, &lt;code&gt;is_valid&lt;/code&gt;. It would not be difficult at all to reproduce this by simply adding such a property (perhaps behind a setter that would make it read-only except internally), and setting it to false once &lt;code&gt;proceed()&lt;/code&gt; is&amp;nbsp;called.&lt;/p&gt;
&lt;p&gt;It could also be expanded to allow more complex communication between the coroutine and the consumer, or to make a long running coroutine cancellable. The controller can only &lt;code&gt;await&lt;/code&gt; one type of signal to continue, but you could have it pass different instructions when resuming e.g. you could give it &lt;code&gt;stop()&lt;/code&gt; and &lt;code&gt;proceed()&lt;/code&gt; methods. Additional properties on the signal object could be used to pass back other data without having to pass it in the signal at&amp;nbsp;all.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;ProceedSignalType&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;STOP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;PROCEED&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;ProceedSignal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;signal&lt;/span&gt; &lt;span class="n"&gt;ready_to_proceed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;consumer_state&lt;/span&gt;

    &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;ready_to_proceed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProceedSignalType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;STOP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;proceed&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;ready_to_proceed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProceedSignalType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROCEED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Cutscene Graph Editor&amp;nbsp;Status&lt;/h2&gt;
&lt;p&gt;The cutscene graph editor has been upgraded to support Godot 4, at a &lt;a href="https://github.com/khoulihan/godot4-cutscene-graph-editor" title="Cutscene Graph Editor project on GitHub"&gt;new home&lt;/a&gt;. I&amp;#8217;ve also added a bunch of minor features, such as multi-node deletion, copy &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; paste and duplication support, and support for dragging from an output port to create a new&amp;nbsp;node.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m now working on improving some parts of the tool that were lacking in flexibility. My current task is improving the addition of conditions to the choice and random nodes, which previously only allowed a single variable to be compared for equality to determine if a branch should be considered. The new system moves the condition specification &lt;span class="caps"&gt;UI&lt;/span&gt; out of the nodes themselves and into a dialog box, and allows any number of variables to be evaluated using a variety of different&amp;nbsp;operators.&lt;/p&gt;
&lt;p&gt;&lt;img alt="The Farnsworth Condition" src="https://blog.hyperlinkyourheart.com/images/all-my-yields/graph.png" title="The Farnsworth Condition"&gt;&lt;/p&gt;
&lt;p&gt;Future plans include new ways of defining and interacting with sub-graphs, more flexible ways of manipulating variables, built-in variables and meta-data, and better ways of defining&amp;nbsp;characters.&lt;/p&gt;
&lt;h2&gt;Update: Pure&amp;nbsp;Signals&lt;/h2&gt;
&lt;p&gt;It occurred to me after posting this that there is an even easier way to achieve this - as long as you don&amp;#8217;t need to keep any state in the signalling object. Because Godot 4 allows you to pass signals and callables, you could just pass the signal to proceed with the signal that initiates the action. When the consumer is ready to proceed they can then just call &lt;code&gt;emit&lt;/code&gt; on&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;I might still prefer the other way of doing it because &lt;code&gt;_current_process.proceed()&lt;/code&gt; reads a little better than &lt;code&gt;_current_process_proceed_signal.emit()&lt;/code&gt;.&lt;/p&gt;</content><category term="Game Development"></category><category term="godot"></category><category term="gdscript"></category><category term="godot4"></category></entry><entry><title>Ludum Dare 48 Results</title><link href="https://blog.hyperlinkyourheart.com/out-of-gas-results.html" rel="alternate"></link><published>2021-05-27T11:11:00+02:00</published><updated>2021-05-27T11:11:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2021-05-27:/out-of-gas-results.html</id><summary type="html">&lt;p&gt;Results for Ludum Dare 48 are out, and I did less well than last&amp;nbsp;time!&lt;/p&gt;</summary><content type="html">&lt;p&gt;Ludum Dare 48 results are out a while now, but I forgot to post about them. I did not do as well as last time, but since my &lt;a href="https://blog.hyperlinkyourheart.com/gophers-results.html"&gt;results for Gophers&lt;/a&gt; were exceptional for me, that was probably to be expected. This time I placed 433rd overall, with the stand-out category being graphics, where I just about broke the top 100, at 99th&amp;nbsp;place.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left;"&gt;Category&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Rating&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Placing&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Percentile&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Overall&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.81&lt;/td&gt;
&lt;td style="text-align: right;"&gt;433&lt;/td&gt;
&lt;td style="text-align: right;"&gt;84th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Fun&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.259&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1044&lt;/td&gt;
&lt;td style="text-align: right;"&gt;61st&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Theme&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.672&lt;/td&gt;
&lt;td style="text-align: right;"&gt;873&lt;/td&gt;
&lt;td style="text-align: right;"&gt;67th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Innovation&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.345&lt;/td&gt;
&lt;td style="text-align: right;"&gt;754&lt;/td&gt;
&lt;td style="text-align: right;"&gt;72nd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Humor&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.596&lt;/td&gt;
&lt;td style="text-align: right;"&gt;277&lt;/td&gt;
&lt;td style="text-align: right;"&gt;89th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Graphics&lt;/td&gt;
&lt;td style="text-align: right;"&gt;4.466&lt;/td&gt;
&lt;td style="text-align: right;"&gt;99&lt;/td&gt;
&lt;td style="text-align: right;"&gt;96th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Audio&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.621&lt;/td&gt;
&lt;td style="text-align: right;"&gt;526&lt;/td&gt;
&lt;td style="text-align: right;"&gt;80th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Mood&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.846&lt;/td&gt;
&lt;td style="text-align: right;"&gt;460&lt;/td&gt;
&lt;td style="text-align: right;"&gt;83rd&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Graphs&lt;/h2&gt;
&lt;p&gt;A couple of graphs demonstrating my &lt;em&gt;trends&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ratings Graph" src="https://blog.hyperlinkyourheart.com/images/out-of-gas-results/overall_graphics_ratings.png"&gt;
&lt;img alt="Placings Graph" src="https://blog.hyperlinkyourheart.com/images/out-of-gas-results/placings.png"&gt;&lt;/p&gt;
&lt;p&gt;Still haven&amp;#8217;t surpassed that Rattendorf peak, &lt;em&gt;sigh&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;More Out of&amp;nbsp;Gas&lt;/h2&gt;
&lt;p&gt;I am committed to working on a post-jam release of this game, as I think with a few tweaks and a bit more content it could be something quite special (more special than the ratings above suggest&amp;nbsp;anyway!)&lt;/p&gt;
&lt;p&gt;I will be posting here with development updates, and also over on the &lt;a href="https://hyperlinkyourheart.itch.io/out-of-gas"&gt;itch.io&lt;/a&gt; page, where you can also still play the jam version for&amp;nbsp;now.&lt;/p&gt;</content><category term="Game Development"></category><category term="ludum-dare"></category><category term="sci-fi"></category><category term="music"></category><category term="pixelart"></category><category term="godot"></category></entry><entry><title>Out of Gas</title><link href="https://blog.hyperlinkyourheart.com/out-of-gas-post-mortem.html" rel="alternate"></link><published>2021-05-04T10:41:00+02:00</published><updated>2021-05-29T22:52:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2021-05-04:/out-of-gas-post-mortem.html</id><summary type="html">&lt;p&gt;A post-mortem of my Ludum Dare 48 entry, Out of Gas - a space-road-trip themed narrative/clicker game about a couple of outlaws fleeing their cryptocurrency&amp;nbsp;debts.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://hyperlinkyourheart.itch.io/out-of-gas"&gt;&lt;img alt="Out of Gas" src="https://blog.hyperlinkyourheart.com/images/out-of-gas-post-mortem/OOG_itch_banner.jpg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://hyperlinkyourheart.itch.io/out-of-gas" title="Out of Gas"&gt;Out of Gas&lt;/a&gt; is my entry for the recent &lt;a href="https://ldjam.com/events/ludum-dare/48" title="Ludum Dare 48"&gt;Ludum Dare 48&lt;/a&gt; game jam. It was intended to be a blatant &lt;a href="https://subsetgames.com/ftl.html" title="FTL game"&gt;&lt;span class="caps"&gt;FTL&lt;/span&gt;&lt;/a&gt; rip-off, but in having much simplified clicker-like combat mechanics and an unusual cars-in-space aesthetic, I think it is sufficiently its own thing (I hope&amp;nbsp;anyway!)&lt;/p&gt;
&lt;h2&gt;Concept&lt;/h2&gt;
&lt;p&gt;My initial thought was more of a straight sci-fi concept of travelling into deep space, facing combat and resource management challenges similar to those which ended up in the game. The first thing that came to mind as a title for this was &amp;#8220;Out of Gas&amp;#8221;, after the &lt;a href="https://www.youtube.com/watch?v=-_heR2ekoxI" title="Out of gas. Out of road. Out of car I don't know how I'm gonna go."&gt;Modest Mouse song&lt;/a&gt; of the same&amp;nbsp;name.&lt;/p&gt;
&lt;p&gt;Thinking about that song got me thinking in another direction - essentially the same mechanics, but Earth-bound, set in a Modest Mouse world of highways and drifters, fleeing problems and starting over. The problem to flee in this case would obviously be debt - something that you can get deeper and deeper&amp;nbsp;into.&lt;/p&gt;
&lt;p&gt;As you can probably tell by now I ended up combining these two ideas! I didn&amp;#8217;t want to let either of them go, and I quickly fell in love with the idea of a universe where space combat meant winding down your window and firing a handgun at your&amp;nbsp;enemy.&lt;/p&gt;
&lt;p&gt;The combined concept suggested that a new, sci-fi twist on the debt problem was required, and that&amp;#8217;s where the idea of a constantly growing cryptocurrency debt came in. I&amp;#8217;ve been thinking about cryptocurrencies a lot lately since they&amp;#8217;re going through another hype cycle. One commonly touted feature of them (or some, like Bitcoin, at least), is that they&amp;#8217;re deflationary, so any debt denominated in them would constantly grow in value instead of decreasing in value like debts denominated in inflationary fiat currencies. This seemed like the perfect notion for the bizarre world of my game. It doesn&amp;#8217;t really have any impact on the gameplay, but I think it&amp;#8217;s a nice bit of narrative flavour, and it fits the theme&amp;nbsp;perfectly.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Late payment" src="https://blog.hyperlinkyourheart.com/images/out-of-gas-post-mortem/OOG-Late-Payment.jpg"&gt;&lt;/p&gt;
&lt;p&gt;The final piece of the conceptual puzzle was the characters. I couldn&amp;#8217;t very well have two anonymous nobodies riding along with the player on a trip like this! I turned to two characters I had vague notions of making a game about years ago, Haze &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Lee, a gun-toting, post-apocalyptic outlaw couple. Unfortunately I don&amp;#8217;t think their personalities really come across in the game due to time constraints, but at least they have&amp;nbsp;names!&lt;/p&gt;
&lt;h2&gt;Art&lt;/h2&gt;
&lt;p&gt;As usual, I did all the art in &lt;a href="https://www.pyxeledit.com/" title="Pyxel Edit"&gt;Pyxel Edit&lt;/a&gt;. I started off very rough with just pink boxes for the ships and a partial circle for the background, and moved immediately onto the gameplay with those placeholders. This is a somewhat unusual approach for me as I usually try to tie down the look of a game early&amp;nbsp;on.&lt;/p&gt;
&lt;p&gt;&lt;img alt="First mockup" src="https://blog.hyperlinkyourheart.com/images/out-of-gas-post-mortem/FirstMockup.png"&gt;&lt;/p&gt;
&lt;p&gt;The thing about rough mockups is that by the time I came back to the art a bunch of stuff in the game was already tied to the resolution and shape of the outlines. I wasn&amp;#8217;t even sure I had done the perspective correctly, but I was stuck with it anyway, and I guess it was close enough because nobody has&amp;nbsp;complained!&lt;/p&gt;
&lt;p&gt;&lt;img alt="First ships" src="https://blog.hyperlinkyourheart.com/images/out-of-gas-post-mortem/art_opt.jpg"&gt;&lt;/p&gt;
&lt;p&gt;I had planned to do some character portraits as well for the narrative side of the game, and express encounters as dialogues between characters, but unfortunately there wasn&amp;#8217;t time for&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;The map screen also didn&amp;#8217;t get much love, with the initial placeholder art surviving into the final&amp;nbsp;game.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Map screen" src="https://blog.hyperlinkyourheart.com/images/out-of-gas-post-mortem/OOG_Map_Trail.jpg"&gt;&lt;/p&gt;
&lt;h2&gt;Code&lt;/h2&gt;
&lt;p&gt;As for my last Ludum Dare entry, &lt;a href="https://blog.hyperlinkyourheart.com/gophers-post-mortem.html"&gt;Gophers&lt;/a&gt;, a &lt;a href="https://github.com/khoulihan/godot-cutscene-graph" title="Cutscene Graph Editor"&gt;cutscene graph editor plugin&lt;/a&gt; for &lt;a href="https://godotengine.org/" title="The game engine you waited for."&gt;Godot&lt;/a&gt; that I had previously developed was essential to getting this game done within the time constraints of the&amp;nbsp;jam.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Those darn teenagers!" src="https://blog.hyperlinkyourheart.com/images/out-of-gas-post-mortem/teenagers_dialogue.jpg"&gt;&lt;/p&gt;
&lt;p&gt;I had done a lot of work on this plugin in recent months, applying lessons learned from using it for Gophers to improve the workflow, functionality, and stability. This paid huge dividends, and I found almost no issues while churning out encounter cutscenes. The cutscene graphs created by this plugin power every encounter in the game up to and after&amp;nbsp;combat.&lt;/p&gt;
&lt;p&gt;My main task on the first day was to get the map screen working - it wouldn&amp;#8217;t be much of a game if you couldn&amp;#8217;t travel between systems. I implemented it as a graph defined by custom &lt;code&gt;MapSystem&lt;/code&gt; and &lt;code&gt;MapConnection&lt;/code&gt; control&amp;nbsp;nodes.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Map graph" src="https://blog.hyperlinkyourheart.com/images/out-of-gas-post-mortem/map_ui.jpg"&gt;&lt;/p&gt;
&lt;p&gt;The new fun thing about this for me was declaring the &lt;code&gt;MapConnection&lt;/code&gt; class to be a &amp;#8220;tool&amp;#8221; script, allowing the connections between systems to be drawn in the editor. This kind of custom tooling is important for the efficient design of systems in larger games, so it was fun to give it a try. Here, it mostly served to prevent me from getting confused about which &lt;code&gt;MapSystem&lt;/code&gt; nodes I had already connected to each other and which I&amp;nbsp;hadn&amp;#8217;t.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_draw&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;usable&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;used&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;_can_draw_in_editor&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;_load_nodes_for_editor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;# TODO: Decide on proper colours for this&lt;/span&gt;
        &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gray&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;usable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hotpink&lt;/span&gt;
        &lt;span class="c1"&gt;# No idea why the offset is required&lt;/span&gt;
        &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;source_center&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_source_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rect_global_position&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;CENTER_OFFSET&lt;/span&gt;
        &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;target_center&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_target_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rect_global_position&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;CENTER_OFFSET&lt;/span&gt;
        &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;source_center&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;target_center&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;source_center&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;SPACE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;finish&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target_center&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;source_center&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;target_center&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;SPACE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;draw_line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;LINE_THICKNESS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;false&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Implementing the combat was a lot tougher. The battle scene is almost entirely &lt;span class="caps"&gt;UI&lt;/span&gt; nodes, which was perhaps a mistake, and for some reason I struggled most of day two to even get player input to register properly. I don&amp;#8217;t really remember now what the obstacle was to this - maybe I was just&amp;nbsp;tired.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Battle UI" src="https://blog.hyperlinkyourheart.com/images/out-of-gas-post-mortem/encounter_ui.jpg"&gt;&lt;/p&gt;
&lt;h2&gt;Sound and&amp;nbsp;Music&lt;/h2&gt;
&lt;p&gt;There&amp;#8217;s nothing special about the sound this time around. Unlike for Gophers I didn&amp;#8217;t have time to do any foley work, and I figured I would probably have a hard time creating gunshot and explosion sounds anyway. Instead I fell back to &lt;span class="caps"&gt;SFXR&lt;/span&gt; (specifically, &lt;a href="https://sfxr.me/" title="jsfxr"&gt;jsfxr&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The music I put together in &lt;a href="https://beepbox.co" title="BeepBox"&gt;BeepBox&lt;/a&gt;, my favourite game jam tool for chiptune music these days due to its simplicity. I tend not to get as bogged down with it as I do with &lt;a href="https://lmms.io/" title="LMMS"&gt;&lt;span class="caps"&gt;LMMS&lt;/span&gt;&lt;/a&gt;. I initially wanted to try to create something in the style of Modest Mouse, given the game&amp;#8217;s inspiration, but it was hard to recreate a guitar sound. Instead I ended up with a simple arpeggiated chord progression over a bass drone that I hope feels kind of epic, kind of longing and lonesome, I&amp;nbsp;dunno.&lt;/p&gt;
&lt;p&gt;The battle music is a variation of the same tune but just swaps out one of the instruments, doubles up the drone, and compresses and spreads the arpeggios across more octaves for a more urgent and chaotic feeling. The two tracks play in sync throughout the game and just cut between each other as&amp;nbsp;necessary.&lt;/p&gt;
&lt;h2&gt;Abandoned&amp;nbsp;Ideas&lt;/h2&gt;
&lt;p&gt;As usual there were many ideas that I had to put aside due to time constraints. I had planned to include a small number of missiles which could be fired during combat with devastating effect, to allow the player to pull themselves back from the brink of defeat. I had also intended to have targetable/damageable systems on the ships, including injury to the characters, so that you could take out the enemy&amp;#8217;s weapon to gain a reprieve from being fired upon, or their engines and reduce their chance to dodge. You can see the proposed &lt;span class="caps"&gt;UI&lt;/span&gt; of these mechanics in the initial mockup above. Finally, I had planned a system of weapon upgrades which almost made it in, but not&amp;nbsp;quite.&lt;/p&gt;
&lt;p&gt;On the art side, I had wanted to include a number of different backgrounds to improve the sense of travelling between&amp;nbsp;systems.&lt;/p&gt;
&lt;p&gt;A big regret that I have is that I wasn&amp;#8217;t able to include the character portraits, and express the encounters primarily as dialogue between characters. I think this would have introduced a lot more personality to the narrative side of the game, as opposed to the third-person narration that I had to go&amp;nbsp;with.&lt;/p&gt;
&lt;h2&gt;Post-Jam&lt;/h2&gt;
&lt;p&gt;I like this game a lot, and I feel it is one that could be polished up and rounded out into a complete experience with relative ease, so I am determined to do a post-jam release with some of the missing features described above, and some of the problems with the jam release resolved (such as the &lt;span class="caps"&gt;RNG&lt;/span&gt; sometimes producing the same encounter multiple times in a row). I have already begun work on this, and I hope to have it completed in the next few months. I&amp;#8217;ll do another post about my progress on it so far, but for now here&amp;#8217;s a little sneak&amp;nbsp;peek:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://hyperlinkyourheart.itch.io/out-of-gas"&gt;&lt;img alt="Haze speaks!" src="https://blog.hyperlinkyourheart.com/images/out-of-gas-post-mortem/HazeSpeaks.jpg"&gt;&lt;/a&gt;&lt;/p&gt;</content><category term="Game Development"></category><category term="ludum-dare"></category><category term="sci-fi"></category><category term="music"></category><category term="pixelart"></category><category term="godot"></category></entry><entry><title>Coroutine Callbacks</title><link href="https://blog.hyperlinkyourheart.com/coroutine-callbacks.html" rel="alternate"></link><published>2021-04-05T18:29:00+02:00</published><updated>2021-04-05T18:29:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2021-04-05:/coroutine-callbacks.html</id><summary type="html">&lt;p&gt;Using coroutines to call back to the emitter of a&amp;nbsp;signal.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I&amp;#8217;m working on a cutscene/dialogue graph editor plugin for the &lt;a href="https://godotengine.org/" title="The game engine you waited for."&gt;Godot game engine&lt;/a&gt; (I mentioned using it for the Ludum Dare in a &lt;a href="https://blog.hyperlinkyourheart.com/gophers-post-mortem.html"&gt;previous post&lt;/a&gt;). When I came to deciding how to actually process the graphs it creates in order to display the dialogue and perform actions in the game, I discovered an interesting use for coroutines that I haven&amp;#8217;t seen described elsewhere. I&amp;#8217;m not all that used to working with coroutines so maybe what I&amp;#8217;ve done is completely normal and everybody does it all the time, or indeed it might be ill-advised for some reason. Either way, I will describe it here and you can ignore me if I&amp;#8217;m being an&amp;nbsp;idiot!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/khoulihan/godot-cutscene-graph" title="Cutscene Graph Editor"&gt;&lt;img alt="Graph editor" src="https://blog.hyperlinkyourheart.com/images/gophers-post-mortem/graph.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Processing a graph that results in activity in-game is a task which frequently needs to be put on hold - waiting for text to be displayed in the &lt;span class="caps"&gt;UI&lt;/span&gt; in a juicy manner, moving characters around, waiting for input etc. This could be achieved in a &lt;code&gt;Node&lt;/code&gt; by implementing &lt;code&gt;_process()&lt;/code&gt; and maintaining state about whether you&amp;#8217;re currently walking a graph or waiting on something, and having methods that other nodes can use to indicate when processing can continue. I have found such methods to be quite messy in the&amp;nbsp;past.&lt;/p&gt;
&lt;p&gt;I decided instead to process the graph in a &lt;code&gt;while&lt;/code&gt; loop and &lt;code&gt;yield&lt;/code&gt; as necessary while other nodes perform tasks initiated by a number of signals. Where tasks are expected to take some time to complete, a coroutine state object is passed to the signal which the listener can use to indicate that processing should continue, or in some cases return&amp;nbsp;values.&lt;/p&gt;
&lt;h2&gt;Let&amp;#8217;s See Some&amp;nbsp;Code&lt;/h2&gt;
&lt;p&gt;Below is an abridged version of the main method that processes the graphs. Processing a dialogue node yields so that the &lt;span class="caps"&gt;UI&lt;/span&gt; that displays the text can wait for player input to continue. Processing a branch node and others like it just proceed directly to the next iteration of the&amp;nbsp;loop.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;process_cutscene&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cutscene&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;_local_store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;_current_graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cutscene&lt;/span&gt;
    &lt;span class="n"&gt;_current_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_current_graph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;root_node&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;DialogueTextNode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_process_dialogue_node&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;completed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;BranchNode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;_process_branch_node&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here is the code that processes dialogue nodes and raises the signal for their text to be displayed in-game. A coroutine is put in a frozen state to be passed with the signal, and the call to emit the signal is deferred so we can make sure we are waiting for the coroutine to complete before any signal listeners have a chance to resume&amp;nbsp;it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_await_response&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_emit_dialogue_signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;character_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;character_variant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;process&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;emit_signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;dialogue_display_requested&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;character_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;character_variant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;process&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_process_dialogue_node&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;

    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_await_response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;call_deferred&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;_emit_dialogue_signal&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;_current_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;character_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;_current_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;character_variant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;variant_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;process&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;completed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;_current_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The code that consumes the signal can take however long it likes to do its thing, and when it&amp;#8217;s ready it can just call &lt;code&gt;resume()&lt;/code&gt; on the coroutine state and the graph processing will proceed to the next&amp;nbsp;node.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_on_CutsceneController_dialogue_display_requested&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;character_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;character_variant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;process&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;_process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;


&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_on_ContinueButton_pressed&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_process&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If a node in the graph needs a response in order to know what to do next (e.g. a choice between multiple dialogue options), we can just accept the return value of the coroutine passed in the signal. The consumer can just pass the value to the &lt;code&gt;resume()&lt;/code&gt; call.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_process_choice_node&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_await_response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;call_deferred&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;_emit_choices_signal&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;process&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;choice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;completed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;_current_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Potential&amp;nbsp;Pitfalls&lt;/h2&gt;
&lt;p&gt;There are two pitfalls that come to mind with this approach, but they&amp;#8217;re not really any different than what could occur with a &lt;code&gt;_process()&lt;/code&gt; based approach like the one I described&amp;nbsp;above.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Processing of another graph could be triggered while one is still in progress. If the previous one is yielding it would end up processing the new graph as well when it resumes. This is quite easy to avoid by just noping out of the method if there is already work in&amp;nbsp;progress.&lt;/li&gt;
&lt;li&gt;If there are multiple listeners connected to the signals there could be ambiguity about which one is responsible for resuming. If there are multiple resume calls, all but the first will result in a runtime&amp;nbsp;exception.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In the event that you actually &lt;em&gt;want&lt;/em&gt; multiple listeners to report in before resuming, it might be possible to craft a different coroutine that takes the &lt;a href="https://docs.godotengine.org/en/stable/classes/class_object.html#class-object-method-get-signal-connection-list" title="get_signal_connection_list()"&gt;number of listeners&lt;/a&gt; and yields until they have all resumed. I think it would look something like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_await_many&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;


&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_await_many_responses&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;responses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;responses&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I haven&amp;#8217;t tested that&amp;nbsp;though.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I don&amp;#8217;t really have one, I just thought it was a neat pattern that doesn&amp;#8217;t require the listeners to keep a reference to the node doing the processing or have any knowledge of it aside from the signals it emits. You can check out &lt;a href="https://github.com/khoulihan/godot-cutscene-graph" title="Cutscene Graph Editor"&gt;the full code on GitHub&lt;/a&gt; as it stands currently. I haven&amp;#8217;t tested it in a game yet, but I will probably enter the &lt;a href="https://ldjam.com/" title="Ludum Dare 48"&gt;Ludum Dare&lt;/a&gt; later this month and I might get a chance to use it then. I will update afterwards whether it is a disaster or a&amp;nbsp;triumph!&lt;/p&gt;</content><category term="Game Development"></category><category term="godot"></category><category term="gdscript"></category></entry><entry><title>Ludum Dare 46 Results</title><link href="https://blog.hyperlinkyourheart.com/gophers-results.html" rel="alternate"></link><published>2020-05-13T22:34:00+02:00</published><updated>2020-05-13T23:58:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2020-05-13:/gophers-results.html</id><summary type="html">&lt;p&gt;Results for Ludum Dare 46 are out, and I did pretty&amp;nbsp;well!&lt;/p&gt;</summary><content type="html">&lt;p&gt;The Ludum Dare 46 results were published yesterday, and &lt;a href="https://hyperlinkyourheart.itch.io/gophers"&gt;my game&lt;/a&gt; did quite well, placing 109th overall and 14th in the &amp;#8220;Mood&amp;#8221; category, as well as 120th and 121st in graphics and audio respectively. In the largest ever Ludum Dare, those are pretty decent placings I think, despite not breaking the top&amp;nbsp;100.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left;"&gt;Category&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Rating&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Placing&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Percentile&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Overall&lt;/td&gt;
&lt;td style="text-align: right;"&gt;4.136&lt;/td&gt;
&lt;td style="text-align: right;"&gt;109&lt;/td&gt;
&lt;td style="text-align: right;"&gt;96th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Fun&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.523&lt;/td&gt;
&lt;td style="text-align: right;"&gt;819&lt;/td&gt;
&lt;td style="text-align: right;"&gt;77th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Theme&lt;/td&gt;
&lt;td style="text-align: right;"&gt;4.14&lt;/td&gt;
&lt;td style="text-align: right;"&gt;279&lt;/td&gt;
&lt;td style="text-align: right;"&gt;92nd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Innovation&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.86&lt;/td&gt;
&lt;td style="text-align: right;"&gt;247&lt;/td&gt;
&lt;td style="text-align: right;"&gt;93rd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Humor&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.656&lt;/td&gt;
&lt;td style="text-align: right;"&gt;365&lt;/td&gt;
&lt;td style="text-align: right;"&gt;89th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Graphics&lt;/td&gt;
&lt;td style="text-align: right;"&gt;4.477&lt;/td&gt;
&lt;td style="text-align: right;"&gt;120&lt;/td&gt;
&lt;td style="text-align: right;"&gt;96th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Audio&lt;/td&gt;
&lt;td style="text-align: right;"&gt;4.102&lt;/td&gt;
&lt;td style="text-align: right;"&gt;121&lt;/td&gt;
&lt;td style="text-align: right;"&gt;96th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Mood&lt;/td&gt;
&lt;td style="text-align: right;"&gt;4.523&lt;/td&gt;
&lt;td style="text-align: right;"&gt;14&lt;/td&gt;
&lt;td style="text-align: right;"&gt;99th&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Graphs&lt;/h2&gt;
&lt;p&gt;I always feel that the real competition in the Ludum Dare is against myself - just trying to do a little bit better and learn a bit more each time. As such, here&amp;#8217;s some indication of my &lt;span class="caps"&gt;LD&lt;/span&gt; result trends over the&amp;nbsp;years.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ratings Graph" src="https://blog.hyperlinkyourheart.com/images/gophers-results/ratings_ogm.png"&gt;
&lt;img alt="Placings Graph" src="https://blog.hyperlinkyourheart.com/images/gophers-results/placings_ogm.png"&gt;
&lt;img alt="Percentiles Graph" src="https://blog.hyperlinkyourheart.com/images/gophers-results/percentiles_ogm.png"&gt;&lt;/p&gt;
&lt;p&gt;Nice upward trends! Note that I was only responsible for the art for &amp;#8220;Claustrophobia&amp;#8221; and &amp;#8220;Rattendorf&amp;#8221;, so I can only take partial credit for the overall and mood ratings of&amp;nbsp;those.&lt;/p&gt;
&lt;p&gt;The real learning experience this time around was on the audio. I&amp;#8217;ve only done the audio for six of the nine Ludum Dares I&amp;#8217;ve entered, so I left it out of the graphs&amp;nbsp;above.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ratings Graph" src="https://blog.hyperlinkyourheart.com/images/gophers-results/ratings_audio.png"&gt;&lt;/p&gt;
&lt;p&gt;Looks like I really cranked it up a notch this time after coasting for a long while.&amp;nbsp;Nice.&lt;/p&gt;
&lt;h2&gt;Moar&amp;nbsp;Gophers&lt;/h2&gt;
&lt;p&gt;I haven&amp;#8217;t decided yet if I&amp;#8217;m going to take the game further. I quite like the concept and I certainly have some ideas for it. I&amp;#8217;ll probably finish off my &lt;a href="https://github.com/khoulihan/gopher-render"&gt;gopher renderer&lt;/a&gt; and phlog generator before I decide, and then I can do a &lt;em&gt;devphlog&lt;/em&gt; for it&amp;nbsp;:D&lt;/p&gt;
&lt;p&gt;You can still play the &lt;a href="https://hyperlinkyourheart.itch.io/gophers"&gt;jam version&lt;/a&gt; for now, if you missed&amp;nbsp;it.&lt;/p&gt;</content><category term="Game Development"></category><category term="ludum-dare"></category><category term="gopher"></category><category term="sci-fi"></category><category term="music"></category><category term="pixelart"></category><category term="godot"></category></entry><entry><title>Gophers</title><link href="https://blog.hyperlinkyourheart.com/gophers-post-mortem.html" rel="alternate"></link><published>2020-04-25T17:24:00+02:00</published><updated>2021-05-30T17:25:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2020-04-25:/gophers-post-mortem.html</id><summary type="html">&lt;p&gt;A post-mortem of my Ludum Dare 46 entry, Gophers - a short adventure game about keeping a gopher network alive after some sort of nuclear event has destroyed&amp;nbsp;civilisation.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://hyperlinkyourheart.itch.io/gophers"&gt;&lt;img alt="Overlooking the city" src="https://blog.hyperlinkyourheart.com/images/gophers-post-mortem/Gophers_tower.jpg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://hyperlinkyourheart.itch.io/gophers" title="Gophers"&gt;Gophers&lt;/a&gt; is my entry for &lt;a href="https://ldjam.com/events/ludum-dare/46" title="Ludum Dare 46"&gt;Ludum Dare 46&lt;/a&gt;, the most recent of the bi-annual Ludum Dare game jams. It is a short adventure game about maintaining a &lt;a href="https://en.wikipedia.org/wiki/Gopher_(protocol)" title="Gopher Protocol"&gt;gopher&lt;/a&gt; network in a post-apocalyptic&amp;nbsp;world.&lt;/p&gt;
&lt;p&gt;The basic concept is one I&amp;#8217;ve been kicking around for a while as a sort of casual &lt;span class="caps"&gt;RPG&lt;/span&gt;/survival game about maintaining computer networks on scavenged technology, so it came to mind immediately when I saw the theme (&amp;#8220;Keep it&amp;nbsp;alive&amp;#8221;).&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve been really interested lately in gopher and other low-overhead technologies, and what the internet would look like if the industries that sustain it collapsed. I&amp;#8217;d previously envisioned a relatively cheerful solarpunk game about connecting distant sustainable communities, but I think it took on a much darker tone because of recent&amp;nbsp;events.&lt;/p&gt;
&lt;h2&gt;Art&lt;/h2&gt;
&lt;p&gt;I did all the art in &lt;a href="https://www.pyxeledit.com/" title="Pyxel Edit"&gt;Pyxel Edit&lt;/a&gt; as usual. My goal was to keep everything abstract and as high-contrast and readable as possible while still allowing for a nice parallax cityscape. I started with a mock-up of the exterior scene, and then essentially flipped the background and foreground colours from that for the bunker scene. I only used 7 colours in the&amp;nbsp;end.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Bunker Scene" src="https://blog.hyperlinkyourheart.com/images/gophers-post-mortem/BunkerScreenie.jpg"&gt;&lt;/p&gt;
&lt;p&gt;I put together a timelapse of the art so you can see the whole&amp;nbsp;process:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=0jPLMCfSE0w"&gt;&lt;img alt="Gophers" src="https://img.youtube.com/vi/0jPLMCfSE0w/0.jpg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Code&lt;/h2&gt;
&lt;p&gt;The only reason why I considered this a viable idea was because I had previously developed a &lt;a href="https://github.com/khoulihan/godot-cutscene-graph" title="Cutscene Graph Editor"&gt;cutscene graph editor plugin&lt;/a&gt; for &lt;a href="https://godotengine.org/" title="The game engine you waited for."&gt;Godot&lt;/a&gt;. It was untested in any game but I thought it would give me enough of a leg up that I would have time for the art and&amp;nbsp;writing.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/khoulihan/godot-cutscene-graph" title="Cutscene Graph Editor"&gt;&lt;img alt="Graph editor" src="https://blog.hyperlinkyourheart.com/images/gophers-post-mortem/graph.jpg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So in effect, the &amp;#8220;gopher network&amp;#8221; in the game is actually a dialogue&amp;nbsp;tree!&lt;/p&gt;
&lt;p&gt;Actually using the editor in a game did reveal some issues with it, but nothing significant enough to prevent me from finishing - and now I have some ideas on what needs work before I use it for another&amp;nbsp;game!&lt;/p&gt;
&lt;p&gt;I also took some code from a &lt;a href="https://hyperlinkyourheart.itch.io/people-poker" title="People Poker"&gt;previous game of mine&lt;/a&gt; for doing the menus and dealing with the settings. Every bit helps when you&amp;#8217;re entering the jam&amp;nbsp;solo.&lt;/p&gt;
&lt;p&gt;One thing that really came together for me in this jam was using coroutines to manage sequences of events. I&amp;#8217;ve always struggled to wrap my head around them previously for some reason, and would clumsily hook up signal handlers for every step. Using the &lt;code&gt;yield&lt;/code&gt; statement in &lt;a href="https://godotengine.org/" title="The game engine you waited for."&gt;Godot&lt;/a&gt; made handling interactions much easier and quicker to&amp;nbsp;write.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_on_Terminal_clicked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;walk_target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;face_direction&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;_player&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_destination&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;walk_target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_player&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;arrived_at_destination&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;_player&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;face&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;face_direction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;GameController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_spawn_location&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;bunker&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;terminal&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;GameController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_spawn_direction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;bunker&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;FadeMask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fade_in&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FadeMask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;fade_in_complete&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Switch to the browser scene&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_tree&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;change_scene&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;res://browser/Browser.tscn&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;FadeMask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fade_out&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FadeMask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;fade_out_complete&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Sound&amp;nbsp;Effects&lt;/h2&gt;
&lt;p&gt;The most exciting part of working on this game, for me, was doing the sound effects. I bought a fancy mic a while back (a &lt;a href="https://www.rode.com/microphones/nt-usb" title="Røde NT-USB"&gt;Røde &lt;span class="caps"&gt;NT&lt;/span&gt;-&lt;span class="caps"&gt;USB&lt;/span&gt;&lt;/a&gt;) to do foley &lt;span class="caps"&gt;SFX&lt;/span&gt; rather than my usual &lt;span class="caps"&gt;SFXR&lt;/span&gt; beeps and boops, but this was the first chance I&amp;#8217;ve had to try it&amp;nbsp;out.&lt;/p&gt;
&lt;p&gt;&lt;img alt="My foley kit, or part of it at least" src="https://blog.hyperlinkyourheart.com/images/gophers-post-mortem/foley-kit.jpg"&gt;&lt;/p&gt;
&lt;p&gt;For the Geiger counter sounds I ran my finger over the teeth of a comb. For the bunker door, I rubbed a hammer and a spanner together in various ways. For the dripping sound in the bunker, I just used an eyedropper to drip drops into a glass of water. The footsteps are real footsteps that I recorded, and the cloth sounds when you&amp;#8217;re walking around the exterior are me crinkling a vinyl jacket. It was a lot of fun to record all these and I don&amp;#8217;t think I was even being all that creative. I couldn&amp;#8217;t figure out how to do buzzing or flickering sounds for the electric light within the time I had though,&amp;nbsp;unfortunately.&lt;/p&gt;
&lt;p&gt;One big problem I encountered was that my apartment is apparently incredibly noisy, as am I. It was a windy day and the shutters on my window were banging constantly, my neighbours were going about their noisy lives, oblivious, and my body stubbornly refused to go without oxygen during the recordings. Noise reduction in &lt;a href="https://www.audacityteam.org/" title="Audacity audio editor"&gt;Audacity&lt;/a&gt; helped a bit (make sure you record periods of &amp;#8220;silence&amp;#8221; to enable this), but there are definitely some extra environmental sounds in there. Thankfully I think they mostly just appear as mysterious underground reverb or get buried by other things. It&amp;#8217;s something I&amp;#8217;m definitely going to have to think about for next&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;I did a bunch of post-processing in &lt;a href="https://www.audacityteam.org/" title="Audacity audio editor"&gt;Audacity&lt;/a&gt; to pick the best bits out of the recordings, and make things sound better. I had to reduce the pitch on the bunker door sound to make it sound heavier, for&amp;nbsp;example.&lt;/p&gt;
&lt;h2&gt;Music&lt;/h2&gt;
&lt;p&gt;I was so proud of the sound effects that I almost wasn&amp;#8217;t going to do any music, but I&amp;#8217;m glad I did. I got to it in the last few hours of the jam, so I had to keep it very simple. It&amp;#8217;s mostly just the notes of a Dmin7 chord played in a few different arrangements on pad instruments, with some slow bass drums coming in and out. The title screen music layers a couple of different pads as well as a Rhodes doing sus4 arpeggios from each note of the&amp;nbsp;chord.&lt;/p&gt;
&lt;p&gt;I put everything together in &lt;a href="https://lmms.io/" title="LMMS"&gt;&lt;span class="caps"&gt;LMMS&lt;/span&gt;&lt;/a&gt;. I spent a good chunk of time experimenting with different instruments so even though it&amp;#8217;s really minimalistic it still took a&amp;nbsp;while!&lt;/p&gt;
&lt;h2&gt;Abandoned&amp;nbsp;Ideas&lt;/h2&gt;
&lt;p&gt;I had planned several other game elements, including the protagonist saying things to himself (or the player), and another type of interaction involving connecting cables and swapping out computer&amp;nbsp;components.&lt;/p&gt;
&lt;p&gt;A full game would probably have more complex survival elements instead of a simple timer, and would see you having to scavenge in the environment for computer equipment and other&amp;nbsp;supplies.&lt;/p&gt;
&lt;p&gt;We&amp;#8217;ll see if anything like that comes to fruition in the&amp;nbsp;future!&lt;/p&gt;</content><category term="Game Development"></category><category term="ludum-dare"></category><category term="gopher"></category><category term="sci-fi"></category><category term="music"></category><category term="pixelart"></category><category term="godot"></category></entry></feed>