Just wanted to commend your dedication / effort - it goes a long way in the field of coding and troubleshooting.
Anyway, long post but lots of explaining, hopefully it gets you squared away
Part 1 - Intro / Goals to accomplish
So, looks like the goal here is to have a system that updates our Level value as we progress through the game, acting as a checkpoint system. General outline of what we want to do would be something like:
- Player joins →
- Setup checkpoint system →
- (Re)spawn their character at the correct place
We also want to make it so when they reach a new checkpoint in game, their checkpoint value updates.
To do this we need to keep track of:
-
When a player joins
We can do this using the PlayerAdded event.
-
When a player spawns / respawns
We can do this using the .CharacterAdded event.
-
What a players checkpoint value is
This can be accomplished by storing a value associated with each player, and that value should correlate to a spawn location.
- We also need to come up with a system of how to update a players checkpoint value. This system is going to be dependent on how we store and read the values in the first place.
Having looked over your code, it looks like you’ve already got most of this done (nice!), it’s just a matter of making sure we have things implemented correctly and then we can resolve any errors we encounter.
Part 2 - Setup
From what I can tell, your current system works like this:
-
Player joins → setup leaderstats.
-
When a checkpoint is touched:
2.1 Check for the players level stat. If it’s there, increase it by 1, and destroy the checkpoint (which also happens to be the scripts parent, thereby destroying our script as well)
2.2 Check for the players checkpoint data. If it’s not there, then create it. Once it is there, then we:
1) Set the checkpoint value to be the spawn object,
2) Create a new .CharacterAdded connection that spawns our character at the the new checkpoint we just reached
This is a good start, but there’s a few things we should do to improve upon it so it works as intended:
-
There’s two different scripts that are both running a Touched event on checkpoints with the goal of updating player data. We can combine these into one script with just one Touched event so there’s less stuff for the game to load / handle (i.e. game will run faster), and it keeps all our relevant code together in one spot, making it easier to work with in the future.
1.1 We also never check to see if the Checkpoint we are touching is for a new area that we haven’t been to. Because of this, we could theoretically go back to the last checkpoint, touch it, and still cause our Level stat to increse by 1 even though we actually went backwards in the levels rather than forwards. I do see that you destroy the checkpoint when it’s touched, however this does come with the caveat that it will be destroyed for all players, meaning a checkpoint can only be used once. It should also be noted that calling Destroy() does more than just deleting the object, and will likely cause your script to stop running given it’s a child of the object you calling Destroy on.
There’s a few ways we can solve this dilemma, but some options may be better than others. For now, lets assign a specific level to each checkpoint by setting it’s Name to be the same as its level. It’s not a perfect system, but it will work for what we need right now. We also want to implement a debounce system to help cut down on lag.
-
We’re handling when a Player joins and when their character spawns/respawns in two separate scripts. Initially, this might seem like the logical way to do it since these two events seem independent of one another, but it’s actually not. I’ve put a full explanation as to why at the end of this post under ‘handling connections’ but for now lets focus on the general idea: We can detect when a player spawns through the .CharacterAdded event, however, this is a member of the “Player” object, so in order to use it, we have to reference the Character’s player somehow. We also need to do this for all Players in the game from the moment they join the game. How do we do that? By using the PlayerAdded event. You’ve already started that part, we just need to merge the two scripts.
2.1 It should also be noted that in your checkpoint script, you make a new CharacterAdded connection every time a player changes checkpoints, without removing the old connections. We only need one CharacterAdded event per player, and we can tailor the instructions we give when CharacterAdded runs.
-
You have the option of either manually adjusting a players spawn point every time they spawn via the CharacterAdded event – OR – you can utilize the Player.RespawnLocation property to specify which SpawnLocation they should spawn at. The linked page has some good information on the topic and should be able to help you make your decision.
There are some other tweaks we could make, such as handling all the Touched events for all the checkpoints in one script, rather than making the game load the same script for every checkpoint, but lets keep it simple for now.
Part 3: Implementing
So, what we need now is:
- A script that handles when we Join, sets up our playerdata and handles when we spawn
- A script that handles when we make it to a new checkpoint
There’s an optional second argument when using Instance.new that lets you specify the objects parent, and saves you the hassle of writing out the full thing every time. I also used an anonymous function when writing the CharacterAdded event. This is basically the same thing, except instead of writing out my function beforehand and calling it, I write the function inside the parenthesis.
local Players = game:GetService("Players")
local Server_Storage = game:GetService("ServerStorage")
local function handle_New_Players(player)
-- Setup the player's leaderstats and checkpoint data
local leaderstats = Instance.new("Folder",player)
leaderstats.Name = "leaderstats"
local level = Instance.new("NumberValue",leaderstats)
level.Name = "Level"
level.Value = 0
checkpoint_Value = Instance.new("ObjectValue",Server_Storage:WaitForChild("CheckpointData"))
checkpoint_Value.Name = tostring(player.UserId)
checkpoint_Value.Value = workspace.Checkpoints:WaitForChild("Start") -- Assuming you have a folder/model of the checkpoints and name the first one "Start", you can change names as needed
-- Handle when a player spawns
player.CharacterAdded:Connect(function(character) -- Anonymous function
character:PivotTo(checkpoint_Value.Value.CFrame * CFrame.new(0,5,0))
end)
-- Note: This wont work the first time we spawn after joining since our character tends to load before the game sets up this connection.
-- We can respawn our character immediately afterwards to solve this.
player:LoadCharacter()
end
-- Connect the handle_New_Player function to the event
Players.PlayerAdded:Connect(handle_New_Players)
Great, now we have a script that sets up our leaderstats and checkpoint data when we join, and will place us at the right spot when we spawn.
local Players = game:GetService("Players")
local Server_Storage = game:GetService("ServerStorage")
local checkpoint = script.Parent
local function onTouched(hit)
local touched_Player = Players:GetPlayerFromCharacter(hit.Parent)
if touched_Player and (not hit.Parent:FindFirstChild("Checkpoint_Debounce") then
touched_Player.leaderstats.Value = tonumber(checkpoint.Name)
Server_Storage.CheckpointData:FindFirstChild(tostring(touched_Player.UserId)).Value = checkpoint
end
end
checkpoint.Touched:Connect(onTouched)
And here’s our script that will update our leaderstats and checkpoint when touched, based on the name of the checkpoint.
Welp, that’s about it. As mentioned earlier, there are certainly numerous improvements we can make to the system and various ways to accomplish the same or similar goals, but as someone famously once wrote: that is left to the reader as an exercise
Bonus: Handling Connections
When we connect an event to a function, we’re actually making an object.
The server (aka ‘The Game’) has to track and handle these objects just like it does for all the scripts, folders, models, etc. That is to say, when we make connections, we increase the amount of memory we are using. If we run out of memory / try to use more than we have, our game crashes. By association, the less memory we use, the less the game has to worry about keeping track of, and the better (faster) our game will tend to run.
So, we want to make sure that all the connections we make are needed, and that we get rid of them once they are no longer needed. You might be thinking “How do I get rid of a connection??” Well, remember how Connections are actually just objects? those objects come with Methods - which are “built in functions” for an object. In this case we can use the Disconnect method that comes with our connection object to get rid of it! I found this to be a bit confusing when first learning, so here’s an example:
local connection_Object = game.Players.PlayerAdded:Connect(example_Function)
connection_Object:Disconnect()
When we call Connect, we can store the resultant connection object as a variable and then use that to call Disconnect if we need to. Additionally, as noted above, calling Destroy() on an object will disconnect all of an objects connections.
So, In the code you posted, we would be making a new connection to CharacterAdded every time a player touches a checkpoint, which adds up quick and will result in lag. By establishing only one of these connections when the player joins, we save memory and only give the game one set of instructions upon spawning. If we really wanted to optimize the checkpoint system, we could attempt to implement it such that we only call the connected function one time when we touch a checkpoint and subsequently disconnect the Touched event until we need to connect it again. You could also do this on the client that way each players ability to use checkpoints is independent of one another.