|
No update for last week, between the Holiday and a few personal obligations, I didn't really accomplish much worth talking about. Mostly I just did some research into my next project goal; tile sets. Turns out Godot has plenty of tools to make setting up tile sets particularly simple, and a bunch more tools to give you just about all the control you could need. So, armed with a bunch of new knowledge I started off this week by... realizing that the tile set sprites I found for Super Metroid were incomplete. Honestly, I'm finding more and more that the Super Metroid sprites that I have access to are far from the comprehensive collection that I had first thought they were. The GBA Metroid games on the other hand, have proven to be quite thorough. In this particular case, the sprites that I was missing were these little guys: These represent tiles that can be destroyed by specific weapons from Samus' arsenal. For some reason, all of the sprite collections I could find for the Super Metroid tiles only had the tiles representing Speed Boost and Power Bomb blocks, and I did quite a bit of digging. Those ones up above are specifically the versions from Zero Mission, and since these are lower resolution than the tiles from the SNES, I've decided to just use the GBA tiles in general moving forward. I would do that same with Samus' own sprites to keep things consistent, but redoing all of the animations, let alone having to clean up and repair the sprite sheets would likely take more than a full work week by itself. If it bothers me enough as I continue through this project I'll consider revisiting the issue later, but for now we'll simply have to live with it. Now, getting the tile sets working within Godot was actually pretty fast and easy once I found the sheets I wanted to use. I messed around a bit with setting up the auto-tiling tool with a few different terrain types, applied physics shapes to the individual tiles I was using, and then went about replacing the white block platforms of my test scene. Here are the results: Definitely an improvement visually. As a bonus, I also figured out how to get slope movement working properly. Usually that's a massive pain, good on Godot for having built-in systems to handle it. In any case, because getting the tiles in went so smoothly, I moved onto the "stretch goal" that I set myself for the week; tile destruction. This is something that I've been thinking over for a decent while, and I think I came up with a pretty decent solution. Tile maps in Godot are considered to be a single object, so no matter what tile a projectile hits, it returns the tile map itself as the object it hit, and the tiles themselves don't have any logic attached to them. That means that I can't attach any sort of "Destroyable" script to tiles that I want to be able to blast away. Instead, I leveraged the ability to create multiple tile map layers, and I placed tiles on a second layer to signify both that they can be destroyed, and specifically what they can be destroyed by. When a projectile registers a collision with a tile map, I get the coordinates of the tile it hit and see if a corresponding tile exists on the "destructible" layer. If it does, I check to see if that tile can be destroyed by the projectile that hit it before removing the tile on both the "destructible" and "terrain" layers. I then spawn a pooled object that plays the tile destruction animation while also storing information about the tile in question, include what type of tile it was originally as well as whether or not it will need to reform after a few seconds (since that's a thing that happens sometimes in Metroid games). Reformed tiles only show the "destructible" layer version of the tile, since that's how it works in the source material. There's still some work to be done here, such as how certain weapons can reveal a destructible tile without actually destroying it, but that sort of stuff is probably going to have to wait until I actually get more weapons implemented. First though, I think I'm going to make my next goal getting a basic enemy working, that way we have something besides the ground to shoot at.
See you next time. 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. 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. 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. 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: 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? 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: 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. 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: 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. 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: 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. 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 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: 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. 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. So, our first full week of development is done. Where are we at? Well, most of the week was actually spent gathering assets and getting those assets into a usable state. The gathering went pretty smoothly; Spriters-Resource had a full collection of character and enemy sprites as well as fully ripped tile sets for me to use. Unfortunately, neither of those could be used in the states I found them in, and that's where most of my effort for the week ended up going. For sprites, I had to convert the sheets to have a transparent background, isolate each frame, and then recombine them into sheets with a standardized grid size, all while fixing any errors I ran into with positioning and framing. Fun fact; did you know that Samus has approximately 720 different animation frames in Super Metroid? Needless to say, this took awhile, especially since I ended up having to go through the whole lot of them twice. Tile sets were a little bit easier; I just had to go in and add mirrored versions for any sloped tiles, since they only included one direction for each tile type. After getting the art settled, I imported all of Samus' sprites and got the animations plugged in to an AnimationPlayer node, which I then started hooking into an AnimationTree. Since I'm just starting out and wanted to get something working on the screen, I only built out the basic idle, move, and turn states. Not the prettiest thing in the world, but we'll spend some time organizing it properly later. Need to think of a system so that this thing doesn't turn into a massive spaghetti pile with just how many different animation states are in this game. With that in place, we made a basic placeholder character object and went to work setting up the architecture for a state machine. I'm not quite used to Godot's practice of needing a separate "scene" for each and every script I want, but I'm sure it'll get easier to wrap my head around over time. Just like the Animation Tree, we focused on getting basic movement working first, and here's what our final progress point looks like for the end of the week: Certainly not bad, but there's still a lot of work to do even with what is here. We'll need to add in acceleration/deceleration for the running to make it more game accurate, make sure the Turning state can properly handle transitioning between crouching, jumping, and falling states, and eventually tackle the hell that will be dealing with all of the different "aiming" sub-states that Samus has. Before we get into the weeds there though, I'm going to try to fill out the rest of the basic movement set, so next up on the agenda will be jumping, crouching, and maybe morphball. Only after we've got the basics accounted for will we loop back around and start fine-tuning and adding more layers of complexity.
See you next time. So, where do I begin... Given the extended nature of my unemployment, I have been spending a decent amount of my time working on various programming projects. For the most part, this has consisted of my attempts to learn the Unreal Engine and subsequently try to create some sort of game with it. I have but nine months of my life into this, and yet I feel like I have very little to show for it. It's not as though I haven't working at a decent pace or even that I've been wasting time; solo game development is a slow process and working in the Unreal Engine by yourself certainly has its challenges. More on that later though. The point is that in spite of what I've managed to accomplish, there's not a whole lot of tangible evidence of what my time has been spent on, which can be.... discouraging to say the least. And that's why I'm adding this blog to my website; the hope is that if I can regularly post updates on what I've been working on, the things that I'm doing will feel more real. I'll have proof that work is being done, even if my brain doesn't think I'm making any progress. It will also make it easier to share the things I'm working on with others across the internet, so that's a nice bonus. So, what exactly are we working on then? Well, as mentioned, solo development for Unreal certainly is a thing. The lack of proper documentation makes learning in a vacuum rather difficult, and I would say it is one of the primary reasons behind why I felt like I was making so little progress. Having to stop every five minutes and look up how built-in functions work, only to find little-to-no information online and end up experimenting until I figure it out, does consume quite a lot of time. As such, I have decided to change tactics. And by tactics I mean engines. The goal is still to learn something besides Unity, due to the greatly reduced demand for Unity developers in recent days, and so we've turned to another engine that has become popular lately; Godot. I've already spent about a week digging in and watching tutorials, and I've liked what I've seen so far. The documentation situation is certainly a whole lot better, considering that information on every class and function is built right into the packaged code-editor. I've got high hopes, but I am still just starting out. Instead of diving right into one of my own personal projects this time, I have decided to make use of an old technique I remember from my college classes. One of the best ways to learn a new engine, is to try to reverse-engineer a game that already exists. Now, I tend to be attracted to platformers and action games when it comes to my own development habits, so something that would make me more familiar with those types of mechanics in Godot would be best. For this reason, the game I have chosen to emulate is.... Super Metroid.
A well known game with thoroughly explored mechanics, fully fleshed out designs, and readily available art and audio assets. A Game that will act as a platform to teach me basically everything I'll need to know about Godot's systems for future projects. The aim is to post here at least once a week, detailing the progress I make, thoughts I have about working in the engine, future plans, and any random things of note that I feel like sharing. Official posts might not start until next week, but we'll see how far we get into it by Friday. Wish me luck, and see you next time. |
Archives
April 2025
Categories
All
|
RSS Feed