Cookbook (Chapter 1)

From Legacy Roblox Wiki
Jump to navigationJump to search

Part instantiation

Problem

You want to create a Part that stays stationary.

Solution

Use the Instance.new function, the Anchored property and the Parent property in order to create the Part.

Instance.new('Part', Workspace).Anchored = true

Discussion

To create an instance of a ROBLOX object (objects are the things that can be manipulated in your game e.g. a brick is an object), you must use the Instance.new function. This function is used to create all the ROBLOX objects. That function returns the Part object and it is then assigned to the variable part. A Part is a brick. There are two properties (a property is a characteristic of an object) that are changed afterwards. In order for a Part to become visible, it must be parented to a directory called the Workspace. The Workspace can be accessed by writing game.Workspace or simply Workspace.

All objects that you wish to be rendered (rendered meaning showing up) must be put in this directory. The Parent property should be set to this directory. The problem states that this part should be stationary, meaning that it should not falling to the ground. In order to do so, the Anchored property should be set to true.

Color and transparency

Problem

You want to create a Part that stays stationary and is semi-transparent and colored red.

Solution

Create a part, then use the Transparency, and BrickColor properties.

local part = Instance.new('Part', Workspace)
part.Anchored = true
part.Transparency = .5
part.BrickColor = BrickColor.new('Bright red')

Discussion

The Transparency property controls the opacity of the object. In this property 0 is opaque and 1 is invisible. By setting this property to .5, it will create a translucent part. The next line sets the BrickColor property to the result of a new function. This new function is the BrickColor.new function. This function creates new BrickColor value. This function will return a BrickColor according to the string it is passed. The colors available are on this page. You may also pass this function a number, and it will return the corresponding color (the number to string comparison is also available on the BrickColor Codes page). It is important to note that the string inputted into the BrickColor.new function is case sensitive. This means that “bright red” is not a valid color.

Applying the touched event

Problem

You want to execute some code when a Part is touched.

Solution

Create a part, then hook the Touched event.

local part = Instance.new('Part', Workspace)
part.Anchored = true
part.Touched:connect(function()
	print("Part was touched.")
end)

Discussion

The result of this is that when something touches the newly created part, “Part was touched.” will be outputted. To see the result of the output open the output window from “View --> Output”.

As before, we create a new part, parent it to the Workspace, and anchor it. Events work in the following manner, you index (to find or get) the event (basically say object.Event), and then hook the event. This means that once this event happens it will trigger the function given. In order to hook an event you must supply the callback function (the function that is called when the event is triggered) to the hooking method called connection.

This may sound confusing, but it is quite simple. An event is just as it sounds, its an event. Events are specific to an object, say for example there is an object called foo. Let's also say that there is the Clicked event when the object foo is clicked. In order to get that event we do something like this: foo.Clicked. In the case of the Part, and the Touched event, we do this: part.Touched in order to get the event. Now when the event is triggered or fires, we want to do something. In our case we printed Part was touched. when the part was touched. In order to do this we have to hook a callback function. The connect function allows us to do that like so: object.Event:connect(callback). Once the event occurs, callback will be called.

Some events pass the callback function arguments. For example, the Touched event passes the callback function the object which touched the original object. This will be covered in more depth later on.

Killing players

Problem

You want to kill a specified player.

Solution

Set the Humanoid's Health property to 0.

Workspace.Player.Humanoid.Health = 0

Discussion

In this recipe we go into the Workspace directory again (remember this is where all rendered parts appear). Then we index Player from this directory. This will contain all pieces which make up your in game character (assuming your character is named “Player”, if I were to kill myself I would replace “Player” with “Camoy”). Notice that your character is made up of many parts, this is the directory which contains all those pieces. One of these pieces is called the Humanoid. The Humanoid has an Health and a WalkSpeed property, and has many events relating to the character.

The Humanoid has a Health property which can be set to 0 (meaning no more health). This in turn will kill the player. There is a whole variety of ways to kill characters, removing your head, breaking the joints that hold your character together, removing your torso, etc. This is just one example.

