What type of data is necessary to keep in a Datastore specifically for a Shooter game?

Hi, my friend and I are currently working on our first-ever shooter party-type game. I was wondering what type of data would be necessary to include in a table for Datastores.

I’ve looked around, and I can’t seem to find specifically anything on what type of data would be valuable to store. I understand that you could obviously add more data in the future or on updates, but I would like to know the gist of info that would be ideal to store before the release of our game.

-- DEFAULT TEMPLATE ON JOINING
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local DefaultSettings = require(ReplicatedStorage.Shared.Library.DefaultSettings)


-- FUTURE NOTATION
-- LEVEL = LEVEL (NUMBER)
-- XP = XP (NUMBER)
-- MONEY = MONEY (NUMBER)
-- WINS = WINS (NUMBER)
-- WINSTREAK = WINSTREAK (NUMBER)
-- TOTALMATCHES = TOTALMATCHES (NUMBER)
-- WEAPONS = WEAPONS (ENUM)
-- INVENTORY = INVENTORY (ENUM)
-- RC = REDEEMEDCODES (STRING)
-- SET = SETTINGS (NUMBERS)


return {
	Level = 0,
	XP = 0,
	Money = 50,
	Wins = 0,
	WinStreak = 0,
	TotalMatches = 0,
	Weapons = {},
	Inventory = {},
	RedeemedCodes = {},
	Settings = DefaultSettings.Values
}

Any help is greatly appreciated. Thank you.

2 Likes

If you’re going to store items in the inventory, ensure it’s typically index / id based. You don’t want any data like descriptions or anything that’ll unnecessarily use up the datastore such as static data. For example, if your guns have a leveling system, you would save the itemName, level. Then you can use this data to tie into a separate modulescript that contains all of the other information such as descriptions, firerate, etc.

I’m also just gonna recommend since you’re new, profile store is my goto since they handle session locking for you. There’s always potential for weird bugs using roblox’s default dataservice framework with limited validation

1 Like
  • assist
  • kills
  • highest kill streak
  • highest win streak
  • rounds lost
  • deaths
  • playtime
1 Like

Hi.

In the comments I have above there, I already state that Enums are used for the inventory and stuff like that to save up on static data with a number instead of a string, and in fact use the system you already describe.

I am also already using the ProfileStore module from loleris already for data (ever since I lost my data when playing my game once lol).

I just need some recommendations of what could be included, like maybe like a hasSeenTutorial or hasSeenNewUpdateLog boolean or a playerFirstTimePlayDate or something like that.

But thank you for the advice on the index usage.

Ooooh, I like that, I’m not sure what it could be used for rn but it sounds useful for the future.
Rounds lost might be a bit redundant since you have totalMatches and the Wins already but idk if that cuts down on time calculating that everytime on the server or something.

Thank you.

For a shooter game I wouldn’t really worry about it, you’d be surprised how much data you can hold on a single key.

I’ve worked on a roleplay game and we had a lot of data we saved and barely scrapped any data limit off a single key. We saved purchased vehicles, vehicle customizations, purchase history etc.

Personally I would recommend to start doing tracking stuff early on so you already have the data in place when you decide to build on it later, I’m not saying things like region or age but how long a player has been playing the game, favorite weapon, and other things.

3 Likes

Icic, I was just worried about hitting that 100 mb limit when it comes to data :sob:.

Then I guess lines like rounds lost, etc., would be good to have. (It would probably be useful for win rate calculations when doing matchmaking)

An individual’s most commonly used weapon, favorite weapon, or how long the player has been playing could also be good info.

I was hoping to track that type of stuff on release via AnalyticsService instead of like datastores through events, though.

Thank you for the advice

You can do it through analytics too but I was saying per player cause then you can show that to the player of what stats they have.

With a fps game you have 1mb per player then 4mb per key but you get an extra 1mb per each unique visit.

Your style of game probably won’t even hit the 1mb mark unlike building games where even then at large scales they might not exceed the limits either.

1 Like

Hey,

This data looks fine, however, I would remove level. You should derive that on runtime using their current XP. Doing this makes future changes to the system easier to maintain, otherwise, you will need to backfill.

Thanks,

1 Like

If you store a list of timestamps when loses and wins happen, you could extract a ton of data out of that! That’d include:

  • Winrate
  • Total plays
  • Total wins
  • Total losses
  • Winstreak
  • Winrate over time
  • Winstreak over time
  • Wins per month, week, day, etc
  • Loses per month, week, day, etc
  • Winrate sampled from a time period
  • etc

Anything like that you could generate out of just those two lists of timestamps. You don’t need to use all of these, but I find it cool how you can get all that data out of just those two lists.

1 Like

Ohhhh, Icic now.

I’ll add those types of entries to my datastore now.

That does sound pretty cool to add for the player; it could definitely be added as a feature post-release with its own UI for the player to see.

I didn’t realize just how little each individual key took in size. I was worried about surpassing that if we had an influx of players or a lot of updates/content to store.

Thanks for the help

