BRANDON FANNIN
  • Home
  • About
  • Résumé
  • Blog
  • Contact

Adventures in Game Development

Week 10 - Heads Up

1/17/2025

 
Another week down with a good amount of progress made. Following up from last week, my primary goal was to finish the player's damage interaction, which meant I had to set up a few extra systems.  First up, we need a way to actually keep track of the player's health, so that means setting up some UI. I created a UI Manager script, defined a basic "UI Screen" to hold shared functionality, and then went about making the HUD from there.  Luckily, unlike many of my past experiences with the art assets I'd been able to find myself, the sprites for the UI chunks I had needed minimal amounts of work in order to get in to "game-ready" state. So, after looking up some basic guides on how to use the UI nodes in Godot, I threw together my replica of the Super Metroid HUD.
Picture
Not bad, eh? I'm not planning on implementing grapple beam, reserve tanks, or X-ray visor for this little project of mine, so I left those bits out of my setup. Next step was actually getting those values hooked up to real player stats, which... lead me to rearrange some of my architecture first. Previously, I just had the player, level, and enemies thrown under an empty node for organizational purposes, but I ended up finally making myself a real "Game Manager" script to exercise more control over how the program runs.  Instead of just using Godot's built in "_ready" function to set up values at game start, I introduced Initialization functions to key scripts and had the Game Manager trigger those in correct load order.  In simpler terms, I needed to be able to make sure certain values were set within the player instance before I could hook up my new "Player Status" script and properly connect the HUD to that.

Speaking of Player Status, that script is effectively just a data holder used to keep track of stats. At the moment it holds the player's max and current health, as well as max and current ammo counts for missiles, super missiles, and power bombs, but it will eventually hold data on what power-ups have been collected and what items are equipped. Basically, this thing will be what I eventually turn into save data once I start working on that system.  For now though, it has been properly hooked into the HUD, which updates the relevant fields as necessary. 
Picture
Energy tanks are shown/hidden based on what the player's max health is, and the icons for your various types of ammo are set to hidden if you can't hold any of that ammo yet. And of course, this all updates in real time as the player's stats change.
Picture
As you might be able to see from the clip above, I also got my i-frame flash working as intended. That one was thankfully as simple as I thought it would be.

For next week, current goals are to get enemy drops working so that there's actually a way to make those numbers go up instead of just down, and also finally maybe make proper hit and death effects for my poor enemies. 

See you next time.

Week 9 - Ouch

1/10/2025

 
Back to work after the new year.  There's not a whole lot of visual progress this week in comparison to earlier updates, but I still got a decent amount of work done. Under the hood I made it so that weapons actually apply damage to enemies instead of just instantly killing them, made them die properly when their health hit zero, and added in functions that allow entities to modify incoming damage values.  I also setup damage property flags to represent different types of incoming damage and made it so that enemies can be set to be immune to specific damage types. That will come into play relatively soon when I start adding in bombs, missiles, and different beam types.

With that set up, I also added a little bit of polish to the damage sequence so that we now get that classic sprite flash when something gets hit by an attack:
Picture
The system I made for this should be pretty versatile, able to take in lists of colors, frame counts, and duration to create looping effects. In theory, it should work perfectly for when I get i-frame visuals up and running. Speaking of player damage, I also made it so that enemies can damage players and even apply knockback to them.
Picture
I had to at least stub in invincibility frame logic to prevent them from applying damage continuously, but I will be adding visuals to let the player know when they're active in the near future. 

That's about it for progress this time, because I actually spent a good portion of the week getting sidetracked by research.  While trying to figure out the best way to handle collision detection for enemies, I stumbled across a post talking about physics optimization and kinda fell down a rabbit hole for a bit.  Evidentially Godot has systems called "servers" that handle all of the engine's rendering, physics, and computations under the hood.  While the Node system is very user friendly, it's not the most efficient in terms of processing, and so it can start to hold you back when you have a large number of nodes active at the same time. You can apparently get around this issue by polling the servers themselves instead of just using Nodes. 

This video does a pretty great job of showing the differences in the two approaches:
It's not quite as simple as they make it sound, especially if you want bullets with non-linear/non-standard movement, but the potential performance gains are pretty obvious. Now, this isn't exactly important for my work on this project because the number of live physics objects in any given scene will always be low, but it's pretty vital information for some of my future plans. 