Removing limbs

Problem

You want to remove a limb from a character.

Solution

Use the Destroy method on the specified limb.

Workspace.Player["Right Arm"]:Destroy()

Discussion

The Player is indexed again, however this time instead of indexing the Humanoid within the character, we index Right Arm. This is the part that is your right arm. Then we call the Destroy method (a method is a function that is specific to an object). This in turn will remove the Right Arm of player.

Just like setting the Parent property to Workspace, you can do the reverse and set it to nil (an empty value). This will remove it from the display and cause the same effect as the Destroy method.

Workspace.Player["Right Arm"].Parent = nil

That code snippet is equivalent to the original solution.

Handling non-existent objects

Problem

Sometimes when you index objects you get an error like: “Something is not a valid member of Model”. How can this be resolved?

Solution

Use the FindFirstChild method.

local hum = Workspace.Player:FindFirstChild("Humanoid")
if hum then
	hum.Health = 0
end

Discussion

The FindFirstChild method sounds quite cryptic and can be a little daunting at first. When a game is first started up (when the server is loading), all the scripts execute. Meaning that before your in game character is even spawned, your scripts have already started running. What happens if you have a script where it expects you to be there, but you aren't? It errors because it cannot find you.

To fix this we use a safe index which as the name implies, safely indexes objects so that it doesn't error. The first line of the solution will safely index Humanoid within the character. If the Humanoid exists then hum will be set to that Humanoid. Otherwise, hum will be set to nil.

The conditional then sees if hum is not nil (this is how conditionals work in Lua) and if it isn't, then the Health of the Humanoid will be set to 0. This begs the question, “What if the Player doesn't exist too, like at the start of the game?”. Very good point. Here is how one would safely index a Humanoid from a character.

local pl = Workspace:FindFirstChild("Player")
if pl then
	local hum = pl:FindFirstChild("Humanoid")
	if hum then
		hum.Health = 0
	end
end

Now this will only run at the start of the game. Since no characters are loaded at the start of the game, this will do nothing, right? Correct. Therefore if you want to kill “Player” once they've entered the game, you would do something like this.

repeat wait() until Workspace:FindFirstChild("Player") and Workspace.Player:FindFirstChild("Humanoid")
Workspace.Player.Humanoid.Health = 0

This code uses the wait function (which will be covered in more depth later) in order to wait until the character named “Player” spawns and this character has a Humanoid. Then it sets the humanoid's health to 0.

Creating a Kill Brick

Problem

You want to create a part that once touched, kills the player who touched it.

Solution

Create a part, and then hook the Touched event and use safe indexing to set the health of the Humanoid to 0.

local part = Instance.new('Part', Workspace)
part.Anchored = true
part.Touched:connect(function(hit)
	local hum = hit.Parent:FindFirstChild("Humanoid")
	if hum then
		hum.Health = 0
	end
end)

Hook the Touched event of the parent part of the script and use safe indexing to set the health of the Humanoid to 0.

local part = script.Parent
part.Touched:connect(function(hit)
	if hit.Parent then --common pitfall of kill bricks, without this the script will break when hit by bullets
		local hum = hit.Parent:FindFirstChild("Humanoid")
		if hum then
			hum.Health = 0
		end
	end
end)

Discussion

Remember in recipe 1.2 where I mentioned that some events passed their callback functions arguments? The Touched event actually passes the part that touched the object to the callback function. Say the left leg of the player named “Camoy” touched the part we created. The callback function would be called and the hit argument would be set to Workspace.Camoy["Left Leg"]. Now in the line where hum is set, we take the hit variable (the Left Leg in my example), and get its parent. The parent of the Left Leg is the “Camoy” directory. Inside this “Camoy” directory will be all the other parts that make up my character including the Humanoid. We then use safe indexing to index this Humanoid which may or may not be present.

