|
Didn't have time to write a blog post last week, so this week you get a double update. For the past two weeks I've been working on the map and saving systems. There's really not much to say about the save functionality, other than the fact that it employs Godot's resource system, which turns out to be very simple to use in that capacity. Basically I just made a resource class that contains all the data I want to keep track of, and then I copy that data from/into the actively running game to save or load. I had to rework a few systems to allow for mid-game loading, but that's about as far as the system goes. I can't really show it in action within the scope of my .gif maker either, so I suppose you'll just have to take my word that it works as intended. Now as for the map... I decided that the best way for me to implement Metroid's specific style of map was to use tilemaps. Rather than just using a single tilemap and changing each cell based on whether the map tile has been revealed, explored, or still unseen, I went with several tilemaps stacked on top of each other. The "hidden" layer is on top, followed by "revealed", "explored", and "occupied". This makes the map itself a little bit of a pain to set up, since I have to draw several different versions of the same geography, but it does make things much simpler at runtime, allowing me to simply hide individual tiles on each layer as the player discovers more of the map. The map itself is currently hidden in game, and will remain that way until I can get the pause menu up and running, but I did also get the mini-map working. I considered just using a second camera pointing at the map proper and projecting it onto the HUD, but I found it difficult to maintain the proper visual with that approach. Instead, I made use of another small tilemap on the HUD. This tilemap looks at the current state of the full-sized map and simply swaps out the tile sprites to reflect the map state in the player's immediate vicinity. A simple timer hooked up to a visibility toggle gets me the effect for the blinking tile on the player's current position. I ran into a little bit of trouble getting the map to play nice with the instant-load system I have hooked up for debug, but it turned out that I was accidentally using C# syntax for one of my for loops instead of GDScript. Turns out it's hard to drop 10 years worth of muscle memory, who would have guessed?
With these two features in a state that I'm satisfied enough with, we're just left with Sound and the Pause menu for implementation. Getting pretty close to the end here now, and my brain is already at work trying to churn up ideas for what comes next. See you next time. Alright, it's been a hot minute since the last time I was able to give an update. Life got a bit hectic so I had to take a bit of a hiatus from my project work in order to deal with everything, but now I should be more or less back on my normal work schedule. So this update is probably roughly equivalent to 2 weeks of actual dev work spread out across a little over a month, and what have we done during that time? Well, I introduced the concept of "Rooms" to the codebase, which store data for the tile sets and trigger volumes of a small chunk of environment as well as map coordinates (which will come in handy later). They also indirectly hold information on their neighboring rooms via the newly added transition triggers I made, which in turn allows us to move from room to room. And that's really what I spent the bulk of the time working on; room transitions. In Metroid, when the player walks through a door, they don't just simply appear in the next room, the game pauses, everything fades to black except for the door itself, the camera pans in the direction of the new room, and then everything fades back in before gameplay resumes. This process hides any load jitters while also being pretty visually appealing at the same time, but each step of that process involves a new system that I had to make. Pausing the world is relatively simple thanks to Godot's timescale and Processing type options, just need to select the top-level node and tell it to pause, and set a few choice objects to be able to continue to run their update loops even when the timescale is paused. The fade involves attaching a giant black rectangle to the camera and adding scripts to allow it to transition from one color to another over time, and the actual camera movement only needed some slight adjustments to the camera manager system I set up as part of my last update. In order to get the whole sequence put together, I also had to figure out how callback functions work in Godot, which luckily ended up not being that much different from what I'm used to in Unity. Technically I could have just used signals to get functions to fire when I need them, but having to connect and disconnect to signals repeatedly every time I ran through the process seemed like a lot more work than just passing in a callback function. There's a little more magic happening behind the scenes when it comes to placing the rooms in the proper position in the world and making sure the player's movement and height are carried over between room transitions, but that's just some simple math at the end of the day.
Now, as for what's next.... 14 weeks is actually a pretty short amount of time when it comes to game development, but the fact of the matter is that this project was supposed to be a relatively short one. Theoretically I could keep chipping away until I more or less recreate the whole game, but the point of this exercise was to familiarize myself with the Godot engine before I move on to working on something of my own. To that end, I have made a list of topics/features that I want to cover before I can declare this project over. Here's what we have left: - Mini-map - Pause Menu - Save/Load - Sound and Music I've determined that basically everything else on the feature list I originally wrote up is either something I have already figured out how to do during my time with the engine, or something that the engine doesn't meaningfully effect the implementation of. As such, those won't be much help as a learning tool, and I'd be better off spending my time elsewhere. So all things considered, we're actually pretty close to the end here. I've been doing some thinking about the project I'll be starting up once I'm done with the Metroid stuff, and I'll talk some more about that when we get there. For now, next thing on the docket is the map functionality. See you next time. Okay technically we missed a progress check-in last week, but I've been busy with job search matters so I had to put this project on the back-burner for a little bit. This past milestone has been all about our game's camera: getting it to follow the player, controlling where it can and can't go, and handling transition behaviors. The good news here is that I learned that Godot has a lot of really nice built in camera functionality. It's got smooth follow, tracking bounds, and even built-in deadzone options. The bad news is that it turns out I couldn't use most of these features for my purposes and had to write my own camera manager instead. So why is that? The short answer is that the default functionality of the godot camera was at odds with the camera design of Super Metroid. This was mainly evident in two areas: tracking and bounds. For Camera Tracking, Godot has set up its cameras to use a very common design for side-scrollers known as a deadzone. The idea is that if you are in the middle of the screen, you can move slightly to the left or right before the camera actually starts moving to follow you. This makes the camera feel more dynamic and more smooth than if it simply always tried to keep the player in the exact center. Super Metroid doesn't do that though, rather the camera moves slightly ahead of the player as they move in a particular direction, moving them to the "back" half of the screen to allow them to see farther forward. This makes sense for a game where your primary attacks are projectiles, as it gives you warning for things ahead of you so that you can better engage at range. For this purpose, I built the camera manager to have the camera track an invisible point that moves in whatever direction the player is going, up to a defined limit in each direction. For the camera bounds, Godot has some built-in variables that you can set to tell the camera not to pan past a certain point. This works fantastically, the only problem is that if you ever try to change the bounds of the camera, say when going through a screen transition, the camera instantly snaps to be inside of the new bounds rather than transitioning to it smoothly. Technically Metroid's camera deals with this in two different ways depending on the specific trigger. Either the camera will simply do a slide transition to its new desire bounds, or the camera will have a ratchet effect applied to it, where moving closer to the bounds shifts the camera as normal, but trying to move away will have it remain in place. As you can see, I got it all working, it simply took a bit longer than expected. Anyway, hope you all enjoy the little primer on camera design. Next up I'll be moving on to real room transitions with all the added fun of scene/asset loading.
See you next time. 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. |
Archives
April 2025
Categories
All
|
RSS Feed