Cookbook (Chapter 4)

From Legacy Roblox Wiki
Jump to navigationJump to search

Using BrickColor

Problem

You want to use BrickColors.

Solution

Use the BrickColor table.

local part = Instance.new('Part')
part.Parent = Workspace
part.Anchored = true
part.BrickColor = BrickColor.random()

Discussion

We've already discussed BrickColor a little bit in Recipe 1.1. You can pass the BrickColor.new function the name of a color, or a number. That recipe provided a link to all the BrickColor codes. We've also used another BrickColor function called BrickColor.random. This creates a random BrickColor. You'll find a lot of helpful functions that belong to the BrickColor here.

Now where do these brick colors come from? If you've ever set the color of a brick in studio you'll notice that there is a little widget that comes up with a bunch of colors you can choose from. That's all the BrickColors. If you hover over a color you'll notice that a tool tip comes up with its name. That name can be put into BrickColor.new and you'll have that color. Also notice that the colors are in some sort of order. This same order can be achieved by using the BrickColor.palette function. Since the first color on the widget is “Br. yellowish green”, then BrickColor.palette(0) would be equal to BrickColor.new(“Br. yellowish green”).

You can also convert a BrickColor to a Color3 by accessing BrickColor.Color like we did in Recipe 3.6. You can also access individual components of the Color3 directly from the BrickColor with BrickColor.r, .g, and .b. Do note these are read only values.

Using Color3

Problem

You want to use Color3s.

Solution

Use the Color3 table.

game.Lighting.Ambient = Color3.new(1, 0, 0)

Discussion

BrickColor values are used for bricks, however for all other types of color applications you use Color3 values which give you a bit more control of the actual color. In this case we're setting the ambience of the light to red. The Color3 system uses RGB (red, green, blue). You can create any color by adding in different amounts of red, green, and blue.

So the arguments to Color3.new are Color3.new(red, green, blue). Now unlike most implementations of have each component be a number between 1 and 255. So (255, 255, 255) would be white, and (0, 0, 0) would be black, and (127, 127, 127) would be about mid gray. However ROBLOX's implementation is a number between 0 and 1. To convert the usual implementation to ROBLOX's you must do number/255. So (127, 127, 127) in the normal implementation would become (127/255, 127/255, 127/255) which is about (.5, .5, .5).

As with BrickColor you can get the individual R, G, B components of the Color3 by doing Color3.r, .g, or .b. Do note these are read only values.

Using Base Classes

Problem

You want to check if an object is a brick-like object, if its a truss, part, seat, wedge, or something similar.

Solution

Use the base class of parts in the IsA method.

if Workspace.Part:IsA('BasePart') then
	print("It is a part-like object")
end

Discussion