You may be asking yourself, “When would this not be present?”. What happens if another random part touches this part, say Workspace.AwesomePart touches your newly created part. Then hit.Parent would map to Workspace, which might not have a Humanoid in it. The next conditional checks if the Humanoid was actually indexed and then sets its Health to 0. What if hit.Parent doesn't exist you may ask? The first bit that checks if hit actually has a parent may seem a bit odd at first, but it handles that very situation! Consider this: A player shoots a gun at the brick, and the bullet hits it triggering the event. What will happen if the game decides to run the bullet script before your touched handler? The bullet's script may end up removing the bullet before the call to your script ever gets a chance to call FindFirstChild on the bullet's parent. Your script would end up failing without the check because it tried to call hit.Parent:FindFirstChild when there is no hit.Parent anymore to call FindFirstChild on!

Removing multiple limbs

Problem

You want to remove multiple limbs without doing a lot of copy and pasting.

Solution

Use a table, and then iterate over that table.

local limbs = {"Right Arm", "Left Arm", "Right Leg", "Left Leg"}
local pl = Workspace:FindFirstChild("Player")
if pl then
	for _, v in ipairs(limbs) do
		local limb = pl:FindFirstChild(v)
		if limb then
			limb:Destroy()
		end
	end
end

Discussion

The first line of code defines a table (a value that holds other values, in this case it holds the strings of types of limbs). The second line of code safely indexes the character and the third line checks if its valid or not. Next is the generic for loop, this is a special type of loop that uses an iterator function to handle the loop. I won't go into detail about it here, all you need to know is that i is set to the iteration (basically a count of how many times the loop has run), and v is set to the value (it will be the contents of the table, e.g. when i is equal to 1, v will be equal to 'Right Arm').

Inside the loop we set the limb variable to the character safely indexing v (the current limb we are at). So the first iteration would safely index Workspace.Player['Right Arm'], the second iteration would do the same for Workspace.Player['Left Arm'], and so forth until the loop ends at the last element in the table. Then the conditional checks if the limb is there, and removes it if so.

The final result is a safe way to remove all the limbs. Even if the player doesn't exist, the script won't error, and if a certain limb doesn't exist, it won't error.

Removing all character parts

Problem

You want to remove all the parts of a character.

Solution

You use the GetChildren method and check if the objects are parts.

local pl = Workspace:FindFirstChild("Player")
if pl then
	for _, v in ipairs(pl:GetChildren()) do
		if v:IsA('BasePart') then
			v:Destroy()
		end
	end
end

Discussion

The first two lines do the standard safe indexing of the character. The interesting part starts on the third line. We do the general for loop, but instead of inputting a table to the iterator function, we input the result of the GetChildren method on the character (which happens to return a table).