We'll talk more about that when I start getting closer to checking everything off my todo list for Metroid though. For now, my next goals are to finish sprucing up my enemy damage effects (VFX for hit and death), adding visuals to the player's i-frames, and then starting set up the HUD so that we can actually see how much health the player has.

See you next time.

Week 5 - Jump and Shoot

11/22/2024

 
We're just over a month into it now, and at this point I think I can say the whole blogging strategy is working pretty well. Forcing myself to sit down at the end of the week and measure my progress has definitely made me feel like I'm getting more done, even in the cases where I'm not working at a noticeably faster pace.  But, that feeling of accomplishment helps boost morale, which in turn does actually make me more productive. I feel like at this point my output is quite a lot higher than it has been in a good while.

Now for the actual progress report; In this past week I have built a pooling system, got basic projectiles working, and threw together a weapon manager for the player. All that is to say, you can now jump and shoot​.
Picture
As a quick aside, I looked intro trying to fix that little leg twitch Samus has at the top of her jump, but there's simply no easy way to do it. I'd either need to redo half of my animation tree, write some incredibly specific code to try to force a behavior that doesn't want to happen, or tear the whole thing up and write my own custom animation manager. Needless to say, since the whole point of this project is to learn, dealing with that isn't exactly high on my priorities. I've learned enough at this point that if I needed to set up animations like this on another project, I could build the systems in a way that better suites my needs, and I'm satisfied with that.

Now, object pooling.... For those not familiar with games programming, here's a quick breakdown.  Whenever you need to create or destroy a new instance of an object, that takes up a decent chunk of time/processing power.  When it happens often enough, or if you're trying to create new instances of particularly complicated objects, that can easily lead to framerate drops and game stutters. Object pooling is a solution to this; instead of creating a new object whenever you need one, you create a bunch of objects ahead of time to setup a "pool"(usually during a loading screen). The object in the pool are just turned off and hidden until they are needed, at which point you switch them on and put them where they need to be.  When you're done with an object, you turn it back off and return it to the pool.

This is a pretty standard technique in the industry, used for things that we would otherwise need to create and destroy with some frequency. A few common examples would be bullets, item drops, and sometimes even enemies. Now, this is usually a pretty simple thing to set up, but Godot's quirks forced me to do a bit of experimentation this time. Godot doesn't support Multiple-Inheritance, a programming concept that's a bit harder to explain for those not already in the know, but the simplest explanation for my situation here is that I can't have projectiles be both physics entities and pooled objects at the same time. That forced me to revise my implementation of pooled objects a bit, but I'm actually pretty satisfied with the results. Basically, instead of telling something that it is a pooled object, I made pooled objects something you attach to other objects.  Once attached, the pooled object has the ability to hijack whatever its connected to and turn them on or off as the Pool needs. This means that any object I make can theoretically be pooled, not just ones that I specifically design to be.

For the projectiles themselves, I had to wrestle with Godot's Rigidbody system a bit more than anticipated, but I eventually got everything working as desired. The bigger lift here was actually making sure the projectiles spawned where they were supposed to. Samus' arm cannon doesn't have consistent positioning between different animations, even if it's aiming in the same direction. In order to deal with this, I had to go into every single animation and key in a position for the projectile's spawn point. Tedious work to say the least, but hey; it's done.
Picture
One other thing of note; while I was setting up the projectile sprites, I discovered that the sprite sheet I'm using is actually missing quite a bit of content. It has all the sprites for missiles and bombs, but it only has the basic power beam and the fully upgraded ice beam... not  any of the combinations in between. I was unable to find any more complete sprite sheets for the Super Metroid projectiles, so if I end up taking this far enough to want to do more than one beam power up, then I might need to substitute in different projectile spites.  I was able to find a complete sheet for the GBA Metroid games, so in that scenario I might switch over to using those instead. 

That's it for this week though. Not 100% sure what the goal for next week is, trying to decide between making an enemy to shoot at or getting tilemaps set up so that I'm not standing in a white and grey void any more. 

​See you next time.

Week 4 - What I'm Aiming For

11/15/2024

 
No, these stupid post titles aren't going to end any time soon. Sorry, not sorry.