I didn’t realize that, I guess lifetime XP being stored does sound better.

I’ll remove both “Levels” and “XP,” and instead add “LifetimeXP.”

Thanks for the advice

1 Like

Hi, thanks for the feedback since I’ve never seen anything online about storing that specifically before, this sounds very interesting. I am pretty curious on how would this work, can you tell me more?

Like assuming an array is filled with Unix timestamps. You would be able to get the total wins and total losses from using an integer counter and a for loop through the arrays. That information could go through math operations and be manipulated to figure out winrate ((total wins / total plays) * 100%), total plays (total wins + losses), winstreak (the last Unix timestamp being either a win or loss to start/end), etc.

My only concern though is that if someone had like a couple hundred or thousand wins/losses, would the code still be efficient to iterate through the for loop over and over again every single time for Unix timestamps when that player joins the game compared to just simply calling an integer value from the datastore from like “Wins”, “Losses”, “TotalGames”, etc.

Also, once again, thanks for the advice

Hmm, well at that scale it wouldn’t really be efficient. I’d limit the tracking to 14-30 days only, anything beyond that should just be merged into one total wins/losses value! It could depend on how long it takes to get a win. If it’s only a minute or two, 30d might be too long. Each unix timestamp is just one float to store, but in datastores it’s converted to JSON which uses strings to represent numbers.

You seem to have a good understanding of ProfileStore so

return {
    -- Core Progression
    Level = 0,
    XP = 0,
    Money = 50,
    PremiumCurrency = 0,

    -- Statistics
    Wins = 0,
    Losses = 0,
    TotalMatches = 0,
    TotalKills = 0,
    TotalDeaths = 0,
    TotalAssists = 0,
    WinStreak = 0,
    BestWinStreak = 0,
    PlayTime = 0,           -- in seconds
    LastPlayed = 0,         -- os.time()

    -- Inventory & Unlockables
    Weapons = {},           -- ["weapon_id"] = true
    Skins = {},             -- ["skin_id"] = true
    Cosmetics = {},         -- ["cosmetic_id"] = true
    Loadouts = {},          -- { loadout1 = {...}, loadout2 = {...} }

    -- Other
    RedeemedCodes = {},
    Achievements = {},      -- ["ach_id"] = true or progress number
    JoinCount = 0,
    FirstJoin = 0,

    -- Settings
    Settings = DefaultSettings.Values,
}

This structure is safe for launch. It will stay well under the 4MB limit for a very long time.

Even More Optimized Storage

return {
    L = 0,      -- Level
    X = 0,      -- XP
    M = 50,     -- Money
    P = 0,      -- PremiumCurrency

    W = 0,      -- Wins
    Ls = 0,     -- Losses
    T = 0,      -- TotalMatches
    K = 0,      -- TotalKills
    D = 0,      -- TotalDeaths
    A = 0,      -- TotalAssists
    WS = 0,     -- WinStreak
    BS = 0,     -- BestWinStreak
    PT = 0,     -- PlayTime (seconds)
    LP = 0,     -- LastPlayed (os.time())

    We = {},    -- Weapons owned
    Sk = {},    -- Skins
    Co = {},    -- Cosmetics
    Lo = {},    -- Loadouts

    C = {},     -- RedeemedCodes
    Ac = {},    -- Achievements
    J = 0,      -- JoinCount
    F = 0,      -- FirstJoin

    S = DefaultSettings.Values,   -- Settings (keep readable)
}
1 Like

I see, personally, I feel like that would make the whole timestamp system a bit redundant than just using simple integer counters, maybe in the future I would implement something like that though.

This looks very detailed and has things that I haven’t thought of before, thank you.

1 Like

Glad it helped! That same info completely shifted how I approach building data systems and working with large datasets, It’s been a game changer.
A year from now I’m sure we’ll have even better tools and options available. Other creators in the space keep blowing my mind with what they’re doing.

1 Like

Kills and Deaths — K/D is the first thing players check in any shooter. Without it, your game feels bare.
BestKillStreak — separate from WinStreak. A player can go 20-0 in a losing round.
Losses — lets you compute W/L ratio client-side without extra logic. Cheap to store, painful to retrofit later.
PlayTime — total seconds in-game. Useful for loyalty rewards, badges, and analytics down the line.
Loadout / persistence:
EquippedWeapons — a small table (e.g. primary/secondary slots) tracking what the player has selected, not just owned. Otherwise on rejoin they lose their loadout.
Meta / account info:
JoinDate — store once on first join (os.time()). Lets you do “OG player” badges, loyalty events, etc. You cannot reconstruct this later.
LastSeen — useful for detecting returning players and triggering welcome-back bonuses.
Economy:
If you ever plan a premium currency (Robux-purchased), add a separate key for it now — e.g. Gems = 0. Mixing it with earned currency causes accounting headaches later.
Cosmetics:
If Inventory is already meant to cover skins/trails/etc., you’re fine. But if Weapons and Inventory are both gear, consider splitting out Cosmetics = {} explicitly so the schema stays readable.

1 Like