XaiJu
oakfells
oakfells

patreon


Cards of Gluttony Dev Log #18 [26/09/2024]

Phew! I can already feel a whole lot lighter now that v0.5 is out! In fact, I feel so light that I decided to use this time to significantly refactor some of the game's oldest code from when CoG was in its earliest stages!

That's right, this month's topic of discussion is the internal workings of the project. The last time I've gone this into detail about the game's inner systems was over a year ago in the very short-lived "How's it made?" posts, of which I did one for art and one for the technical aspect.

... and you'll notice the technical post actually barely goes into how the game is actually built, it's actually more of an introduction than anything!

And since refactoring old code is the primary work I've been doing, why not tackle it for this month's development log? This is easily one of the most technical development logs so far. There will be a whole lot of text, and very little art or creative discussion, so buckle up!

Refactor?

To start things off, let's explain the fancy words. For those who aren't familiar, refactoring in software development refers to the act of revisiting existing code and rewriting it so that it still works exactly the same, but is designed in a better way that allows you to work with it more easily.

This is important because it'll keep the doors open in the future for when you need to make changes or additions, and it typically also makes troubleshooting and bug fixing much easier.

A lot of what I've been doing the past two weeks has been completely revamping some of the awful code design, which I first employed because I didn't expect the game to grow like it did! I've been reaching into some of the oldest, first ever written code for the game!

Models

First up, let's talk about how the character models work in the game. In the How's it Made for art, I actually talked about how the folder structure of the various sprites are made, so allow me to reiterate. Each character model consists of 6 main parts.

Notice how the eyebrows, eyes and mouth are all separated? This means I can mix and match various eyebrow and mouth sprites instead of just having one single sprite for representing each facial expression.

Every time I draw the model for a new character, I give all of them three base expressions: neutral, concerned, horrified. These actually correspond to the faces they make during battle as the match progresses and gets closer to the end.

(Character expressions: neutral, concerned, horrified.)

Now the next part is important: the eyebrow and mouth both have a sprite for each of these expression sand the file is named in the exact same way. For example:

The code is written in a way that: if I want to set the expression to neutral, concerned or horrified, I don't have to set the eyebrows or mouth separately, I just tell it to use the "neutral" expression, and it will take care of both of the facial parts.

... and why am I bringing this up? Take a look at this:

(Player expressions: neutral, concerned, horrified.)

These are the expressions for the Player character. As you can see, it also uses the same three base expressions: neutral, concerned and horrified.

... now look at the way those files are named. It's a goddamn mess!

What? Concerned doesn't use a sprite called "concerned.png", but for some reason horrified uses "concerned2.png" Where did it the first one go?

Well the answer to that lies in the following:

There's... a fourth extra expression that the Player Model uses during battles, that occurs in between neutral and concerned. It's called "huh". What an apt name for the mess that this is. Two things:

But it doesn't end there, the player model actually needs to also have a separate layer for the inside of the mouth, that is for the mouth sprites where it's open. This is because the outline of the mouth needs to get tinted with the player's skin tone, but the teeth and fleshy insides most definitely do not.

... so how did I name these teeth layers?

distressed_teeth.png
scared_back.png

W-what... what in the world? Why do they both use different wording!? The hell was I doing? Truly all over the place...

Ok, so Player Model needed its own unique code unlike NPC Models to deal with this whole mess, but it was going to need that anyways, actually. There's some extra work required in the Player Model due to the character customization, which means the code also needs to pull the correct eyebrow, mouth and eye base sprite was selected at character creation.

... but the problem is: until now the Player Model wasn't even considered to be the same as NPC Models. Internally, the Player Model was seen as its own different thing, which meant it sometimes couldn't even be used in places where other NPC models were used. This meant that things like the Mode Viewer perk had to always do things like check if the model was an NPC Model or Player Model, and do different code depending on which it was.

And that... everybody, is the mess that is CoG code. In conclusion: lots of folder and file reorganization was done, on top of rewriting a ton of Model display code so that NPC Models and Player Model actually use the same code.

Facial Escalation

There was actually another issue with how the escalating battle expressions were tied to and updated by the model itself.

As I said earlier, the models escalate from looking neutral, to concerned, and finally: horrified, as the battle goes on. But instead of this being called by the battle... I just had it tied to the character models themselves, and they updated the expression all on their own whenever they updated the Wgt sprite! Here's the problem with that:

... so the solution is clear, right? I just needed to move that code over to something used in the battle code. And for that I already had the perfect structure: Battle Flavor Text.

Throughout battles, characters will let out various little quips and reactions as the match progresses. The way it works is that: whenever the Battle manager registers a Wgt change, it notifies something called the TextBase, which then displays the appropriate text, and sometimes even plays sound effects.  

Each character has their own TextBase that reacts at various different times with different text, so all the Battle manager needs to do is load in the corresponding TextBase of a character, and then let it do all the work.

... this means it also happens to be the perfect location to house the new escalating expression code! Not just that, it also meant I could customize each character individually, to use expressions other than just neutral, concerned and horrified in that order, and it also means I can trigger them at times other than 1/4 through the battle... halfway through the battle... etc.

You may have also noticed that characters smile at the beginning of a battle. That's actually the full cycle during a battle: smile -> neutral -> concerned -> horrified. However, them smiling does not make sense in a few of the scenarios: most notably the Carver battle during MQ3, where you basically ambush him while he's trying to run away.

And so when that originally came out, I basically tacked on some code as a temporary fix that overwrote that behaviour and made Carver look dour throughout his entire battle. With this change, I finally got back to it and finally finished it up all proper and stuff!

