1
votes

this is a general question using lua and roblox studio. I am at the point in my game in which I need to save some information in order to share it with players that join. Particularly, I am coding a platform game in which players jump on platforms and when they do, the platforms turn to the color of their team. I need the platforms colours to update for all players but also if a player joins mid game, the platforms that have already been 'conquered' need to be already of that colour. For this I am using getAsync and setAsync. For what I have seen, people mostly use datastores to save data in-between games, but in my case I want the data saved during the game to be retrieved during the game. Once the game is over, the data will be set to nil. I am assuming datastores are the way to go (...?). The problem that I have is that, as far as I can tell, datastores in roblox only allow to save one value per key and it saves it with the key being the player. But I need three values by key. These are: the row, the column and the colour of the platform conquered. I don't necessarily need to know the id of the player either. I want then every player to access this datastore when they join or when it updates. Ideally I would want something like this:

Set up:

local dataStoreService = game:GetService("DataStoreService")
local PlatformsScored = dataStoreService:GetDataStore('PlatformsScored')

inside a function when player scores:

PlatformsScored:SetAsync({col, row}, platformColor)

Then, retrieved and fired to all clients and/or when new player joins in:

local pScoredNow = PlatformsScored:GettAsync({col, row}, platformColor)

for k, platform in (myPlatforms) do
   if platform.col == col and platform.row = row do
   platform.BrickColor = BrickColor.new(platformColor)
end

The idea being that this for loop loops through all the platforms and sets the colours. I have been looking on the internet and I am unsure as if this is even possible. Can you save tables to datastore in Roblox studio? Can datastore be ''impersonal'' that is not associated necessarily with a player ID as the key? Thanks.

2

2 Answers

1
votes

You've asked a lot of questions, so I'll try to address them all

Can you save tables to datastore in Roblox studio? ... I need three values by key. These are: the row, the column and the colour of the platform conquered.

Tables cannot be saved, but if your data can be serialized, you can convert the table to a string using HttpService:JSONEncode(), and back to a table using HttpService:JSONDecode().

local HttpService = game:GetService("HttpService")
local dataString = HttpService:JSONEncode({
    row = 5,
    column = 10,
    color = "Red",
})
print(dataString)

local reconstructedData = HttpService:JSONDecode(dataString)
print(reconstructedData.row, reconstructedData.column, reconstructedData.color)

Can datastore be ''impersonal'' that is not associated necessarily with a player ID as the key

Sure, you can store information under any key you want. But you should be very careful choosing keys because every single game server will be writing to those keys. This is why playerID is always recommended, because it is a guaranteed unique key, and players cannot be in two servers at the same time, so there is no risk of two servers accidentally writing to the same key at the same time.

If you have multiple game servers writing to the same key, there is a very very high chance that two servers are going to overwrite each other's data and someone's stuff is going to get lost.

I want the data saved during the game to be retrieved during the game... I want then every player to access this datastore when they join or when it updates.

This isn't a good use-case for DataStores. DataStores should be used for persistent storage of data about players or the world that must carry across multiple servers. For example, think of a global leaderboard, or a player's game progress, or how deep a hole is that has been dug by multiple people (assuming you want that hole to persist the next time the game starts up).

If you are trying to access game state information and you are still in an active game, you can just have a new player construct their game board based on the current state. There's no need to communicate this through DataStores.

A good way to handle active game state information is to make a "GameManager" object in a server Script, and make it the authority over changes that happen in the game. A player scores? Tell the GameManager, it will update the scoreboard. A player joins? Ask the GameManager what the current state of the platforms/game board is.

You can accomplish all of this with simple lua tables, RemoteEvents, and RemoteFunctions. I like to use a ModuleScript to make my GameManager class. I rough outline of the architecture would be something like this ...

local PlayerService = game:GetService("Players")

-- keep a list of RemoteFunctions and RemoteEvents that are fired by players when they do something
local GetState = game.ReplicatedStorage.Functions.GetState
local PlayerAction = game.ReplicatedStorage.Events.PlayerAction