Another decently productive week down; I managed to get aim functionality in pretty smoothly, all things considered. The blend spaces I set up last week to clean up my animation tree made plugging this stuff in pretty easy. For anyone who's curious, here's what the inside of a blend space looks like: 
Picture
Direction along the bottom represents which way the player is facing, and Aim represents where their gun is pointed vertically. If the player pressed up or down, I set the value to 1/-1, and if they press diagonally up or down I set it to 0.5/-0.5 instead. The zig-zag lines... really aren't important since this animation is sprite based. Since I already put placeholder nodes in, once I got the input reading functionality working under the hood I just had to swap out the animations at each node as needed.  Of course, that involved setting up more animations, but I find that I've been getting better at the Godot animation workflow so it didn't end up being as time consuming as I feared. Nintendo really didn't skip on detail for this games animations though. Did you know that there are separate turning animations depending on whether you're currently aiming up or down?
Picture
Picture
As part of the aiming functionality, I also hooked in the 'diagonal lock' input. For those of you that may not be as familiar with Metroid, it is as simple as the name sounds; you hold a button and Samus aims diagonally up or down, even if you're not currently pressing the D-pad. Super Metroid was set up to have two such buttons; one for aiming upward and one for aiming downward. There was a bit of an oddity in the design however, and the game did not allow you to re-assign these inputs to non-shoulder buttons. This might not have been a problem for most players, but if you're like me and you can't stand having the Sprint button assigned to a face button (it makes trying to run, jump, and shoot at the same time basically impossible), that meant you effectively lost the ability to aim in one of the directions while standing still.

That being said, the GBA Metroid games had a solution for this, since the handheld had fewer buttons to work with.  Holding the button locked you in a diagonal aim mode, and pressing up or down transitioned your aim in that direction. Pressing down while already aiming diagonally down would make Samus crouch while maintaining the same aim direction, and similar transition would happen if you pressed up while aiming diagonally up when crouching. I always preferred the feel of this setup over the one that they had in Super Metroid, and so I opted to use that implementation instead in this case.

​Anyway, with the directional aiming taken care of, I was able to finish implementing some more movement tech that is dependent on it. Specifically, the ability to break out of a spin jump by aiming vertically, and the ability to enter and exit morphball while in the air:
Picture
Picture
Aiming downward while in the air is pretty interesting, as it's treated differently than aiming in any other direction. If the player releases the Down button, they don't actually stop aiming downward. Only pressing a different direction will break you out of it, so it's sorta like an airborne equivalent to the crouch state... though I didn't give it it's own state here, just some fancy logic to prevent Samus from returning to neutral from a downward aim and a bit more to prevent her from entering morphball when she isn't already pointing down.

I know for a fact that there's plenty more nuance to the work I did this week that I'm largely glazing over, but the end result is simple; you can point your gun at things now. The next task on my todo list is the obvious continuation from this one; make it so that you can actually shoot the gun. Projectiles in Super Metroid are not at all complicated, so I don't expect any issues there, but I imagine making sure the projectile spawns in the right spot and goes the right direction might be a little more tricky since there aren't animation bones I can use and the positioning of the cannon isn't consistent between different animation states. I think I have a few ideas on how to handle it, but we'll see how it goes.

​See you next time.

Week 3 - Keep Rolling

11/8/2024

 
Picking right up from last week, I added in the extra animation for moving forward during the neutral jump state. While at first I had assumed this would be a very simple task, after reviewing the game I found that the specifics of when this animation played were a bit tricky. I'll go over those in a second, but here's what we got working:
Picture
There's a little hitch with the animation when it swaps over from the jump to fall state, but that's something I'll deal with later. Now, in the source game this animation starts playing when you perform a neutral jump and then start moving forward. That part is just as simple as it sounds, but what took a minute to figure out was what caused it to transition back to the normal jump animation, because I found that frequently in testing, after entering this "forward jump" animation, Samus would simply stay in that state even after I stopped moving forward. turns out there are a handful of requirements. First and most obvious is that the player has stopped forward movement. The second is also pretty straight-forward; the player cannot be firing or charging their beam. The third one was what took some experimentation; the player only transitions back to the normal jump animation if the player is holding down the jump button when they release the d-pad. A bit weird in my opinion, but I coded that bit in all the same. 