The concept of class inheritance is powerful, and is really neat. In ROBLOX we have objects, but also things called structural objects. This are special types of objects that you cannot create, but are used to structure creatable parts. Inheritance is a large topic in object oriented programming so this will only be a small piece of it. All parts (seats, parts, trusses) have similar properties and methods. All of them have a Size property, Position property, and many other similar members. Instead of each of them existing on their own, they inherit these attributes from the structural objects. All objects have the Name, className, and archivable properties. The objects inherit these properties from an ancestor object. All objects are children of the Instance object (don't confuse this with the Instance table that holds things like Instance.new).

The Part object extends (or is a child of) the FormFactorPart structural object or class. In actuality the Part object only has one property that's the Shape property. All the other properties come from the FormFactorPart object which it extends. The FormFactorPart class has the FormFactor property, all other properties it has comes from its parent, the BasePart class. The BasePart class has a whole slew of different fundamental properties of all parts like Position and Size. The chain all the way up until you hit the master object, Instance in which all objects extend.

When we do IsA we check if that object is or inherits members from a class. The following all result in true.

Instance.new('Part'):IsA('Part') --> true
Instance.new('Part'):IsA('FormFactorPart') --> true
Instance.new('Part'):IsA('BasePart') --> true
Instance.new('Part'):IsA('PVInstance') --> true
Instance.new('Part'):IsA('Instance') --> true

Using Enumerations

Problem

You want to use enumerations.

Solution

Use the Enum table.

Workspace.Part.Shape = Enum.PartType.Ball

Discussion

You may have noticed an interesting phenomenon. Take this code for example.

local part = Instance.new('Part')
part.Shape = 'Ball'
part.Parent = Workspace
print(part.Shape == 'Ball') --> false

Why is the Shape of the part not a “Ball”. We clearly set it to be a “Ball” and if you look in the Workspace you will see a ball. This is because part.Shape is not a string, its an enumeration. An enumeration or enum for short is a strict list of values for a property. There are only 3 types of shapes that a part can be, a Ball, Block or Cylinder. These are part of the PartType enum accessible by Enum.PartType.

Why would we ever need to use Enum when we can use the shortcut of passing the string of the name of the enum? For one its good practice to explicitly state that what you're setting is an enumeration value, it serves for better reading code. You must use enumerations if you are checking an enumeration value. If you use the shortcut notation, the property itself is automatically transformed (or more accurately stated coerced) into an enumeration value. Here is an example.

local part = Instance.new('Part')
part.Shape = 'Ball'
part.Parent = Workspace
print(part.Shape == Enum.PartType.Ball) --> true

Event Members

Problem

You want to use some of the others members of events.

Solution

Use several of the other methods besides connect.

local hit = Workspace.Part.Touched:wait()
print(hit)

Discussion

There are quite a few unknown members of events. One of the most useful is the :wait method. This function will wait until the event occurs and then return the values usually supplied as arguments to a callback function. Therefore if you only want to connect an event once, you should use the wait function.

There is also the connectFirst and connectLast functions. These are useful if you have multiple connections to one object and you want to ensure the order in which they trigger the callback functions.

Disconnecting a Connection

Problem

You want to disconnect a connection.

Solution

Use the disconnect method of the Connection object.

local connection
connection = Workspace.Part.Touched:connect(function(hit)
	print(hit)
	connection:disconnect()
end)

Discussion

Its quite beneficial to under how an event connection works. So first we have the Event itself. An Event is a member of an object used to identify an occasion. Now an event has several members as we discovered in Recipe 4.4. An event is only when we can do something when it happens, so we have to connect a callback to the event. We call this the event listener or event handler. Now notice that connect is just like any other method. Its just a function so it can return a value. What it returns is a special Connection object.

In our example we first define the variable “connection” because inside of its definition we're going to be using it. We set “connection” to be the Connection object when we connect the Touched event to a callback. This callback prints what hit it and disconnects the event. This results in the connection firing, the object that touched Workspace.Part to be printed and the connection to be disconnected. In fact the code above is equal to the solution in Recipe 4.4.

Using LocalScripts

When programming GUI we always used a LocalScript to put the code instead of a regular Script. Why is that? A LocalScript runs on the client only. What in the world does that mean? Let's introduce the client server model. A client is an instance of an application running on one computer. Say I open ROBLOX studio, I'm opening the ROBLOX client. If there are 3 players in a game, there are 3 clients connected to the game. There is also the server, the server is an instance of a game. Note that when you join a ROBLOX game you join a specific server with multiple clients connected to it most likely.

A regular script put into the Workspace gets run on the server meaning that it just runs once. On the other hand, a LocalScript runs on the client meaning it is run locally. If a LocalScript has specific client can we get special properties of that client? Yes, we absolutely can if we know what we're doing. A LocalScript must be put into a specific local directory, either the Player's Backpack, character model, or PlayerGui. A LocalScript must descend from one of these places to work. When we were creating GUI we were putting it in a ScreenGui within the PlayerGui.

By using a LocalScript we can access the Player of the client its running on by going to game.Players.LocalPlayer. Again, you can only access this if its in a LocalScript in a local directory. You can also access the Camera of the local client by going to Workspace.CurrentCamera.

Creating a Regeneration Button

Problem

You want to create a button that regenerates a model.

Solution

Create a new copy of the model using the clone method.

local model, button, enabled = Workspace.Model, Workspace.Button, true
local clone = model:Clone()
 
button.Touched:connect(function(hit)
	if game.Players:FindFirstChild(hit.Parent.Name) and enabled then
		enabled = false
		model:Destroy()
		wait(5)
		local lclone = clone:Clone()
		lclone.Parent = Workspace
		model = lclone
		enabled = true
	end
end)

Discussion

This is a very simple regeneration with none of the fancy purple or black coloring many have these days, however that functionality can be added very easily. We have a model Workspace.Model that we clone and set to the variable “clone”. When you call clone on an object you clone it and all of its children and set the object's parent to nil. We then connect the Touched event on our regeneration button and make sure whatever hit the button was a player. We also do the standard debounce check. We then remove the original model, wait 5 seconds and then create a clone of our cloned model. We parent this clone to the Workspace and then set “model” to our newly created clone.

Think of “model” is our display and “clone” as a reference of how the model looked in the first place. When we regenerate “model”, we create a clone of our reference model (because we want to keep the reference if we want to regenerate another time) and then set that as the new display.

Creating a Template

Problem

You want to create an object, set all its properties, and then use it throughout your code without setting it up over and over again.

Solution

Use the Clone method.

local frame = Instance.new('Frame')
frame.BackgroundColor3 = Color3.new(1, 1, 1)
frame.BackgroundTransparency = .5
frame.BorderSizePixel = 0
frame.Size = UDim2.new(.25, 0, .25, 0)
 
for i, v in ipairs(game.Players:GetPlayers()) do
	local lframe = frame:Clone()
	lframe.Position = lframe.Position + UDim2.new(0, i, 0, i)
	lframe.Parent = game.Players.LocalPlayer.PlayerGui.Screen
end

Discussion

First we created the Frame object and then set some of its properties. Then we had a loop and we cloned an instance of this Frame that had all its properties set, then changed its Position and Parent. This method of creating an object, and then using it later with the clone method is called templating (there isn't an official name for this, I coined the term “templating” myself).

Creating a Leaderboard

Problem

You want to create a leaderboard in which you can change points and such.

Solution

Create a “leaderstats” directory and place all values within IntValues.

game.Players.PlayerAdded:connect(function(player)
	local leader, score = Instance.new('IntValue', player), Instance.new('IntValue')
	leader.Name = 'leaderstats'
	score.Name = 'Score'
	score.Parent = leader
end)

Discussion

We first connect the PlayerAdded event, so when a Player enters the game, this code will fire. We create two IntValues which are assigned to “leader” and “score”. An IntValue is an object that just holds an integer. To change the Value of an IntValue you use IntValue.Value, simple. Notice that we pass Instance.new a second argument, “player”. The second argument to Instance.new parents the newly created instance to that argument.

Then we set the name of the first IntValue to be “leaderstats”. ROBLOX itself will look into the Player directory and if it finds the “leaderstats”, it will use that for its actual leaderboard (the in game GUI where you see all the player names, and KOs and WOs if applicable). We then name the second IntValue “Score” and parent it to the “leaderstats”.

A good question would be... “Why didn't we just do local leader, score = Instance.new('IntValue', player), Instance.new('IntValue', leader). We did it for the leaderstats, why did we use the long way for the Score?”. The Lua language itself simplifies all values before it assigns them. Therefore it would try to evaluate Instance.new('IntValue', leader) before leader would be existed. Therefore, we cannot use the shortcut in a multiple variable declaration. We could however do this:

game.Players.PlayerAdded:connect(function(player)
	local leader = Instance.new('IntValue', player)
local score =  Instance.new('IntValue', leader)
	leader.Name = 'leaderstats'
	score.Name = 'Score'
end)

Since we already finished defining “leader”, we could then use it in a second variable declaration.

Creating a Point Earning Button

Problem

You want to create a button that when you touch it, you earn points.

Solution

Create a leaderboard, and edit the point values when Touched fires.

local enabled = true
 
game.Players.PlayerAdded:connect(function(player)
	local leader, score = Instance.new('IntValue', player), Instance.new('IntValue')
	leader.Name = 'leaderstats'
	score.Name = 'Score'
	score.Parent = leader
end)
 
Workspace.Button.Touched:connect(function(hit)
	local p = game.Players:FindFirstChild(hit.Parent.Name)
	if pl and enabled == true then
		enabled = false
		p.leaderstats.Score.Value = p.leaderstats.Score.Value + 5
		wait(20)
		enabled = true
	end
end)

Discussion

We first create the leaderboard exactly how we did in Recipe 4.9. Then we listen for the Touched event on some button in the Workspace. We check to see if its a valid Player that hit the button and we also use debounce to prevent the button from giving out too many points. Then we set leaderstats.Score.Value the value of that “Score” IntValue to be its own value plus 5.

Applying the Chatted Event

Problem

You want to use the Chatted event so you can run code when you type something.

Solution

Use the Chatted event of Player.

game.Players.PlayerAdded:connect(function(pl)
	pl.Chatted:connect(function(msg)
		if msg == 'removehead' and pl.Character and pl.Character:FindFirstChild('Head') then
			pl.Character.Head:Destroy()
		end
	end)
end)

Discussion

First we connect the PlayerAdded event and then we connect a new event, then Chatted event to the Player. This event will fire when the player chats something and passes its callback function with the message. We check if the message is “removehead”, and also if the player's head exists. Then we proceed to remove the head.

This means when any player chats “removehead”, their head will be removed if it exists.

Applying the Changed Event

Problem

You want to print a player's health if it changes.

Solution

Use the Changed event.

game.Players.PlayerAdded:connect(function(pl)
	pl.CharacterAdded:connect(function(char)
		repeat wait() until char:FindFirstChild('Humanoid')
		local hum = char.Humanoid
		hum.Changed:connect(function(p)
			if p == 'Health' then
				print(pl.Name..'\'s health is: '..pl.Health)
			end
		end)
	end)
end)

Discussion

The following code will print “Player's health is: 60” or whatever it is, when it changes. First we use the PlayerAdded event and CharacterAdded event to get to the character of all the players. Then we wait until we can find the Humanoid in the character. Then we used the Changed event and connect it with a callback function. The Changed event passes the callback one argument, the property that was changed. We check to see if that property is health and then print out the health if it is.

There is another flavor of the Chaged event for the …Value objects (like the IntValue object, but there are many more of them). Instead of firing when a property is changed, it fires when the Value changes and passes in the value directly.

Applying the ChildAdded/Removing Event

Problem

You want to remove all parts coming into the Workspace and prevent any ones that are being removed to be removed.

Solution

Use the ChildAdded and ChildRemoving events.

Workspace.ChildAdded:connect(function(p)
	if p:IsA('BasePart') then
		p:Destroy()
	end
end)
 
Workspace.ChildRemoving:connect(function(p)
	if p:IsA('BasePart') then
		p.Parent = Workspace
	end
end)

Discussion

We simply connect both events, check if what's leaving is a BasePart, and if so, apply the appropriate behavior. ChildAdded will fire when a new Instance is parented to the object (in this case Workspace). Note that they must be direct children in order to fire. So doing Instance.new('Part', Workspace) would make it fire, but Instance.new('Part', Workspace.Model) would not. The same with the ChildRemoving event.

Descendants

Problem

You want to have the same functionality as in Recipe 4.13, but have it fire when you add it to a descendant too (e.g. Instance.new('Part', Workspace) would fire and Instance.new('Part', Workspace.Model) would fire, but Instance.new('Part', game.Players) would not).

Solution

Use the DescendantAdded and DescendantRemoving events.

Workspace.DescendantAdded:connect(function(p)
	if p:IsA('BasePart') then
		p:Destroy()
	end
end)
 
Workspace.DescendantRemoving:connect(function(p)
	if p:IsA('BasePart') then
		p.Parent = Workspace
	end
end)

Discussion

We do the exact same thing as last time except we change the events. The Descendant events fire on direct children, and descendants of the object.

There is also a function called IsDescendantOf which will check if something is a descendant of another object.

local part = Instance.new('Part', Workspace)
print(part:IsDescendantOf(game)) --> true
print(part:IsDescendantOf(Workspace)) --> true
print(Part:IsDescendantOf(game.Lighting)) --> false

Creating a Marquee

Problem

Say we have a serious of parts inside of a model. We want to animate an image over the parts to create a scrolling marquee.

Solution

Use a Decal and use an infinite loop iterating over parts within a model.

local decal = Instance.new('Decal')
decal.Texture = 'http://roblox.com/?id=12345'
decal.Face = Enum.NormalId.Front
 
while true do
	for _,v in ipairs(Workspace.Model:GetChildren()) do
		decal.Parent = v
		wait(.25)
	end
end

Discussion

We first create a Decal which and set its Texture. The Texture property sets the image in which is shown on the Decal. A Decal is just an object that controls an image placed on a rendered part. We also set the Face of the decal to an enumeration (Recipe 4.3). The NormalId enumeration controls sides of a part. We want the decal to be positioned on the front of the decal so we use the Enum.NormalId.Front option.

We then create an infinite loop using a while loop. This will make it so that the marquee will loop over and over again. We then iterate through all of the children of a model in the Workspace. We then simply parent the decal to that child of the model (presumably a part) and wait ¼ of a second. Then it will continue on to the next part in the model. Once it has completed it will loop again (due to the infinite loop defined in the beginning).

Using a SpecialMesh

Problem

You want to make the torso of all ROBLOX characters a sphere.

Solution

Use a SpecialMesh with a MeshType of Sphere.

game.Players.PlayerAdded:connect(function(pl)
	pl.Character:connect(function(c)
		repeat wait() until c:FindFirstChild('Torso')
		local mesh = Instance.new('SpecialMesh', c.Torso)
		mesh.MeshType = Enum.MeshType.Sphere
	end)
end)

Discussion

There are several different types of ways you can get a mesh on a part, but there is an object called a SpecialMesh which provides us with a couple of preset meshes. One of these presets happens to be Sphere.

First we connect the PlayerAdded event as well as the CharacterAdded event. We then proceed to wait until the Torso is in the Character. Then we create a SpecialMesh inside of the torso of the new character. We then set the type of the new mesh to be a sphere using the MeshType enumeration.

Using a VehicleSeat

Problem

You want to use a VehicleSeat.

Solution

Use the Steer property of VehicleSeat.

local seat = Workspace.VehicleSeat
 
seat.Touched:connect(function(c)
	local pl = game.Players:FindFirstChild(c.Parent.Name)
	if pl then
		print(pl.Name..' has sat on the seat.')
		seat.Changed:connect(function(p)
			if p == 'Steer' then
				if seat.Steer == -1 then
					pl.Character.Humanoid.Health = 0
				end
			end
		end)
	end
end)
 
seat.ChildRemoved:connect(function(c)
	print(c.Part1.Parent.Name..' is leaving the seat!')
end)

Discussion

In this example we have a VehicleSeat in the Workspace called “VehicleSeat” and the “seat” variable is set to it. We a user touches the seat we safely index their player and print “Player has sat in the seat”. Then when a property of the seat changes we check to see if the property that changed was the “Steer” property. If so and its equal to -1 (meaning the user was trying to turn left), we kill them.

We also attach the ChildRemoved event on the VehicleSeat. When a user sits in the seat a connection object called a Weld is placed into the seat. When they leave, the connection is removed. If we use the ChildRemoved event on the VehicleSeat we can determine who is leaving. We'll discuss more about Welds and connection objects in later chapters.

Using Fire

Problem

You want to make fire on a part.

Solution

Use the Fire object.

Instance.new('Fire', Workspace.Torso).Size = 25

Discussion

I used the shortcut for creating an object within a directory by using the second argument to Instance.new. Then I set the returned object's size to 25. You may also change the base color of the Fire by using the Color property and you may also change the outter color (basically the ends of the flames), using the SecondaryColor property.

Continue on to Chapter 5, HopperBins and Tools