v3launchunit

i like snakes and a free palestine

aside from the aforementioned affection towards snakes, i also hold a great deal of fondness in my heart for hollow knight (i am extremely normal™ about collector), rain world (miros birds are the best creature i will not be accepting criticism on this), command and conquer red alert 2 (kirov reporting), in stars and time (one must imagine sisyphus stuck in a time loop), and about a million other things.
i played through slay the princess and spent the whole game pretty much completely ignoring her in favor of dicking around with the narrator (there is no good ending because the narrator always dies) and the voices (contrarian is the best one), which probably says a lot about me (i am aromantic asexual (this will not stop me from rebugging horny™ shit that i am tangentially interested in)).
fuck it i'm a girl now (still he/they tho)
i also like to draw and make games & shit.


my goblin.band
goblin.band/@v

eniko
@eniko

hello, little gamedev pro tip from me to you: always put a version number in any file your game saves externally. this includes save files but it also comes in real handy in config files since if you change the way your game's configuration work you can use it to invalidate or upgrade a user's previous config!


mrhands
@mrhands

If there's one neat trick I've picked up from the Amazon© Lumberyard™ engine, a horrible product that has thankfully been discontinued, it's the idea of an upgrade() method for your serializable data. As the extremely experienced Ms. Fox rightfully points out, you should always put a version number in your data. With this version number in hand, you can (theoretically) upgrade all user data to the latest version without ever losing data!

For example, my game had a TaskDataModel struct that needed to be loaded from the save file. In version 1, the task had an "alignment" stored as an integer value. But for my own sanity, I wanted to change this to a string value in the save file. So, I wrote an upgrade function for the struct:

template <>
inline bool upgrade(TaskDataModel& to, QJsonObject& data, int32_t dataVersion, Mode mode)
{
	// ChangedAlignmentIdentifier

	if (dataVersion < TaskDataModel::Version::ChangedAlignmentIdentifier)
	{
		if (data.find("alignment") != data.end())
		{
			int32_t alignment = data["alignment"].toInt();
			if (alignment >= 0 &&
				alignment <= 2)
			{
				const char* alignment_ids[] = { "nerd", "cool", "jock" };
				data["alignment"] = alignment_ids[alignment];
			}
			else
			{
				data["alignment"] = "";
			}
		}
		else
		{
			data["alignment"] = "";
		}
	}

	return true;
}

This function reads the old data, extracts the alignment as an integer, and then writes the new alignment as a string value. The new serialization code can then always assume that the alignment is read as a string.

If the incoming struct already has the latest version, we don't need to call this method at all, and this is handled at a higher level of my serialization system. But crucially, we can upgrade from version 1 to version 24 by running each step in sequence! The real version of this function has these steps:

template <>
inline bool upgrade(TaskDataModel& to, QJsonObject& data, int32_t dataVersion, Mode mode)
{
	// ChangedAlignmentIdentifier

	// AddedPosition

	// ChangedSuccessToDifficulty

	// RebalancedDifficulty

	// ChangedLocationIdentifierToEnum

	// RenamedPhaseToShift

	// RenamedPhaseToShift

	// MovedIdentifierToSheet

	// ChangedDiceSlotsToDataModelList

	return true;
}

If I ever publish my game (ha!), this system means that as long as I keep incrementing the version number for the structs, I can continuously make patches while keeping my players' save files intact. 😊


You must log in to comment.

in reply to @eniko's post:

in reply to @mrhands's post:

Absolutely did this for 1000xRESIST, a game that we expected to do basically zero post-launch support for because you never know. Picked up the habit while working on mobile live service games.