After this, I went to work setting up the crouching and morphball states. Nothing too complicated here, except that I added in functionality to change the size of the player's hitbox when they enter/exit a given state.
Picture
We got through our main tasks for the week pretty quickly, so I moved on to a bit of clean up to round things out. First of all, I went through my scripts and removed any hard-coded strings for things like state names and animations, and instead set up a bunch of constant variables in a global class. easier to keep track of, easier to change something if I need to. Next I went through my animation tree and turned all my states into blend trees so that I didn't need separate nodes for the left and right version of each animation.  While I was doing that, I learned something rather handy; I don't need to have states connected in the animation tree unless I want them to automatically transition from one to another. Those two adjustments cleaned things up quite considerably, here's the before and after:
Picture
Picture
The blend states also have the added bonus of helping prep for my next big undertaking; directional aiming. Each animation is currently set up to take a facing direction and a vertical aim value, and I already switched up my input logic in code to include the vertical axis when I implemented crouching/morphball. Theoretically it should just be a matter of turning the input into an aim value, and then slotting in the different animation variations, but.... we'll see just how smoothly that part goes. There are alot of animations for me to plug in here after all. In any case, a pretty productive week I'd say, let's hope the momentum I'm gaining here keeps going strong.

See you next time.

Week 2 - How do you Jump?

11/1/2024

 
Bit of a busy week between Holiday plans and job search shenanigans, but we still managed to make a chunk of progress in between everything. My primary goal for the week was to sort out the jump state and the various mechanics attached to that. ie; Jump, spin jump, fall, and land. Setting up the states themselves and the animations was simple enough. just more of what we already did last week, but when it came to figuring out the specific jump physics I came to an interesting realization. For how many years I've been working as a gameplay programmer, I've never actually programmed a jump before that wasn't just a basic "apply upward force, add gravity until you fall" sort of thing. Anyone who has played Super Metroid (or any Nintendo platformer for that matter) knows that that's not how it works; jumps are dynamic, you go higher when you hold the button down longer and you can short-hop by just tapping the button briefly.

So after setting up the basic framework, I had a bit of homework to do. Luckily this is a very well explored topic and I was able to find a GDC talk from 2016 going over exactly what I needed to know.

​Here's a link for anyone who's interested: https://www.youtube.com/watch?v=hG9SzQxaCm8
Picture
Pretty simple setup after do we our math magic; we apply an upward force right when we enter the jump state, and then apply one of two constant gravity values depending on whether or not the player is holding down the jump button. Basically, gravity gets a lot heavier when you let go of the button. Once the player's vertical velocity becomes negative, we swap to the falling state and then apply a different gravity value that's slightly heavier to give the jump a bit more weight. Once we hit the ground, we swap to the Landing state just until the animation finishes before going back to Standing.

Now technically I'm not sure this is exactly how they handle jump momentum in Super Metroid, based on my observations they might literally just zero out the player's vertical velocity when they release the jump button, but I feel like this solution is probably more valuable for me in terms of future projects. 

Speaking of Super Metroid, I noticed an interesting quirk while I was testing the game's jump feel against my own;  the game seems to differentiate between when the player falls after jumping and when they fall from a standing state. Specifically, it has a different animation and the maximum fall speed is different, so I set up my system to work similarly:
Picture
Last up for the week was the spin jump. This is the state you enter when you jump while moving as opposed to standing still, and it comes with a few distinctions. First, you have a constant forward velocity while spin jumping, even if you stop pressing forward on your controller. Second, there is no turn animation for spin jumping; you instantly flip around and move in the opposite direction instead. And Third, the Spin jump has a slightly different animation when landing than a normal jump. There a few other notable differences as well, but those won't be important until I get to things like aiming and wall jumping. Because the rest of the jump physics are the same though, getting this state working was a pretty simple task.
Picture
And that's about it for now. I need to adjust the normal jump slightly in order to add in the separate animation the plays when you start moving forward, but besides that my main plans for next week are to get crouching and morphball working, and maybe start on figuring out the aiming logic.

See you next time.

    Archives

    April 2025
    March 2025
    February 2025
    January 2025
    December 2024
    November 2024
    October 2024

    Categories

    All
    AI
    Animation
    Attack Logic
    Camera
    Clean Up
    Enemies
    Game Development
    Godot
    Level Design
    Music
    Optimization
    Player Mechanics
    Projectiles
    Project Start
    Research
    Sound
    State Machines
    Super Metroid
    Tile Maps
    UI
    VFX

    RSS Feed

Proudly powered by Weebly
  • Home
  • About
  • Résumé
  • Blog
  • Contact