# Scoring Points In previous courses, you’ve used events to create playable scenes. Most recently, in [Deadly Arena], you’ve made a board of tumbling platforms above a deadly lava floor. Now it’s time to make something a little more like a real game. You’ll create a set of scripted platforms, but this time players should earn points for surviving for longer, and should lose all their points when they die. The platforms should also reset, so that players can play the game more than once. You’ll need to use some new events and features to achieve this. ## Setting Up Before you start coding, you’ll need to set the scene for your game. 1. **Insert** a **Part** for the platform you’ll be scripting - you’ll duplicate this later once you’ve written the code. Make sure it has an appropriate name, like ‘DisappearingPlatform’. 2. Put a **spawn point** just above it so that players can jump down to the platform to start the game, and make sure it’s big enough for more than one player to stand on. [image of part and spawn point in place] Players need to die when they fall off a platform. You can use the deadly lava floor from [Deadly Arena] in your game to achieve this - or you can just remove the floor entirely and let gravity do the job for you. How do you award points to players? Even if you made a ‘points’ property for each player, you’d still need a way to display them. Fortunately, Roblox has an easy system for showing points for players called the **Leaderboard**. [image of leaderboards with points stat visible] When you set a player’s points through the Leaderboard, it’ll show up on the right side of the screen in the game. Later, you’ll learn better ways to display all kinds of information through user interfaces, but for now the Leaderboard is the simplest way of making a scoring system in Roblox you can actually see in-game. - Create a **Script** in the **Workspace** called **SetupLeaderboard**. You’ll use this to set up the Leaderboard for each player that joins your game. ## Listening for Players You need an event that will fire when a player joins the game, so that you can set up the points each time and ensure every player has their points displayed. Fortunately, the <a class="api-link">Players</a> service has an event called <a class="api-link" data-name="Players.PlayerAdded">PlayerAdded</a> which does exactly that. A **service** is an object which exists at the top level of your Roblox game, like Workspace and Lighting. They perform a range of useful functions. The Players service contains a list of all the players in the game, along with other related information. Each player in your game has a corresponding <a class="api-link">Player</a> object in the Players service. [image of players in the Players service in the Explorer] You can access services with the <a class="api-link" data-name="ServiceProvider.GetService">GetService</a> function in the `game` object. GetService takes a name for a service and returns the service with that name. If it doesn’t exist yet, it’ll create it and then return it. `game` is a variable accessible from anywhere. It contains all the things you see in the Explorer window. 1. Create a **variable** for the Players service using the **GetService** function. 2. Create a **function** called `onPlayerAdded` with a **parameter** for the player, so the PlayerAdded event can pass the player to the function when it fires. 3. **Connect** the `onPlayerAdded` function to the **PlayerAdded** event. <pre class="code-line-highlights" data-start="1" data-highlight="1-7"> local Players = game:GetService("Players") local function onPlayerAdded(player) end Players.PlayerAdded:Connect(onPlayerAdded) </pre> Just like the Touched event in [Deadly Arena], the PlayerAdded event calls any function connected to it with an argument. In this case, the argument is the player that was added to the game. You’ll need to use the player that was added in your code, so you need to have the parameter to make sure you can access it. ## Leaderstats Time to set up the Leaderboard. To create a points entry in the Leaderboard for a given player, all you need to do is create a new folder in their Player object called ‘**leaderstats**’, and put the points value in there. Roblox automatically reads and displays any values you put in that folder. You can create a new folder using `Instance.new`, which is a special function that can create objects if you give it the name of the type of the object you want. In this case, you want a folder, so you’d write `Instance.new(“Folder”)`. 1. Create a new **folder** variable called `leaderstats` using `Instance.new`. 2. Set the **Name** property of the folder to “**leaderstats**”. 3. Put the folder in the Player object by setting the **Parent** property to `player`. <pre class="code-line-highlights" data-start="1" data-highlight="4-6"> local Players = game:GetService("Players") local function onPlayerAdded(player) local leaderstats = Instance.new("Folder") leaderstats.Name = "leaderstats" leaderstats.Parent = player end Players.PlayerAdded:Connect(onPlayerAdded) </pre> ## Points Value The Leaderboard system reads any <a class="api-link">IntValue</a> objects in the leaderstats folder and will display whatever it finds - so you’ll need to store the points as an IntValue. An IntValue is a type of object that stores a whole number (an integer - **int** for short) and a name to associate with it. Numbers which need to be visible and accessible outside of a given script are typically stored this way. 1. Create a new **IntValue** variable called ‘`points`’ using `Instance.new`. 2. Set the `Name` property of `points` to “**Points**”. 3. Set the `Value` property of `points` to **0**. 4. Put the `points` object in the leaderstats folder by setting the `Parent` property to `leaderstats`. <pre class="code-line-highlights" data-start="1" data-highlight="8-11"> local Players = game:GetService("Players") local function onPlayerAdded(player) local leaderstats = Instance.new("Folder") leaderstats.Name = "leaderstats" leaderstats.Parent = player local points = Instance.new("IntValue") points.Name = "Points" points.Value = 0 points.Parent = leaderstats end Players.PlayerAdded:Connect(onPlayerAdded) </pre> Test your game now, and you should see the **Leaderboard** appear in the top right with the names of your players and a points score next to them. If you look at the Explorer, you can see that the leaderstats folder is inside the Player object for your player, with the Points object being inside that. [image of the Explorer showing this] ## Scripting the Platform Next up: making the platforms disappear and award points when you step on them. You should already have inserted a part to act as your platform. 1. Insert a **Script** into the platform part. 2. Create a **variable** for the platform with `script.Parent`. 3. Create a **function** called `onTouched` with a parameter called `otherPart`. 4. **Connect** the `onTouched` function to the platform’s **Touched** event. 5. Use a Boolean variable to **debounce** the onTouched function. <pre class="code-line-highlights" data-start="1" data-highlight="1-13"> local platform = script.Parent local isTouched = false local function onTouched(otherPart) if not isTouched then isTouched = true isTouched = false end end platform.Touched:Connect(onTouched) </pre> The code for the platform behavior will go between the `isTouched = true` assignment and the `isTouched = false` assignment. ## Making the Platform Disappear The <a class="api-link" data-name="BasePart.Transparency">Transparency</a> and <a class="api-link" data-name="BasePart.CanCollide">CanCollide</a> properties can be used to make the platform disappear. You can use the code from the [Simple Trap] course to achieve this. 1. Create a **for** loop and use the iterator to set the **Transparency** property of the platform. 2. Set the **CanCollide** property of the platform to **false**, then **wait** for 20 seconds. 3. Set the **Transparency** and **CanCollide** properties back to their default values. <pre class="code-line-highlights" data-start="1" data-highlight="8-15"> local platform = script.Parent local isTouched = false local function onTouched(otherPart) if not isTouched then isTouched = true for i = 0, 1 do platform.Transparency = i/10 wait(0.1) end platform.CanCollide = false wait(20) platform.Transparency = 0 platform.CanCollide = true isTouched = false end end platform.Touched:Connect(onTouched) </pre> ## Getting the Player In order to give a point to the player when they step on the platform, you need to somehow get their **Player** object out of the `otherPart` parameter in the onTouched function, as the Player object is where we put the leaderstats folder. The [Deadly Arena] course covered how every player has a **Character** model, and a part of this model - e.g. the right leg - is what will actually touch the platform and be passed as an argument to the `onTouched` function. If a player touches a platform, the parent of the part that touched the platform will therefore be a Character. So how do you get the Player object from the Character? The <a class="api-link">Players</a> service has a <a class="api-link" data-name="Players.GetPlayerFromCharacter">GetPlayerFromCharacter</a> function that takes a Character as an argument and returns a corresponding Player object. It returns **nil** if the player doesn’t exist, so it’s important to check the result in case you later cause an **error**. 1. Use the **GetPlayerFromCharacter** function to get the Player object from the parent of the part that touched the platform. 2. Check that the result exists using an **if** statement before proceeding. <pre class="code-line-highlights" data-start="1" data-highlight="3;8-11;20-21"> local platform = script.Parent local isTouched = false local Players = game:GetService("Players") local function onTouched(otherPart) if not isTouched then isTouched = true local character = part.Parent if character then local player = Players:GetPlayerFromCharacter(character) if player then for i = 0, 1 do platform.Transparency = i/10 wait(0.1) end platform.CanCollide = false wait(20) platform.Transparency = 0 platform.CanCollide = true end end isTouched = false end end platform.Touched:Connect(disappear) </pre> ## Organizing the Script At this point, the code in the onTouched function is getting unwieldy and difficult to read. Before carrying on, it would be best to split it up and organize it better by creating functions to include the code for each part of the platform’s behavior. The code for making the platform disappear and reappear is straightforward to move out of the onTouched function, but the code for getting the player object would require access to the otherPart parameter, and would need to provide the result of attempting to get the player object from it. This means we’ll need to create a function with a **parameter** and a **return** statement. <div class="panel panel-info"> <div class="panel-heading"> Return statements </div> <div class="panel-body"> A return statement **passes back** a value from a function, to be used where the function was called. It’s how you make your own functions which can do the same sort of thing as e.g. GetPlayerFromCharacter, where the function produces a value you can use. You just have to write `return`, followed by the thing you want to pass back </div> </div> 1. Move the code for making the platform **disappear** into its own function. 2. Move the code for making the platform **reappear** into its own function. 3. Move the code for getting the **Player** object from the `otherPart` parameter into its own function. <pre class="code-line-highlights" data-start="1" data-highlight="5-24;29;31;33"> local platform = script.Parent local isTouched = false local Players = game:GetService("Players") local function getPlayerFromPart(part) local character = part.Parent if character then local player = Players:GetPlayerFromCharacter(character) return player end end local function disappear() for i = 0, 1 do platform.Transparency = i/10 wait(0.1) end platform.CanCollide = false end local function reappear() platform.Transparency = 0 platform.CanCollide = true end local function onTouched(otherPart) if not isTouched then isTouched = true local player = getPlayerFromPart(otherPart) if player then disappear() wait(20) reappear() end isTouched = false end end platform.Touched:Connect(onTouched) </pre> This has all made the script a bit longer, but it’s much easier to read and understand now. It’s important when coding to be efficient, but it’s rarely a good idea to prioritise brevity over clarity. ## Awarding a Point Once you have a Player object in your code, you can add a point to the player’s Points value when they step on the platform. In Lua, you add a number to a variable through assigning it the same way you would assign any other value. 1. Create a **variable** called `points` for the player’s Points object. 2. Add **1** to the Value property of the `points` variable. <pre class="code-line-highlights" data-start="1" data-highlight="31-32"> local platform = script.Parent local isTouched = false local Players = game:GetService("Players") local function getPlayerFromPart(part) local character = part.Parent if character then local player = Players:GetPlayerFromCharacter(character) return player end end local function disappear() for i = 0, 1 do platform.Transparency = i/10 wait(0.1) end platform.CanCollide = false end local function reappear() platform.Transparency = 0 platform.CanCollide = true end local function onTouched(otherPart) if not isTouched then isTouched = true local player = getPlayerFromPart(otherPart) if player then local points = player.leaderstats.Points points.Value = points.Value + 1 disappear() wait(20) reappear() end isTouched = false end end platform.Touched:Connect(onTouched) </pre> Test your code now, and you should find that you get a point on the Leaderboard when you step on the platform. ## Listening for Characters Currently, players who die in the game still get to keep all their points, so there’s no incentive to survive for longer. A player’s points should reset to 0 when they die. The lava floor script from [Deadly Arena] used the <a class="api-link">Humanoid</a> inside the player’s Character model to kill them, since the Humanoid contains information about a player’s health. Humanoid also has a <a class="api-link" data-name="Humanoid.Died">Died</a> event, which you can use here to find out when a player has died so that you can reset their points. In order to do this, first you’ll need to get the **Character** for the player. The Character is only added to the game after the Player object has been loaded. Use the **CharacterAdded** event in the Player object to listen for when the Character is ready to use. 1. Create a **function** called `onCharacterAdded`, with two parameters: one for the character, one for the player. 2. Connect an **anonymous** function to the **CharacterAdded** event with a parameter for the character. 3. In the body of the anonymous function, **call** the `onCharacterAdded` function with the character as the first argument and the player as the second. <pre class="code-line-highlights" data-start="1" data-highlight="3-5;17-19"> local players = game:GetService("Players") local function onCharacterAdded(character, player) end local function onPlayerAdded(player) local leaderstats = Instance.new("Folder") leaderstats.Name = "leaderstats" leaderstats.Parent = player local points = Instance.new("IntValue") points.Name = "Points" points.Value = 0 points.Parent = leaderstats player.CharacterAdded:Connect(function(character) onCharacterAdded(character, player) end) end players.PlayerAdded:Connect(onPlayerAdded) </pre> Why the second argument for `onCharacterAdded`, and why the strange function for the CharacterAdded event? You need to have the Player object later in your code, because you’ll need to get the Points value from the player’s leaderstats folder. But the CharacterAdded event doesn’t provide the Player object to the function that connects to it - it only provides the Character. Ideally, you’d want the CharacterAdded event to call a function which then calls the onCharacterAdded function with an extra `player` argument for the Player object. Otherwise, if you connected the onCharacterAdded function directly to the event, you wouldn’t be able to pass it the Player object. Here, an **anonymous** function is used to create this ‘wrapper’ function. <div class="panel panel-info"> <div class="panel-heading"> Anonymous functions </div> <div class="panel-body"> An anonymous function is a function with no name, which can be treated as a value. It’s used here to efficiently ‘wrap’ a call to the function you actually want to run - i.e. onCharacterAdded - so that we can call it with a bonus argument for the player. A wrapper function like this doesn’t need a name, so being able to put it directly into the parentheses for the event connection saves space and keeps things simple. </div> </div> ## Listening for the Humanoid The **Died** event you’re looking for is in the Humanoid, inside the Character object. You need to wait for the Humanoid object to be loaded in first - the Humanoid won’t necessarily be there just because the Character model is present. If you try to connect to the event before the Humanoid has loaded, you’ll cause an **error**. Every object in Roblox has a <a class="api-link" data-name="Instance.WaitForChild">WaitForChild</a> function, which looks for an object with a given name and stops the code until it finds it. It takes the name of the object you’re looking for as a String. You can use it here to get the Humanoid safely before connecting to the Died event. It’s best to connect to the Died event using an anonymous function as a wrapper, so that you can pass the player through and use it later to reset the points. 1. Create a **variable** for the Humanoid, and assign to it the result of calling WaitForChild on the **character** with the argument ‘**Humanoid**’. 2. Create a **function** called `onCharacterDied`, with a parameter for the player. 3. Connect an **anonymous function** to the **Died** event of the Humanoid variable. 4. In the body of the anonymous function, **call** the `onCharacterDied` function with the player as an argument. <pre class="code-line-highlights" data-start="1" data-highlight="3-5;8-11"> local players = game:GetService("Players") local function onCharacterDied(player) end local function onCharacterAdded(character, player) local humanoid = character:WaitForChild("Humanoid") humanoid.Died:Connect(function() onCharacterDied(player) end) end local function onPlayerAdded(player) local leaderstats = Instance.new("Folder") leaderstats.Name = "leaderstats" leaderstats.Parent = player local points = Instance.new("IntValue") points.Name = "Points" points.Value = 0 points.Parent = leaderstats player.CharacterAdded:Connect(function(character) onCharacterAdded(character, player) end) end players.PlayerAdded:Connect(onPlayerAdded) </pre> ## Resetting the Points Finally, you can reset the player’s points in the onCharacterDied function using the `player` parameter. 1. Create a **variable** called `points` for the player’s Points object. 2. Set the **Value** property of the points variable to **0**. <pre class="code-line-highlights" data-start="1" data-highlight="4-5"> local players = game:GetService("Players") local function onCharacterDied(player) local points = player.leaderstats.Points points.Value = 0 end local function onCharacterAdded(character, player) local humanoid = character:WaitForChild("Humanoid") humanoid.Died:Connect(function() onCharacterDied(player) end) end local function onPlayerAdded(player) local leaderstats = Instance.new("Folder") leaderstats.Name = "leaderstats" leaderstats.Parent = player local points = Instance.new("IntValue") points.Name = "Points" points.Value = 0 points.Parent = leaderstats player.CharacterAdded:Connect(function(character) onCharacterAdded(character, player) end) end players.PlayerAdded:Connect(onPlayerAdded) </pre> Your game is now complete! If you test it out, you’ll see your point score reset to 0 if you die. [footage of player getting a point, falling into lava, dying, losing points] Duplicate your platform across your world to create a whole board of disappearing platforms to play on. Try to outlast your friends and see who gets the highest score. [footage of game being played by multiple players] Of course, this isn’t a perfect implementation. There are some issues that prevent this from being as playable and customizable as it could be. - If you wanted to **change** something in the PlatformBehavior script, you’d have to change it **dozens** of times, making the change in each platform. - Starting on a spawn point in this way also only really works if everyone joins at the same time and agrees to start playing at the same time - a **lobby** system **teleporting** you into the game from a separate space when ready would be much better. - Players can’t really **interact** with each other in this game - it would be much more fun if they could whack each other off platforms, or blow platforms up from a distance. You’ll learn more about what you need to know to make these kinds of improvements in upcoming courses, as you get closer and closer to making great Roblox games.