Cookbook (Chapter 4): Difference between revisions
>Camoy No edit summary |
>JulienDethurens No edit summary |
||
(49 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
{{Map|Cookbook}} | |||
==Using BrickColor== | ==Using BrickColor== | ||
===Problem=== | ===Problem=== | ||
Line 5: | Line 6: | ||
===Solution=== | ===Solution=== | ||
Use the BrickColor table. | Use the BrickColor table. | ||
{{ | {{lua|= | ||
local part = Instance.new('Part') | local part = Instance.new('Part') | ||
part.Parent = | part.Parent = Workspace | ||
part.Anchored = true | part.Anchored = true | ||
part.BrickColor = BrickColor.random() | part.BrickColor = BrickColor.random() | ||
}} | |||
===Discussion=== | ===Discussion=== | ||
Line 25: | Line 26: | ||
===Solution=== | ===Solution=== | ||
Use the Color3 table. | Use the Color3 table. | ||
{{ | {{lua|= | ||
game.Lighting.Ambient = Color3.new(1, 0, 0) | game.Lighting.Ambient = Color3.new(1, 0, 0) | ||
}} | |||
===Discussion=== | ===Discussion=== | ||
Line 42: | Line 43: | ||
===Solution=== | ===Solution=== | ||
Use the base class of parts in the IsA method. | Use the base class of parts in the IsA method. | ||
{{ | {{lua|= | ||
if | if Workspace.Part:IsA('BasePart') then | ||
print("It is a part-like object") | |||
end | end | ||
}} | |||
===Discussion=== | ===Discussion=== | ||
Line 54: | Line 55: | ||
When we do IsA we check if that object is or inherits members from a class. The following all result in true. | When we do IsA we check if that object is or inherits members from a class. The following all result in true. | ||
{{ | {{lua|= | ||
Instance.new('Part'):IsA('Part') --> true | Instance.new('Part'):IsA('Part') --> true | ||
Instance.new('Part'):IsA('FormFactorPart') --> true | Instance.new('Part'):IsA('FormFactorPart') --> true | ||
Line 60: | Line 61: | ||
Instance.new('Part'):IsA('PVInstance') --> true | Instance.new('Part'):IsA('PVInstance') --> true | ||
Instance.new('Part'):IsA('Instance') --> true | Instance.new('Part'):IsA('Instance') --> true | ||
}} | |||
==Using Enumerations== | ==Using Enumerations== | ||
Line 68: | Line 69: | ||
===Solution=== | ===Solution=== | ||
Use the Enum table. | Use the Enum table. | ||
{{ | {{lua|= | ||
Workspace.Part.Shape = Enum.PartType.Ball | |||
}} | |||
===Discussion=== | ===Discussion=== | ||
You may have noticed an interesting phenomenon. Take this code for example. | You may have noticed an interesting phenomenon. Take this code for example. | ||
{{ | {{lua|= | ||
local part = Instance.new('Part') | local part = Instance.new('Part') | ||
part.Shape = 'Ball' | part.Shape = 'Ball' | ||
part.Parent = | part.Parent = Workspace | ||
print(part.Shape == 'Ball') --> false | 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 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. | ||
Line 85: | Line 86: | ||
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. | 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. | ||
{{ | {{lua|= | ||
local part = Instance.new('Part') | local part = Instance.new('Part') | ||
part.Shape = 'Ball' | part.Shape = 'Ball' | ||
part.Parent = | part.Parent = Workspace | ||
print(part.Shape == Enum.PartType.Ball) --> true | print(part.Shape == Enum.PartType.Ball) --> true | ||
}} | |||
==Event Members== | ==Event Members== | ||
Line 99: | Line 100: | ||
Use several of the other methods besides connect. | Use several of the other methods besides connect. | ||
{{ | {{lua|= | ||
local hit = | local hit = Workspace.Part.Touched:wait() | ||
print(hit) | print(hit) | ||
}} | |||
===Discussion=== | ===Discussion=== | ||
Line 115: | Line 116: | ||
===Solution=== | ===Solution=== | ||
Use the disconnect method of the Connection object. | Use the disconnect method of the Connection object. | ||
{{ | {{lua|= | ||
local connection | local connection | ||
connection = | connection = Workspace.Part.Touched:connect(function(hit) | ||
print(hit) | |||
connection:disconnect() | |||
end) | end) | ||
}} | |||
===Discussion=== | ===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. | 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 | 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== | ==Using LocalScripts== | ||
Line 133: | Line 134: | ||
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. | 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 | 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== | ==Creating a Regeneration Button== | ||
Line 141: | Line 142: | ||
===Solution=== | ===Solution=== | ||
Create a new copy of the model using the clone method. | Create a new copy of the model using the clone method. | ||
{{ | {{lua|= | ||
local model, button, enabled = | local model, button, enabled = Workspace.Model, Workspace.Button, true | ||
local clone = model: | local clone = model:Clone() | ||
button.Touched:connect(function(hit) | 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) | end) | ||
}} | |||
===Discussion=== | ===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 | 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. | 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. | ||
Line 168: | Line 169: | ||
===Solution=== | ===Solution=== | ||
Use the | Use the Clone method. | ||
{{ | {{lua|= | ||
local frame = Instance.new('Frame') | local frame = Instance.new('Frame') | ||
frame.BackgroundColor3 = Color3.new(1, 1, 1) | frame.BackgroundColor3 = Color3.new(1, 1, 1) | ||
Line 176: | Line 177: | ||
frame.Size = UDim2.new(.25, 0, .25, 0) | frame.Size = UDim2.new(.25, 0, .25, 0) | ||
for i,v in ipairs(game.Players:GetPlayers()) do | for i, v in ipairs(game.Players:GetPlayers()) do | ||
local lframe = frame: | local lframe = frame:Clone() | ||
lframe.Position = lframe.Position + UDim2.new(0, i, 0, i) | lframe.Position = lframe.Position + UDim2.new(0, i, 0, i) | ||
lframe.Parent = game.Players.LocalPlayer.PlayerGui.Screen | lframe.Parent = game.Players.LocalPlayer.PlayerGui.Screen | ||
end | end | ||
}} | |||
===Discussion=== | ===Discussion=== | ||
Line 192: | Line 193: | ||
===Solution=== | ===Solution=== | ||
Create a “leaderstats” directory and place all values within IntValues. | Create a “leaderstats” directory and place all values within IntValues. | ||
{{ | {{lua|= | ||
game.Players.PlayerAdded:connect(function(player) | 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) | end) | ||
}} | |||
===Discussion=== | ===Discussion=== | ||
Line 208: | Line 209: | ||
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: | 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: | ||
{{ | {{lua|= | ||
game.Players.PlayerAdded:connect(function(player) | game.Players.PlayerAdded:connect(function(player) | ||
local leader = Instance.new('IntValue', player) | |||
local score = Instance.new('IntValue', leader) | local score = Instance.new('IntValue', leader) | ||
leader.Name = 'leaderstats' | |||
score.Name = 'Score' | |||
end) | end) | ||
}} | |||
Since we already finished defining “leader”, we could then use it in a second variable declaration. | Since we already finished defining “leader”, we could then use it in a second variable declaration. | ||
Line 225: | Line 226: | ||
===Solution=== | ===Solution=== | ||
Create a leaderboard, and edit the point values when Touched fires. | Create a leaderboard, and edit the point values when Touched fires. | ||
{{ | {{lua|= | ||
local enabled = true | local enabled = true | ||
game.Players.PlayerAdded:connect(function(player) | 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) | 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) | end) | ||
}} | |||
===Discussion=== | ===Discussion=== | ||
Line 255: | Line 256: | ||
===Solution=== | ===Solution=== | ||
Use the Chatted event of Player. | Use the Chatted event of Player. | ||
{{ | {{lua|= | ||
game.Players.PlayerAdded:connect(function(pl) | 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) | end) | ||
}} | |||
===Discussion=== | ===Discussion=== | ||
Line 276: | Line 277: | ||
===Solution=== | ===Solution=== | ||
Use the Changed event. | Use the Changed event. | ||
{{ | {{lua|= | ||
game.Players.PlayerAdded:connect(function(pl) | 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) | end) | ||
}} | |||
===Discussion=== | ===Discussion=== | ||
Line 295: | Line 296: | ||
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. | 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 | ==Applying the ChildAdded/Removing Event== | ||
===Problem=== | ===Problem=== | ||
You want to remove all parts coming into the Workspace and prevent any ones that are being removed to be removed. | You want to remove all parts coming into the Workspace and prevent any ones that are being removed to be removed. | ||
Line 301: | Line 302: | ||
===Solution=== | ===Solution=== | ||
Use the ChildAdded and ChildRemoving events. | Use the ChildAdded and ChildRemoving events. | ||
{{ | {{lua|= | ||
Workspace.ChildAdded:connect(function(p) | |||
if p:IsA('BasePart') then | |||
p:Destroy() | |||
end | |||
end) | end) | ||
Workspace.ChildRemoving:connect(function(p) | |||
if p:IsA('BasePart') then | |||
p.Parent = Workspace | |||
end | |||
end) | end) | ||
}} | |||
===Discussion=== | ===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 | 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== | ==Descendants== | ||
===Problem=== | ===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', | 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=== | ===Solution=== | ||
Use the DescendantAdded and DescendantRemoving events. | Use the DescendantAdded and DescendantRemoving events. | ||
{{ | {{lua|= | ||
Workspace.DescendantAdded:connect(function(p) | |||
if p:IsA('BasePart') then | |||
p:Destroy() | |||
end | |||
end) | end) | ||
Workspace.DescendantRemoving:connect(function(p) | |||
if p:IsA('BasePart') then | |||
p.Parent = Workspace | |||
end | |||
end) | end) | ||
}} | |||
===Discussion=== | ===Discussion=== | ||
Line 342: | Line 343: | ||
There is also a function called IsDescendantOf which will check if something is a descendant of another object. | There is also a function called IsDescendantOf which will check if something is a descendant of another object. | ||
{{ | {{lua|= | ||
local part = Instance.new('Part', | local part = Instance.new('Part', Workspace) | ||
print(part:IsDescendantOf(game)) --> true | print(part:IsDescendantOf(game)) --> true | ||
print(part:IsDescendantOf( | print(part:IsDescendantOf(Workspace)) --> true | ||
print(Part:IsDescendantOf(game.Lighting)) --> false | print(Part:IsDescendantOf(game.Lighting)) --> false | ||
}} | |||
==Creating a Marquee== | ==Creating a Marquee== | ||
Line 355: | Line 356: | ||
===Solution=== | ===Solution=== | ||
Use a Decal and use an infinite loop iterating over parts within a model. | Use a Decal and use an infinite loop iterating over parts within a model. | ||
{{ | {{lua|= | ||
local decal = Instance.new('Decal') | local decal = Instance.new('Decal') | ||
decal.Texture = 'http://roblox.com/?id=12345' | decal.Texture = 'http://roblox.com/?id=12345' | ||
Line 361: | Line 362: | ||
while true do | while true do | ||
for _,v in ipairs(Workspace.Model:GetChildren()) do | |||
decal.Parent = v | |||
wait(.25) | |||
end | |||
end | end | ||
}} | |||
===Discussion=== | ===Discussion=== | ||
Line 379: | Line 380: | ||
===Solution=== | ===Solution=== | ||
Use a SpecialMesh with a MeshType of Sphere. | Use a SpecialMesh with a MeshType of Sphere. | ||
{{ | {{lua|= | ||
game.Players.PlayerAdded:connect(function(pl) | 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) | end) | ||
}} | |||
===Discussion=== | ===Discussion=== | ||
Line 400: | Line 401: | ||
===Solution=== | ===Solution=== | ||
Use the Steer property of VehicleSeat. | Use the Steer property of VehicleSeat. | ||
{{ | {{lua|= | ||
local seat = | local seat = Workspace.VehicleSeat | ||
seat.Touched:connect(function(c) | 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) | end) | ||
seat.ChildRemoved:connect(function(c) | seat.ChildRemoved:connect(function(c) | ||
print(c.Part1.Parent.Name..' is leaving the seat!') | |||
end) | end) | ||
}} | |||
===Discussion=== | ===Discussion=== | ||
Line 433: | Line 434: | ||
===Solution=== | ===Solution=== | ||
Use the Fire object. | Use the Fire object. | ||
{{ | {{lua|= | ||
Instance.new('Fire', | Instance.new('Fire', Workspace.Torso).Size = 25 | ||
}} | |||
===Discussion=== | ===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. | 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. | ||
[[Cookbook_(Chapter_5)|Continue on to Chapter 5, HopperBins and Tools]] | |||
[[Category:Cookbook]] |
Latest revision as of 23:25, 27 February 2012
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.