<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Blog on Kevin Young</title><link>https://kyoung.codes/blog/</link><description>Recent content in Blog on Kevin Young</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><managingEditor>youngks93@gmail.com (Kevin Young)</managingEditor><webMaster>youngks93@gmail.com (Kevin Young)</webMaster><copyright>Kevin Young</copyright><lastBuildDate>Sat, 16 May 2026 20:29:02 -0400</lastBuildDate><atom:link href="https://kyoung.codes/blog/index.xml" rel="self" type="application/rss+xml"/><item><title>Focus for 2026</title><link>https://kyoung.codes/blog/focus-for-2026/</link><pubDate>Sun, 08 Mar 2026 18:29:47 -0400</pubDate><author>youngks93@gmail.com (Kevin Young)</author><guid>https://kyoung.codes/blog/focus-for-2026/</guid><description>The past couple years have been a bit of a roller coaster for me, and as you may be able to tell by the date on this post about setting my goals for the year, I&amp;rsquo;m still working on it :D
This isn&amp;rsquo;t the place for me to tell you why, but I am excited to talk about all the stuff I plan to work on in 2026.
scorebug.sh Live mlb scores in the terminal</description><content:encoded><![CDATA[<p>The past couple years have been a bit of a roller coaster for me, and as you may be able to tell by the date on this post about setting my goals for the year, I&rsquo;m still working on it :D</p>
<p>This isn&rsquo;t the place for me to tell you why, but I am excited to talk about all the stuff I plan to work on in 2026.</p>
<h1 id="_scorebugshhttpsgithubcomkevinstirlingscorebugsh_"><em><a href="https://github.com/KevinStirling/scorebug.sh">scorebug.sh</a></em></h1>
<blockquote>
<p><em>Live mlb scores in the terminal</em></p>
</blockquote>
<p>Late last year, I was looking for an excuse to use <a href="https://charm.land/">Charmbracelet&rsquo;s</a> popular ecosystem for building TUI&rsquo;s, and found my excuse while watching the lead up to the 2025 MLB post season.</p>
<p>I had recently been getting really annoyed with the MLB apps, the various hoops to jump through, and insane fees to pay just to be able to track and watch the sport. I started poking around at the MLB stats api and&hellip; damn this is all just kinda out there for free huh?</p>
<p>I started piecing together a way to display live game scores in the terminal that update automatically. Even with just this feature alone, I could see the potential.</p>
<p><img alt="scorebug" src="/images/projects/sb_preview_sm.png" title="scorebug"></p>
<p>Last month I just finished up a refactor that will allow me to iterate on this alot faster. I have a lot of fun ideas planned, including player stats, play-by-play, and possibly even visualizing the plays with the help of some of charm&rsquo;s more experimental libraries.</p>
<p>You can check out <a href="https://github.com/KevinStirling/scorebug.sh">scorebug.sh here</a></p>
<h1 id="_radixhttpsgithubcomkevinstirlingradix_"><em><a href="https://github.com/KevinStirling/radix">Radix</a></em></h1>
<blockquote>
<p><em>Retro fps godot project</em></p>
</blockquote>
<p>This is a project that I started ~2 years ago, initially just to test out some Godot pluins (<a href="https://github.com/ratmarrow/GoldGdt">GoldGdt</a> &amp; <a href="https://github.com/func-godot">func_godot</a>), to see if I could quickly whip up something that &ldquo;felt&rdquo; like half-life in the Godot engine. I quickly realized that the GoldGdt plugin, which provides a half-life-esque character controller, was not really the most extensible for my needs. I put the project down for about a year.</p>
<p>Fast forward after several quake &amp; source engine history lessons, source code deep-dives, obsessing over source engine modding / map making, and creating an automated set of <a href="https://github.com/KevinStirling/one-point-six">shell scripts and docker-compose</a> for running cs 1.6 servers later&hellip; I return to Radix with my new goals!</p>
<h2 id="radix-goals">Radix goals</h2>
<p>I have so many thoughts I want to share on this endeavor, but for now my most important goals for this project are to&hellip;</p>
<pre><code>- Create an FPS game that feels good to ME. This may just be me chasing the &quot;source engine feel&quot; but hey... why discount perfection ;)

- Build a set of tools to create cool FPS experiences with, for years to come.
</code></pre>
<p>I gave some serious thought to commiting to such a large project, with such an open ended goal. I ended up coming back to it becuase it&rsquo;s both an obsession, and just satisfying to slowly chip away at these large undertakings. I don&rsquo;t expect it to be done fast, and that&rsquo;s fine. I can always take breaks for game jams and stuff anyways :)</p>
<p>If you are still reading- thanks, stranger! Not much to show for now, so feel free to poke around the <a href="https://github.com/KevinStirling/radix">Radix codebase</a>.</p>
]]></content:encoded></item><item><title>Godot Wild Jam 64</title><link>https://kyoung.codes/blog/godot-wild-jam-64/</link><pubDate>Sun, 31 Dec 2023 15:46:28 -0500</pubDate><author>youngks93@gmail.com (Kevin Young)</author><guid>https://kyoung.codes/blog/godot-wild-jam-64/</guid><description>Godot Wild Jam 64 happened this past month, with the theme Illumination, and I decided to give it a go! This was my 3rd game jam attempt, although this would be my first submitted entry. It&amp;rsquo;s called CandleRun, and you can check it out here.
Along with the main theme, the 3 option &amp;ldquo;wild card&amp;rdquo; themes were &amp;ldquo;white out/ freezing out&amp;rdquo;, &amp;ldquo;blood is fuel&amp;rdquo;, and &amp;ldquo;trash everywhere&amp;rdquo;.
Design Process I originally planned on a completely different game.</description><content:encoded><![CDATA[<p>Godot Wild Jam 64 happened this past month, with the theme Illumination, and I decided to give it a go! This was my 3rd game jam attempt, although this would be my first submitted entry. It&rsquo;s called CandleRun, and you can check it out <a href="https://necrokev.itch.io/candlerun">here</a>.</p>
<p>Along with the main theme, the 3 option &ldquo;wild card&rdquo; themes were &ldquo;white out/ freezing out&rdquo;, &ldquo;blood is fuel&rdquo;, and &ldquo;trash everywhere&rdquo;.</p>
<h2 id="design-process">Design Process</h2>
<p>I originally planned on a completely different game. Aside from many of the ideas I had during my brainstorming session, which included a souls-like style game about climbing a freezing mountain, a game that involved writing medieval illuminated manuscripts, and even a game about lighting dumpsters on fire&hellip;</p>
<p>What I eventually landed on was a colony sim game, where you manage a community living through a post apocalyptic eternal winter. At the center, a garbage fire keeps their village warm. Your job would be keep their fire burning bright by sending colonists out to scavenge for trash to burn, hunt to keep the colony satiated, and eventually accrue enough weapons and numbers to take back the junk yard. I sketched up some rough concept art and called it a day.</p>
<p>The next day I woke up to get to work. I sat down with my design doc open, fired up Godot, and started to slap some basic components together. However it was during this process that I had the realization that there would be no way I could finish this in time with a proper game loop. I tried to strip it down to the most basic game loop possible, and considered building the same idea out into an idle game format. But even after reworking the doc in various ways, I was losing sight of one of my main goal for the week: whatever I make, just make sure its fun. If you can&rsquo;t find the fun, move on and try something else.</p>
<p>I put this initial concept aside and started to think of something else to make that was more realistic for my skill level. By this point the day was half over, so I decided to do something unconventional for me, which was to just start by drawing something fun. This is how I ended up with the character design seen in the final version. What I ended up with was this little man right here.</p>
<p><img alt="anim_test" src="/images/posts/anim_test.gif" title="animation_test"></p>
<p>At this point there was no design doc aside from a couple bullet points I had written down.</p>





<pre tabindex="0"><code>CANDLE RUNNER
- You play as a lit candle guy
- As you run, the candle burns out
- You cannot burn out fully
- You can pick up wax by standing under big melting candles
- You illuminate checkpoints by standing under a wick that lights a flame
- The check point forms you into a fresh tall candle
- Some areas require you to be shorter</code></pre><p>It was pretty minimal, but it was enough to improvise off of. This moment kind of set the tone for my development process over the rest of the week as well, since it became more of a rapid prototyping session, which allowed me to become more comfortable throwing away ideas that didn&rsquo;t work quite right.</p>
<h2 id="rapid-and-reactive-prototyping">Rapid and Reactive Prototyping</h2>
<p>So, it was settled then. A puzzle game where the &ldquo;wind&rdquo; caused by the candle running around would make the flame burn brighter and the wax run out faster. I got to work on creating the mechanics necessary, and had it worked out by the end of Tuesday.</p>
<p><img alt="anim_test_3" src="/images/posts/anim_test_3.gif" title="animation_test"></p>
<p>Over the next couple days I had made all of the other components necessary to build some puzzles out. I built the wax candle spawner that allows you to grow the candle size, the lanterns you need to light, and I even added a bonus spider web obstacle, that you could get past by lighting it on fire with your flame!</p>
<p>There was one small problem that I had realized on Wednesday night though&hellip; while the mechanics were coming together to form some semblance of a game&hellip; it <em>still</em> didn&rsquo;t <em>feel</em> fun. I didn&rsquo;t want to lose sight of this core goal I gave myself, so I decided to just sit and play around with game feel for the rest of the night. The first thing I realized was, the movement just didn&rsquo;t feel smooth or natural enough. So that would be the first thing to fix. The other key takeaway was, I really didn&rsquo;t like the wax running out based on the player movement. It almost felt like the game was de-incentivizing the player&hellip; playing the game? &ldquo;Don&rsquo;t move around too much, or you just die!&rdquo; It sounds so obvious now but when I was designing the mechanic on paper, it just simply didn&rsquo;t occur to me.</p>
<p>It was because of this short late night play test that I decided to instead give the player the choice of what size the candle should be, and base the puzzle on this aspect. Need to fit into a tight area? Shrink down! Need to reach a lantern up high? Find a wax spawner to grow! This was the moment the game went from puzzle game, to puzzle platformer. I decided to focus on the movement aspect more, and make it feel more like a platformer, which is now fully realized the in the final release.</p>
<p><img alt="movement_test" src="/images/posts/movement_test1.gif" title="movement_test"></p>
<h2 id="polishing-it-up">Polishing it up</h2>
<p>For the movement changes, I really wanted the animations and physics to work with each other, to make the player feel like the movement felt natural and believable, all while being somewhat whimsical. I added a slight skewing effect based on the direction and speed of the player, which kind of gives the effect that the candle is a little bit top heavy.</p>
<p>I also wanted to add some momentum to the movement, so it was here that I was starting to tweak the jump physics, and the amount the player would slide around when changing directions. It was now Thursday night, and I was getting a little concerned that I would not have enough time to create enough levels for the game.</p>
<p>You see, I had held off making levels entirely up until this point. Not out of procrastination, but because I was afraid of wasting time making levels, and then having to either fix them or throw them away if I made any movement tweaks that ended up breaking the levels. I was able to finish the movement tweaks by the end of Thursday, and now I had all of Friday, Saturday, and whatever would be left of Sunday to design levels.</p>
<p>Designing the levels was a massive learning process. I had gone into it thinking &ldquo;Ah finally, this will be easy! I&rsquo;ve been working towards this moment and now all I have to do is the most fun part!&rdquo;&hellip;.no it was not easy. 3 Days resulted in 6 levels. Not terrible but I was hoping to reach an even 10 levels.</p>
<p>I knew I wanted to have some easy levels dedicated to introducing new mechanics. They hard part was filling in the gaps between those introductions, and combining the mechanics learned in creative ways. Puzzles are hard to make! And because of this, I am glad that I ended up dedicating more time to making the movement feel good, because it allowed me to lean on more skill based levels than thinking based levels.</p>
<p>After making all 6 levels, I ended up having an hour left, and that&rsquo;s where I added a basic pause function, sound effects, and some graphics for the itch page.</p>
<h2 id="results">Results</h2>
<p>My game ended up taking <strong>13th place overall out of 120 submissions</strong>! What was even more exiting to me, I placed <strong>5th in the fun category</strong>! I couldn&rsquo;t believe that my goal I had set for myself for the week actually paid off.</p>
<p>My take aways from this experience were:</p>
<ul>
<li>Don&rsquo;t spend the time creating a design doc, until you&rsquo;ve prototyped enough to determine that the design will actually be fun</li>
<li>Always be testing your game, always make sure it feels right, don&rsquo;t let an un-fun game become such a big undertaking that you don&rsquo;t even know why you are making it anymore. Figure out how to make it fun as early as possible.</li>
</ul>
<p>Things I would change based off of user feedback:</p>
<ul>
<li>Make the controls a little tighter. I went a little hard on the slidey-ness of the momentum and made the game a little too slippery.</li>
<li>Levels are going to be harder than you think, because you designed them</li>
</ul>
]]></content:encoded></item><item><title>A new space for snake</title><link>https://kyoung.codes/blog/micro-snake-3/</link><pubDate>Sun, 06 Nov 2022 14:01:46 -0500</pubDate><author>youngks93@gmail.com (Kevin Young)</author><guid>https://kyoung.codes/blog/micro-snake-3/</guid><description>Oh yeah. We&amp;rsquo;re rollin now. Bug fixes and new state trigger logic!
Diagonal movement bug Had to change the movement vector code to fix a bug that would allow the player to move in diagonals&amp;hellip; which kind of breaks the whole rule set of Snake now doesn&amp;rsquo;t it. This is because the Input.get_vector() func will create a vector based on all inputs it recieves. Good for some games like top down 2d or 3d games, not good for snake.</description><content:encoded><![CDATA[<p>Oh yeah. We&rsquo;re rollin now. Bug fixes and new state trigger logic!</p>
<h2 id="diagonal-movement-bug">Diagonal movement bug</h2>
<p>Had to change the movement vector code to fix a bug that would allow the player to move in diagonals&hellip; which kind of breaks the whole rule set of Snake now doesn&rsquo;t it. This is because the <code>Input.get_vector()</code> func will create a vector based on all inputs it recieves. Good for some games like top down 2d or 3d games, not good for snake.</p>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gd" data-lang="gd"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Snake.gd</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kd">func</span> <span class="nf">_process</span><span class="p">(</span><span class="n">_delta</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">	<span class="k">if</span> <span class="nc">Input</span><span class="o">.</span><span class="nf">get_vector</span><span class="p">(</span><span class="s2">&#34;left&#34;</span><span class="p">,</span> <span class="s2">&#34;right&#34;</span><span class="p">,</span> <span class="s2">&#34;up&#34;</span><span class="p">,</span> <span class="s2">&#34;down&#34;</span><span class="p">)</span> <span class="o">!=</span> <span class="nc">Vector2</span><span class="o">.</span><span class="n">ZERO</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">		<span class="k">if</span> <span class="n">manager</span><span class="o">.</span><span class="n">current_state</span> <span class="o">==</span> <span class="n">GlobalVars</span><span class="o">.</span><span class="n">State</span><span class="o">.</span><span class="n">PAUSE</span> <span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">			<span class="n">manager</span><span class="o">.</span><span class="nf">set_state</span><span class="p">(</span><span class="n">GlobalVars</span><span class="o">.</span><span class="n">State</span><span class="o">.</span><span class="n">PLAY</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">	<span class="k">if</span> <span class="nc">Input</span><span class="o">.</span><span class="nf">is_action_just_pressed</span><span class="p">(</span><span class="s2">&#34;left&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">		<span class="n">input_buffer</span> <span class="o">=</span> <span class="nc">Vector2</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">	<span class="k">elif</span> <span class="nc">Input</span><span class="o">.</span><span class="nf">is_action_just_pressed</span><span class="p">(</span><span class="s2">&#34;right&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">		<span class="n">input_buffer</span> <span class="o">=</span> <span class="nc">Vector2</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">	<span class="k">elif</span> <span class="nc">Input</span><span class="o">.</span><span class="nf">is_action_just_pressed</span><span class="p">(</span><span class="s2">&#34;up&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">		<span class="n">input_buffer</span> <span class="o">=</span> <span class="nc">Vector2</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">	<span class="k">elif</span> <span class="nc">Input</span><span class="o">.</span><span class="nf">is_action_just_pressed</span><span class="p">(</span><span class="s2">&#34;down&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">		<span class="n">input_buffer</span> <span class="o">=</span> <span class="nc">Vector2</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="o">...</span></span></span></code></pre></div>
<p>So I unfortunately had to break up my nice neatly coded movement code into a sequence of if statements, while still keeping the <code>get_vector</code> for detecting an input to start the game. I&rsquo;m sure theres a hip way to consolidate this but for now this&rsquo;ll do.</p>
<h2 id="dead-snake">Dead snake</h2>
<p>To detect if the snake&rsquo;s head has made contact with the snake&rsquo;s tail, I just added a <code>CollisionArea2d</code> to the <code>SnakeTail.tscn</code>, and add the following code to connect the <code>body_entered</code> signal.</p>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gd" data-lang="gd"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># SnakeTail.gd</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kd">extends</span> <span class="nc">StaticBody2D</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nd">@onready</span> <span class="kd">var</span> <span class="n">col_area</span> <span class="o">=</span> <span class="nx">$CollisionArea</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nd">@onready</span> <span class="kd">var</span> <span class="n">manager</span> <span class="o">=</span> <span class="nf">get_parent</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kd">func</span> <span class="nf">_ready</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">	<span class="n">col_area</span><span class="o">.</span><span class="n">body_entered</span><span class="o">.</span><span class="nf">connect</span><span class="p">(</span><span class="n">_on_area_2d_body_entered</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="kd">func</span> <span class="nf">_on_area_2d_body_entered</span><span class="p">(</span><span class="n">body</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">	<span class="k">if</span> <span class="n">body</span><span class="o">.</span><span class="nf">is_in_group</span><span class="p">(</span><span class="s2">&#34;SnakeHead&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">		<span class="n">manager</span><span class="o">.</span><span class="nf">set_state</span><span class="p">(</span><span class="n">GlobalVars</span><span class="o">.</span><span class="n">State</span><span class="o">.</span><span class="n">DEAD</span><span class="p">)</span></span></span></code></pre></div>
<p>I added the SnakeHead node to a group &ldquo;SnakeHead&rdquo; as a lazy check, I&rsquo;ll admit. This may change later if I find it stupid but it works well enough for now.</p>
<h3 id="play-area-changes">Play area changes</h3>
<p>As you can see from the header image, its pretty minimal. I added a new <code>TileMap</code> layer for the &ldquo;frame&rdquo;, so the playarea does not end up being massive and take forever to traverse, and I can also decorate it later if I want / when I choose a final visual style. Overall a simple but pleasing enhancement from the janky floating window with dimensions that were slightly off from the <code>TileMap</code> :D</p>
<h3 id="detecting-wall-collision">Detecting wall collision</h3>
<p>Ideally, in Godot 4 I could simply add a physics collision layer to the <code>TileMap</code>. However, because I&rsquo;ve chosen not to use any physics based movement, I don&rsquo;t think this is possible.</p>
<p>The good news is, since I&rsquo;m already storing the coordinates for all the cells that make up the playarea, all I need to do is check to see if the cell that the snake&rsquo;s head is about to move into is going to have an x or y value greater or less than the maximum x or y values used for the playarea.</p>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gd" data-lang="gd"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># GameManager.gd</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kd">func</span> <span class="nf">_ready</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">	<span class="n">bg_layer_coords</span> <span class="o">=</span> <span class="n">grid</span><span class="o">.</span><span class="nf">get_used_cells</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">	<span class="n">min_nav_x_y</span> <span class="o">=</span> <span class="n">bg_layer_coords</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">	<span class="n">max_nav_x_y</span> <span class="o">=</span> <span class="n">bg_layer_coords</span><span class="p">[</span><span class="n">bg_layer_coords</span><span class="o">.</span><span class="nf">size</span><span class="p">()</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">	
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="kd">func</span> <span class="nf">is_in_boundaries</span><span class="p">(</span><span class="n">grid_coord</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="n">min_nav_x_y</span><span class="o">.</span><span class="n">x</span> <span class="o">&lt;</span> <span class="n">grid_coord</span><span class="o">.</span><span class="n">x</span><span class="p">)</span> <span class="ow">and</span> <span class="p">(</span><span class="n">max_nav_x_y</span><span class="o">.</span><span class="n">x</span> <span class="o">&gt;</span> <span class="n">grid_coord</span><span class="o">.</span><span class="n">x</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">		<span class="k">if</span> <span class="p">(</span><span class="n">min_nav_x_y</span><span class="o">.</span><span class="n">y</span> <span class="o">&lt;</span> <span class="n">grid_coord</span><span class="o">.</span><span class="n">y</span><span class="p">)</span> <span class="ow">and</span> <span class="p">(</span><span class="n">max_nav_x_y</span><span class="o">.</span><span class="n">y</span> <span class="o">&gt;</span> <span class="n">grid_coord</span><span class="o">.</span><span class="n">y</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">			<span class="k">return</span> <span class="kc">true</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">	<span class="k">return</span> <span class="kc">false</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="o">...</span></span></span></code></pre></div>
<p>This function gets called in the <code>_timer_timeout()</code> func.</p>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gd" data-lang="gd"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># GameManager.gd</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kd">func</span> <span class="nf">_timer_timeout</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">	<span class="k">if</span> <span class="n">snake</span><span class="o">.</span><span class="n">grid_coords</span><span class="o">.</span><span class="nf">size</span><span class="p">()</span> <span class="o">&gt;</span> <span class="n">snake</span><span class="o">.</span><span class="n">body_segments</span><span class="o">.</span><span class="nf">size</span><span class="p">()</span> <span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">		<span class="nf">update_snake</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">	<span class="k">for</span> <span class="n">s</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">snake</span><span class="o">.</span><span class="n">grid_coords</span><span class="o">.</span><span class="nf">size</span><span class="p">()):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">		<span class="k">if</span> <span class="n">s</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">			<span class="k">if</span> <span class="o">!</span><span class="nf">is_in_boundaries</span><span class="p">(</span><span class="n">snake</span><span class="o">.</span><span class="n">grid_coords</span><span class="p">[</span><span class="n">s</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">				<span class="nf">set_state</span><span class="p">(</span><span class="n">GlobalVars</span><span class="o">.</span><span class="n">State</span><span class="o">.</span><span class="n">DEAD</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="o">...</span></span></span></code></pre></div>
<p><em>So close I can almost smell the polishing phase.</em></p>
]]></content:encoded></item><item><title>Giving snake an apple</title><link>https://kyoung.codes/blog/micro-snake-2/</link><pubDate>Sat, 05 Nov 2022 10:57:44 -0400</pubDate><author>youngks93@gmail.com (Kevin Young)</author><guid>https://kyoung.codes/blog/micro-snake-2/</guid><description>Before tackling any new features, I wanted to make sure I atleast had a super bare minimum state management system in place. And a new beta for Godot 4 is out and I get to use a new (very minor) enhancement :D
Managing state This is pretty much as simple as you can get, which I think fits for the tiny scope of this project. For now this is what that looks like</description><content:encoded><![CDATA[<p>Before tackling any new features, I wanted to make sure I atleast had a super bare minimum state management system in place. And a new beta for Godot 4 is out and I get to use a new (very minor) enhancement :D</p>
<h2 id="managing-state">Managing state</h2>
<p>This is pretty much as simple as you can get, which I think fits for the tiny scope of this project. For now this is what that looks like</p>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gd" data-lang="gd"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># GameManager.gd</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kd">var</span> <span class="n">current_state</span> <span class="o">:=</span> <span class="n">GlobalVars</span><span class="o">.</span><span class="n">State</span><span class="o">.</span><span class="n">PAUSE</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="kd">func</span> <span class="nf">set_state</span><span class="p">(</span><span class="n">new_state</span><span class="p">:</span> <span class="n">GlobalVars</span><span class="o">.</span><span class="n">State</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">	<span class="n">current_state</span> <span class="o">=</span> <span class="n">new_state</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">	<span class="k">match</span> <span class="n">current_state</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">		<span class="n">GlobalVars</span><span class="o">.</span><span class="n">State</span><span class="o">.</span><span class="n">PLAY</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">			<span class="n">timer</span><span class="o">.</span><span class="nf">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">			<span class="n">timer</span><span class="o">.</span><span class="n">paused</span> <span class="o">=</span> <span class="kc">false</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">		<span class="n">GlobalVars</span><span class="o">.</span><span class="n">State</span><span class="o">.</span><span class="n">PAUSE</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">			<span class="n">timer</span><span class="o">.</span><span class="n">paused</span> <span class="o">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">		<span class="n">GlobalVars</span><span class="o">.</span><span class="n">State</span><span class="o">.</span><span class="n">DEAD</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">			<span class="n">timer</span><span class="o">.</span><span class="nf">stop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="o">...</span></span></span></code></pre></div>
<p>As you can see, it really only needs to manage the timer for now.</p>
<p><code>GlobalVars.gd</code> is an autoloaded file that simply has an enum called State.</p>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gd" data-lang="gd"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># GlobalVars.gd</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kd">extends</span> <span class="nc">Node</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="kd">enum</span> <span class="n">State</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">	<span class="n">PAUSE</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">	<span class="n">PLAY</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">	<span class="n">DEAD</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="p">}</span></span></span></code></pre></div>
<p>Now, any child nodes of the GameManager.tscn can set the state of the game by creating a reference to the GameManager node, and calling the <code>set_state</code> func.</p>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gd" data-lang="gd"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># Snake.gd</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="nd">@onready</span> <span class="kd">var</span> <span class="n">manager</span> <span class="o">=</span> <span class="nf">get_parent</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="n">manager</span><span class="o">.</span><span class="nf">set_state</span><span class="p">(</span><span class="n">GlobalVars</span><span class="o">.</span><span class="n">State</span><span class="o">.</span><span class="n">PLAY</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="o">...</span></span></span></code></pre></div>
<h2 id="time-to-eat">Time to eat</h2>
<p>Setting up the apple scene is simple. A <code>StaticBody2d</code> for the root node, attatch a <code>Sprite</code> node which for now I will lazily drop the snake tail&rsquo;s sprite into as a placehodler, and a <code>CollisionShape2d</code>.  For the snake to be able to eat this thing, we&rsquo;ll need a <code>Area2d</code> with a <code>CollisionShape2d</code> as its child node, which I have named <code>PickUpBoundary</code>.</p>
<p>My inital plan was to use the <code>Area2d</code>&rsquo;s <code>body_entered</code> signal to trigger the function to make the snake grow. However for whatever reason, that signal is not working when the <code>Snake</code> body enters it. Everything is on the same collision layer and mask layer too, so it should be detectable. Might investigate this later and see if its a Godot 4 beta bug.</p>
<p>So for a backup plan, I gave the <code>snake.tscn</code> an <code>Area2d</code> node called <code>PickUpArea</code>, and I instead connected the <code>area_entered</code> signal.</p>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gd" data-lang="gd"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Apple.gd</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kd">extends</span> <span class="nc">Node2D</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nd">@onready</span> <span class="kd">var</span> <span class="n">pickUpArea</span> <span class="o">=</span> <span class="nx">$PickUpBoundary</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kd">signal</span> <span class="n">spawn</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="kd">func</span> <span class="nf">_ready</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">	<span class="n">pickUpArea</span><span class="o">.</span><span class="n">area_entered</span><span class="o">.</span><span class="nf">connect</span><span class="p">(</span><span class="n">_area_pickup</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="kd">func</span> <span class="nf">_area_pickup</span><span class="p">(</span><span class="n">area</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">	<span class="k">if</span> <span class="n">area</span><span class="o">.</span><span class="nf">get_parent</span><span class="p">()</span><span class="o">.</span><span class="nf">has_method</span><span class="p">(</span><span class="s2">&#34;grow&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">		<span class="n">area</span><span class="o">.</span><span class="nf">get_parent</span><span class="p">()</span><span class="o">.</span><span class="nf">grow</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">		<span class="nf">emit_signal</span><span class="p">(</span><span class="s2">&#34;spawn&#34;</span><span class="p">)</span></span></span></code></pre></div>
<p>The callback function simply checks if the parent of the <code>Area2d</code> that entered the <code>PickUpBoundary</code> has a <code>grow</code> method, and calls it if it does.  Which looks like this</p>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gd" data-lang="gd"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># Snake.gd</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="kd">func</span> <span class="nf">grow</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">	<span class="kd">var</span> <span class="n">new_seg</span> <span class="o">=</span> <span class="n">grid_coords</span><span class="p">[</span><span class="n">grid_coords</span><span class="o">.</span><span class="nf">size</span><span class="p">()</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">	<span class="n">grid_coords</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="n">new_seg</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="o">...</span></span></span></code></pre></div>
<p>The reason I am able to simply set the  <code>Vector2</code> position for the new segment, rather than doing some vector math to calculate the next position, is thanks to my logic for moving the snake in my <code>_timer_timeout</code> func in the <code>GameManager.gd</code>.</p>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gd" data-lang="gd"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">#GameManager.gd</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kd">func</span> <span class="nf">_timer_timeout</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">	<span class="kd">var</span> <span class="n">segment_next</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">	<span class="kd">var</span> <span class="n">segment_previous</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">	<span class="k">for</span> <span class="n">s</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">snake</span><span class="o">.</span><span class="n">grid_coords</span><span class="o">.</span><span class="nf">size</span><span class="p">()):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">		<span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">			<span class="n">grid</span><span class="o">.</span><span class="nf">erase_cell</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">snake</span><span class="o">.</span><span class="n">grid_coords</span><span class="p">[</span><span class="n">s</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">			<span class="n">segment_previous</span> <span class="o">=</span> <span class="n">snake</span><span class="o">.</span><span class="n">grid_coords</span><span class="p">[</span><span class="n">s</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">			<span class="n">snake</span><span class="o">.</span><span class="n">grid_coords</span><span class="p">[</span><span class="n">s</span><span class="p">]</span> <span class="o">=</span> <span class="n">segment_next</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">			<span class="n">snake</span><span class="o">.</span><span class="n">body_segments</span><span class="p">[</span><span class="n">s</span><span class="p">]</span><span class="o">.</span><span class="n">position</span> <span class="o">=</span> <span class="n">grid</span><span class="o">.</span><span class="nf">map_to_local</span><span class="p">(</span><span class="n">snake</span><span class="o">.</span><span class="n">grid_coords</span><span class="p">[</span><span class="n">s</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">			<span class="n">segment_next</span> <span class="o">=</span> <span class="n">segment_previous</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">	<span class="k">if</span> <span class="n">current_state</span> <span class="o">==</span> <span class="n">GlobalVars</span><span class="o">.</span><span class="n">State</span><span class="o">.</span><span class="n">PLAY</span> <span class="p">:</span> 
</span></span><span class="line"><span class="ln">17</span><span class="cl">		<span class="n">timer</span><span class="o">.</span><span class="nf">start</span><span class="p">()</span></span></span></code></pre></div>
<p>Because I&rsquo;m just scooting the <code>Vector2</code>&rsquo;s by keeping the previous segment in memory, it just kinda works. It might <em>feel</em> a little lazy to have the last 2 <code>Vector2</code>&rsquo;s in the array be the same value for a moment, but in reality this means that when that last line <code>segment_next = segment_previous</code> is executed for the new snake tail segment, it&rsquo;s making the previous snake&rsquo;s last tail segment the new one, which is exactly what we want. It ends up having effect of &ldquo;growing forward&rdquo; as the snake moves, since the end of the tail does not move for one of the timer timeouts.</p>
<p><img alt="snek_apple" src="/images/posts/snek_apple.gif" title="snek"></p>
<h2 id="more-apple">More apple</h2>
<p>As you can see in the gif above, we get a new apple when we eat one! I created a <code>spawn</code> signal in <code>Apple.gd</code> that is emitted when the apple&rsquo;s <code>area_entered</code> callback is triggered by the snake eating the apple.</p>
<p>In the <code>GameManager.gd</code>, I set up a <code>_new_apple()</code> func that will spawn the new instance in a random location in the playarea.</p>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gd" data-lang="gd"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># GameManager.gd</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="kd">func</span> <span class="nf">_new_apple</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">	<span class="k">if</span> <span class="nf">get_node_or_null</span><span class="p">(</span><span class="s2">&#34;Apple&#34;</span><span class="p">)</span> <span class="o">==</span> <span class="kt">null</span> <span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">		<span class="n">apple</span> <span class="o">=</span> <span class="n">apple</span><span class="o">.</span><span class="nf">instantiate</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">		<span class="nf">add_child</span><span class="p">(</span><span class="n">apple</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">		<span class="n">apple</span><span class="o">.</span><span class="n">spawn</span><span class="o">.</span><span class="nf">connect</span><span class="p">(</span><span class="n">_new_apple</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">	<span class="kd">var</span> <span class="n">temp_coords</span> <span class="o">=</span> <span class="n">bg_layer_coords</span> 
</span></span><span class="line"><span class="ln">11</span><span class="cl">	<span class="kd">var</span> <span class="n">new_apple_pos</span> <span class="p">:</span> <span class="nc">Vector2</span> <span class="o">=</span> <span class="n">temp_coords</span><span class="p">[</span><span class="nb">randi</span><span class="p">()</span> <span class="o">%</span> <span class="n">temp_coords</span><span class="o">.</span><span class="nf">size</span><span class="p">()]</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">	<span class="k">while</span> <span class="n">snake</span><span class="o">.</span><span class="n">grid_coords</span><span class="o">.</span><span class="nf">has</span><span class="p">(</span><span class="n">new_apple_pos</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">		<span class="n">new_apple_pos</span> <span class="o">=</span> <span class="n">temp_coords</span><span class="o">.</span><span class="nf">pick_random</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">	<span class="n">apple</span><span class="o">.</span><span class="n">position</span> <span class="o">=</span> <span class="n">grid</span><span class="o">.</span><span class="nf">map_to_local</span><span class="p">(</span><span class="n">new_apple_pos</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">	
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="o">...</span></span></span></code></pre></div>
<p>Here, we check to see if there is an Apple node in the scene tree. If there isn&rsquo;t we create an instance of one, and connect the <code>spawn</code> signal, the same signal that it emits on pickup. This is to make sure there is only one <code>Apple.tscn</code> instance, but this could change in the future.jh}</p>
<p>Oh, and thanks to the newest Godot 4 beta, which is Beta 4, I get to use a new built in array method, <code>pick_random</code>!</p>
<p>Previously to get a random array element you would have to do something like this




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gd" data-lang="gd"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">new_apple_pos</span> <span class="o">=</span> <span class="n">temp_coords</span><span class="p">[</span><span class="nb">randi</span><span class="p">()</span> <span class="o">%</span> <span class="n">temp_coords</span><span class="o">.</span><span class="nf">size</span><span class="p">()]</span></span></span></code></pre></div></p>
<p>Not the most riveting new feature to test but hey I&rsquo;m down. After that I have a while loop that will garuntee that the random value for the new Apple position is not occupied by the snake body.</p>
<h2 id="other-improvements">Other improvements</h2>
<p>I moved the code that is used to update the snake when there are new segments available at the moment of a timer timeout to an <code>update_snake()</code> function. This way it can be used by the <code>ready()</code>, <code>_timer_timeout()</code>, and whatever other function may need it.</p>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gd" data-lang="gd"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># GameManager.gd</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="kd">func</span> <span class="nf">update_snake</span><span class="p">(</span><span class="n">grid_coords_pos</span> <span class="o">=</span> <span class="n">snake</span><span class="o">.</span><span class="n">grid_coords</span><span class="o">.</span><span class="nf">size</span><span class="p">()</span> <span class="o">-</span> <span class="mi">1</span><span class="p">):</span> 
</span></span><span class="line"><span class="ln">5</span><span class="cl">	<span class="kd">var</span> <span class="n">current_segment</span> <span class="o">=</span> <span class="n">snake_segment</span><span class="o">.</span><span class="nf">instantiate</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">	<span class="nf">add_child</span><span class="p">(</span><span class="n">current_segment</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">	<span class="n">current_segment</span><span class="o">.</span><span class="n">position</span> <span class="o">=</span> <span class="n">grid</span><span class="o">.</span><span class="nf">map_to_local</span><span class="p">(</span><span class="n">snake</span><span class="o">.</span><span class="n">grid_coords</span><span class="p">[</span><span class="n">grid_coords_pos</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">	<span class="n">snake</span><span class="o">.</span><span class="n">body_segments</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="n">current_segment</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="o">...</span></span></span></code></pre></div>
<p>I also had to define a default value for the <code>grid_coords_pos</code> argument, since the <code>_timer_timeout()</code> func specifically needs the last segment of the snake to be updated, but the <code>ready()</code> func actually needs to call this for every snake body segment after the head, so the <code>ready()</code> func now looks like this</p>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gd" data-lang="gd"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># GameManager.gd</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kd">func</span> <span class="nf">_ready</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">	<span class="n">bg_layer_coords</span> <span class="o">=</span> <span class="n">grid</span><span class="o">.</span><span class="nf">get_used_cells</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">	<span class="n">timer</span><span class="o">.</span><span class="n">timeout</span><span class="o">.</span><span class="nf">connect</span><span class="p">(</span><span class="n">_timer_timeout</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">	<span class="n">snake</span> <span class="o">=</span> <span class="n">snake_head</span><span class="o">.</span><span class="nf">instantiate</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">	<span class="k">for</span> <span class="n">s</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">snake</span><span class="o">.</span><span class="n">grid_coords</span><span class="o">.</span><span class="nf">size</span><span class="p">()):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">		<span class="k">if</span> <span class="n">s</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">			<span class="nf">add_child</span><span class="p">(</span><span class="n">snake</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">			<span class="n">snake</span><span class="o">.</span><span class="n">position</span> <span class="o">=</span> <span class="n">grid</span><span class="o">.</span><span class="nf">map_to_local</span><span class="p">(</span><span class="n">snake</span><span class="o">.</span><span class="n">grid_coords</span><span class="p">[</span><span class="n">s</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">			<span class="n">snake</span><span class="o">.</span><span class="n">body_segments</span><span class="o">.</span><span class="nf">push_front</span><span class="p">(</span><span class="n">snake</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">		<span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">			<span class="nf">update_snake</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">	<span class="nf">_new_apple</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="o">...</span></span></span></code></pre></div>
<p>Next update will most likely be triggering the death state, and some bug fixes</p>
]]></content:encoded></item><item><title>Learning Godot 4 Tilemaps via Snake</title><link>https://kyoung.codes/blog/godot-4-tilemaps/</link><pubDate>Sun, 30 Oct 2022 17:29:03 -0400</pubDate><author>youngks93@gmail.com (Kevin Young)</author><guid>https://kyoung.codes/blog/godot-4-tilemaps/</guid><description>With Godot 4 now in beta, I&amp;rsquo;ve been starting to mess around with the new kit. And although there&amp;rsquo;s a lot of really exciting 3d features coming to Godot 4, the 2d space is a bit more in my wheelhouse for now, as I continue to learn the engine.
One new feature I am looking forward to using in Godot 4 2d is the new TileMap node updates. To get myself farmiliar with the new TileMap api, I wanted a project that would let me dip my toes into this node, but I also wanted a project that had a small scope and ideally I wouldn&amp;rsquo;t have to think up rules or functionality from scratch.</description><content:encoded><![CDATA[<p>With Godot 4 now in beta, I&rsquo;ve been starting to mess around with the new kit. And although there&rsquo;s a lot of really exciting 3d features coming to Godot 4, the 2d space is a bit more in my wheelhouse for now, as I continue to learn the engine.</p>
<p>One new feature I am looking forward to using in Godot 4 2d is the new <code>TileMap</code> node updates. To get myself farmiliar with the new <code>TileMap</code> api, I wanted a project that would let me dip my toes into this node, but I also wanted a project that had a small scope and ideally I wouldn&rsquo;t have to think up rules or functionality from scratch. So, I chose to remake Snake, using a <code>TileMap</code> as my main playarea!</p>
<h2 id="concept">Concept</h2>
<p>While this isn&rsquo;t really going to be using many of the new features of <code>TileMap</code>, most of it is new to me anyway as a Godot noob. The idea is to use the <code>TileMap</code> as a grid for the play space. The snake the player controls is made up of a series of grid squares, and every <code>x</code> seconds, the snake moves one grid square in the direction the snake is pointing, which is determined by input that is buffered between the movement timer&rsquo;s timeout duration.</p>
<h2 id="on-the-grid">On the grid</h2>
<p>The main scene contains a <code>TileMap</code> and a <code>Timer</code> node, underneath the parent <code>Node2d</code>, which has a script attached called <code>GameManager.gd</code>. The <code>TileMap</code> being the play area, and the <code>Timer</code> being the amount of time between each movement of the snake, which I currently have set to .5 seconds.</p>
<p>Using the <code>TileMap</code> node&rsquo;s Layer system, I am able to split the background layer from the snake. This way, when I&rsquo;m modifying grid squares of the <code>TileMap</code> to show the snake move, I don&rsquo;t need to worry about redrawing the background.</p>
<p><img alt="tilemap_layer" src="/images/posts/tilemap_layer.png" title="tilemap"></p>
<p>I created a <code>Snake</code> scene and added a <code>Snake.gd</code> script to the root <code>StaticBody2d</code> node, as well as adding a <code>Sprite2d</code> and <code>CollisionShape2d</code> of course.</p>
<p>Here&rsquo;s what that script looks like at the moment for just covering the movement.</p>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gd" data-lang="gd"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Snake.gd</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kd">extends</span> <span class="nc">StaticBody2D</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nd">@onready</span> <span class="kd">var</span> <span class="n">manager</span> <span class="o">=</span> <span class="nf">get_parent</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kd">var</span> <span class="n">input_buffer</span> <span class="o">:=</span> <span class="nc">Vector2</span><span class="o">.</span><span class="n">ZERO</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="kd">var</span> <span class="n">grid_coords</span> <span class="o">:=</span> <span class="p">[</span><span class="nc">Vector2</span><span class="p">(</span><span class="mi">21</span><span class="p">,</span> <span class="mi">22</span><span class="p">),</span> <span class="nc">Vector2</span><span class="p">(</span><span class="mi">22</span><span class="p">,</span><span class="mi">22</span><span class="p">),</span> <span class="nc">Vector2</span><span class="p">(</span><span class="mi">23</span><span class="p">,</span><span class="mi">22</span><span class="p">),</span> <span class="nc">Vector2</span><span class="p">(</span><span class="mi">24</span><span class="p">,</span><span class="mi">22</span><span class="p">)]</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="kd">var</span> <span class="n">body_segments</span><span class="p">:</span> <span class="nc">Array</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="kd">func</span> <span class="nf">_process</span><span class="p">(</span><span class="n">_delta</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">	<span class="k">if</span> <span class="nc">Input</span><span class="o">.</span><span class="nf">get_vector</span><span class="p">(</span><span class="s2">&#34;left&#34;</span><span class="p">,</span> <span class="s2">&#34;right&#34;</span><span class="p">,</span> <span class="s2">&#34;up&#34;</span><span class="p">,</span> <span class="s2">&#34;down&#34;</span><span class="p">)</span> <span class="o">!=</span> <span class="nc">Vector2</span><span class="o">.</span><span class="n">ZERO</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">		<span class="n">input_buffer</span> <span class="o">=</span> <span class="nc">Input</span><span class="o">.</span><span class="nf">get_vector</span><span class="p">(</span><span class="s2">&#34;left&#34;</span><span class="p">,</span> <span class="s2">&#34;right&#34;</span><span class="p">,</span> <span class="s2">&#34;up&#34;</span><span class="p">,</span> <span class="s2">&#34;down&#34;</span><span class="p">)</span></span></span></code></pre></div>
<p>It&rsquo;s a little different from a basic 2d game set up, since I&rsquo;m not moving the player kinematically, hence the <code>StaticBody2d</code> node. I&rsquo;m just storing the vector from <code>Input.get_vector</code>, and using that to influce the direction the snake will move on the timer&rsquo;s timeout duration. The <code>grid_coords</code> is an array that will be storing the position of each snake segment position on the <code>TileMap</code>. Which leads me to the <code>SnakeTail</code> scene&hellip;. which is actually pretty much identical to the <code>Snake</code> scene at the moment, just without a script and a different sprite. For now it is just serving the purpose of being an instanced scene.</p>
<p>Now to tie it all together in the <code>GameManager.gd</code> script.</p>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gd" data-lang="gd"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># GameManager.gd</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kd">extends</span> <span class="nc">Node2D</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nd">@onready</span> <span class="kd">var</span> <span class="n">grid</span> <span class="o">=</span> <span class="nx">$TileMap</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nd">@onready</span> <span class="kd">var</span> <span class="n">timer</span> <span class="o">=</span> <span class="nx">$Timer</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nd">@onready</span> <span class="kd">var</span> <span class="n">snake_head</span> <span class="o">=</span> <span class="nb">preload</span><span class="p">(</span><span class="s2">&#34;res://Snake/Snake.tscn&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nd">@onready</span> <span class="kd">var</span> <span class="n">snake_segment</span> <span class="o">=</span> <span class="nb">preload</span><span class="p">(</span><span class="s2">&#34;res://Snake/SnakeTail.tscn&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="kd">var</span> <span class="n">snake</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="kd">func</span> <span class="nf">_ready</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">	<span class="n">timer</span><span class="o">.</span><span class="n">timeout</span><span class="o">.</span><span class="nf">connect</span><span class="p">(</span><span class="n">_timer_timeout</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">	<span class="n">snake</span> <span class="o">=</span> <span class="n">snake_head</span><span class="o">.</span><span class="nf">instantiate</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">	<span class="k">for</span> <span class="n">s</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">snake</span><span class="o">.</span><span class="n">grid_coords</span><span class="o">.</span><span class="nf">size</span><span class="p">()):</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">		<span class="k">if</span> <span class="n">s</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">			<span class="nf">add_child</span><span class="p">(</span><span class="n">snake</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">			<span class="n">snake</span><span class="o">.</span><span class="n">position</span> <span class="o">=</span> <span class="n">grid</span><span class="o">.</span><span class="nf">map_to_local</span><span class="p">(</span><span class="n">snake</span><span class="o">.</span><span class="n">grid_coords</span><span class="p">[</span><span class="n">s</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">			<span class="n">snake</span><span class="o">.</span><span class="n">body_segments</span><span class="o">.</span><span class="nf">push_front</span><span class="p">(</span><span class="n">snake</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">		<span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">			<span class="kd">var</span> <span class="n">current_segment</span> <span class="o">=</span> <span class="n">snake_segment</span><span class="o">.</span><span class="nf">instantiate</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">			<span class="nf">add_child</span><span class="p">(</span><span class="n">current_segment</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">			<span class="n">current_segment</span><span class="o">.</span><span class="n">position</span> <span class="o">=</span> <span class="n">grid</span><span class="o">.</span><span class="nf">map_to_local</span><span class="p">(</span><span class="n">snake</span><span class="o">.</span><span class="n">grid_coords</span><span class="p">[</span><span class="n">s</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">			<span class="n">snake</span><span class="o">.</span><span class="n">body_segments</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="n">current_segment</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">	<span class="n">timer</span><span class="o">.</span><span class="nf">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="kd">func</span> <span class="nf">_timer_timeout</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">	<span class="kd">var</span> <span class="n">segment_next</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">	<span class="kd">var</span> <span class="n">segment_previous</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">	<span class="k">for</span> <span class="n">s</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">snake</span><span class="o">.</span><span class="n">grid_coords</span><span class="o">.</span><span class="nf">size</span><span class="p">()):</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">		<span class="k">if</span> <span class="n">s</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">			<span class="n">segment_next</span> <span class="o">=</span> <span class="n">snake</span><span class="o">.</span><span class="n">grid_coords</span><span class="p">[</span><span class="n">s</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">			<span class="n">snake</span><span class="o">.</span><span class="n">grid_coords</span><span class="p">[</span><span class="n">s</span><span class="p">]</span> <span class="o">=</span> <span class="n">snake</span><span class="o">.</span><span class="n">grid_coords</span><span class="p">[</span><span class="n">s</span><span class="p">]</span> <span class="o">+</span> <span class="n">snake</span><span class="o">.</span><span class="n">input_buffer</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">			<span class="n">snake</span><span class="o">.</span><span class="n">position</span> <span class="o">=</span> <span class="n">grid</span><span class="o">.</span><span class="nf">map_to_local</span><span class="p">(</span><span class="n">snake</span><span class="o">.</span><span class="n">grid_coords</span><span class="p">[</span><span class="n">s</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">		<span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">			<span class="n">grid</span><span class="o">.</span><span class="nf">erase_cell</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">snake</span><span class="o">.</span><span class="n">grid_coords</span><span class="p">[</span><span class="n">s</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">			<span class="n">segment_previous</span> <span class="o">=</span> <span class="n">snake</span><span class="o">.</span><span class="n">grid_coords</span><span class="p">[</span><span class="n">s</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">			<span class="n">snake</span><span class="o">.</span><span class="n">grid_coords</span><span class="p">[</span><span class="n">s</span><span class="p">]</span> <span class="o">=</span> <span class="n">segment_next</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">			<span class="n">snake</span><span class="o">.</span><span class="n">body_segments</span><span class="p">[</span><span class="n">s</span><span class="p">]</span><span class="o">.</span><span class="n">position</span> <span class="o">=</span> <span class="n">grid</span><span class="o">.</span><span class="nf">map_to_local</span><span class="p">(</span><span class="n">snake</span><span class="o">.</span><span class="n">grid_coords</span><span class="p">[</span><span class="n">s</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">			<span class="n">segment_next</span> <span class="o">=</span> <span class="n">segment_previous</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">		<span class="n">timer</span><span class="o">.</span><span class="nf">start</span><span class="p">()</span></span></span></code></pre></div>
<p>Basically, in the <code>_ready</code> func we set up the snake by instancing all of the body parts from the <code>Vector2</code>&rsquo;s that are stored in the <code>Snake</code> scene&rsquo;s <code>grid_coords</code> array. To make things easy, the snake&rsquo;s head is always position <code>0</code> of the array, and the rest of the snake tail segments are added in sequential order. For both the snake head and body segments, instances of each scene are created for each of the <code>Vector2</code>&rsquo;s in <code>grid_coords</code>, and pushed to the <code>Snake</code> scene&rsquo;s <code>body_segments</code> array. Since they are in sequential order, we know that <code>grid_coords[2]</code> is the position of the instanced scene stored in <code>body_segments[2]</code>. Ain&rsquo;t that fun.</p>
<p>Here you can see some of the <code>TileMap</code> api coming into play, utilizing the <code>map_to_local(Vector2i)</code> func, which takes a <code>Vector2</code> and returns the cenetered position of the cell in the <code>TileMap</code>&rsquo;s local coordinate space. We do this to prvent the snake&rsquo;s sprites being off center on the grid, which would make things confusing when we start moving things around and adding pick up items to the <code>TileMap</code>.</p>
<p>Then, to actually move this bad boy, we connect the timer&rsquo;s timeout signal. When the <code>Timer</code> times out, the signal will call the <code>_timer_timeout()</code> func, and start to shift the position of the snake&rsquo;s head stored in <code>grid_coord</code> by adding the <code>input_vector</code> to it&rsquo;s current position <code>Vector2</code>. Then we just use the previous position of the snake&rsquo;s head as the position of the next object in <code>grid_coords</code>, and do the same for each body segment.</p>
<p><img alt="snek_move" src="/images/posts/snek.gif" title="snek"></p>
<p>And that&rsquo;s it for basic movement! Next up is adding some basic state management, and adding the apple pickup item so you can grow up to be a nice big snake.</p>
]]></content:encoded></item></channel></rss>