As you have probably noticed all the objects are in a hierarchical structure. At the top of this hierarchy is game. The next level is Workspace (in later recipes we will get into different directories than just the workspace) which is where all rendered parts go. Inside the workspace are all types of different objects, parts, models (which just serve as containers for other objects), etc. As it turns out Workspace.Player is actually a model (since all objects related to that specific player's character are put in there).

By doing Workspace.Player:GetChildren() we are getting all the objects inside of that player model. This includes the limbs, humanoids, and various other types of objects relating to the character. Then we can put the result of that function into an iterator function (since it returns a table with all these objects), and we can iterate over them.

Now, we cannot just do v:Destroy() because the goal of this was to remove all the parts of the character, we want to save the Humanoid object and the other objects. Therefore, we must check to see if the object we are iterating over is in fact a Part. In order to do this we use the IsA method. It returns true of false based on whether the object is the specified type. This is then put into a conditional and if the object is a part, then it's removed.

We can also do something like if v.ClassName == 'Part' then. The ClassName property is the type of object the object is. We can see if that is equal to part and get the same result. Do note however there is a subtle difference between x.ClassName == 'y' and x:IsA('y'). This is more advanced and will be covered later on.

Killing all players

Problem

You want to kill all the players in game.

Solution

You use the GetPlayers method and set the character's health to 0.

for i, player in ipairs(game.Players:GetPlayers()) do
	if player.Character then
		local hum = player.Character:FindFirstChild('Humanoid')
		if hum then
			hum.Health = 0
		end
	end
end

Discussion

We are passing our iterator function the result of a new function from a whole new directory! This is the GetPlayers function in the game.Players directory. This will return all the in game players.

What is game.Players you may ask? Its just like the Workspace, but instead of where all the rendered parts reside, Player objects reside there. These Player objects contain all the useful information for a Player. The player object contains information like the team color, user ID, and points. It also contains a link to the character model in its Character property.

Why didn't we just use GetChildren on game.Players, wouldn't that have the same effect? You can use GetChildren on game.Players, but the difference is that GetPlayers will get all the Players (e.g. if there are objects that are not players in game.Players, it won't select them), however GetChildren will select all children regardless of whether they are actually a Player or not. Normally you will only have Players in this directory, however if something does get in there, its safer to use GetPlayers.

Inside the loop we first check if the character is there (the character might not be there if the player is respawning) and then safely index the Humanoid from Player.Character, check if its exists, and then set its health to 0.

Killing random players on interval

Problem

You want to kill a random player in game every 30 seconds.

Solution

Use a while loop whose condition is set to the result of the wait function.

while wait(30) and game.Players.NumPlayers ~= 0 do
	local hum = game.Players:GetPlayers()[math.random(1, game.Players.NumPlayers].Character:FindFirstChild("Humanoid")
	if hum then
		hum.Health = 0
	end
end

Discussion

In the while loop condition we are calling the wait function which will delay execution by the inputted number of seconds (in this case 30), and will return the amount of time it waited. This while loop will wait 30 seconds and then execute the next clause in the while loop condition. This is the statement game.Players.NumPlayers ~= 0. The NumPlayers property in game.Players is the numbers of players currently in game. This property is equivalent to #game.Players:GetPlayers().

Inside the loop, we set the variable hum to game.Players:GetPlayers() which returns a table of all the players. Then from this table we use brackets to denote we are indexing a value. Inside of these brackets we're using the math.random function to pick a random number between 1, and the amount of players there are in game. This will get a random player, however we want the Humanoid, so we use safe indexing on the character property to get the humanoid. Then we set its Health to 0.

The reason we need to check to see if game.Players.NumPlayers is not 0 before entering the loop is because if it were 0, then we would have the statement math.random(1, 0) which isn't valid (which says a random number with a minimum of 1, and a maximum of 0).

Using an exception table

Problem

You want to remove the Right Arm of everyone except for the people you specify in a table.

Solution

Use the player name as the keys of your table with the value to true. Then index the state of the current player name while iterating over all the players.

function lookupify(table) --utility function to create a lookup table
	for i = 1, #table do
		table[table[i]] = true
	end
	return table
end

local safe = lookupify {"Player", "Camoy"} --note, this is equivalent to lookupify({ ... })
for _, player in ipairs(game.Players:GetPlayers()) do
	if not safe[player.Name] then
		local ra = player.Character:FindFirstChild("Right Arm")
		if ra then
			ra:Destroy()
		end
	end
end

Discussion

We first state a table where the keys (in Lua you use a key to index, and the corresponding value will be returned e.g. safe.Camoy will return true) are the player names, and the values can be anything except nil or false (those would make a conditional not pass). The lookupify function simplifies this task by taking a normal table of names as values, and adding those names as keys which we can easily search the table by.

Next, we iterate over the players like normal and then do `if not safe[player.Name] then` in which player is a reference to one of the players in the game, different for each execution of the loop, such that it is run one for each player in the game. The Name property returns the name of the object, in our case all player objects are named the user name of the person they belong to. The resolution of the name property goes like so: `if not safe['Player']`. We then index “Player” from safe which happens to be true since lookupify set it to that earlier. Then we go and take if not true then, which results in if false then, which doesn't run the conditional which actually removes the arm.

The more common naive solution would looks like this:

local safe = {"Player", "Camoy"}
local findInTable = function(value, t)
	for i,v in ipairs(t) do
		if value == v then
			return true
		end
	end
end

for i,v in ipairs(game.Players:GetPlayers()) do
	if not findInTable(v.Name, safe) then
		local ra = v.Character:FindFirstChild("Right Arm")
		if ra then
			ra:Destroy()
		end
	end
end

This alternative solution is less efficient because it has to do extra iteration. The “findInTable” function iterates through all the values in a table and sees if the first argument supplied to it is equal to the current iteration, if so it returns true. We then use that function supplying it with v.Name and the “safe” table to see if the current character's name is one of the values in the “safe” table. There is no reason for using this method rather than the first one: In Lua you should always create your tables so that the thing you want to search the table by (the players names in this case) is used as the key to the table, and the data you want to associate with that entry is the value (In this case you simply want to associate some non-nil value, "true" being the cheapest one to use).

Killing spawning characters

Problem

You want to loop kill newly spawned characters. This is probably not a good idea in practice, and it would likely be better to teleport players to some sequestering area rather than repeatedly kill them.

Solution

Use the PlayerAdded, and CharacterAdded events.

game.Players.PlayerAdded:connect(function(player)
	player.CharacterAdded:connect(function(character)
		character.Humanoid.Health = 0
	end)
end)

Discussion

There is an event called PlayerAdded that fires when a new player enters, then passes the callback the player. We can use this player to fire another event called CharacterAdded. This event fires once the character spawns because the Player directory is created before the Player.Character directory. It then gives the callback function the character equivalent to Player.Character. We can then index the Humanoid from this and set its Health to 0.

Why are we not safely indexing the Humanoid? It is likely that the Player has the humanoid the second it spawns. Therefore it is fairly safe to assume that it exists.

Creating a message

Problem

You want to create a message each time someone enters the game, then have it disappear in 5 seconds.

Solution

Use the PlayerAdded event along with the Message object.

game.Players.PlayerAdded:connect(function(player)
	local msg = Instance.new('Message', Workspace)
	msg.Text = player.Name .. " has entered the game."
	wait(5)
	msg:Destroy()
end)

Discussion

We set up a standard connection for the PlayerAdded event and create a new Message object with the Instance.new function. Then we set the Text property of the new message to the new player's name and concatenate (adding a string to another string) it to a small message. We then parent the message to the workspace, wait 5 seconds and remove it.

Messages placed in the workspace appear to all players. If you parent a message to the Player directory, it will be seen only by that player. Do note that the Message object is deprecated. You may still use it, however it has been replaced with more customizable GUI objects which will be discussed later on.

Debounce

Problem

You want to create a message when someone steps on a part that notifies the name of the person who stepped on the part.

Solution

Use debounce to restrict too many triggers.

local enabled = true
Workspace.Button.Touched:connect(function(hit)
	if hit.Parent and game.Players:GetPlayerFromCharacter(hit.Parent) and enabled == true then
		local msg = Instance.new('Message', Workspace)
		msg.Text = hit.Parent.Name .. " stepped on me!"
		enabled = false
		wait(3)
		enabled = true
		msg:Destroy()
	end
end)

Discussion

In the first line I declare enabled to be the boolean (true of false value) true. I then proceed to do a stand connection to the Touched event. This time I assume there is a part in the workspace called “Button” since we did not dynamically create it.

Next, we check to see if the part's parent is a character, with the GetPlayerFromCharacter method. Say for example a character's right leg hit the button, hit.Parent would be the character. We also check to see if enabled is true (this is where the debounce comes in).

Debounce is restricting the Touched event from triggering too many times. Say for example you jump on the part multiple times before the 3 seconds to remove the message is up. This means that many new messages will be created on top of each other. Also, the way the ROBLOX physics engine works, one touch is triggered as many touches so you end up getting bombarded with extra messages even for one touch.

After the conditional we create a new message, and set it up with some text and parent it to the workspace so it can be seen. Then we set enabled to false so that if the event is triggered again, the conditional won't let it create new messages. We then wait 3 seconds and set enabled to true so that events thereafter can fire once more. The message is then removed.

Creating a clickable part

Problem

You want to create a part that, when clicked, shows a message.

Solution

Use the MouseClick event and the ClickDetector object.

local part, clickd, msg = Workspace.Part, Instance.new('ClickDetector'), Instance.new('Message')
clickd.Parent = part
msg.Text = "Clicked!"

clickd.MouseClick:connect(function()
	msg.Parent = Workspace
	wait(2)
	msg.Parent = nil
end)

Discussion

The variable declaration statement looks a tad different this time. Here we're using a multiple assignment which is just syntactic sugar (syntax shortcuts used to save time or make something more readable) for just three normal variable declarations. Make sure you have a part in the Workspace named “Part”. Notice we create a new instance of a ClickDetector which is just an object that controls clicks to its parented object. We parent it to the part variable (since we want to record clicks to that object) and we also set the message's text.

We then connect the MouseClick event of the ClickDetector, this obviously fires once the ClickDetector's parent is clicked. Inside the callback we just parent the message to the Workspace, wait 2 seconds and remove it.

You may be wondering why MouseClick requires an entirely new object in order for you to be able to record clicks a part. Why couldn't it be like the Touched event and be part.MouseClick? It is probably because, when you put a ClickDetector in a part, putting your cursor over it makes the cursor's image change. Usually, you'd only want that to happen with parts you can click. It was likely an API design choice made by the developers.

Touch activated explosion

Problem

You want to make the person who touches a part explode.

Solution

Use the Explosion object.

Workspace.Part.Touched:connect(function(hit)
	if hit.Parent and game.Players:GetPlayerFromCharacter(hit.Parent) then
		local exp = Instance.new('Explosion', Workspace)
		exp.Position = hit.Position
	end
end)

Discussion

We start with a standard Touched connection and check if hit.Parent is a character, using the GetPlayerFromCharacter method of the Players service. By doing this we can ensure that whatever hit the part was part of a Player (we don't want to explode when another part touches it, just when a player touches it). Within the conditional we instantiate an Explosion object and parent it to the Workspace. Then we set the Position property of the explosion the the Position property of the object that touched the part.

Without setting the Position property correctly, we would get an explosion happening in the center of the map. Positioning will be covered in great detail later on.

Dance floor

Problem

You have a Model that contains many parts. You want to randomly change the color of these parts every ¾ of a second to make a dance floor.

Solution

Iterate through the model and use the BrickColor.Random function.

while wait(.75) do
	for _, v in pairs(Workspace.floor:GetChildren()) do
		v.BrickColor = BrickColor.Random()
	end
end

Discussion

We first use an infinite loop to apply this functionality over and over again. We use the while loop in order to do this. Be very cautious with infinite loops! Its bad practice to use them if not necessary, infinite loops without delay WILL crash ROBLOX. Within the loop we iterate through the model game.Workspace.floor which I assume is a model containing only Parts. Then we set the BrickColor of the parts to the result of a new function, BrickColor.Random. This function returns a random color.

Say we had some hand picked BrickColors that we wanted to randomly pick from in order to color the floor. We could do this:

local colors = {'Bright red', 'Bright green', 'Bright blue'}
while wait(.75) do
	for _, v in pairs(Workspace.floor:GetChildren()) do
		v.BrickColor = BrickColor.new(colors[math.random(1, #colors)])
	end
end

I define a colors table outside of the loop (since the table doesn't change there is no point in redefining it each time the loop runs) and the BrickColor property is set the BrickColor returned from BrickColor.new whose color is a randomly picked value from the “colors” table. I used the math.random function to accomplish this.

Creating a healing part

Problem

You want a part to heal a player when they touch it.

Solution

Set the Humanoid's Health property to its MaxHealth property.

Workspace.HealPart.Touched:connect(function(hit)
        local hum = hit.Parent:FindFirstChild("Humanoid")
        if hum then
                hum.Health = hum.MaxHealth
        end
end)

Discussion

We connect the Touched event to a part in the Workspace called “HealPart”. We then safely index a Humanoid from the parent of the object which touched the healing part. If this Humanoid exists, then we set its Health to the total amount of health it can have, which is contained within its MaxHealth property.

Creating a restricted access door

Problem

You want to restrict access from a door and allow only specified players in.

Solution

Use CanCollide in conjunction with an exception table.

local allowed, door = {Player = true, Camoy = true}, Workspace.restrictedDoor
door.Touched:connect(function(hit)
	if game.Players:FindFirstChild(hit.Parent.Name) and allowed[hit.Parent.Name] then
		door.CanCollide = false
		wait(1.5)
		door.CanCollide = true
	end
end)

Discussion

We first define our allowance array, then the door in which we want to use as our restricted access door. We connect the Touched event to the door and check to see if what hit the door is a player, and that they're allowed inside.

We use a new CanCollide property in order to set the ability of whether you can walk through the part or not. We then wait 1½ seconds and switch it back on. You want to make sure that the amount you wait is a low number. Say for example an allowed person enters the door and right behind them comes a not allowed person. They will be able to get through because the door has not become solid yet.

The reason we don't need debounce here is because the action can be applied 20 times in a row and the door will act correctly. If we set CanCollide to false twice, the first register will just make it true after its 1½ seconds is up, and the other will do the same only a little later.

Creating an invisibility part

Problem

You want to create a part which, once touched, will fade out the character who touched it and give them 10 seconds of invisibility, then you want them to fade in again.

Solution

We use debounce to prevent trying to become invisible while already invisible. We then use a table to control the fading, and iterate through the parts of the character setting parts to their appropriate transparency.

function setTransparency(char, value)
	for _, child in pairs(char:GetChildren()) do
		if child:IsA('Hat') and child:FindFirstChild("Handle") then
			child = child.Handle
		elseif child:IsA('BasePart') then
			child.Transparency = value
		end
	end
end

local enabled = true
Workspace.TransPart.Touched:connect(function(hit)
	local char = hit.Parent
	if char then
		local head = char:FindFirstChild("Head")
		local face = head:FindFirstChild("face")
		if enabled and head and game.Players:GetPlayerFromCharacter(char) then
			enabled = false
			if face then face.Parent = nil end
			for t = 0, 1, .1 do
				setTransparency(char, t)
				wait(.1)
			end
			wait(10)
			for t = 1, 0, -.1 do
				setTransparency(char, t)
				wait(.1)
			end
			if face then face.Parent = head end
			wait(2)
			enabled = true
		end
end)

Discussion

This is the longest piece of code we have come across thus far, but don't frightened, there isn't much new material. Recall the debounce recipe we used the “enabled” variable to control the ability to activate the button. We're going to do the exact same thing here. While a player is invisible (and 2 seconds after they become visible) no other players will be able to become invisible. This prevents spam invisibility and handling already invisible players in the button's conditional.

We connect the Touched event to a part called “TransPart” which will contain this functionality. We then define 3 variables, char which is the character model, head which is the head part, and face which is an object inside the head part. The reason we cannot use a multiple variable declaration statement here is because in the assignment of the new variables, we are using ones previously defined (e.g. defining the head variable uses the char variable). This face object is of the type Decal which is basically an image which is attached to a Part. This is the face of your in game character.

In the first conditional we check the debounce, and whether whatever touched “TransPart” is a player. We also make sure that this character has a head because we will be doing manipulations to the head later on. We then set enabled to false to create the debounce. Here is the interesting part. We're iterating over a table with two elements {.1, -.1}. Basically the first is the fade out, the second is the fade in. We then inside proceed wait 10 seconds if we're fading in, then set x and y to be the opposite of the original. This is because once again, this is for fading in. Otherwise elseif (this is basically if we're fading out), we want to remove the face if it exists. Since decals don't have a transparency property, we will just have to remove it. We then proceed to do the fading itself and apply the transparency to all parts within the character.

You'll notice that if the object we're currently on is a Hat, we look inside the hat and set the iteration to the handle. This is because the hats you wear have a special Hat object wrapper, to get to the actual hat itself you must index “Handle” from it. After all the iteration is done, we return the face to the character and enable the button once more.

Continue on to Chapter 2, Positioning