Exploring a cleaner solution for storing character statistics

In the game I am working on, users can play as different characters, each with their own statistics (health, movement speed, and so on). On top of that users can level-up those characters, where each level improves a certain statistic.

Currently we use two ModuleScripts for this. One ModuleScript contains all default values for all characters. The other ModuleScript contains for each character, for each level what should be upgraded, and by how much. Here is rough example of what these modules look like:

----------------------------------------------------
-- ModuleScript with statistics
local CharacterStatistics = {
  ["Character1"] = {
    ["Health"] = 100;
    ["WalkSpeed"] = 16;
    -- more values
  };
  -- more keys for other characters
}
----------------------------------------------------
-- ModuleScript with upgrade information
local Upgrades = {
  ["Character1"] = {
    ["1"] = { -- at which level the upgrade applies
      ["Upgrade"] = "Health"; -- thing to upgrade
      ["Value"] = 10; -- amount to increase it by
    };
    -- more levels
  };
  -- more characters
}
----------------------------------------------------

As you can imagine, this ModuleScript grows real big, real fast. On top of that, the vertical nature of code makes it difficult to compare the statistics of two characters next to each other.

For balancing purposes, I would like to figure out a better approach to storing and / or visualizing character statistics, with a focus on two factors:

  1. Making it easy to add and change values if it turns out that some character is very strong / weak.
  2. Having a clean way of comparing the statistics of two different characters, to see how they differ numerically, including their upgrades at different levels.

I am considering using a spreadsheet software which will allow me to better compare individual character statistics, however that would require me to change values both within the ModuleScripts and the spreadsheet software. I could also perhaps develop a plugin that can better visualize the contents of the ModuleScripts and allow me to make changes as well, but that would require a ton of development time for a single use-case.

I am wondering if other people have any valuable insights. Are there any approaches I am missing? Would love to hear your thoughts!

1 Like

I faced a similar issue with configs for cosmetic items in my game. Every so often I would change the actual data structure of the item configs, which quickly became a problem since we had well over 1,000 items. Bulk-updating specific items of a certain type also became a hassle, because I would have to manually go through all the keys in the table and update the values inside.

The solution I came to was pretty simple. Instead of storing configs in a table inside of a modulescript, I store item configs inside of folders and *Value objects under the folders. Those are parented to a modulescript, which converts the folders & *Value objects to key/value pairs when the server starts up. This allows for easy bulk-editing in studio from the commandbar, while still allowing the convenience of reading as a table.

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local function ConvertFolderToTable(Folder)
	local Table = {}
	
	for _,Object in pairs(Folder:GetChildren()) do
		local KeyName;
		local Value;	

		if Object:IsA("Folder") then
			KeyName = Object.Name
			Value = ConvertFolderToTable(Object)
		else
			KeyName = Object.Name
			Value = Object.Value
		end
		Table[KeyName] = Value
	end

	return Table
end

------------------------------------------------------------
-- Replacing model name values with actual refs to models --
------------------------------------------------------------
for _,ItemTypeFolder in pairs(script.ConfigValues:GetChildren()) do
	for _,Item in pairs(ItemTypeFolder:GetChildren()) do
		if Item:FindFirstChild("Model") ~= nil then
			local ModelName = Item.Model.Value
			local ModelRefObject = Instance.new('ObjectValue')
			ModelRefObject.Name = "Model"
			
			if ModelName ~= "" then
				ModelRefObject.Value = ReplicatedStorage.Assets.MarketItems[ItemTypeFolder.Name][ModelName]
			end
			Item.Model:Destroy()
			ModelRefObject.Parent = Item
		end
	end
end

local ItemConfigs = ConvertFolderToTable(script.ConfigValues)
script.ConfigValues:Destroy()

return ItemConfigs
1 Like