Ahh, just like getting your spring cleaning done.

BuildData, or: the programming method of putting everything together with duct tape

Now here comes the big one. The one that I knew needed a big refactor from the very beginning, but didn't get around to doing it because it would be so much work: the BuildData class.

This class holds the information about the Wgt of all your opponents. It's how the game tracks their Wgt in the background while you're out and about wandering the streets of Plursdott.

... but that's not the only thing it holds, no. It also has their current decks and battle data loaded in, at all times. This is because the game prototype was originally just a single battle between you and Cedric: the overworld and time mechanic wasn't even conceived even at all back at this point!

And so, it would seem that when I expanded CoG into the exploration game it is today, I decided to just keep and use the entire battle data of the opponent, which included the Wgt variable I needed.

There's a few things wrong with that:

The solution to this one was obvious: separate the build data containing the deck from the actual Wgt variable needed to be tracked in the background. That way, when it came time to start a battle, the BuildData would simply load the correct deck info and pass it over to the Battle manager.

This also meant that the build wouldn't be held in the BuildData itself, but some kind of new structure that had to be made. So all in all, that meant this part of the game needed three separate objects:

This was all simple and straight-forward work, but the problem was it meant a lot of existing code in other places had to be changed, and so it was going to take a lot of time.

With a bit of clever searching and replacing, I managed to update most of the code to use the new BuildManager with surprisingly minimal amount of work!

It's not that simple though

Unfortunately I ran into an issue while refactoring: the characters also have minimum and maximum Wgt ranges. Those also change depending on storyline progress with the character, and it is also something the overworld needs to track in the background.

Until now, they were both also held in the build data, which meant I had to find a way to track and update those ranges independently outside of battle. But... should a character update their build and Wgt ranges at different times, or use the exact same "storyline time to update values" code?

Aside from that, there was also one other big issue caused by the "duct tape" style of building the game: One-sided battles (I still haven't decided on a new name for it... appreciate all the ideas in the comments previous post though, thanks everybody!)

Because One-sided battles were a later addition to the game after all of the main battle mechanics were already implemented and finished, I didn't do a whole of redesigning of the existing build data structures to accommodate for them.

Instead... I just implemented one-sided builds by tacking on all the extra parameters to normal builds. This means that... internally, the code considers one-sided builds to be normal builds, just with the special conditions, turn count, and all that tacked on top of it. So... technically, the one-sided builds also have decks, hands, battle AI... they just doesn't use them.

This was once again remedied by just taking the time, being responsible, and redesigning the existing build data to accommodate two seperate and independent structures: RegularBuilds and OnesidedBuilds. Inside the code, OnesidedBuilds were also called "SoloBuilds", so I had to rename a few of them as well.

... how about one final caveat? This one concerns content that isn't in the game currently, so this is a case of refactoring code so that future additions will be possible. This one's about Ricky and how his build is done internally.

Currently in the game, Ricky features in one battle, a One-sided battle. Despite being a Stout, the fight is one-sided due to the circumstances of him letting you do it freely without fighting back. However, I also have plans to implement a proper, regular fight with him, which meant his BuildInfo needed to be able to handle both RegularBuilds and OnesidedBuilds at the same time. 

This actually ended up affecting every other BuildInfo as well, because it meant each of them had to differentiate between whether they were returning a RegularBuild, or a OnesidedBuild! This meant some changes even had to be made for the Free Battle Mode, as it relied on the old code to be able to see which builds a character had available.

Phew..! With all of that finally done, I could let out a sigh of relief at last . Originally I thought all this build refactoring work would just be very tedious and repetitive, but these few bits of design dilemmas at the end actually had me stumped for a bit as I mulled over how I could redesign it!

Refactoring: in conclusion

If anyone reading this is an aspiring game dev who's just getting into coding: don't let all of what I just talked about deter you. In fact: I encourage you to do as shoddy of a job as I did, because badly written code is better than no code at all.

Hell, despite all of the above, you'll notice that the game doesn't run that bad at all! There are a few moments in the game where it might stutter as it attempts to load a ton of stuff, such as when you open the deck editor with all your cards... but the the things I updated are completely unrelated to this.

In fact, all the changes I made likely won't affect the performance at all! Luckily for me, this is a fairly simple 2D game which takes far fewer resources than... say, an a 3D open-world game.

This won't be the end of all the refactoring though, as there are still a lot of structures in the game that need an update. However, I'll leave those for some later development cycle, and get back to creating content for now. Speaking of...

What's next on the story agenda?

The farm MQ4, while featuring a pretty much fully written scene with a conclusion, is not over yet! There's still one missing post-discussion scene where we need to talk about what happens afterwards! Especially with how much there is to talk to depending on how it all turned out, and away from prying eyes...

Here's a sneak-peek from the scene, and in the interest of not spoiling it any further, that's all I have to show you today!

And that's it for today!

There's no card of the month this time because I've mostly been tinkering with the code, and the upcoming side storyline updates, which will largely involve talking and more narrative-focused story beats and not battles.

Hope you didn't mind the far wordier and more technical development log today! With that said: once again, thank you for reading and for supporting the project!

Cards of Gluttony Dev Log #18 [26/09/2024]

Comments

Finally had the chance to really dig into some of the later stuff after following the development of this from the original demo and it's truly a joy to watch. Appreciate this update from the technical end! The diversity in the art for your style is also just quite impressive, you're managing to combine skills in an excellent way!

Mass


More Creators