User Save Data

This isn’t much of a package, but it’s something I end up writing every time I get going on a game, so I figured I’d bundle it up. It’s just a wrapper around the save data api to make it isomorphic.

User Save Data provides an isomorphic way to persist data on a user. A lot of the time, I want an item that a user is interacting with to save something to the user - like the time last interacted, or something like that. Something that really only matters to the world entity.

This is my solution.

Installation

Search for User Save Data in community packages.
Drag the User Save Data template onto the user User.

Usage

This package makes 2 methods available to you.

SaveData(key, value)

SaveData accepts a string key as the first argument, followed by a string or number as the second argument.

This method works on both the server and the client.

GetData(key)

GetData accepts a single key, and will return the data for this key.

This method works on both the server and the client.

Example

This is an example pulled out of one of my script. It’s an interactable object that a user can interact with after some amount of time. I’ve smooshed it together for readability reasons.

Consider this method:

function TriggerPaymentScript:TimeLeft(user)
    -- Fetch the time since last update off the user's save data
    local lastTime = user.saveDataScript:GetData("trigger-last-used")

    local cooldownSeconds = self.properties.cooldownTime * 60
    local timePassed = GetWorld():GetUTCTime() - lastTime

	return cooldownSeconds - timePassed
end

This method interacts with the user’s save data. The cool thing about the User Save Data package is we can actually use this on the client and the server.

So this code runs on the server when a user interacts with an entity in the world.

-- User interacts with something, it runs this
function TriggerPaymentScript:PerformActivity(player)
	local user = player:GetUser()

	local diff = self:TimeLeft(user)

	if diff < 0 then
		-- Do Stuff
	else
		-- Don't do stuff 
	end
end

Now, the important part - this entity also has a world widget.

function TriggerPaymentScript:UpdateTimer(user)
  -- This is important, we're running this function on the client
  if IsServer() then
    self:SendToAllClients("UpdateTimer", user)
    return
  end

  if user ~- GetWorld():GetLocalUser() then return end 
  
  -- We're using the _same_ function we used in PerformActivity to get the time left,
  -- even though save data is only accessible on the server, User Save Data lets us access it
  self.widget.js.data.timeLeft = self:TimeLeft(user)
end

So we get to share the same code to calculate the time left on both the server and the client, and get to keep all of the related logic all nicely encapsulated on the concerned world entity, rather than mixing and matching between the world and the user

1 Like