Categories
alchementalist alchementalist devlog personal blog

Alchementalog #2: A Song of Stats and Boosts (Part 1)


All Alchementalogs


Despite the fact I’ve been doing pretty regular mini updates on Twitter, Reddit and Youtube, I haven’t really written a proper devlog entry for Alchementalist yet (I mean, I’ve kind of done one previously, but that’s it!) So let’s rectify that!

In this devlog, I want to talk about the work that’s gone into the stats and boosts system. It’s been quite a journey so far and the magic system I envisioned for Alchementalist covers a fairly extensive amount of unknown ground for me, so there’s been a lot of false starts and dead ends I’ve run into. But I think I’ve finally managed to come up with a system that works well and has the flexibility to contain the many different effects I want to be able to implement with the boosts (at least I haven’t run into anything that breaks it yet).

I’ll begin with an overview of the stats system. I wanted both the player and the spells to have separate stats, with the spell stats obviously changing depending on what spell you had selected. Spells can be crafted and dropped into your spell slots, so everything to do with spells had to be dynamically changeable on the fly (player stats also had to be dynamic, but we’ll cover that when we get to spell boosts).

Spell stats include things like the element associated with the spell, the strength, range, cooldown, crit chance, damage radius, etc. Generally the stats you’d imagine. Player stats included things like experience, level, element type level (so, a separate simple level for Fire, Water, Wind and Earth), hp, move speed, etc. These are all pretty basic concepts for an RPG-ish roguelite thingy. However, I haven’t actually worked with a system like this before. It’s my first dive into RPG stats.

To begin with, I had the simplest implementation imaginable. An array I accessed with enums storing the value of the stats:

player_stats[e_player_stats.HP] = 50;
player_stats[e_player_stats.HP_MAX] = 50;
// Etc

spells[spell_selected][e_spell_stats.STRENGTH] = 3;
spells[spell_selected][e_spell_stats.RANGE] = 5;
// Etc

This was fine while I was simply building up the prototype, it let me access what I needed when I needed it and so on. So after building that simple system, I moved on to other things like level generation (part of the joy of being an independent developer is being able to work on things whenever you feel like it and swap between tasks at random, keeping things interesting). During this time I also built the elemental effects system for the tiles, which I’ll probably cover in a future devlog.

Once I’d finished getting the barebones skeleton of a game up and running, I desperately needed to do some refactoring as things were really starting to crumble under the weight of all the half-assed systems I was piling on top of each other. So it was time to dive back into the stats system code and start getting things running so I could start testing out spell boosts and experimenting with how they would work within the game.

So what’s the problem with my original implementation of stats? Any experienced programmer should be able to spot it. How in the world do I keep track of the changing nature of stats. What happens if I want to implement a +5 to player HP on one boost and a 25% increase to player HP on another boost? How do those two increases interact? How do I keep track of what the base stat was? What happens if I need to remove one of those boosts? My simple little implementation was going to need a big overhaul in order to deal with these problems.

The first thing I always do when I need to implement a system I haven’t done before is to do some research! A lot of problems a programmer will run into have been solved before and there’s no point reinventing the wheel if you don’t need to (not to mention someone else’s wheel might be rounder than the one you are imagining). After a deep dive through google, I had a few different examples of how to handle RPG stats and so I set to work implementing my second version of stats.

To get down to some of the nitty gritty details, each stat itself is a struct. These structs have a base value, a max value (if applicable), a variable keeping track of if anything has changed, a variable holding the final_value (the base_value + any modifiers) and an array that will hold any modifiers (as well as a few other “bookkeeping” variables). There’s also a bunch of functions in there to deal with adding and subtracting modifiers, as well as calculating the final_value of the stat and things like that.

A portion of code from the Stat constructor function. This creates the struct for a single stat.

Whenever I want to modify the stat, I called the AddModifier() function, which creates another struct containing the details for the specific mod. So for example, if I want to add +5 to hp, the modifier struct created by the AddModifier() function will look something like this:

{
	value = 5;
	type = e_stat_mod_type.FLAT;
	source = spell_boost_id;
	duration = -1;
	order = e_stat_mod_type.FLAT;
}

The value and type combine to tell the computer how this mod interacts with the stat. The source is the id of whatever thing added the mod (this is useful to track, as it both allows you to display to the user the sources of all the mods if you want, and also allows you to do things like remove the mod if the source is no longer valid, etc). The duration tells the computer how long lived the mod is (-1 is “forever” in this context), so I can implement time-limited buffs/debuffs. And finally the order defaults to the type (as in this case), but can be changed. This just lets me order the mod entries in the array according to whatever scheme I want, meaning I can optimise how the array is read.

This modifier struct gets stored in the array of modifiers for the stat. Whenever this array gets changed, I set the variable keeping track of whether or not the stat has changed (is_dirty) to true. Then whenever I call the function to output the “real” value of the stat, it checks is_dirty. If it’s true (as in, the stat has been modified since I last called the “output final value” function), it redoes the calculation, which involves looping through the array of modifiers and applying the modifiers in the desired order, finally updating the final_value to the appropriate value and outputting it. If the stat has not been changed, it simply outputs the final_value straight up, cutting down on needless processing.

The actual implementation is slightly more complex than this, but that’s the general gist of it. And it works great. I can keep track of everything, add or remove modifiers as needed and data only gets processed and updated as needed.

I have a stats constructor that I can call to build the stats structs for whatever entity I wish and I use the same stats for both spells, players and enemies. This is simply because it makes it a little easier to generalise the system and the few stats that are unused in the different cases don’t really add much overhead to memory at all. It just makes it easier to deal with (there’s also a more pertinent reason when it comes to spell-boosts, but we’ll get to that).

The Stats constructor builds the struct for an entity containing all the stats I might want to access/tweak.

In the case of spells, there’s also some additional information I need to store, like the sprite, the icon and various functions for different spell “events” (things like OnLaunch, OnStep, OnHit, etc). So the spells themselves are a struct that holds those variables and functions, alongside a stat variable which holds the struct containing all the stats (it’s structs all the way down). I call the spell functions at the appropriate times in-game, so when the spell is cast, OnLaunch() gets called for it. When it’s traveling OnStep() gets called. This lets me do very specific things with each spell (such as creating a burst of damage around the player when the spell is cast, or stuff like that) while maintaining a generalised system where each spell functions in the same way from the “outside”.

Now let’s say I want to add a new spell to the player. Well, I have a global array containing all the different spell structs in the game. I first create a copy of whatever spell struct I’m interested in adding and then I store that copy in the spells array that each entity has. The reason for the copy is that in the case I want to edit the spells data, I don’t want to be editing the original “base” spell.

So that’s the stats done, I feel pretty happy with it and it’s basically as extensible as I want it to be. If I want to add new stats, I can simply change the stat constructor and it gets reflected throughout every entity. Cool, cool, cool.

But what about spell-boosts? These were (are) a lot more complicated for me to implement. Part of the problem is the general fuzziness with which I viewed them. Before I started seriously working on them, I just had a general outline of what I wanted them to be able to do. Things like change stats, maybe create entities of some sort, alter elemental effects on terrain, etc. Not having a clear idea of the functionality of something you are trying to make always makes a good implementation much harder to achieve (some might say impossible). So the first thing I would need to do is start drilling down into the actualities of what boosts can do.

But this devlog is getting a little long, so I’ll save that for part 2!

Hope you enjoyed reading this little look into the process of creating Alchementalist. Stay tuned for the exciting conclusion of how I implemented spell-boosts into the game!

By RefresherTowel

I'm a solo indie game developer, based in Innisfail, Australia.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s