What do you want to achieve?
I want to save the player’s position values into IntValues on DataStore.
What is the issue?
player.Character.Position.X/Y/Z does not save into DataStore.
What solutions have you tried so far?
Saving the position data into variables and saving the variables to DataStore.
I am trying to save the player’s position information into DataStore so that I can use it to have the player start off in the world where he left off. However, my script is not saving the player.Character.Position.X to DataStore.
I’ve tried saving hardcoded numbers, and it works fine. I’ve printed out player.Character.Position.X’s value and type and it’s a number. For some reason, DataStore does not want to save this into the database.
I would appreciate it if someone can tell me what I’m doing wrong.
local DS = game:GetService("DataStoreService")
local SaveCFrame = DS:GetDataStore("SaveCFrame")
game.Players.PlayerAdded:Connect(function(plr)
local Location_Storage = Instance.new("Folder", plr)
Location_Storage.Name = "Location_Storage"
local CFrameX = Instance.new("IntValue", Location_Storage)
CFrameX.Name = "CFrameX"
local CFrameY = Instance.new("IntValue", Location_Storage)
CFrameY.Name = "CFrameY"
local CFrameZ = Instance.new("IntValue", Location_Storage)
CFrameZ.Name = "CFrameZ"
local Location_StorageData
local success, errormessage = pcall(function()
Location_StorageData = SaveCFrame:GetAsync(plr.UserId.."-Location_StorageData")
end)
if success then
if Location_StorageData then
CFrameX.Value = Location_StorageData[1] --CFrameX
CFrameY.Value = Location_StorageData[2] --CFrameY
CFrameZ.Value = Location_StorageData[3] --CFrameZ
else
CFrameX.Value = 0
CFrameY.Value = 0
CFrameZ.Value = 0
end
end
end)
game.Players.PlayerRemoving:Connect(function(plr)
local xpos = plr.Character.LowerTorso.Position.X
local ypos = plr.Character.LowerTorso.Position.Y
local zpos = plr.Character.LowerTorso.Position.Z
local success, errormessage = pcall(function()
SaveCFrame:SetAsync(plr.UserId.."-Location_StorageData", {
123,
123,
-123
--[[
plr.Character.LowerTorso.Position.X,
plr.Character.LowerTorso.Position.Y,
plr.Character.LowerTorso.Position.Z
]]
})
end)
if success then
print("Successfully saved data!")
else
warn("ERROR: "..errormessage)
end
end)
Please do not ask people to write entire scripts or design entire systems for you. If you can’t answer the three questions above, you should probably pick a different category.
The reason this is failing is because Roblox removes the character before the PlayerRemoving event is fired. You are trying to access plr.Character.LowerTorso while plr.Character is a nil reference.
It seems like you’re making this a lot more complicated than it needs to be as well.
I’ll write up a better example for you shortly.
Nope, this is incorrect. Roblox’s built-in types do not serialize to JSON so they are not supported by DataStores.
Here’s a full, (mostly) best-practices implementation of what you’re trying to do here.
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local CollectionService = game:GetService("CollectionService")
local saveCFrame = DataStoreService:GetDataStore("SaveCFrame")
local saveSemaphore = 0
local function onGameClosing()
-- Don't shut down until we've finished every save request.
while saveSemaphore > 0 do
wait(1)
end
end
local function onCharacterAdded(character)
local player = Players:GetPlayerFromCharacter(character)
if not player then
return
end
local rootPart = character.PrimaryPart
local lastLoc = player:FindFirstChild("LastLocation")
local humanoid = character:FindFirstChildOfClass("Humanoid")
if not (humanoid and rootPart and lastLoc) then
return
end
-- Wait for a brief moment so the CFrame change we apply isn't
-- overwritten by the player's own automatic spawn CFrame.
-- We only do this once on the player's first spawn.
if not CollectionService:HasTag(player, "HasRestored") then
local cf = lastLoc.Value
delay(.1, function ()
if game:IsAncestorOf(character) then
character:SetPrimaryPartCFrame(cf)
CollectionService:AddTag(player, "HasRestored")
end
end)
end
spawn(function ()
while game:IsAncestorOf(character) do
if humanoid.FloorMaterial ~= Enum.Material.Air then
lastLoc.Value = rootPart.CFrame
end
wait(1)
end
end)
end
local function onPlayerAdded(player)
local lastLoc = Instance.new("CFrameValue")
lastLoc.Name = "LastLocation"
lastLoc.Parent = player
local success, errorMsg = pcall(function ()
local components = saveCFrame:GetAsync(player.UserId .. "-Location_StorageData")
if components then
local cf = CFrame.new(unpack(components))
lastLoc.Value = cf
end
end)
if not success then
warn("ERROR LOADING LOCATION FOR " .. player.Name .. ":", errorMsg)
end
if player.Character then
onCharacterAdded(player.Character)
end
saveSemaphore += 1
player.CharacterAdded:Connect(onCharacterAdded)
end
local function onPlayerRemoving(player)
local lastLoc = player:FindFirstChild("LastLocation")
if lastLoc and lastLoc:IsA("CFrameValue") then
-- Wrap the CFrame's components into an array.
local saveCF = { lastLoc.Value:GetComponents() }
local success, errorMsg = pcall(function ()
saveCFrame:UpdateAsync(player.UserId .. "-Location_StorageData", function (oldCF)
-- Just overwrite what is there.
return saveCF
end)
end)
if not success then
warn("ERROR SAVING LOCATION FOR" .. player.Name .. ":", errorMsg)
end
end
saveSemaphore -= 1
end
game:BindToClose(onGameClosing)
Players.PlayerAdded:Connect(onPlayerAdded)
Players.PlayerRemoving:Connect(onPlayerRemoving)
There’s a race condition between when PlayerRemoving is fired and when the game is removing the player’s character.
Player.Character is a referent property. If the property isn’t set, reading it will return nil. So it’s kind of like a pointer.
By the time your function callback to Players.PlayerRemoving was invoked, the game may have already cleaned up the player’s character from the game and null’d out its reference in the Character property.
Thus, in trying to evaluate: plr.Character.LowerTorso.Position.X
The action is interpreted as: nil.LowerTorso.Position.X
Which is invalid because you cannot index a nil value.
This race condition can be reproduced 100% of the time if you deliberately insert a delay between when PlayerRemoving fired and when you were trying to read the LowerTorso’s position.
game:BindToClose(function ()
-- Hold the DataModel open for an extra second.
wait(1)
end)
game.Players.PlayerRemoving:Connect(function (player)
wait()
print(player.Character.LowerTorso.Position.X)
end)
In general, its a bad practice to directly index objects if you aren’t 100% confident they are still there and haven’t been deleted or changed by something else.
Heck, the Character property might not be nil, but there’s a non-zero chance the LowerTorso may have been deleted if the player fell out of the world and left the game.
Trying to index an object’s child will throw an error if that child doesn’t exist.
That’s why we have functions like FindFirstChild and WaitForChild!
Exactly. However, on the flip side, you can manually take apart the coordinate frame components and arrange them into a table, and convert that table to JSON. Some developers will take that extra step to save a CFrame.
However, the only applicable case for this, or at least that I can think of, is something that would require an absolute exact CFrame to work, such as a custom animator. If you want to save a position, save by a checkpoint name or by a decompiled vector.