-- keep a list of RemoteEvents that are fired by the GameManager when something should be communicated to the players
local StartGame = game.ReplicatedStorage.Events.StartGame
local UpdateBoard = game.ReplicatedStorage.Events.UpdateBoard
local EndGame = game.ReplicatedStorage.Events.EndGame


-- make an authority over the game state
local GameManager = {}
GameManager.__index = GameManager

function GameManager.new()
    local gm = {
        -- keep a list of players
        teams = { "red" = {}, "blue" = {} },

        -- keep a list of scores per team
        scores = { "red" = 0, "blue" = 0 },

        -- keep track of the board colors
        platforms = {},

        -- keep track of the state of the game 
        currentGameState = "WaitingForPlayers", --InGame, PostGame

        -- as good housecleaning, hold onto connection tokens
        __tokens = {},
    }
    setmetatable(gm, GameManager)


    -- if anyone ever asks what the current state of the game is, let them know!
    GetState.OnServerInvoke = function()
        return gm.scores, gm.platforms, gm.currentGameState, gm.teams
    end

    return gm
end

function GameManager:waitForPlayers()
   -- wait for players to join to start the game
   while #PlayerService:GetPlayers() < 1 do
       wait(5)
   end

   wait(5)
   -- tell everyone the round is starting!
   self:startNewGame()
end

function GameManager:startNewGame()
    -- start listening to game events
    local playerActionToken = PlayerAction:OnServerEvent(function(player, ...)
        -- the player has done something, update the game state!
        local args = { ... }
        print("PlayerAction : ", player.Name, unpack(args))

        -- if a platform was taken, alert all players so they can update their stuff
        UpdateBoard:FireAllClients(self.platforms)
    end)
    table.insert(self.__tokens, playerActionToken)

    -- assign players to teams...

    -- tell all the players that the game has begun
    StartGame:FireAllClients()

    -- rather than code a game loop, just kill the game for now
    spawn(function()
        wait(30)
        -- tell everyone the game is over
        self:endGame()
    end)
end

function GameManager:endGame()
    self.currentGameState = "PostGame"

    -- communicate to all players that the game is over, and let them know the score
    EndGame:FireAllClients(self.scores)

    -- stop accepting actions from the game and clean up connections
    for _, token in ipairs(self.__tokens) do
        token:Disconnect()
    end

    -- start the game over again!
    spawn(function()
        wait(30)
        self:waitForPlayers()
    end)
end

return GameManager

Then in a server Script, just create the GameManager...

local GameManager = require(script.Parent.GameManager) -- or wherever you've put it
local gm = GameManager.new()
gm:waitForPlayers()

Then in a LocalScript, have the players request the game state when they join the game...

-- connect to all the game signals that the server might send
game.ReplicatedStorage.Events.StartGame:Connect(function(args)
    -- construct the local game board
    print("Game has started!")
end)
game.ReplicatedStorage.Events.UpdateBoard:Connect(function(newBoardState)
    print("Board state has changed!")
end)
game.ReplicatedStorage.Events.EndGame:Connect(function(scores)
    print("Game has ended!")
end)


-- request the current game state with a RemoteFunction
local GetState = game.ReplicatedStorage.Functions.GetState
local scores, platforms, currentGameState, teams= GetState:InvokeServer()

-- parse the current game state and make the board...
for k, v in pairs(scores) do
    print(k, v)
end
for k, v in pairs(platforms) do
    print(k, v)
end
print(currentGameState)
for k, v in pairs(teams) do
    print(k, v)
end
0
votes

Going through your questions, I saw you asked if you could save tables in datastores. Yes, you can. I'm not sure if you can do it through SetAsync(), but I know from personal experience you can do it through UpdateAsync(), I've done it countless times. If you don't know how to do that, here's how:

PlatformsScored:UpdateAsync("Platforms",function()
     local returningTable = {col,row}
     return returningTable
end)

This also answers your question of having keys that don't revolve around players. You can. Also just do the same thing to make the datastore nil, just replace everything inside the table with nil.