BLOG
BLOG
Welcome back, long time no see! I'm here with a quick update on the procedural generation system, but more importantly, to wish you a happy International Wombat Day!
For a while I've been trying to find a way to make the game's map truly infinite (that is, it procedurally generates tiles as long as you keep scrolling) and I think I may have found something. Here it is:
A world that can grow as big as you'd like (and the ability to zoom much further out so that you can actually see it, and move around it easier). The new system I switched to (more on that from a technical standpoint in a second) also means that we get a lot of performance improvements! I plan on doing some more benchmarking later, but from my previous notes I can see that the game used to run at ~20 fps with a 50x50 grid, and crashed loading anything bigger than 100x100, which I can happily say is no longer a problem (as you can see in the gif, it doesn't crash, and I get at least 60fps on my laptop consistently). There are only 2 downsides of this new system: The first is that you can only clear tiles adjacent to tiles that have already been cleared (as you can see in the gif, tiles around the perimeter of the already-cleared-area are a lighter grey. Those are the only ones you can clear) but I think that makes sense (who on Earth just runs around clicking in the middle of nowhere anyway?). The second is that I had to redo pretty much everything, which means that I'll have to reimplement some systems I had working previously (i.e. placing buildings, defuser navigation, currency gain/loss) but I'm okay with that. In planning this project, I had infinite procedural map generation tagged as by far the most difficult technical issue I had to overcome; I essentially said "if I can do this, I can complete the project, and it's all downhill from there (from a technical standpoint, still a LOT of work to do). If not? the project is over before it started". Anyway, given that, I'm happy to say that the project is moving along!
That leads me into my next topic; the potential for a steam page. One of my new years resolutions this year was to put up a steam page for a game, and the more I tinker with MineSettler (tentative name) the more I think it has the potential to be that game. Now that I've got map generation out of the way, I can focus on reimplementing the features I had working previously, and then adding the next exciting part: enemy encounters. That's going to take a lot of work; adding a new building so you can spawn your own troops, having a random chance that in uncovered open areas around the map enemy camps spawn, and then having troops on either side navigate to meet in the middle and fight it out. Once I have a rough prototype of this in action, alongside a system so that both your camp and the enemy camps have HQs so that we can have a loss state (if the enemy destroys your HQ, you lose. If you destroy theirs, you capture their colony), I'll have a much better idea of how the game feels, whether or not it's actually fun, and if I want to move forward with a steam page. Stay tuned for that verdict in a future Wombat Wednesday!
Now onto the more technical side of things. I tried many different approaches (loading tiles based on their distance to the camera, loading tiles in chunks, removing components from tiles to make them more performant) but the one I settled on is the simplest (in my opinion). Essentially we just store the coordinate of each perimeter tile in a dictionary, and when we go to load the map we check if the current coordinate (starting at the origin) is in the dictionary. If not, we spawn a cleared tile, and recursively call the function on the surrounding coordinates. If it is, we spawn a covered tile, and stop the recursion. This gives us that entire inner area you see in the gif. Then, all the tiles in the background are just that; a big background image behind all the actual tile objects (as they don't need to be interactable; you can only clear tiles adjacent to ones you've already cleared). There's a bit more to it then that (like knowing which way to spawn tiles when we're clearing; how do we know which side of the perimeter is in and which ones out?) but that's less interesting.
Anyway, that's all for this very special Wombat Wednesday! Make sure to celebrate International Wombat Day today (though it is pretty late as I'm posting this, so it may be a belated celebration) and tune in next time for more updates! Thanks for reading, and have a good one! :)
P.S. Look at this lil dude! I took this picture on a trip to Maria Island a couple years ago, and it's been my phone's lock screen ever since.
Welcome back! This week was a bit of a slow one, so this update will be pretty quick (and mostly technical).
Originally I was going to work some more on resource management and placeables (i.e. creating placeable walls, an HQ building, some sentry guns) but I decided to start tackling a more pressing issue.
One of the core elements of this game is that the world will be procedurally generated, and ever-expanding (or at least big enough that the player can progress through all the stages I have planned), but there's been a problem: performance. I know that, judging by the screenshots from previous updates, the game doesn't look like much, so hearing that it has some performance issues could be surprising. Regardless, it does; anything bigger than 50x50 tiles tanks the performance, and trying to generate a map bigger than 100x100 just crashes the process immediately. Or at least, it did, until this week I started taking a look into performance.
I did some research into optimizing systems in Godot, and implemented a few different methods (mainly just pausing the processing of any offscreen node). Now, the game is able to launch (and runs smoothly) at 100x100 tiles, but testing much larger boards (i.e. 200x200) still either tanks performance or crashes on launch (part of me thinks it's to do with giving the engine that much to load right on launch, as dragging that initial process on too long may be what's causing it to close down; a deferred approach could be preferential, for instance with a loading screen).
There's still lots more to do! Next week I'm planning on looking into optimizing the tiles more, and hopefully boosting the framerate. More importantly, I want to start designing the actual procedural generation system. I'll start on pen and paper, so it's entirely possible next week's update will be a hypothetical system that's only half implemented (depends on how much time I get to work on it, it's only the first week of lectures so I doubt it'll be all that busy lol).
That's all for this update! Sorry there's no fun screenshots or interesting new features, hopefully the next update I'll have something cool to show off. Happy Wombat Wednesday! :)
This week I finished off the defuser system by adding in flagged-tile assignment (as I mentioned last time), and began work on resource management.
Now, when a tile is flagged, the nearest available defuser (one that is not already defusing a different mine) will walk over and defuse it, clearing the space and earning you some cash. This means you can go about clearing tiles and flagging potential mines without having to worry about manually calling defusers over; the closest one will make its way over on its own.
From a technical standpoint, this system was a little more complex than it may sound. I was worried about certain edge cases that may upset the player.
For example, if there is only one defuser, a basic system that pops the next flagged tile in the queue and assigns it to the defuser one at a time could work, but in some cases would be very annoying (what if there's a flagged tile right next to the defuser, but instead of assigning them that one it assigns them one all the way across the map? That wasted travel time would make the defusers seem really unintelligent.) Alternatively, you could make it so that the system either
Uses the defuser’s position to find the nearest flagged tile, and assigns it that way
or,
Uses the flagged tile’s position to find the nearest defuser, and assigns it that way.
Either way, there are issues. Say we have two defusers, defuser A and B, and two flagged tiles, tile 1 and 2. System A would use defuser A’s position to find the nearest flagged tile, tile 1. But what if defuser B was closer? We wouldn’t know, because the system only checked which tile was closest to defuser A, not which defuser was closest to tile 1. We have the same problem in reverse with system B: what if tile 1 chooses defuser B, when defuser B was right beside tile 2? Watching a defuser walk away from a flagged tile they were so close to, to go defuse one that is much further away, would be aggravating.
To fix this, I basically mixed the two systems. If there are no available defusers, but there are flagged tiles, and a defuser becomes available, then just assign it to its nearest flagged tile (system A). Otherwise, if there are no flagged tiles, but there are available defusers, and a flagged tile is added, use that flagged tile to find the nearest defuser, and assign it to defuse the new tile (system B). I’ve done a fair amount of testing with this system, and so far it’s worked with arbitrary numbers of defusers and flagged tiles without making me pull my hair out, so I’m quite happy with it.
Other than that, all that was needed for the system was a signal to cancel flagged tiles (so if a defuser is assigned a flagged tile, and it’s on its way there - or even currently defusing it - and the tile is unflagged, then cancel the defusal). This will be extra important in combination with resource management, because I’m planning on having the player lose money if they defuse a tile that wasn’t actually a mine, so being able to cancel them is crucial.
Speaking of resource management, the base system was implemented this week! Now, when a defuser defuses a mine, the player’s currency count goes up (currently by $185, but obviously that’s subject to change with balancing as the game expands). If the player incorrectly flags a non-mine tile, and a defuser defuses it, the player loses money. The total currency count is displayed at the top right of the screen, and the most recent transaction is shown just below in either red or green to show whether it was a gain or a loss (i.e. +$185 in green, -$185 in red). The most recent transaction also fades after about a second to avoid any confusion.
From a technical standpoint, this was primarily about setting up signals between the tiles and the GameManager (and UI) to display currency updates. It was pretty simple, and I made it quite modular so that the same system can be used with different revenue sources/sinks.
I think that’s about it for this week! Next week I’m planning on updating the placeables (structures) system so that placeables actually cost money, and cannot be placed unless the player has enough (hopefully I’ll get some other stuff done too; I’ve got to check up on the ol’ Trello board and see what's next lol). Thanks for checking out the blog, and I hope you have a lovely Wombat Wednesday!
Welcome to the first official Wombat Wednesday! This will basically just be a way for me to check in weekly with progress on upcoming projects and other cool stuff I'm up to. I've been wanting to do this for a while, for a few reasons:
To keep anyone interested in our work up to date with what's going on, and to show that we haven't abandoned our projects.
To hold myself accountable. In order to post these updates every Wednesday, I have to have done actual work worth posting. I'm hoping this will drive me to work on projects more consistently, and actually see more of them through to completion (crazy, I know).
To serve as a timeline/time capsule. I figured it would be fun in the future to look back on the development of our early projects and see what decisions we made, why we made them, and how long it took to reach certain milestones (the latter will hopefully be helpful in the nearer future as well, mostly when estimating dev time when starting a new project).
Anyway, on to what I've been up to!
Right now, I'm working on (the tentatively named) Project Mine Settler, which is a city building strategy game. Here's the gist of what's planned:
You land in the middle of a grey expanse, and the goal is to clear the tiles (not unlike a certain classic PC game about mine removal) in order to reveal the landscape below, and make space for your ever-expanding settlement. Using the mouse you can click tiles to reveal them, but be careful: some tiles are mines, and will have negative repercussions when uncovered. Using the numbers displayed on uncovered tiles (which show you how many mines are in the 8 tiles surrounding that one), you can deduce which ones are mines, and flag them. In order to remove flagged tiles, you can build a defuser tent, which will spawn defusers - little troops who march around defusing flagged tiles, earning you money.
As you expand, you'll uncover different monuments, biomes, and occasionally enemy encampments, triggering combat encounters. During combat encounters, your troops will duke it out with enemy combatants in a deadly tug-of-war: whoever manages to push their way into the enemy camp and destroy their HQ first wins, eradicating that enemy entirely and claiming its resources.
There will be tons of things to build, of all different types and sizes. Generally, the categories I'm thinking of at the moment are defensive structures (i.e. walls, sentry turrets, repurposed mines), resource generation structures (i.e. a gemstone mine, an oil pump) and spawner structures (structures that spawn different troop types, like the defuser tent, or barracks for spawning armed troops, or even an army depot for spawning larger units like tanks). Those will most likely be the bulk of it (unless I think of more, lol) other than obvious things like decorations.
I currently have the tile-grid generation up and running. The player can specify integers A and B and receive an AxB sized grid of tiles, which is sprinkled with mines. The player can then clear those tiles, using the aforementioned mines-in-proximity count to deduce which not to uncover (which they can then flag).
I'm not sure how technical I really want to get here (I want to avoid boring any potential non-technical reader to death) but the gist of how the general clearing system works is that when a mine is clicked it sends a signal to all of its neighbours (each of who it stores a pointer to) to check whether or not they should be cleared. If they're not mines and they have no mines surrounding them, then they are cleared, and they send the same signal to all of their neighbours. If they're not a mine but they do have a mine somewhere in their proximity, they clear themselves but stop the signal there (not sending it to their neighbours, thus stopping the recursion).
I've also implemented a system for placing structures, and have created a couple of stand-ins to test the structure (one of which is a very, very basic version of the defuser tent). Additionally, I added a basic version of the structure selection menu so that I can test different structures (though it is very much a work in progress).
On the more technical side of things, essentially the system just spawns an instance of the building and makes it a child of the cursor. Then, using an Area2D, I detect when it's colliding with tiles, and using that information I can dictate whether or not the structure can be placed (a shader is applied to either highlight the structure red, if no, and green if yes). When placed, the structure is re-parented to the board and is sent a signal so that it knows to start doing whatever it's supposed to do (i.e. for the defuser tent, it starts its 'spawn defuser' timer, which spawns a defuser troop upon timing out).
(As you can see in the gif, I am still very much operating with placeholder art lol. I figure it's a bit early to pull in Louisa, though I'm planning on asking for her help to think of an overall art style soon)
Last but not least, I implemented the defuser's base functionality. They can move anywhere within the cleared space, and will move towards flagged tiles, defusing then when within range.
From the more technical side, this implementation is definitely not done. While entirely functional, there is a lot of work to be done in terms of flagged-tile assignment. Currently, defusers aren't actually moving towards flagged tiles - they're moving towards wherever the mouse has right clicked most recently. Then, when they arrive there, if they have also entered an Area2D that is in the group "flagged", they send a signal to the area to defuse the associated tile. This presents a whole lot of technical issues, like what if the defuser is right between two flags and defuses the wrong one, among other things (like just refusing to defuse a tile if it was within range and flagged when it defused the previous tile). And that's not to mention the obvious big issue: if all defusers just move towards the mouse's most recent right click, then they'll all be defusing the same mine, making having more than one defuser useless (and leaving the player to have to micromanage one defuser the whole time).
The next step in this implementation will be to create a system for flagged-tile assignment, meaning that when a tile is flagged it's added to a queue, and when a defuser becomes free it moves towards the position of that tile (though it most likely won't be that simple; if there are free defusers at the time when a tile is flagged, we will want to ensure that the nearest defuser is dispatched, so I'll have to loop over available defusers and get their relative distances and choose the shortest). But that's a job for later (potentially tomorrow, but hopefully at the very least between now and the next Wombat Wednesday).
That's all for this update! Thanks for checking out the blog, and please make sure to come back next Wednesday for more!
In the future this end segment might have calls to action, like "Wishlist the game on steam here:____", but there's no steam page for the game at this point (still very early on, especially with the $100 upfront fee lol, 'tis a lot of money for a student).
Anyway, hope you're having (or had... it's pretty late at this point) a lovely Wombat Wednesday!