This book teaches Roblox game development using Lua programming language. It's structured into 24 one-hour lessons, covering topics from basic coding concepts to advanced techniques.
The book includes step-by-step instructions, Q&As, quizzes, exercises, and "Try It Yourself" sections for practical application. It emphasizes best practices for code organization and creating engaging experiences. The book also touches upon monetization strategies and advanced techniques such as raycasting.
Coding with | Roblox Lua In
Pearson ours The Official R&eBLEX Guide
Coding with Robiox Lua
ee : @ Pearson Coding with Roblox Lua in 24 Hours: The Official Roblox Guide Editor-in-Chief Copyright © 2022 by Roblox Corporation. âRoblox,â the Roblox logo, and âPowering Imaginationâ Debra Williams are among the Roblox registered and unregistered trademarks in the U.S. and other countries. All Cauley rights reserved. Acquisitions Editor All rights reserved. This publication is protected by copyright, and permission must be obtained from the publisher prior to any prohibited reproduction, storage in a retrieval system, or transmis- Kim Spenceley sion in any form or by any means, electronic, mechanical, photocopying, recording, or likewise. Editorial Services For information regarding permissions, request forms, and the appropriate contacts within the Pearson Education Global Rights & Permissions Department, please visit www.pearsoned.com/ The Wordsmithery permissions/. No patent liability is assumed with respect to the use of the information contained ic herein. Although every precaution has been taken in the preparation of this book, the publisher and author assume no responsibility for errors or omissions. Nor is any liability assumed for dam- Managing Editor ages resulting from the use of the information contained herein. Sandra Schroeder ISBN-13: 978-0-13-682942-3 Senior Project ISBN-10: 0-13-682942-2 Editor Library of Congress Control Number: 2021948694 Tonya Simpson 1 2021 Copy Editor Trademarks Charlotte Kughen All terms mentioned in this book that are known to be trademarks or service marks have been appropriately capitalized. Pearson cannot attest to the accuracy of this information. Use of a term Indexer in this book should not be regarded as affecting the validity of any trademark or service mark. Cheryl Lenser Warning and Disclaimer Proofreader Every effort has been made to make this book as complete and as accurate as possible, but no Sarah Kearns warranty or fitness is implied. The information provided is on an âas isâ basis. The author and the publisher shall have neither liability nor responsibility to any person or entity with respect to any Editorial Assistant loss or damages arising from the information contained in this book. Cindy Teeters Special Sales Cover Designer For information about buying this title in bulk quantities, or for special sales opportunities (which may include electronic versions; custom cover designs; and content particular to your business, Chuti Prasertsith training goals, marketing focus, or branding interests), please contact our corporate sales depart- Compositor ment at corpsales@pearsoned.com or (800) 382-3419. Bronkella For government sales inquiries, please contact governmentsales@pearsoned.com. Publishing LLC For questions about sales outside the United States, please contact intlcs@pearson.com. Graphics Processing TJ Graham Art Pearsonâs Commitment to Diversity, Equity, and Inclusion
Pearson is dedicated to creating bias-free content that reflects the diversity of all learners. We embrace the many dimensions of diversity, including but not limited to race, ethnic- ity, gender, socioeconomic status, ability, age, sexual orientation, and religious or political beliefs.
Education is a powerful force for equity and change in our world. It has the potential to deliver opportunities that improve lives and enable economic mobility. As we work with authors to create content for every product and service, we acknowledge our responsibility to demonstrate inclusivity and incorporate diverse scholarship so that everyone can achieve their potential through learning. As the worldâs leading learning company, we have a duty to help drive change and live up to our purpose to help more people create a better life for themselves and to create a better world.
>» Everyone has an equitable and lifelong opportunity to succeed through learning.
> Our educational products and services are inclusive and represent the rich diversity of learners.
>» Our educational content accurately reflects the histories and experiences of the learners we serve.
>» Our educational content prompts deeper discussions with learners and motivates them to expand their own learning (and worldview).
While we work hard to present unbiased content, we want to hear from you about any con- cerns or needs with this Pearson product so that we can investigate and address them.
Index 355 About the Author
As the reader of this book, you are our most important critic and commentator. We value your opinion and want to know what weâre doing right, what we could do better, what areas you'd like to see us publish in, and any other words of wisdom youâre willing to pass our way.
You can email or write to let us know what you did or didnât like about this bookâas well as what we can do to make our books better.
Please note that we cannot help you with technical problems related to the topic of this book.
When you email, please be sure to include this bookâs title and author as well as your name, email address, and phone number. We will carefully review your comments and share them with the author and editors who worked on the book.
Email: community@informit.com
Reader Services
*Be sure to check the box that you would like to hear from us to receive exclusive discounts on future editions of this product. {Re ssiy One
Roblox is the worldâs most popular game development platform. All types of people come together to create amazing virtual experiences: artists, musicians, andâyou guessed itâcoders. Coding is what allows players to interact with the world that they see.
In Roblox, the coding language used is Lua. Lua is one of the easiest coding languages to learn, and when used with Roblox Studio, you can see the results of your code fast. For example, want to create an enormous explosion with a massive blast radius? You can do that with just a couple of lines of Lua.
Roblox Studio is the tool in which all Roblox games are created, and when paired with Lua, it offers seamless access to multiplayer servers, physics and lighting systems, world-building tools, monetization systems, and more. And even though Roblox provides the environment in which your program runs, you control the vision. You are the creator and artist. Roblox gives you the canvas and paints, and Lua the brushes and actions. But you, with some well-placed dabs of code, get to create your masterpiece. This first hour covers how to set up Roblox Studio, make your first script, and test your code.
Studio ;t
Start Creating
Manage my games
FIGURE 1.1 You need an account to use Roblox Studio. Itâs free and just a quick sign-up away.
Go ahead and launch Roblox Studio to see the window shown in Figure 1.2. Enter the login infor- mation for the account you created when you signed up on the Roblox website and click Log In.
Roblox Studio el oO x
R@BLEX Studio Start creating your own games!
FIGURE 1.2 Enter your normal Roblox account information. Let's Take a Tour 3
When you first open up Studio, you see templates. These are starting places you can use for your experiences. The simplest starting point for any project is the Baseplate template. Click on the Baseplate template, as shown in Figure 1.3.
All Templates
FIGURE 1.3 Studio offers template places you can use as starting points.
Letâs start with a quick overview of the main parts of the screen in Figure 1.4, and then move straight into your first line of code:
al The offerings in the Toolbar ribbon change according to the menu tab you've selected.
2. The Toolbox contains existing assets to add to your game. You can also create your own assets through a 3D modeling program such as Blender3D, and Studio includes a set of mesh-editing tools to customize the 3D models already available.
The 3D Editor provides a view of the world. Hold your right mouse button to turn the view, and use the WASD keys to reposition the camera. Table 1.1 describes the different controls to move the camera.
The Explorer window provides convenient access to every key asset or system in the game. You use this to insert objects into your experience.
Use the Properties window to make changes to objects in the game, such as color, scale, value, and attributes. Select an object in the Explorer to see available properties. 4 HOUR Coding Your First Project
FIGURE 1.4 There are a number of panels, buttons, and lists in the Studio, and youâll quickly become familiar with them.
Key Movement
There are numerous ways to configure this main screen, including hiding different sections, rearranging their positioning to be more convenient, and changing their size.
Roblox Studio is a very complete game development environment that goes well beyond Lua. Itâs a big topic on its own, so you may want to check out our other book, Roblox Game Development in 24 Hours, for help. Opening the Output Window 5
1. Click the View tab (see Figure 1.5). If you ever close a window and need to reopen it, you can find it here.
FIGURE 1.5 Use the View tab to control which windows are open.
2. Click Output (see Figure 1.6) to display the Output window at the bottom of your screen, as shown in Figure 1.7.
FIGURE 1.6 Click the Output option to open the Output window.
3 a.
PETRERDOE:
FIGURE 1.7 The Output window opens beneath the 3D Editor. 6 HOUR âi: Coding Your First Project
1. Return to the Home tab and click Part (see Figure 1.8). The part appears in the 3D Editor at the center of your camera view.
FIGURE 1.8 Click Part on the Home tab to insert a part.
2. To add a script, in Explorer, hover over the part and click the + symbol, and then select Script from the drop-down menu (see Figure 1.9).
FIGURE 1.9 You use Explorer to insert a script into the part. Writing Your First Script
TIP Finding Items Quickly Typing the first letter (S, in this case) or two of the items you are adding filters the list so you can locate that item quickly. SSS ae ee
The script automatically opens. At the top, you see words familiar to any coder: "Hello world!" (see Figure 1.10).
FIGURE 1.10 The window shows the default script and code.
print () displays a string, which is a type of data usually used with letters and numbers that need to stay together. In this case, youâre printing "Hello world!":
1. Make this code your own by changing the message inside of the quotation marks to what you want for dinner tonight. Hereâs an example: print ("I want lots of pasta")
2. To test the code, in the Home tab, click Play (see Figure 1.11). 8 J)UR i: Coding Your First Project
FIGURE 1.11 Click Play to test your script.
Your avatar will fall into the world, and you can see your dinner dreams displayed in the Output window, along with a note about which script that message came from (see Figure 1.12);
FIGURE 1.12 The string is displayed in Output.
3. To stop the playtest, click the Stop button (see Figure 1.13).
FIGURE 1.13 Click Stop to quit the playtest.
4. Return to your script by clicking on the tab above the 3D Editor, as shown in Figure 1.14. Writing Your First Script 9
FIGURE 1.14 Click Script to return to the window where your script is visible.
Code an Explosion Code of course can do more than just display messages to the output window. It can completely change how players interact with the world and make it come alive. Letâs take a slightly longer piece of code and make the block in the Baseplate template destroy anything it touches:
1. Use the Move tool (see Figure 1.15) to move the block off the ground and away from the spawn point. The code youâre going to write will destroy anything it touches, and you donât want it to go off prematurely.
w@& Baseplate x
FIGURE 1.15 Move the part up and away from the spawn. 10 HOUR 1: Coding Your First Project
2. In the Properties window, scroll to Behavior and make sure Anchored (see Figure 1.16) is selected so the block doesnât fall when you click Play.
Parent
Position
Behavior
Anchored
Archivable
CollisionGroupid orilicinanf . da
FIGURE 1.16 Check Anchored to keep the blocks from falling.
3. In the script, below the print function, add the following code:
NOTE Code Boxes Code boxes for this book will be presented in light mode, unless specifically calling attention to Studio UX.
The result should be that your character breaks or parts of your avatar are destroyed. You may notice that this code only destroys what touches it directly, such as your feet. Try jumping on top Error Messages ddl
of the block or brushing against it with just a hand. You'll see only that part of your avatar is destroyed.
The reason is that code only does what you tell it, and you told the part to destroy only what it touches and nothing more. You have to tell it how to destroy the rest of the player. Throughout this book, you'll learn how to write additional instructions so that the code can handle more scenarios like this one. In Hour 4, âParameters and Arguments,â you'll learn how to make sure it destroys the entire player character.
Error Messages What if the code didnât work? The truth is, all engineers make mistakes in their code. Itâs no big deal, and the editor and the output window can help you spot mistakes and fix them. Try mak- ing a couple of mistakes to learn how to better spot them later:
1. Delete the second parenthesis from the print function. A red line appears under local. (See Figure 1.17.) In the editor, red lines indicate a problem.
FIGURE 1.17 A red line indicates Studio has spotted an error.
2. Hover over the red line, and the editor gives you a clue about whatâs gone wrong, as shown in Figure 1.18. But donât fix the mistake quite yet. 12 HOUR 1: Coding Your First Project
FIGURE 1.18 An error message displays when you hover over the red line.
3. Click Play, which causes an error message to display in the Output window, as shown in Figure 1.19. Click the red error, and Studio takes you to where it thinks the problem is.
FIGURE 1.19 The error shows up as a clickable red message in the Output window.
TIP Changes Made While Playtesting Arenât Permanent Be careful about making changes while in a playtest because the work you've done is not automati- cally saved. If you do make changes, be sure to click Preserve Changes when you stop the playtest.
Coders use comments to leave notes to themselves and others about what the code does. Trust us: When you haven't looked at a piece of code in months, itâs very easy to forget what it does.
The following code shows what it might look like to add a comment at the top of the script you wrote earlier in this hour: -- What do I want for dinner? print ("I want lots of pasta!")
Summary In just one hour, youâve come a long way, particularly if this happened to be your first time cod- ing or using Roblox Studio. This hour covered creating an account and opening Roblox for the first time. By using the + button, you were able to insert a script into a part, and then you added code that turned the part into a trap for anyone who happened to touch it.
In addition, you learned how to test code using the Play button and use the built-in error detec- tion within the script editor and Output window to help you troubleshoot when something goes wrong.
Finally, you learned about comments, which are only readable in the script editor and can be used to leave notes about the purpose of the code.
Q&A Q. Can you use Studio on a Chromebook?
A. To create, Studio must be run on a MacOS or Windows machine. Once a game has been published, itâs available to be played on Android, Apple, Mac, PC, Chrome, and potentially even XBox Live.
A. If you close out of the script editor, you can reopen it by double-clicking the script object in Explorer.
A. Go to File, Publish to Roblox to save to the cloud, which makes your game accessible from any computer.
A. You can visit developer.roblox.com to find documentation on all of Studioâs features and API. 14 HOUR 11: Coding Your First Project
Workshop Now that you have finished, letâs review what you've learned. Take a moment to answer the fol- lowing questions.
Quiz 1. Roblox uses the coding language.
2. Aspects of an object such as color, rotation, and anchored can be found in the window.
To enable the Output window, which displays code messages and errors, enable it in the tab.
Answers 1. Lua
2. Properties
3. Explorer
4. View
5 . False. Comments do not affect the code and are used to leave notes to yourself and other coders as to the purpose of the script.
Anchored
Exercise Before moving on, take a moment to experiment with the creation tools by creating a mini obsta- cle course. It could be individual parts the player has to avoid, or it could be a lava floor like the one shown in Figure 1.20. Exercise 15
FIGURE 1.20 Use what youâve learned so far to create a lava obstacle course.
Tips >» Create more parts and manipulate them with the Move, Translate, and Scale tools found on the Home tab (see Figure 1.21). You can also change the partsâ appearance with Material and Color.
FIGURE 1.21 The Home tab has the tools you need to create and manipulate parts.
> Use a single large part and insert a script as you did earlier to turn it into lava.
> Additional models can be found in the Toolbox; just be aware that some models may already have scripts in them.
If you know how to use the terrain tools, you can work that into your obstacle course as well. ae Pranedte: ead! Variablesâ
In this hour, you learn how to find the objects you want to make changes to in the hierarchy and create an adorable NPC (Non Playable Character) guide like the one in Figure 2.1 that can warn players of upcoming danger. To create the guide, you use code to update a partâs appear- ance and behavior.
FIGURE 2.1 An NPC warns players of upcoming danger. 18 HOUR 2: Properties and Variables
Object Hierarchy If you want to affect objects with code, you have to be able to say where those objects are within the gameâs hierarchy. As you look in the Explorer, you can see some game objects are nested inside of others. For example, in Figure 2.2, you can see that the Baseplate object is nested inside of Workspace. This makes Baseplate a child of Workspace, which makes Workspace the parent object. And even though you canât see it in Explorer, Workspace is a child of Game.
FIGURE 2.2 Baseplate is a child of Workspace.
In code, you can navigate the hierarchy of the game using the dot operatorâfor example, game.Workspace.Baseplate.
This is how you give directions in the script to tell the code what object to work with.
VV TRY YOURSELE
1. Insert a new script into Baseplate. Rename the script DestroyBaseplate by double-click ing it or pressing F2 (see Figure 2.3).
TIP
yBasepiate
FIGURE 2.3 You can rename a script.
3. Playtest the game, and the baseplate will be destroyed, possibly even before your charac- ter loads.
Keywords Now letâs talk about keywords. Keywords can be thought of as the words that make up a coding language. Each keyword serves a special purpose. Lua has fewer keywords than most coding lan- guages, which makes it one of the easiest to learn. Some keywords are built into Lua automati- cally, and some have been added by Roblox to make things easier.
One keyword in Roblox Lua is workspace, lowercased, because game .Workspace was typed so much, the thoughtful Roblox engineers decided to supply a keyword to shorten it.
TRY IT YOURSELF W Use the workspace Keyword Update the code you just wrote with the keyword workspace in place of game.Workspace.
TIP Correct Capitalization Is Important Keywords are case-sensitive, so make sure workspace is lowercased.
Now back to hierarchy. Not only can the children of objects be accessed with the dot operator, but so can parent objects. This time, use the keyword script, which always represents the Script object no matter what the object is named, and use the dot operator to access the parent.
Vv TRY IT YOURSELF
1. In the same script as before, replace your code with script .Parent:Destroy().
TIP Take Advantage of Autocomplete As you type, you may see suggested code appear. You can accept the suggestion by pressing Enter. This will save time on typing and minimize the risk of making typos.
You can find a complete list of keywords in the appendix at the back of the book.
Properties In addition to navigating the hierarchy, the dot operator also allows you access to the properties of an object. So what are properties? Iâll explain with an example: Take a look at the flower in Figure 2.4. How would you describe it to someone?
Maybe you would start off saying that itâs a plant. When pressed for more information, you might say that itâs a green plant with yellow petals. An engineer might add additional details like itâs a green plant with yellow petals, three units tall and two units wide. Someone else might mention itâs on fire (see Figure 2.5). Properties 21
FIGURE 2.4 How would you describe this flower?
FIGURE 2.5 The flower is also flammable.
> String: A collection of letters and/or numbers sandwiched within quotation marks. Good for storing readable information. print() accepts string valuesâfor example, "99 bananas".
> Boolean: The values true and false. Properties that have states like on/off or checked/ unchecked are often booleans.
Creating Variables Now that you have an understanding of how to find objects in the hierarchy and how each property has its own specific value format called a data type, you can begin making variables.
Variables are placeholders for information. They can be used to keep track of objects and data types for use in your code. Once created, some variables can only be used in specific scripts or chunks of code. These are called local variables. Other variables are designed so that they can be used more broadly across scripts. Those are called global variables.
Unless you have a good reason, you almost always want to use local variables. Your code runs faster with local variables, and you're less likely to end up with clashing variable names in your code. Almost all of the variables you create in this book will be local variables.
To create a local variable, type local and then the desired name of the variable, for example: local baseplate
Once the variable is created, you can assign, or set, the value of the variable using the equal sign, for example: local baseplate = script.Parent
In your head, you can think of the equal sign as the word is. So, the prior variable would read basePlate is script.Parent. Once the variable is created, you can access the information being held as many times as you want with just the name, for example: local basePlate = script.Parent basePlate.Transparency = 0.5 Creating Variables 93
Variables can be updated as often as you want. So if you're keeping score in a game, every time the player scores a new point, you can keep using the same variable and assign it the updated score, like so: local playerScore = 10
FIGURE 2.6 First, the original value of playerScore is printed, and then the updated playerScore prints.
TIP Combining Strings and Variables print () can accept both strings and variables, but they need to be combined with two dots. Combining values is called concatenation.
TRY IT YOURSELF W
Create an NPC With just the knowledge you have so far, you can create an NPC guide that delivers a warning about the upcoming lava field to the player. This exercise will help you practice navigating hierar- chy and properties using the dot operator, as well as using variables and data types.
Ba 4. Insert a Dialog object into GuidenPc. Do not rename it. (See Figure 2.7.)
FIGURE 2.7 The NPC hierarchy includes a Script and a Dialog.
1. Replace the default code in NPCScript with a new local variable named guideNPc that points at the scriptâs parent. local guide = script.Parent
TIP Object Naming Conventions For consistency, in-game objects are named using CamelCase with the first letter in uppercase, and variables named after them are pascalCased, with the first letter in lowercase.
2. Create a second variable holding the guideâs message with a string value. The message can be anything you like as long as itâs a string. local guideNPC = script.Parent local message = "Danger ahead, stay on the rocks!"
3. Make the NPC more ghostly by accessing its properties and setting Transparency to 0.5. local guideNPC = script.Parent local message = "Danger ahead, stay on the rocks!" guideNPC.Transparency = 0.5 Changing the Color Property 25
4. Access the child Dialog object and its property InitialPrompt. Set InitialPrompt to message. |„ | local guideNPC= script.Parent local message = "Danger ahead, stay on the rocks!" guideNPC.Transparency = 0.5 guideNPC.Dialog.InitialPrompt = message
Playtest and click the question mark above the NPC's head to see the message.
Turn your NPC purple by mixing a little red with a lot of blue: guideNPC.Color = Color3.fromRGB(40, 0, 160)
TIP Use the Color Picker to Find the Right Values As you type, a small color wheel will appear (See Figure 2.8). If you click it, you can select the color you want, and click OK to automatically set the correct RGB value.
FIGURE 2.8 Click the color wheel to bring up the RGB color selector. 26 HOUR 2: Properties and Variables
Instances The last topic for this hour is instances. Instances are copies of game objects like parts, scripts, and sparkles.
Rather than using the + button like you have so far, instances can instead be created with the function Instance.new(), as shown here: local part = Instance.new("Part")
Once you've created a part, you can access all of its properties like normal. Make any desired changes, and then parent it to the workspace.
Vv TRY IT YOURSELF
2. Create an instance of a part; then set the color and finally the parent: local part = Instance.new("Part") part.Color = Color3.fromRGB(40, 0, 160) part.Parent = workspace
You can even take this one step further by creating instances inside of instances: local part = Instance.new("Part") local particles = Instance.new("ParticleEmitter") part.Color = Color3.fromRGB(40, 0, 160) particle.Parent = part part.Parent = workspace
TIP New Part Instance Appears at the Worldâs Center When new parts are created via code, they appear at the very center of the world, where the default spawn point is. If you canât see your part when testing, try moving the spawn point and then testing again.
Summary Every object in a game has properties like Color, Scale, and Transparency that determine how the object looks and behaves in game. Each property uses values formatted in a specific way called a data type. A few common data types are strings, booleans, and numbers. Q&A 27
Within code, dot notation is used to access properties of an object, as well as to find the object in the Explorer hierarchy.
Once you understand an objectâs properties and how to access them in the gameâs hierarchy, you can begin making changes using code.
Variables can be used as placeholders for information that you want the script to work with. There are two main types of variables, global and local. Of the two, local variables should always be used unless there is a specific reason not to.
Game objects such as Parts, Scripts, Dialogs, and ParticleEmitters can be created in a running script by using the function Instance .new(), which accepts the name of the object type as a string.
Q&A Q. How do you know what data type a property accepts?
A. You can look up a game object, its properties, and their corresponding types on developer.Roblox.com. For example, in a search engine, type Roblox Dialog Properties and look for API results on the Roblox domain.
In Figure 2.9, you can see a portion of the Dialog API page. It has a short description and a list of properties with their matching data types. You can click each property and data type to learn more about how to use it.
Dialog The Dialog object allows users to create non-player characters (NPCs) that players can talk to using a list of choices. The Dialog object can be inserted into a part such as a Humanoid's head, and then a player will see a speech bubble above the part that they can click on to start a conversation. The creator of a place can choose what choices the player can say by inserting DiatogChoice objects into the dialog.
See Also:
Properties DialogBehaviorType BehaviorType
float ConversationDistance
The furthest distance that | player can be from the Dialog's parent to start a conversation.
bool GoodbyeChoiceActive
FIGURE 2.9 This snippet of the API page for Dialog shows a short description of properties and matching data types. 28 HOUR 2: Properties and Variables
Why should you not set a variable to include the property you want to change? Like local partColor = workspace. Part.Color?
The hierarchy information and the property information are two different types of data and canât be mixed.
Workshop Now that you have finished, letâs review what youâve learned. Take a moment to answer the fol- lowing questions.
Quiz 1. What type of data type only accepts the values true or false?
If | were storing a playerâs name, which would be a good data type: a string, a boolean, an enum, or a float?
Answers 2 Booleans
Placeholders
String
script.Parent
Child
Po RK aPw Concatenation
Exercises A face would make your NPC much more personable. One can easily be added on by inserting a decal. For the texture, you can use the one in the link provided or upload your own.
Tips > Give the spirit a face (see Figure 2.10) by inserting a decal instance and updat- ing the texture property with the following string: "http://www. roblox.com/ asset ?id=494290547" Exercises 29
» You may have to rotate the NPC to get it to face the right way. Or you can try updating the decalâs face property to change the placement of the decal.
FIGURE 2.10 The NPC now has a face, making it feel more alive to players.
>» For the second exercise, see if you can create the spirit guide from start to finish only using code.
Tips > Insert a new Script object into ServerScriptService to write your code in.
>» Use Instance.new() to create the part that will act as the guideâs body, the Dialog object.
» Donât forget to anchor the part. Anchored accepts boolean data types.
>» Make all changes to the part, including adding the children objects before finally parenting the guide to the workspace.
>» The NPC appears at the dead center of the world as a cube. In Hour 14, âCoding in 3D World Space,â you learn how to work with the coordinate system to move objects to exactly where you would like them.
green 7. 225an Pet, ay„ ys HOUR 3 Creating and Using Functions
In Hours 1 and 2, you used the prebuilt functions print (), destroy(), and new(). This hour talks more about what functions actually are, how to create your own functions, and how to get your functions to run using events that happen in the world.
The second half of the hour talks a little bit about how code is organized so you can better understand how placement in a script matters when making sure that code will run.
The code that you used in the last hour to create your NPC ran as soon as the playtest session started. But what if you donât want the code to run right away? Say if you only want the NPC to appear after the player clicks a button or completes a quest. Or what if you want to create mul- tiple NPCs, but donât want to write all the same code again. These types of scenarios are great for functions. You can write the code the same as you did, package it in a function, and have it run whenever you want.
2. Press Enter to automatically close the function with end. Your code will look like this: local function nameOfFunction()
end 32 HOUR 3: Creating and Using Functions
3. Inside the function, add your code on an indented line. In this case, we used print () just for testing purposes. All of the code for the function must be typed before end: local function nameOfFunction() print ("Function Test") end
TIP Indent Your Code The code will work if not properly indented. Even so, proper indentation makes code a lot easier for you and other people to read, so we highly recommend that you use indentation in your code.
4. Once the function is created, all thatâs left is to tell it to run. To do this, you have to call the function by typing the function name followed by a parenthesis: local function nameOfFunction() print ("Function Test") end
nameOfFunction()
5. The function will run as many times as you call it. Try calling it a few more times: local function nameOfFunction() print ("Function Test") end
nameOfFunction() nameOf Function () nameOf Function ()
The name of the function can be whatever you like, as long as itâs followed by (), but letâs take a second to think about properly naming functions. Here are some guidelines to follow:
> Names should tell you what the function does. For example, destroy () clearly destroys things.
» Function names in Lua are typically pascalCased. They begin lowercase, and each new word is capitalized.
> Do not include spaces or special characters in function names. This will cause errors. Using Events to Call Functions 33
TIP
Understanding Scope Just mentioned was that any code not between the first and last lines of a function wonât run when the function is called. Code that is outside a function is out of scope. Scope is the informa- tion that a particular chunk of code, such as a function, can see and access.
If you run the following code, the print function inside of the function will run three times, and the print function on the outside will run only once: local function scopeTest() print ("This is in scope") end
scopeTest() scopeTest() scopeTest()
For these sorts of scenarios, you donât know in advance when they'll happen, but you do know what code you want to run when they do. What you're waiting for is a specific event. When the event happens, a signal is fired that can be used to tell code to run. To call a function whenever an event has been fired, use Connect () and pass in the name of the function to run, but leave off the (). 34 HOUR 3: Creating and Using Functions
Hereâs an example: partName. Touched: Connect (functionName)
The Touched event is built into parts, so it can be accessed using the dot operator like other chil- dren. The colon is then used to access the function named Connect ().
Vv TRY IT YOURSELF
1. Create a bridge piece (see Figure 3.1) using parts or models. Make sure to anchor the part in place.
FIGURE 3.1 Use a model or a part to act as part of a bridge.
2. Insert a script into the part, and rename the script BridgeScript (see Figure 3.2). Using Events to Call Functions 35
ieyain
Location
FIGURE 3.2 Insert a script into BridgePiece.
end
TIP Naming Functions Used With Events A common naming pattern for functions that are called with events is onBlank, where Blank is the name of the event. Just another way of making your own code easy to read when you have to update it after a year.
5. Connect the function to the partâs Touched event. Once thatâs done, you can use print () to test your code so far: local bridgePart = script.Parent local function onTouch() print ("Touch event fired!) end
bridgePart . Touched: Connect (onTouch) 36 HOUR 3: Creating and Using Functions
hy 6. Inside of the function, add the code that should run when the event fires. Here, the part will turn transparent, and in 0.5 seconds, anyone standing on the bridge will be dropped. If you added a print statement in the last step, go ahead and delete it: local bridgePart = script.Parent
bridgePart . Touched: Connect (onTouch)
TIP Using Booleans CanCollide is a boolean. When true, that object can interact with things in the world. When false, it canât. So in this case, when false, the bridge no longer supports the user.
So if you try to use a variable or a function before itâs been created in the script, youâll run into problems (see Figure 3.3 and Figure 3.4). Take a look at the BridgeScript you just completed: local bridgePart = script.Parent
bridgePart . Touched: Connect (onTouch)
If you move the first line to the bottom, then the previous mentions of the variable now show errors. Understanding Order and Placement 37
bridgePart = script.Parens
FIGURE 3.3 Moving the creation of bridgePart to the bottom causes errors due to the unknown variable.
FIGURE 3.4 Calling a function before itâs assigned also causes issues.
In both of these examples, errors are caused because the script is trying to call something that isnât there yet.
Now that you know that order matters, letâs talk about variables that are created inside of a func- tion. Start with this basic set of three variables: one before the function, one inside, and one below: local above = "above"
At the bottom of the script, try to print all three variables. What happens? inside will error (see Figure 3.5) despite having been assigned previously. Thatâs because local variables inside of a function canât be accessed from the outside.
FIGURE 3.5 i Local variables inside of a function canât be accessed outside of the function. 38 HOUR 3: Creating and Using Functions
To understand why this doesnât work, you need to understand that a script is a series of nested blocks of code. Each time you create a new function, youâre creating a new block. Figure 3.6 illustrates how those blocks can overlap. The first block, block A, is the script itself. Inside is a function, shown as block B.
Script
Local Variable
Local Function
Local Variable
Conditional Statement
Local Variable
FIGURE 3.6 Functions create a new block of code within the script.
Within the function can be more blocks, created by conditional statements and other things that you'll learn about in a couple of hours. Each block can access local variables/functions in its par- ent block, but not those in any child blocks:
FIGURE 3.7 The bridge needs to be reset so users can cross it again.
1. In the same script you used in the earlier Try It Yourself, create a new function called activateBridge() above the onTouch function: local bridgePart = script.Parent
end
bridgePart . Touched: Connect (onTouch)
bridgePart . Touched: Connect (onTouch)
TIP Pay Attention to the Order of Your Functions activateBridge() needed to come before onTouch() in order to keep it in scope.
Summary Functions are reusable chunks of code that you can use again and again and again. Once defined, they can be called by simply typing functionName (). Or alternatively, if you donât know exactly when the function will be needed, you can connect them to an event. That way, the function will be called each time the event fires.
When creating a script, itâs important to keep in mind what information a chunk of code has access to. Variables and functions need to be within the scope of a code chunk to be used. Code chunks can access information within their own chunk and within their parent chunk. Trying to access information thatâs out of scope results in errors in the script.
A good way to practice scope is take a piece of code that you have working, like the bridge script, and move around functions and variables to see when things break. Workshop 41
Q&A Can you create a variable without assigning it a value if you donât know it yet?
te ase Yes, the variable can be created earlier and then later assigned a value.
Yes, you'll quite often have multiple functions created within a script.
Why not make everything global and not worry about scope?
In addition to global variables running slower than local variables, thereâs a lot of time you'll have to create multiple functions within the same script. These functions will all need their own variables. If you donât make your variables local, itâs very easy to accidentally overwrite a variable when you meant to make a new one.
Workshop Now that you have finished, letâs review what youâve learned. Take a moment to answer the fol- lowing questions.
Quiz 1. Whatâs another word for telling a function to run?
4. What are two reasons for using local variables instead of global variables?
5. True or false: If a local variable is inside of a function, it can be accessed by all functions further down the script.
6. What symbol is used to run a function associated with an object? Hint, think of how Connect () and Destroy() were used.
Answers 1. Call
2. Connect ()
3. Dot notation
4. Local variables run faster and prevent accidentally overwriting values in the case of dupli- cate names.
5. False. Variables are only accessible within their own code block and child code blocks.
A colon is used to call a function associated with an objectâfor example, part :Destroy(). 42 HOUR 3: Creating and Using Functions
Exercise Instead of having the bridge collapse after being touched, try creating a bridge or track piece that solidifies after a player touches a button and then resets. Create one function that activates the bridge when touched and a second function that deactivates the bridge (see Figure 3.8).
FIGURE 3.8 Users need to touch the buttons to activate the missing bridge piece.
Tips > Itâll be easier to use a single bridge part rather than multiple parts.
> Place the script in the button to take advantage of the Touched event.
> Make sure the bridge piece starts off disabled so that it can be enabled by the script.
Functions can not only perform tasks; they can perform like little factory machines that take things in, transform them, and then gives back the results. This hour covers the information that goes inside of the parenthesesâparameters and argumentsâand what a function can do with that info.
_ The values that get passed into a function through the parentheses are called arguments. When creating your own functions where information will get passed in, you need to create placehold- ers for the arguments. Those placeholders are called parameters.
_ To create your own parameters, add a variable name within the parentheses when you define the function, like so: local function functionName ( Dar n
wend: 44 HOUR 4: Working with Parameters and Arguments
W TRY IT YOURSELF
FIGURE 4.1 Use a function to repaint part of the building.
end
Create a parameter named paintColor that will act as a placeholder for what color to paint the wall with: local wall = script.Parent local function paint (paintColor)
end Working with Multiple Parameters and Arguments 45
4. Inside the function, set the color of the wall to the paintColor placeholder: local wall = script.Parent local function paint (paintColor) wall.Color = paintColor end
5. Add a variable or two with different RGB colors you might want to paint the wall: local wall = script.Parent
TIP Variable Placement You may have already noticed, but variables usually go at the top of the script or the code chunk they belong to.
6. Call the paint function and pass in one of the color variables: local wall = script.Parent
paint (blue)
Test it out, and whatever part you painted will be the color you picked!
Hard-coding really limits the use of the function unless you plan on changing the color of that one wall a lot. Luckily, you can pass more than one argument into a function. All you have to do is create more than one parameter.
Multiple parameter names can be separated with a comma when defining the function, as shown here: local function functionName (firstParameter, secondParameter) print (firstParameter .." and ".. secondParameter) end
aR How Many Is Too Many? Thereâs no technical limit to how many parameters you can have, but most people agree that no more than three is a good rule of thumb.
The arguments that get passed in always fill up the parameters in order. The first argument always goes through the first parameter, and the second argument always goes through the sec- ond parameter: Llocalstirst) = âtrrsit! local second = "second"
VS TRYAIYOURSEEE
FIGURE 4.2 A function with two parameters can be used to designate both what object to paint and what color to use.
2. Assign variables for two different colors and two different objects: -- Available colors local red = Color3.fromRGB(170, 0, 0) local olive = Color3.fromRGB(151, 15, 156)
~- Objects to paint local car = workspace.Car local restaurant = workspace.Buildings.Restaurant
=-TIP Finding Embedded Objects Note that for the second object in the example, the restaurant was in a folder, so dot notation was used to navigate one more step down the hierarchy.
3. Create a function that has a parameter to take in an object to paint and the color to paint it: -- Paints objects local function painter(objectToPaint, paintColor) objectToPaint.Color = paintColor end 48 Working with Parameters and Arguments
Ka 4. Call the function and pass in which object should be painted and what color: -- Available colors local red = Color3.fromRGB(170, 0, 0) local olive = Color3.fromRGB(151, 15, 156)
-- Objects to paint local car = workspace.Car local restaurant = workspace.Buildings.Restaurant
-- Paints objects local function painter(objectToPaint, paintColor) objectToPaint.Color = paintColor end
5. Test your code. Instead of Play, in the drop-down menu, select Run (see Figure 4.3) if you want to see the changes to the world without playing the experience.
FIGURE 4.3 Use Run to test code without loading a character avatar.
Figure 4.4 shows the finished car and building, which are no longer startlingly white. Returning Values from Functions 49
FIGURE 4.4 Once the script runs, both the building and the car have had their color changed.
-- Use add() to add rent and electricity and return the result local costOfliiving = add(rent, electricity) print ("Rent in New York is " .. costOfLiving) 50 HOUR 4: Working with Parameters and Arguments
1. Create a custom function with variables for wins, losses, and ties.
2. Type return followed by the desired variable. Use a comma to separate them: local function getWinRate() local wins = 4 local losses iT oO
local ties = 1 return wins, losses, ties end
3. Rather than creating variables for each received value on separate lines, create them on the same line as in the following example. They'll be filled in order with the returned values: local function getWinRate() local wins = 4 local losses " oOo
local ties) = 1 return wins, losses, ties end local userWins, userLosses, userTies = getWinRate()
Returning Nil Nil means something canât be found or doesnât exist. If you see nil printed instead of the expected output, do the following:
>» Check that the number of values received is the same as those returned.
TRY TT CYOURSELE. VV
1. In any script, pass a fake variable name like doesnt Exist into print ().
2. Run the code and check Output. You should see the nil alongside the name of the script and the line number of where the variable couldnât be found, such as the error shown in Figure 4.5.
If insufficient arguments are passed into a function, an error occurs when the function reaches the nil value: local function whoWon(first, second) print ("First place is " .. first .. "Second place is ") end
If more values are passed back than there are available variables, the values fill up in order, and any variables left over will be dropped and lost. In this example, three values are passed back, but there are only spaces for two: local function giveBack() local a = "Apple" local b = "Banana" local c = "Carrot" return a, b, c end
"Carrot" only exists within the scope of the function and is never returned, so nil is printed for the third value.
First, here is the script where a named function is created and then called whenever the Touched event is fired:
trap.Touched: Connect (onTouch) Summary 53
Now here is code that does the same thing, but the function is created in the same place where itâs being called:
If you were to run both pieces of code, they do the exact same thing: destroy anything that touches the scriptâs parent. So why wouldnât you use an anonymous function? The table shows some pros and cons for unnamed functions.
Pros Cons
Can be used with functions that donât return Trickier to update and reuse. values otherwise.
ile Named Functions Makes Collaborating Easier The Roblox Lua Style Guide discourages the use of anonymous functions when not necessary because most projects will have multiple coders, and anonymous functions make code much more difficult to read and update.
Summary Functions can be used and reused in a lot of different ways. They can be used to create some- thing, like the NPC in Hour 2. They can be used to make changes to an object by updating prop- erties or even destroying them altogether, as with a trap part. To do so, they can take in values from outside of the function by passing those values through parameters. The actual chunks of information that get passed through parameters are called arguments.
When the function completes its work, information can be passed back and used by the script. A classic example of information being returned is the calculator on your phone. If you use the calculator to add two numbers, itâll pass back the answer. Another example we've seen is when- ever the Touched event is fired, Touched passes back the name of the object that caused it to fire. 54 HOUR 4: Working with Parameters and Arguments
Sometimes, if thereâs nothing to return, you might use an anonymous function and create the function in the same place as itâs called. This can be convenient, but it also makes your code a lot harder to read, which possibly means slowing down teammates who are working with the script. It can even make it more challenging for yourself if you want to make updates to the code later.
Q&A Q. Is there a maximum amount of parameters a function can have?
A. Thereâs not a strict maximum, but most of the time, youâll want to limit it to just three. The more parameters you have, the harder it becomes to remember what each one is for, and easier to mess up the order.
Workshop Now that you have finished, letâs review what youâve learned. Take a moment to answer the fol- lowing questions.
Quiz = Handing information from outside of a function to the inside is called What keyword allows values to be handed back when a function is done?
The placeholder for values that will later be used by a function are called
oe. The keyword that is used when values canât be found or donât exist is :
Answers 1. Passing
return
Parameters
Arguments
ng og RR es Obnil Exercise 55
Exercise All coders will at times research how other people have made something. However, the code you find online might not do exactly what you want or be formatted in a way that makes it easy for your team members to read. Itâs important that you take the time to examine borrowed code and make improvements where you can. In this exercise, practice by taking an anonymous function and trying to reformat it as a named function: script .Parent .Touched: Connect (function (otherPart) local fire = Instance.new"Fire" fire.Parent = otherPart end)
Have you ever told someone you would do somethingâon one condition? For example, you'll help them move to a new place, but they have to help you study for finals. Thatâs a conditional structure. Youâll do something if something else happens.
The same thing can happen in scripts. You can set up code so that itâll run only if something else _ is true. Figure 5.1 shows a flowchart of how a conditional structure works.
This hour explores the world of conditional structures in which code runs only if certain condi- tions are met. op , LR 58 HOUR 5: Conditional Structures
if/then Statements The most common conditional structure is probably an if/then statement. If something is true, then the code will do something.
> Ifa quest is completed, then the user will receive a free pet.
> Ifsomeone says happy birthday in chat, then make a burst of balloons on screen.
If the first line is true, then the print command in the indented code will run.
Conditional statements can use operators to evaluate if something is true. Operators are symbols that give directions about how to evaluate something. Table 5.1 shows some of the most com- mon operators. For a complete list, refer to the appendix.
= Subtraction Tf 3 - 3 == 0 then i Multiplication TÂŁ 3 * 3 == 9 then
Pay close attention to the double equal sign used as an operator as compared to a single equal sign used to assign a value to a variable. A double equal sign == is used to check if something is equal. if/then Statements 59
ZÂŁ health-== 10 then print ("You're at full health") end
if health == 10 then print ("You're at full health") end
What if the player has temporary bonus health? You can check for that with the operator for greater than or equal to (>=): local health = 12
You can also check for the presence of a value if no operator is used. The following code snippet checks to see if a roof is on fire: local roof = script.Parent
It uses FindFirstChildWhichIsA () to see if any of the roofâs children objects are Fire objects. FindFirstChildWhichIsA () only gets the first object it finds that matches its search.
TRY 11 YOURSECE. „
Introducing Humanoids The lava in Hour 1 had a major flaw: It only destroyed whatever touched it directly, which means that users could possibly be running around without feet or hands if they managed only to brush against the trap, as in Figure 5.2. 60 UR 5: Conditional Structures
FIGURE 5.2 This person has lost their feet.
To reset the user completely, you need to find the object that controls the userâs health. In Roblox, by default thatâs the Humanoid object. If you use the Humanoid to set the userâs health to O, theyâll be forced to respawnâlegs, feet, hands, and all.
1. Create a part and insert a new script. You can use your lava from Hour 1 as long as you delete the old script.
4. Inside of the function, create a variable named character to find otherPartâs parent, if it has one: local trap = script.Parent
end if/then Statements 61
5. The next step is to check whether the character has a Humanoid. If it does, itâs most likely kd a user or an NPC:
if humanoid then
end end
if humanoid then humanoid.Health = 0 end end
if humanoid then humanoid.Health = 0 end end
trap.Touched: Connect (onTouch)
elseif OK, but what if you want the code to check for more than one scenario? For example, you want the code to do one thing if the userâs health is full, but a different thing if the userâs health is not full. In these scenarios, add a second conditional called with the keyword elseif: local health = 5
The elseif is still part of the same code block as if/then. It does not have its own end.
Logical Operators A few special operators arenât symbols; instead, logical operators are the words and, or, and not. and and or allow you to check against multiple conditions at the same time. not lets you make sure something isnât something else. Table 5.2 explains how these operators are evaluated.
These operators consider both false and nil as âfalseâ and anything else as âtrue.â
In the following snippet, and is used to check for a range rather than a single value. In this sce- nario, we imagine the user has a maximum of 10 health, and at 0 health respawns. So the user needs to eat only if they are less than full health: local health = 1 SSSSSSSSSS
if health S= 10 then print ("You're at full health")
elseif health >= 1 and health < 10 then -- Check if health is in a specific range print ("Find something to eat to regain health!") end else 63
You can keep creating code for specific scenarios as needed with additional elseif statements: local health = 1
else Finally, itâs always wise to tell the script what to do if none of the other contingencies are met. Use the keyword else to mark what should be done if no other conditions are met: local health = 0
Once again, else is not its own code block; the entire conditional should only use the keyword end once.
TRY 17 YOURSELF. „
FIGURE 5.3 A keystone (left) that needs to be activated before people can use the portal (right).
First, you need to set up the portal and keystone using either parts or models. In Figure 5.3, a model was used for the portal arch, but the portal itself is just a black part acting as a barrier. The arch is just for show.
Both the portal and keystone will need a new attribute to make the script work. Attributes are custom properties that you can name and set a value type for. An attribute named Activated will be created for each part to track whether the key has been found and if the portal can be used:
2. Select Portal, and insert a ProximityPrompt (see Figure 5.4). ProximityPrompts enable users to click and interact with parts instead of only being able to run up and touch them.
Ae tone
Ports
â ProximityPrompt +) ers
FIGURE 5.4 Insert a ProximityPrompt object.
3. With Portal still selected, in Properties, scroll all the way down and click Add Attribute (see Figure 5.5). else 65
Attributes
FIGURE 5.5 Click Add Attribute.
4. Name the attribute Activated, set the type to boolean, and then click Save (see Figure 5.6).
FIGURE 5.6 Name the attribute Activated and set the type to boolean.
5. Select KeyStone, create another new attribute named Activated, and set the type to boolean. 66 HOUR 5: Conditional Structures
WARNING Leave the New Attributes Unchecked Make sure to leave both new attributes disabled, as in not checked. Enabled/checked attributes and properties are true, whereas disabled/unchecked are false.
Next, you create two scripts: one for the key and one for the portal.
. Check to see if a person has touched the part by looking for a Humanoid. You donât want the code to be triggered by a touching baseplate or something similar. Refer to the code earlier in the hour if you canât remember how.
. Inside the function, use SetAttribute() to pass in Activated and change the value to true as shown:
if humanoid then keyStone:SetAttribute("Activated", true) end end
keyStone. Touched: Connect (onTouch)
Change the KeyStoneâs material to Neon to show the user that the KeyStone has been activated: local keyStone = script.Parent
if humanoid then keyStone:SetAttribute("Activated", true) keyStone.Material = Enum.Material.Neon end end
keyStone. Touched: Connect (onTouch)
TIP Alternatives for Textured Parts If your part has a texture, changes to materials and color wonât show up. You can delete the texture so these properties show or enable a particle when activated. The important thing here is to always show users when they have an interaction with a part.
6. Test and make sure the part changes to neon, as shown in Figure 5.7.
FIGURE 5.7 is The KeyStone (left) glows neon blue (right) after itâs activated so users know itâs working.
Portal Script Back to the portal itself: When the user walks up to the portal, the ProximityPrompt displays a message showing the barrier can be interacted with, as shown in Figure 5.8. 68 HOUR 5: Conditional Structures
(e) Interact
re |
FIGURE 5.8 A ProximityPromptâs default message is shown to users when they get close enough.
ProximityPrompts have a number of associated functions that can be used, but they arenât auto- matically included with the functions normally available in a script. We can make these functions available to us by adding the ProximityPromptService to the script. Services are optional sets of code that provide additional functions for your script to use. These code sets can be made avail- able for use by assigning them to a variable with GetService(). Hereâs an example: local ProximityPromptService= game:GetService ("ProximityPromptService")
TIP Using Colons with Methods As a reminder, when accessing methodsâthat is, functions associated with an objectâyou use colons. Here, GetService() is associated with the top-level object, game:
to oe Create a new function named onPromptTriggered: local ProximityPromptService = game:GetService ("ProximityPromptService")
end
TIP Naming Functions You may have noticed that the formula of on and the name of the event is a common way to name functions.
5. Connect the function to the Prompt Triggered event that comes with ProximityPromptService: local function onPromptTriggered()
end
ProximityPromptService.PromptTriggered: Connect (onPromptTriggered)
6. Inside of the function, get the current value of the KeyStoneâs attribute, Activated: local function onPromptTriggered() local KeyActivated = keyStone:GetAttribute ("Activated") end
else portal. Color = Color3.fromRGB(255, 0, 0) wait (1) portal.Color = originalColor print ("Activate the key stone to pass through the portal") end end
ProximityPromptService.PromptTriggered: Connect (onPromptTriggered)
TIP Taking into Account Multiple Player Interactions A variable is used to get the portalâs original color at the top of the script instead of in the function. Otherwise, if you spam the interaction, the function might start while the portal is still red. The vari- able would then be assigned red instead of the original color.
The script is finished! Test it out and make sure people can use the portal. In the Property win- dow, you can even customize the text of the ProximityPrompt (See Figure 5.9) and how far away players have to be.
FIGURE 5.9 You can customize the ProximityPrompt text. Workshop 71
Summary Using conditional statements can really make your world start to come to life, allowing you to set up cause-and-effect reactions in the world. If people touch something dangerous, then they lose health. If they touch something else, magical powers can be given or new doors can open.
The keywords if/then, elseif, and else are what allow you to create the flowchart for what code should run under what circumstances. The script checks each condition starting from the top, and if the condition is true, the code for that section is run. The rest of the code in the if / then statement is skipped. If nothing is true, an else can be used to say what the code should do.
While setting up these interactions, always think of the people who will be experiencing the world you're creating. Include visual clues such as color changes or special effects to make sure the user understands an object is working as intended.
Workshop Now that you have finished, letâs review what youâve learned. Take a moment to answer the fol- lowing questions.
Quiz 1. A double equal sign (==) means ___
$8 7 PASWhat does the operator or do?
Answers 1. Is equal
2. elseif shouldnât be its own code chunk. It should be on the same indent level as iÂŁ, and there should only be one end. 72 HOUR 5: Conditional Structures
GetService()
<=
Logical operators
& 2 Sue Evaluates as true if either condition is true.
Exercise Give a player super powers by making them go fast when they touch a speed booster! A Humanoidâs default WalkSpeed property is set to 16. Not so bad, but itâd be a lot cooler to goa lot faster. Make a part that temporarily allows a user to go way faster and then returns them to their original speed after a few seconds.
To do so, you can use the onTouch pattern youâve been working with and some if/then state- ments. As an added twist, use a ParticleEmitter object to stream sparkles behind them while theyâre powered up (see Figure 5.10).
Tips >» ParticleEmitters can be stored in ServerStorage.
>» Change the Humanoidâs WalkSpeed property from the default value of 16 to 50.
» Use the method Clone() to make a copy of the particle and then parent it to the runner.
» After a few seconds, reset WalkSpeed to 16, and destroy the ParticleEmitter.
FIGURE 5.10 Stars stream behind this running ninja while theyâre powered up.
Now that you know what a humanoid is, and how to check for one using if, you can start cre- ating code that doesnât just destroy things or set a userâs health all the way to 0. Instead, you can start making things happen incrementallyâthat is, only by a certain amount at a time. Instead of taking a userâs health all the way to 0, you can make it go down only part way, or a userâs wealth can go up by one gold every time they mine a piece of ore.
The second half of this hour shows methods for checking and improving existing code. You'll use string statements to check where your code might have gone wrong, see how to set up systems that prevent individual users from spamming interactions, and find out how to start bringing more of the design process into your coding.
The problem is thatâbecause of how the physics engine handles collisionsâthe code will trigger multiple near-simultaneous events and cause more damage than intended. In Figure 6.1, you can see from the time stamp on the left that the personâs current health went down very quickly.
FIGURE 6.1 The output message shows the trap was activated more often than anticipated.
We donât want the code to run so many times so fast. We want to make sure it runs only once and doesnât run again until we say it can. Ensuring that an action is triggered only once when it would otherwise be triggered multiple times is known as debouncing.
Hereâs the previous code snippet but with a debounce system that deactivates the trap for a set amount of time: local trap = script.Parent local RESET SECONDS = 1 -- How long the trap will be disabled local enabled = true -- Needs to be true to damage user local function damageUser (otherPart ) local partParent = otherPart.Parent local humanoid = partParent :FindFirstChildWhichIsA ("Humanoid") if humanoid then if enabled == true then -- Check that trap is currently enabled enabled = false ~-- Set variable to false to disable the trap humanoid.Health = humanoid.Health - 10 print ("OUCH!") wait (RESET SECONDS) -- Wait for reset time duration enabled = true -- Re-arms trap end end end trap .Touched: Connect (damageUser) Donât Destroy, Debounce 75
With this system, the player will only be harmed if enabled is equal to true.
TRY IT YOURSELF W
Mining Simulator An excellent example of when you donât want code to run more often than intended is when giving users gold or points. Here you create a mining simulator where users get gold every time they mine a pile of ore like the one in Figure 6.2. This Try It Yourself uses ProximityPrompts for the mining mechanic and a leaderboard where people can see how much gold they've collected so far.
FIGURE 6.2 Sparkling gold ore, just waiting to be mined.
People
@ Cherpl
» Gold Ore ' Mine
FIGURE 6.3 The leaderboard in the top-right corner shows peopleâs names and how much gold they've collected so far.
Whenever a player enters the game, they should be added to the leaderboard. This can be done as follows:
> im ReplicatedStorage v 4 ServerScriptService fF? Leaderboard â=â ServerStorage > gay StarterGui
FIGURE 6.4 The Leaderboard script should be placed in ServerScriptService.
2. Get the Players service and connect a function to the PlayerAdded event: local Players = game:GetService ("Players") local function leaderboardSetup (player)
end -- Connect the "leaderboardSetup()" function to the "PlayerAdded" event Players. PlayerAdded: Connect (leaderboardSetup) Donât Destroy, Debounce UT
3. Inside the connected function, create a new Folder instance, name it leaderstats, and ha parent it to the player: local function leaderboardSetup (player) local leaderstats = Instance.new("Folder") leaderstats.Name = "leaderstats" leaderstats.Parent = player end
TIP Make Sure the Name Is leaderstats Itâs really important that the folder is named leaderstats (all lowercase). Roblox wonât add the player to the leaderboard if any other name variation is used.
4. Set up the actual stat that you see in the corner of the screen. Do your best to follow the steps without looking at the code box:
b. Name the IntValue Gold. What you type here is exactly what will be shown to users.
TIP IntValue Objects Can Help Track Values IntValues are special objects that only accept integersâthat is, whole numbers. That way, you donât accidentally end up with something like 6.7 points. 2 a eS ee un 78 {OUR 6: Debouncing and Debugging
2. Insert a ProximityPrompt.
3. Name the ProximityPrompt Goldore. This is important because we're going to use the name to check whether we have the right proximity prompt.
> HoldDuration: 1 (This is the amount of time users need to hold the interaction to mine the ore.)
FIGURE 6.5 Modify the ActionText, HoldDuration, and ObjectText properties. Donât Destroy, Debounce 79
5. Select the gold ore part, and add a new attribute named ResourceType, set to string. Lv 6. Set ResourceType to Gold, as shown in Figure 6.6.
air Attributes Can Make Your Code Reusable Using an attribute to tag the ResourceType means you can use this same script for other collectible objects.
Surface
Attributes
Resourcelype
FIGURE 6.6 Add a new attribute named Resource with its value set to Gold.
With the PromptTriggered event, you can tell if the player has held the button the required amount of time:
2. At the top of the script, get ProximityPromptService. Then, create a variable for how long the prompt will be disabled once it is used. See if you can remember how to do it without referring to the code example in step 3. 80 /OUR 6: Debouncing and Debugging
RA 3. Create a new function connected to the PromptTriggered event with parameters for prompt and for player, in that order. This way, you know when the user is done holding the button: local Players = game:GetService ("Players") local ProximityPromptService = game:GetService ("ProximityPromptService")
end ProximityPrompt Service. PromptTriggered: Connect (onPromptTriggered)
TIP You Need to Account for Both Arguments When the prompt is triggered, both the specific prompt that triggered it and the player who triggers it is returned. You only need to know the player, but remember that returned values are always returned in order. So if you want the second returned value, you need two placeholders.
4. There can be lots of proximity prompts in your game, so find the promptâs parent and see if it has an attribute named ResourceType: local ProximityPromptService = game:GetService ("ProximityPromptService")
ProximityPromptService.PromptTriggered: Connect (onPromptTriggered)
5. If there is a resourceType, and prompt . Enabled is equal to true, disable the prompt: local function onPromptTriggered (prompt, player) local node = prompt.Parent local resourceType = node:GetAttribute("ResourceType") if resourceType and prompt.Enabled then prompt.Enabled = false end end Donât Destroy, Debounce 81
6. Find the player's leaderstats and then use the resourceType to update the leaderboard Aa stats as shown:
end end
7. After a certain amount of time has passed, re-enable the prompt so that it can be used again: local ProximityPromptService = game:GetService("ProximityPromptService") local DISABLED DURATION = 4
end end
ProximityPromptService. PromptTriggered: Connect (onPromptTriggered)
You've finished! Add some visual indicators when the ore is disabled, like changing the transpar- ency or color of the ore. (See Figure 6.7.) When itâs working as you like, go ahead and duplicate the ore as many times as you want.
The cool thing is that because you just have the single script in ServerScriptService, if you need to make changes to the script later, itâs really easy no matter how many copies of the ore you add to your game. 82 HOU Debouncing and Debugging
People
@ Cherpi
FIGURE 6.7 Now you have a dark disabled gold ore in the foreground and an enabled bright gold ore in the background.
TIP Saving Player Data With the code you have so far, the user has to start over every time they join the game. Hour 17 explores how to save usersâ data between sessions.
The next step in figuring out where things went wrong is trying to find where the code didnât run as expected. Maybe a function wasnât actually called or the given values werenât what you'd expect. One way to narrow things down is combining print statements with your knowledge of scope. Use print statements to verify variables are what you expect and code is running when you would expect.
For example, if you want to make sure a function was called, put a print statement right at the beginning of the function: local speedBoost = script.Parent
speedBoost. Touched: Connect (onTouch)
If for some reason you donât see "onTouch was called!" in the output window, you know the function was never called. Maybe the event didnât fire, or itâs not connected to the function. If you do see the message, you need to check whether the problem was with the next code chunk and verify if code chunks are running when expected. The following code snippet is for creating a speed boost. The code can be placed within a script inserted into a part.
A print statement is used to verify the userâs walk speed before the conditional, when the walk speed is supposed to have been changed, and after itâs set back to normal.
This way, you can verify the code is running, and WalkSpeed is changing as you would expect: local speedBoost = script.Parent
speedBoost . Touched: Connect (onTouch) 84 HOUR 6: Debouncing and Debugging
Once you're done testing your code, always go back and delete all unnecessary print state- ments. Every line of code that runs makes the script just a little bit slower because there is more for the script to do. Deleting unnecessary code keeps things running as quickly as possible.
This code snippet does the same thing as the previous snippet, but variables have been created at the top for how fast players will go, and for how long the boost will last: local speedBoost = script.Parent
speedBoost . Touched: Connect (onTouch)
In a very long script, this saves you a lot of time with updating while experimenting to find just the right value to useâparticularly if the same value is used in multiple places.
Variables like the ones created for NEW_SPEED and BOOST_DURATION, which never change value throughout the entirety of a script, are called constants. Unlike normal variables, they are typed in ALL_CAPS with words separated by an underscore (_). Figuring Out Where Things Go Wrong 85
TRY IT YOURSELF W
1. Add a new part or mesh and insert a script into it, as shown in Figure 6.8.
FIGURE 6.8 Insert a new script into a mesh or part.
2. Give people a temporary speed boost after having touched the part. You can use the code snippets from earlier in this chapter, or if you did the Try It Yourself in Hour 5, use that and modify the code to use constants.
3. Experiment with the values for BOOSTED SPEED and BOOST DURATION by doubling the values for both BOOSTED SPEED and BOOST DURATION.
4. Test and see the results. If it doesnât seem fast enough or boosted for long enough, dou- ble the amount again. If it feels like too much, subtract half of what you added.
Good code takes into account that errors happen when incorrect value types are passed in. If you were to try to pass a string into wait (), the string is ignored, and a built-in default value of a thirtieth of a second is used: local part = script. Parent wait ("twenty") -- Will use default value because strings aren't accepted part.Color = Color3..fromRGB(170; (07 255)
Summary There are a lot of ways to make sure your code runs only once using different types of debounce systems. One way you may have used before is deleting a part as soon as something touches it. Two other ways used in this hour are setting up a debounce variable and using a proximity prompt with a long hold. No matter what way is used, always think about how your choices will affect your end user.
A big part of being a coder is thinking about all the possible scenarios that might come up and trying to create code that wonât break while still giving your users the best possible experience. It is, of course, totally normal for things to go wrong. It happens to the very best codersâeven the ones who make your favorite Roblox experiences.
If things arenât going as anticipated, you can use your knowledge of scope, functions, and events to narrow down the problem. A few well-placed print statements can help you verify whether your functions are called and if values are what you expect.
Q&A Q. In leaderboards, can you use values other than IntValues?
A. Yes, you can create other types of values. For example, in Figure 6.9, a StringValue was used to display the faction name of the character. Workshop 87
a Cherp! Ancients 4
FIGURE 6.9 The leaderboard at the top right uses strings to display faction names and integers to display owned gold.
A. A maximum of four stats can be displayed, although additional stats can still be tracked.
> You can! In later hours, you learn more about how to display information to individual peo- ple and to everyone in the server as a whole.
Workshop Now that you have finished, letâs review what youâve learned. Take a moment to answer the fol- lowing questions.
Quiz 4. What is it called when you make sure code can only be triggered once and not multiple times?
2. Whatâs a name for variables whose values donât change as the script runs?
3. How are the variables in the preceding question formatted compared to other variables?
4. What's an easy technique for figuring out what value numbers to use when tweaking code for a better user experience?
Answers Ls Debouncing
. Constants
ON Ww pf ao You need to first assign the attribute to a variable, and then print the variable: local armorValue = Helm:GetAttribute ("Armor") print (armorValue)
Exercises A large part of any engineerâs job is thinking about both what can go wrong and how to make things better. Think about the code youâve created in all of the hours up until now, and for this first exer- cise, write down at least three ways in which the code could be better. It might be things that can go wrong with the code, or features that would enable people to better enjoy your experience.
You might not be able to code the solutions just yet, but you should get in the habit of being criti- cal of the code youâre creating.
For the second exercise, make two pickups: one that makes you smaller and one that makes you larger (See Figure 6.10). Instead of setting three specific sizes, use a multiplier to change the avatarâs current scale.
a =.
FIGURE 6.10 A giant mech avatar strolls through a city after being enlarged. Exercises 89
Remember to create a debounce variable to control how fast the person is allowed to grow and shrink. This is one case where if the function is triggered more often than intended, your experi- ence will crash.
Tips >» You can modify the userâs default scale with the following properties:
» Set up a simple debounce before experimenting with the avatarâs scale to avoid crashes.
>» Save your work before testing! If the avatarâs scale gets too big, it will crash your experience. âą ore oy te (bem â2
gas Âą \ be#03 : â_ _ , " stand 1
F fimo! « :
| | i = t <> =p wa 2 QU Nal? â9s âse , e I = hm @eeogney Ne ; ; . mt âye = drat oe - a i+ » GA ime " ee one at ih mi HOUR 7 while Loops
Do you ever feel like youâre stuck in a loop where you just keep doing the same thing over and over and over and over? Get up, eat breakfast, work hard, go back to bed, and then the same thing all over again the next day. We see loops throughout our world. The minutes on our clocks loop through 60 minutes, and the hours loop 24 times to create a day.
Scripts have loops as well. When theyâre inside of the loop, they keep doing the same task until something makes them stop. This hour covers just one kind of loop that can be found in code: while loops.
The main keywords here are while and do. In the middle of those keywords is the condition for the while loop to check against. As long as that condition is true, the code keeps running. In fact, if you want the code to run forever, you can simply set the condition to true: while true do print (count) 92 HOUR 7: while Loops
Count, = count +d wait (1.0) end
The preceding example would count every second and display the result in the output until you stop the playtest.
VeaIRY-li YOURSELF
while true do discoPiece.Color = Color3.fromRGB(0, 0, 255) wait (1.0) discoPiece.Color = Color3.fromRGB(255, 170, 0) end
Run the code. The only color you see while the code runs is blue. Because the next loop starts immediately, the orange blinks so fast itâs not even visible.
Fix this by adding a second wait function after the color change: local discoPiece = script.Parent
while true do discoPiece.Color = Color3.fromRGB(0, 0, 255) wait (1.0) discoPiece.Color = Color3.fromRGB(255, 170, 0) wait (1.0) end Some Things to Keep in Mind 93
If the entire loop only needs a single wait function, it can be worked into the condition. This is demonstrated in the following code chunk that assigns a new random color every second for a floor like the one in Figure 7.1: local discoPiece = script.Parent
while wait(1.0) do -- Get random values for RGB local red = math.random(0, 255) local green = math.random(0, 255) local blue = math.random(0, 255) -- Assigns color values discoPiece.Color = Color3.fromRGB(red, green, blue) end
FIGURE 7.1 A while loop and random number generation are used to create an ever-changing disco floor.
TRYSTE-YOURSELE. 2
FIGURE 7.2 A fire where fuel will be spent over time using a while loop.
Set Up First, set up the fire and the ProximityPrompt. Once everything is scripted, the fire can be copied into any environment or model you please: 1. Use an invisible part to hold your fire.
Name: Fuel
Type: Number
3. Insert a Particle Emitter named Fire, and a ProximityPrompt named AddFuel. (See Figure fee)
FIGURE 7.3 Use an invisible part with a ParticleEmitter and ProximityPrompt inserted.
TIP Designing a Fire For the fire particles, setting the Texture property to 4797593940 and Speed to 0 will help you get a particle like the one shown in the example. After that, try playing with the color, drag, and lifetime values. repe agesal Se NS ev eS ano Some Things to Keep in Mind 95
4. In the ParticleEmitterâs properties, uncheck Enabled because itâll be turned on within the Ly Script.
The Script When the ProximityPrompt is triggered, fuel is added to the fire, and the fire is enabled. A while loop spends fuel every second, and when the fuel reaches 0, the fire is disabled:
2. Get ProximityPromptService and set up a function that is called when the prompt is triggered. Inside, make sure the prompt is enabled and confirm that the triggering prompt iS "AddFuel":
local BURN_DURATION = 3
end end
ProximityPromptService.PromptTriggered: Connect (onPromptTriggered)
3. Create a constant to control how long the fire will burn; inside the if statement, create variables for the campfire part and the fire particles: local ProximityPromptService = game:GetService ("ProximityPromptService")
ProximityPromptService. Prompt Triggered: Connect (onPromptTriggered)
end end
5. Use another if to check whether the particles are off, and if so, turn the particles on: local function onPromptTriggered(prompt, player) if prompt.Enabled and prompt.Name == "AddFuel" then local campfire = prompt.Parent local fire = campfire.Fire -- This should be the particle emitter
6. Burn off one piece of fuel at a time with a while loop, and then disable the particles: local ProximityPromptService = game:GetService ("ProximityPromptService")
local BURN_DURATION = 3
ProximityPromptService . Prompt Triggered: Connect (onPrompt Triggered) Some Things to Keep in Mind 97
Check your work. If the UI is getting in the way of seeing the fire, you can move it higher in the Aa promptâs properties by using the UlOffset (see Figure 7.4).
Behavior
Archivabl
Localization
FIGURE 7.4 The promptâs UlOffset property can be used to move it out of the way.
Once you know the campfire works as intended, you can add it to fancier environments like those shown in Figure 7.5.
© Add Fuel
es Ck
FIGURE 7.5 Fire inserted into a great chalice on the left and a hearth on the right.
If you want to expand on this, you could have players collect wood from nearby trees before being able to light the fire. 98 HOUR 7: while Loops
Summary As you create more experiences, youâll find more instances of when you want code to keep repeating forever or under certain circumstances. Some loops will be small and quick, like a loop creating a flickering light. Other loops will be longer and control the entire flow of a gameâfor example, the loops found in round-based games where people wait in a lobby for a certain amount of time and then are transported to wherever the action is. At the end of the round, everything is cleaned up, people are sent back to the lobby, and then the loop starts over again.
Of course, there are things to keep in mind when using while loops. Because a while loop runs forever, code beneath the loop will never be reached unless the loop stops. Itâs also its own code chunk, so you need to keep in mind how that affects scope.
If you donât want the loop to begin as soon as the server is launched, you can always wrap the while loop in a function if you want to control when it starts.
Q&A Q. What if you want a piece of code to repeat only a certain number of times?
A. If you want a piece of code to repeat a certain number of timesâfor example, if you want to create exactly ten treesâyou can use what's called a for loop. for loops are covered in Hour 8.
Q. What if you want a loop to run while something is false instead of while true?
A. If you want a piece of code to run while a condition is false, you have a couple of options. The first is that you can set a condition such as while NumberOfPlayers ~= 0 do. Here, a piece of code runs as long as the number of players is not equal to zero. Alternatively, you can use repeat action until (condition), which instructs a piece of code to repeat indefinitely until a condition becomes true. Workshop 99
Workshop Now that you have finished, letâs review what you've learned. Take a moment to answer the fol- lowing questions.
Quiz 1. How long will a while loop run?
4. How many colors will the discoFloor referred to in this code turn? local discoFloor = script.Parent
while wait(2.0) do print ("hello") end
while true do discofloor.Color = Color3.fromRGB(0, 0, 255) -- Blue wait (1.0) discofloor.Color = Color3.fromRGB(255, 255, 0)-â-- Yellow end
Answers 1. Until the given condition is false.
2. A wait function must always be included; otherwise, the code loop will run faster than the engine can handle and crash.
3. Hello will print every 2 seconds. Thereâs a one-second wait in the condition, and a one- second wait in the loop. The second wait isnât needed, however. It could just be a two-sec- ond wait in the condition.
4. The floor will never change colors. The first loop prevents the second loop from ever run- ning. If that wasnât there, however, it would appear blue. Yellow would flash by too quickly to see, and pink is outside the scope of the loop. 100 HOUR 7: while Loops
Exercises In this first exercise, modify the code so that people have to collect wood for the fire rather than being able to simply walk up to a fire and light it (see Figure 7.6).
FIGURE 7.6 Logs can be collected from the tree and used to fuel the campfire.
Tips >» Use the leaderboard to track how much wood the player has.
>» You can use nearly the exact code and set up that was used for ore in the last hour to collect the logs.
> Modify the campfire script so that it takes logs from the player to use as fuel.
A universal truth to coding and design is that youâre going to find yourself wanting to update things later. The more copies you have of something in your game, the harder making updates becomes, whether scripts, particles, or models. For the second exercise, try updating the fire script so that instead of enabling an existing particle emitter, it inserts a cloned particle emitter into the campfire.
Tips > You still need a part to hold the ProximityPrompt.
> See if you can remember how to clone things out of ReplicatedStorage. What Youâll Learn in This Hour: » How to repeat tasks with for loops >» How to use nested loops >» How to exit nested loops >» How to create displays for information >» How to do damage over time
So far, weâve covered one type of loopâthe while loop, which can go forever and ever and ever if thatâs what you want it to do.
If you want to make sure that code updates only a certain number of times, you use a different kind of loop: a for loop. Unlike while loops, for loops repeat themselves a certain number of times until a goal is reached.
Figure 8.1 shows a for loop being used to count down until an expected meteor collision.
FIGURE 8.1 âe A clock uses a for loop to show three seconds until impact. 102 HOUR 8: for Loops
Vv TRY IT YOURSELF
Create a Countdown Test out this simple for loop that counts down to O. The individual parts of the code will be explained in the next section:
2. Run the code. In the Output window, you should see a countdown like the one in Figure 8.2.
FIGURE 8.2 Numbers count down one by one from 10 to O.
> Control variable: Tracks the current value. The assigned value marks the starting place. A control variable can be any acceptable variable name. Like other variable names, a con- trol variable name should be clear and descriptive about what the for loop is doing.
> End or goal value: The value at which the loop should stop running. The script checks the control variable against the end value before starting the next loop.
» Increment value: The amount by which the control variable changes every time. Positive increment values count up; negative increment values count down.
FIGURE 8.3 The three values that control how many times a for loop runs are the control value, the end value, and the increment value.
Beginning at the initial value of the control variable, the for loop counts toward the ending goal value, stopping once the goal value is reached:
1. The for loop compares the control variable with the end value. (See Figure 8.4.)
Start loop
Do stuff
FIGURE 8.4 Before executing the code in the loop, the control variable is checked against the goal value.
2. After running the code, the increment value is added to the control variable. The loop then checks the control variable and starts over. (See Figure 8.5.) 104 HOUR 8: for Loops
FIGURE 8.5 At the end of the loop, the increment value is added to the control variable.
3. Once the control variable passes the end value, the loop will stop. For example, if a loop has an end value of 10, once the control variable has passed 10, the for loop will stop (see Figure 8.6).
End loop
FIGURE 8.6 This is the flow of a complete for loop process.
Letâs take another look at the Output shown in the Try It Yourself, displayed in Figure 8.7. How for Loops Work 105
FIGURE 8.7 This output of a for loop counts down every second.
The loop that ran each time a number was printed is called an iteration. An iteration is the com- plete process of checking the control value, running code, and updating the increment value. Since the count started at O and ended after 10, the code actually went through eleven iterations.
Keep this in mind as you design your loops. If itâs important for a count to go a specific number of times, you'll probably want the starting value to be 1 instead of 0.
Counting Up by One for count = 0, 5, 1 do print (count) wait (1.0) end
Be careful not to reverse the starting and goal values, like so: for count = 10, 0, 3 do print (count) wait (1.0) end
If the control variable starts out beyond the end value, like in the earlier example, the for loop doesnât run at all. In this case, the for loop is counting up and checking if count is greater than 0. When the for loop does its first check, it sees that 10 is greater than 0, so it stops the loop without printing anything.
WV ctRYAT YOURSELF
In-World Countdown So far, messages have only been displayed within the Output window. Now itâs time to start com- municating information to people in your environments. In this Try It Yourself, you use a graphical user interface (GUI) to display information where everyone can see it. GUIs are like sticker labels that can be used to display information within the world.
Setup For the setup, you create a SurfaceGui and TextLabel and size them to the part to display the countdown. Since this is a coding book, we won't get too much into how these work. If you want to know more, you can find more detailed explanations on the Roblox Developer Hub:
2. Insert a SurfaceGui object into the part. Nothing obvious happens, but SurfaceGUI objects act as containers for anything you want to display. How for Loops Work 107
3. Select SurfaceGui and insert a TextLabel object. This displays the actual text. (See Figure iv 8.8.)
FIGURE 8.8 The TextLabel is added on the front of a part.
TIP
4. Select the TextLabel. In Properties, expand Size. For X Scale, type 1, and in Offset, type 0. Do the same for Y. This should make the TextLabel take up the entire side of the part. (See Figure 8.9.) 108 HOUR 8: for Loops
FIGURE 8.9 The TextLabel takes up the entirety of the side.
5. Still in TextLabelâs properties, scroll almost to the bottom to TextScaled and enable it. This sizes the font to fit as shown in Figure 8.10.
FIGURE 8.10 The text is automatically scaled to fit the entire TextLabel. Nested Loops 109
2. Use variables to reference the scriptâs parent and the TextLabel. Hint: You can go down the hierarchy a couple of times.
4. Within the loop, set the TextLabelâs Text property to the current value of the countdown: local sign = script.Parent local textLabel = sign.SurfaceGui.TextLabel
TIP A Note on Load Times You may notice that sometimes the count seems to start in the middle. Thatâs because the script started before your character and camera loaded all the way in. You can verify that the countdown ran correctly by using a print statement or delay the beginning of the count with a small pause at the beginning of the script. As you create more scripts, you begin having to take into account load times more often.
Nested Loops Loops can be used within loops. One of the most common ways you see this done is placing a for loop inside of a while loop. This way, you can repeat events that repeat every so often, such as firework shows:
while true do for countDown = 10, 1, -1 do 110 HOUR 8: for Loops
textLabel.Text = countDown print (countDown) wait (1.0) end
When loops are nested, the script starts from the top line and works its way down. When a new loop is reached, that loop runs to completion before continuing with the next lines of code.
while wait(1.0) do if goodToGo == true then print ("Keep going") else break -- will stop loop if goodToGo changes to false end end
Summary Loops are everywhere in code. They can run forever or a set amount of times; it just depends on what type of loop you use. while loops keep running unless the initial condition becomes false or the keyword break is used. This type of loop gets used for things like a day/night cycle, which only ends when the world ends.
On the other hand, for loops are best used when you're trying to reach a specific value, like counting down to midnight on New Year's Eve. Exercises lal
Q&A Q. Why do some people just type i?
A. Thereâs a bit of controversy over exactly what i stands for, but a common theory is that it originally stood for integer. i was first used as a stand in for unknown numbers by ancient mathematicians and then by early computer programmers who had to keep their code very, very brief. In short, itâs just a common control variable name, which is why you might some- times see for loops that look like this:
Workshop Now that you have finished, letâs review what youâve learned. Take a moment to answer the fol- lowing questions.
Quiz 1. How long will a for loop run?
2. What is an increment?
3. How many times will this code loop (backward starting values)? FoOR.Ccoune =120:, 0), -2 .do print (count) end
Answers 1. Until the given condition is reached.
3. Zero times. The starting value of 10 is greater than the goal value of O.
Exercises The concept of Damage Over Time (DoT) is used in lots of experiences. With DoT, people take ongoing damage for a certain amount of time rather than taking it all at once. Common examples include encountering poison or taking burn damage after touching a fire. 112 JR 8: for Loops
Because you already have a fire from an earlier Try It Yourself, for this first exercise, use the same model to temporarily inflict burn damage to anyone who happens to touch it.
Tips >» Use the same fire you created previously or use a part to act as a stand-in.
> Insert a new invisible part named HitBox. Scale it to encompass the fire. (See Figure 8.11.)
> If somebody touches HitBox, use a for loop to inflict 10 points of damage every second for three seconds.
FIGURE 8.11 An invisible box is used to mark the boundaries of the fire.
For the second exercise, take a moment to think of at least five other ways that you can use for loops and while loops in your 3D Experiences. Donât worry about whether you know how to create the code. The important thing here is to be able to start recognizing where they might be found.
Now itâs time to work with multiple objects at once so you can do things like give every member of the team a shiny new weapon or modify every item in a folder. You handle tasks like this with tables. Tables allow you to organize multiple pieces of data or objects into groups, such as groups of players or a list of item requirements for a recipe.
This hour covers the first of two different table types: arrays. Youâll learn how to make changes to a whole folder full of objects by turning on multiple lights at once rather than making people turn them on individually.
Every item on the list has a specific number assigned to it, called an index. If you had a grocery list, it might look something like the following table:
GroceryList
Index 1 2 &}
Creating an array is the same as creating other variables; the only difference is it gets assigned curly brackets, like so: local myArray = {} 114 HOUR 9: Working with Arrays
The curly brackets are what make it a table data type. Items can be added to the array by listing them within the brackets, although you need to be sure to separate values with commas. The index number is assigned automatically in the order in which the values are added. Hereâs an example of three-item array: local groceryList = {"Apples", "Bananas", "Carrots"}
Arrays can hold any value typeâeven other arrays. The third array in the following example contains the first two arrays, and a fourth unnamed array is assigned to index 3: local firstArray = {1, 2, 3} local secondArray = {"first", "second", "third"} local thirdArray = {firstArray, secondArray, {"unnamed array"}}
print (groceryList)
As you can see in Figure 9.1, the value at index 1âand the value at index 4, which was added to the table laterâwere both printed. No value was found at index 5, so nil was returned. Printing an Entire List with ipairs() 115
FIGURE 9.1 The first two array values are displayed, but the third value is nil because it doesnât exist.
>» index: This references the current index the loop is working through. It can be any valid variable name. People often just use the lowercase letter i.
> value: References the value of the current index. This can also be any valid variable name.
So, if you have a list of player names, and you want to print them out in order, it might look like this: local players = {"Ali", "Ben", "Cammy"} for playerIndex, playerName in ipairs(players) do print (playerIndex .. "is " .. playerName) end
TAP. Generic Loops Sometimes youâll see this type of loop referred to as a generic loop. ES ee ee 116 JUR 9: Working with Arrays
Letâs say you have a folder full of parts, and you want every part in that folder to turn a different color. You can use something like this code snippet: local folder = workspace.Folder -- Make sure to use the name of your folder
VW TRY IT YOURSELF
FIGURE 9.2 A kitchen scene softly lit with track lighting. All the lights are controlled by a single switch. Folders and ipairs() 117
1. Find a part to act as a light. In Figure 9.3, a small glass cylinder part is acting as a prop iv for the lens of the light. Insert a SpotLight into the part.
TIP. bite
SpotLights SpotLights shine a cone of light, like a flashlight. nn
FIGURE 9.3 A tiny one-stud-wide glass disk that can be used as a light source.
2. To modify which direction the SpotLight shines, in Properties > Face, use the drop-down menu to select the correct face that makes the light appear to shine downward. For this example, thatâs Left. (See Figure 9.4.) Yours may be different. 118 HOUR 9: Working with Arrays
Appearance
FIGURE 9.4 Use SpotLightâs Face property to control which direction the light shines in.
3. With SpotLight still selected, in Properties, increase Brightness and Range until itâs right for the scene. (See Figure 9.5.)
Appearance
Brightness
FIGURE 9.5 Increase the Brightness of the SpotLight to make it brighter and the Range to make the light reach farther.
4. Duplicate the light around your scene. You can even use different models. In Figure 9.6, the disk has been copied into track lighting around the ceiling of this kitchen. Folders and ipairs() 119
FIGURE 9.6 Light props have been placed into track lighting within a kitchen scene.
5. Create a new folder called Lights, and move all of the lightbulbs into the folder. (See Figure 9.7.)
v © workspace Gr c ⹠Terrain
v Lightbulb © Spotlight @ Lightbulb @ ii
FIGURE 9.7 All of the lightbulbs have been moved into a single folder. 120 HOUR 9: Working with Arrays
4. Create a new for loop using ipairs() and pass in the array: local lightsFolder = workspace.Lights local lights = LightsFolder:GetChildren()
end
5. Inside of the for loop, use FindFirstChildWhichIsA() to find the SpotLight nested inside of the lightbulb: local lightsFolder = workspace.Lights local lights = LightsFolder:GetChildren()
end
a. If the spotlight is found and the light is off, enable the SpotLight.
TIP Glowing Spotlight If youâre using a part, you can also change the material to neon to make it appear to be glowing.
b. If the spotlight is found, and the light is on, disable the SpotLight. c. If the loop finds something in the folder that does not have a SpotLight, print âNot a lightbulb.â Finding a Value on the List and Printing the Index 121
else print ("Not a light") end end
Test your code by turning on some of the lights, disabling others, and throwing a random part into the Lights folder. If it works as intended, place your code inside of a function to be run when somebody interacts with a proximity prompt, as shown in the last few hours. If youâve forgotten how, look at your previous code, or look in the appendix at the end of the book.
The second parameter for table. remove () only accepts a numerical index. Typing something like table.remove(playerItems, "Health Pack") returns an error. You can try printing the results of the table to confirm everything works as expected.
When an item is removed from an array, the rest of the values will shift to fill in the gap. You can test this by printing the array before and after the item is removed. Of course, we donât want to type the code for printing an array more than once, so in the following code snippet, itâs part of a function that can be called as often as you want: local function printArray (arrayToPrint) for index, value in ipairs(arrayToPrint) do Print (index >. andex .. "qa "2... value) end end
printArray (playerInventory)
In Figure 9.8, you can see that originally index 2 is Stamina Booster, but once the value is removed, index 2 becomes Cell Key.
FIGURE 9.8 First, the original array prints. Then, the updated array without Stamina Booster prints. Numeric for Loops and Arrays 123
Numeric for loops can easily be used with arrays as well. Letâs go through a couple of examples.
Remember, removing items causes later indexes to shift. Instead of starting at the beginning of the array, start at the end to avoid accidentally skipping values. By starting at the last index, you won't change the indexes of the values before it.
The size of the array can be found using #arrayName and used as the starting index number: local playerInventory = {"Gold Coin", "Health Pack", "Stamina Booster", "Cell Key", "Gold Coin", "Gold Coin"}
print (playerInventory)
local fastestThree = {}
end
print (fastestThree) 124 HOUR 9: Working with Arrays
The preceding code snippet takes the first three values of shipsRaced and adds them to fastestThree.
Summary Tables, of which arrays are one type, let you organize your experience. With arrays, you can make a list of every player in your game and give each of them a new avatar item or weapon. You can also use arrays to create a list of every item in a folder that needs to have changes made to it.
Once you have all the items you want in an array, you can use a for loop to iterate over the array for whatever purpose you would like. You could print the names in the list, you could update the color of every object in the array, or execute much more complicated code. There are two types of for loops that can be used with an array. The for loop you used in the last hour is called a numeric for loop. Itâs good at times when you want to make changes to only a por- tion of the array or are working with very large arrays. The second type of for loop is called a generic for. In this case, ipairs() is used to go over the complete array, in order.
Q&A Q. What are some other reasons for using a numeric for loop?
A. Over a very long lists of objects, numeric for loops run slightly faster. If you need to iterate through hundreds and hundreds of parts, itâs worth keeping in mind.
Workshop Now that you have finished, letâs review what you've learned. Take a moment to answer the fol- lowing questions.
Quiz 1. Arrays are a type of
Answers 1. Table
Index
Array
oe OM rah Scrat Generic
Exercises One way developers make their experiences feel more tied to the real world is by updating assets as seasons go by. In this first exercise, see if you can figure out how to make the pine tree in Figure 9.9 go from a summery green to a wintery white.
FIGURE 9.9 Updating your world to match the seasons makes your world feel more alive.
Tips > This pine tree can easily be found in the ToolBox (see Figure 9.10). Search tree if you donât see it. We picked it for this exercise because itâs a known good model made out of several parts, so you donât have to worry about swapping textures or it having extra scripts inside. FIGURE 9.10 A pine tree with the endorsed model logo. The model is made out of base parts, so you'll easily be able to update the color of each leaf.
You can find the code solution in the appendix at the end of the book. HOUR 10 Working with Dictionaries
The second type of table you'll learn about in this book is dictionaries. This type of table enables you to gather information into groups and tag individual entries with something other than just a number, which opens up a whole world of possibilities. This hour covers creating dictionaries, adding and removing values, and iterating through dictionaries using pairs ().
One way you'll use dictionaries this hour is to keep track of who has the most votes in a voting simulator. The person with the most votes will be kicked off the island. This will give you practice using both arrays and dictionaries to keep track of participants, and what their votes are.
Intro to Dictionaries Dictionaries are table objects that use a key to identify values instead of numbered indexes. The key can be a personâs ID number, properties like Health or Stamina, or any valid data type. The following table is what a dictionary of player names and their respective scores might look like.
activePlayer Dictionary
Use dictionaries when you need to label values, not just list them in a specific order as an array does.
Coding a Dictionary Like arrays, dictionaries are created with curly brackets ({ }).
When you're constructing a new dictionary, you'll often see the brackets separated so that people can tell it apart from arrays, as shown in the following snippet: local newDictionary = { }
Key-value pairs are stored on separate lines followed by a comma. Keys and values can be any data type, including strings, numbers, instances, and other tables. The following dictionary uses strings as keys: local inventory = { Batteries = 4, {"Ammo Packs"] = 1, ["Emergency Rations"] = 0,
Formatting Keys How a key is formatted depends on if it is a string, instance, or something else. If strings are used as the key, they donât need to be in brackets unless there are spaces in the string. Then they must be enclosed in quotation marks and brackets: local seedInventory = { -- String keys with no spaces Wheat = 1, Rice = 4, -- String key with spaces ("Sweet Potatoes"] = 3, Intro to Dictionaries 129
However, if the keys are an instance such as a part or someone in the game, then brackets should be used to mark that. In the following example, a dictionary uses boolean values to track whether all of the required portalStones are activated before opening the master portal: local eastStone = workspace.EastStone local westStone = workspace.WestStone local northStone = workspace.NorthStone local southStone = workspace.SouthStone
Dictionaries are often used for organizing information for a character or object where theyâre used to label properties like name or level. In this case, neither the brackets nor the quotation marks are needed.
The following example uses a dictionary to track a characterâs name and level: local hero = { Name = "Maria", Level = 1000,
WARNING Donât Mix Keys and Indexes Once you create a table, be consistent with using either key-value pairs or indexed values. Never use both within the same table. Combining keys and indexes in the same table can lead to errors.
} -- Remember that Name is a string and can be accessed with brackets
TIP Dot Notation Only Works with String Keys Once again, dot notation only works with strings, but itâs something you'll see quite a bit.
Adding players to a dictionary when they join the game, and then starting them off with 0 points, might look like: playerPoints.Points = 0
Be careful! As mentioned earlier, if the key already exists, the existing value will be overwritten.
local flashLight = { Removing Key-Value Pairs 131
Brightness = 6, [lightBulb) = "Enabled,
This also means if you are ever trying to get a value from a dictionary, and you only get nil, that means you're looking for something that doesnât exist.
TRY AT YOURSELE -„
bal 5. Insert the name into the teamAssignments dictionary as a key, and set the value to "Red":
6. Use name to print the name of the player and teamAssignment [name] to print the value of the key: Players = game:GetService("Players") local teams = { } -- Assign player to "Red" team local function assignTeam(newPlayer) local name = newPlayer.Name teams.name = "Red" print( name .. "is on " .. teams.name .. " team.") end Players. PlayerAdded: Connect (assignTeam)
TIP Comma Instead of Dots If just printing two variables, you can use a comma instead of two dots. nen ee ee ee Returning Values from Tables 133
tRY-TT YOURSELF: „
» Each playerâs name needs to be represented in some way that players can interact with.
>» The votes for each person need to be kept track of.
There are other things you could possibly solve for, but this is enough of a list to work with for now. 134 HOUR LO: Working with Dictionaries
Set Up The first problem will be solved by allowing players to click a button when theyâre ready to begin voting. In a more complex experience, the voting might happen after a series of mini-games or something like that. To solve the second problem, once the voting starts, a new set of buttons representing each player will appear. (See Figure 10.1.)
FIGURE 10.1 One button will start the vote, and then more buttons will be created to represent everyone on the island.
For this example, the names of the ProximityPrompts are important. Differently named prompts will be used to do different things:
1. Set up a part to act as the first button that starts the voting:
b. Set HoldDuration to 1.
2. Set up a second part to act as the button that will hold the playerâs name:
c. Once set up, move the button to ServerStorage where copies can be made. (See Figure 10.2,) Do not move the StartVote button; that needs to be where people can see it. Returning Values from Tables 135
FIGURE 10.2 The button to start the vote stays in Workspace, whereas the button with AddVote goes into ServerStorage.
a. ServerStorage
b. ProximityPromptService
c. Players service
local activePlayers = {} local votes = {
3. At this point, you start breaking your code into smaller solutions. Create a new function that adds players to the activePlayers array when they are added to the experience. Use the PlayerAdded event to call the function: local function onPlayerAdded (player) table.insert (activePlayers, player) end
PlayersService.PlayerAdded: Connect (onPlayerAdded)
4. Create a new function that creates a button for each player instance in the active- Players array. This function is called later in the script with the StartVote prompt: local function onPlayerAdded (player) table.insert (activePlayers, player) end
newBooth.Parent = workspace end end
PlayersService.PlayerAdded: Connect (onPlayerAdded) Returning Values from Tables 137
5. Find the ProximityPrompt within the button, and set ActionText to match the playerâs ive name: local function makeButtons() for index, player in pairs(activePlayers) do local newBooth = ServerStorage.VotingBooth:Clone()
local proximityPrompt = newBooth: FindFirstChildWhichIsA("ProximityPrompt") local playerName = player.Name proximityPrompt.ActionText = playerName
newBooth.Parent = workspace end end
6. Add the highlighted code additions to space the buttons apart a little. You learn more about positioning objects in Hour 14, âCoding in 3D World Spaceâ: local function makeButtons() local position = Vector3.new(0,1,0) local DISTANCE APART = Vector3.new(0,0,5)
local proximityPrompt = newBooth: FindFirstChildWhichIsA ("ProximityPrompt") local playerName = player.Name proximityPrompt .ActionText = playerName
newBooth.Parent = workspace end end
7. Add a third function connected to the Prompt Triggered event. Inside, use the StartVote proximity prompt to call makeButtons (): local function makeButtons() -- Earlier code end
end end
PlayersService.PlayerAdded: Connect (onPlayerAdded) ProximityPromptService. PromptTriggered: Connect (onPromptTriggered)
TIP Keep Event Connections Together When Possible Notice that all of the event connections are at the bottom of the script. This keeps everything organized.
1. In the Test tab, find the section titled Clients and Servers.
2. Set the bottom drop-down menu to two or more players, as shown in Figure 10.3.
FIGURE 10.3 Use the drop-down menu to select at least two players.
3. Clicking Start will bring up a new Studio instance representing the server and an additional window for each pretend player. Player windows have a blue outline (see Figure 10.4), whereas the Server window has a green outline.
4. Click any of the blue windows to control that dummy character. While testing, any errors and printed messages show up in the Server Output window.
5. Interact with StartVote and make sure that a button is spawned for each test player. Returning Values from Tables 139
an > as > @ = >a > 2 Mn >s i
FIGURE 10.4 The blue outline indicates this is one of the player instances.
TIP Positioning Objects The buttons should appear just slightly above the center of your experience, 0,0,0. Later in the book, youâll learn how to control the position of objects when added to the workspace.
FIGURE 10.5 Click the red X to close the extra Studio instances. ? 10: Working with Dictionaries
showVotes() end end
TIP Further Code Organization If you wanted to, you could make the countdown its own function as well. That would allow it to be called by other means than just a prompt.
3. Also in onPromptTriggered(), add a second condition that listens for Proximity Prompts named AddVote: local function onPromptTriggered(prompt, player) if prompt.Name == "StartVote" then makeButtons() -- Countdown code showVotes() elseif prompt.Name == "AddVote" then
end end Returning Values from Tables 141
4. Get the name of the player who was voted for from ActionText, where it was used to ve label the button: local function onPromptTriggered (prompt, player) if prompt.Name == "StartVote" then makeButtons() -- Countdown code showVotes() elseif prompt.Name == "AddVote" then local chosenPlayer = prompt .ActionText end end
5. If the votes dictionary doesnât already have an entry by that name, add the playerâs name as a key and set their points to 1. If a key does exist, take the current value and add one to it: local function onPromptTriggered(prompt, player) if prompt.Name == "StartVoteâ then makeButtons()
showVotes()
TIP If All Else Fails Included are a couple of print statements and an else that can be used for testing the code. A final else that runs if no other condition proves true can be quite helpful for making sure that the func- tion was called as expected.
6. Use Network Simulator with at least two players to test the code, and look for the results in the Server Output.
Summary In all Roblox experiences, tables are behind the scenes tracking information. Arrays are used to create lists of objects, and the information stored will always be in order. While dictionaries are used to track information about objects and properties, and unlike arrays, the entries within arenât guaranteed to stay in any particular order.
To iterate through a dictionary, you want to use pairs () instead of ipairs(). The two func- tions are very similar, but ipairs() only works with arrays.
Q&A Q. Iâve seen pairs () used with arrays, so why not just use pairs() with both arrays and dic- tionaries?
A. One of the benefits of using arrays is that it stores things in order. pairs () is not guaran- teed to return every object in order, whereas ipairs() is.
Q. If pairs() can technically work with arrays, why canât pairs () work with dictionaries?
A. ipairs() requires an ordered index to work. Dictionaries donât have that. On the other hand, pairs accept any valid datatype as a key, including indexes.
Workshop Now that you have finished, letâs review what you've learned. Take a moment to answer the fol- lowing questions.
Quiz 1. Instead of using indexes, dictionaries use __.
bi ed A Why does showVotes() have to be above onPrompt Triggered ()?
Answers 1. Keys
2. False. Although dictionaries might sometimes return values in the order in which they were stored, itâs by no means guaranteed.
pairs ()
Nil
OT Because code is read from top to bottom, showVotes() needs to be created before itâs called in onPromptTriggered().
Exercise Earlier in this hour, a person was assigned to the "Red" team upon joining the experience. For this exercise, can you figure out how to alternate team assignments between "Red" and "Blue"? Print the members of each team.
Tips >» Test using Network Simulator.
» Instances canât concatenate with strings, but the name of the instance will. r A NEO ek ee rman :x ec Tess!
, 2 aS
tac # : ae vie : okah s: os
i 7 ees 7 = S 4 main rT ry , on 7 ie HOUR: 41 Client Versus Server
There are two sides to every Roblox experience. One side is where people interact with the experi- ence, and the other side is in the cloud, controlling everything. This hour covers how these two sides work together and how messages are sent between them. At the end of the hour, you create a shop where players can click a button to buy firewood for the resource game created in Hour 9.
Some things about the experience are calculated on the individual client device, whereas other things are taken care of by super powerful Roblox hardware called the server. The server and the client are always talking to each other. The server tells the client what the overall world is like, and the client tells the server what a person is doing within the world.
Typically, you want important information like scores, in-game money, and progress levels to be handled by the server. The server is more secure than the client and is harder to hack into. Meanwhile, the client handles things that apply only to the particular person using the device or for when itâs important to have the least amount of lag possible, such as for showing them their own score or when controlling the camera. 146 HOUR 14: Client Versus Server
CUSTOMIZE DELETE
FIGURE 11.1 In World//Zero by Red Manta Studio, returning players are greeted by GUI showing their charactersâ levels and current locations. Additional GUI elements allow for customization and deletion, and a big green button starts the game.
With GUIs, you can also create onscreen buttons that allow you to build out things like shops.
The majority of GUI items that can only be seen by the local client should be placed in Starter- GUI, and you type the code into a LocalScript object instead of a Script object. Anything in StarterGUI is duplicated to anyone who joins the experience.
Set Up 1. In Explorer, select StarterGUI.
2. Insert a new ScreenGui (see Figure 11.2). This will be the container for any buttons and labels you want to create. Working with GUIs 147
FIGURE 11.2 Insert a ScreenGui object into StarterGui.
FIGURE 11.3 Insert a TextLabel into the ScreenGui just created.
TIP Customizing GUIs To learn about customizing the appearance and placements of ScreenGuis, check out the compan- ion book Roblox Game Development in 24 Hours or look up Intro to ScreenGuis on the Developer Hub. i a a cr em 148 HOUR 14: Client Versus Server
vo Script You use a LocalScript instead of the normal Script object. The Script object is for server-side code:
1. With the ScreenGui selected, insert a new LocalScript object (see Figure 11.4).
FIGURE 11.4 LocalScripts are for client-side code.
TIP GUI Script Placement GUI scripts must be inside of StarterGUI. ServerScriptService can only access server Script objects.
2. Within the LocalScript, create variables for the Players service and the ScreenGui.
4. Get the local player. In LocalScripts, this can easily be done with Players .LocalPlayer: local Players = game:GetService ("Players")
textLabel.Text = localPlayer.Name
6. Use the Network Simulator to test your code. You'll see that each personâs name is dis- played on screen. Using RemoteFunctions 149
Understanding RemoteFunctions One thing to keep in mind is that the server and the client donât have access to the same infor- mation. There are certain folders that the client canât access, and vice versa. Here are a few examples:
ServerScriptService yes no
ServerStorage yes no
Also, the server and the client donât share information. Some people call this the server/client divide, but you can just imagine it as if there was a wall between the two environments keeping them separate.
To get information from one side to the other, special objects are used to toss information over the wall. This can be done through RemoteEvent and RemoteFunction objects that both Scripts and LocalScripts can use to communicate with each other. In this hour, RemoteFunctions are covered, and the next hour gets into the different types of RemoteEvents.
Using RemoteFunctions As stated earlier, RemoteFunctions are designed to send a request across the server-client boundary.
What makes RemoteFunctions special is that they can also wait for a response from the other side acting as a messenger between the client and the server. Usually this is a request from the local client for the server to do something, and then the server sends the results back.
RemoteFunctions must be created where both clients and the server can access itâfor instance, ReplicatedStorage (see Figure 11.5).
Meanwhile, you have a normal server Script in ServerScriptService and a LocalScript in Starter- PlayerScripts, as shown in Figure 11.6. 150 HOUR 11: Client Versus Server
@ worl y VWIOrKS
FIGURE 11.5 RemoteFunctions must be placed someplace like ReplicatedStorage, which both the client and server can access.
FIGURE 11.6 LocalScript in StarterPlayerScripts and server Script in ServerScriptService.
Get a message from the server, and print it locally: On the server side, set up a function that returns a simple string to print. Bind the function to the RemoteFunction object, as highlighted here: local ReplicatedStorage = game:GetService ("ReplicatedStorage") local remoteFunction = ReplicatedStorage:WaitForChild("RemoteFunction")
remoteFunction.OnServerInvoke = sayHello
RemoteFunctions can only have one function bound to them at a time. On the local side, the code to invoke (indirectly call) the server would look like this: Using RemoteFunctions 151
print (messageFromServer)
Server to Client It is possible to go the other directionâfrom server to client to server. However, itâs quite risky and won't be covered within this book for the following reasons:
> If the client throws an error, the server will throw the error, too.
> If the client disconnects while itâs being invoked, the InvokeClient () call will error.
> If the client never returns a value, the server will hang forever.
TRY TEeYOURSELFE
Make a Store A good example of when you might need to double-check with the server and wait for a response is if someone wants to buy something. A client clicks a button to buy something, and then the server checks whether the client actually has enough money and confirms the purchase. For the purposes of this Try It Yourself, you take the leaderboard system youâve worked with before and modify it to allow players to spend gold to buy more logs to burn for the fires (see Figure 11.7).
fbuy Blogs
FIGURE 11.7 The end result will allow people to buy logs for the fire. 152 JR L1: Client Versus Server
ha Set Up For the sake of speed, use the leaderboard system for fuel and fire you previously set up. If you donât have it, you can use the code in the Hour 11 section of the appendix to quickly set it up.
CheckPurchase +)
FIGURE 11.8 Add a RemoteFunction named CheckPurchase.
FIGURE 11.9 Add a folder named Shopltems.
4. In Shopltems, add a Folder object named 3Logs and add the three attributes shown on the right in Figure 11.10. You'll use these names and values in the script. Using RemoteFunctions 153
Prog Explorer Parent Shopitems
? gg ReplicatedStorage Behavior
Me Sloss Price StarterGui ; StarterPack StatName
FIGURE 11.10 A new folder with custom attributes for NumberToGive, Price, and StatName.
TIP Future Proofing the Shop In a more advanced shop, the folder can also be used to hold mesh models, image icons, and more.
5. In StarterGUI, add
> In ShopGui, add a new TextButton named Buy3Logs (see Figure 11.11).
FIGURE 11.11 Set up for the GUls. 154 HOUR 14: Client Versus Server
KA TIP Moving GUIs To move the GUls around, you can select the GUI objects in Explorer and then move and scale them. aE ETSI ann nnn RRS nnneenseeeeneneseenenennenesneneenenennnnmna
Attributes
PurchaseType
FIGURE 11.12 An attribute for Buy3Logs.
LocalScript LocalScripts for GUI buttons need to be a direct child of the button they affect. In the LocalScript, you set up the code to invoke the server and tell the person if their purchase was successful or if they need more gold:
4. Use the PurchaseType to create and assign default text for the button and then set up a cooldown for how long the button will be deactivated between purchases: local defaultText = "Buy " .. purchaseType button.Text = defaultText
AIP BA Make Sure to Assign Default Property Values You'll be changing the Text property several times, so you want to make sure you have a default message assigned in the beginning of the script. I ee ale
end
button.Activated: Connect (onButtonActivated)
6. Create a variable to invoke the server to send the purchaseType and hold the returned purchase confirmation: local function onButtonActivated() local confirmationText = checkPurchase: InvokeServer (purchaseType) end
7. Display the confirmation text while disabling the button and then return the button to normal: local ReplicatedStorage = game:GetService("ReplicatedStorage") local checkPurchase = ReplicatedStorage:WaitForChild("CheckPurchase")
button.Activated: Connect (onButtonActivated) 156 HOUR 14: Client Versus Server
2. Think about what your script needs to do and make the references you think it will need. Compare your work to the following snippet: local ReplicatedStorage = game:GetService ("ReplicatedStorage") local Players = game:GetService ("Players") local ServerStorage = game:GetService ("ServerStorage")
end
checkPurchase.OnServerInvoke = confirmPurchase
end
5. Use the passed in purchaseType to find the item they want to buy. Get the resource stat that will be updated on the leaderboard, the item's price, and how many of the resource will be received: local function confirmPurchase (player, purchaseType) local leaderstats = player.leaderstats local currentGold = leaderstats:FindFirstChild("Gold@")
TIP Lv Check Your Work You should have four variables here. Notice how purchaseType:GetAttribute("StatName") is passed into shopItems: FindFirstChild(). er I oor Se
6. Set up a variable for the server message that will be sent back once everything is checked:
return serverMessage end
TIP Set Undetermined Values to nil In this code, the value of serverMessage will be determined in the next step. Rather than just leaving the variable without a value for now, set it to nil so itâs clear that a value was not mistak- enly left out.
7. Set up conditionals to check how much gold the person has and whether they can buy the item. Depending on the results, send back an appropriate message to the client and update the leaderboard. Hereâs the completed script: local ReplicatedStorage = game:GetService("ReplicatedStorage") local Players = game:GetService ("Players") local ServerStorage = game:GetService("ServerStorage")
else serverMessage = ("Didn't find necessary info")
end
return serverMessage end
checkPurchase.OnServerInvoke = confirmPurchase
8. Test everything out! You can make the store prettier by adding images for the items and styling the look and font of the text and buttons.
Summary Every Roblox experience has two sides that come together to make the world users see. The first side is the local client, which is the device such as a computer or phone where people are inter- acting with Roblox. The second side is the server, which is making sure that everyone is going through the experience in mostly the same way.
Generally, you want to make sure as much of the code that you create stays on the server side of things where itâs more secure. The last thing you want is people taking advantage of the local client to make unauthorized purchases or updates to their stats.
Some things, however, are specific to a client. If one person opens a shop window or makes a purchase, you donât want everyone in the experience to have to look at the shop window until theyâre done. So, shops are an example of something you would want done locally. Workshop 159
Q&A Q. Can anything other than a function be bound to a RemoteFunction?
A. RemoteFunctions expect a function; trying to bind something like a variable will cause an error.
Workshop Now that you have finished, letâs review what youâve learned. Take a moment to answer the fol- lowing questions.
Quiz 1. The device somebody uses to join a Roblox experience is the___.
2. The super powerful hardware running most of a Roblox experience is the ___.
3. Code that only makes changes to what a client sees is typed intoa__ object.
4. A RemoteFunction is
A. A type of event
B. An object
C. A data type
5. RemoteFunctions can be used for ___ -way communication between the server and the client.
Answers 1. Local client
Server
LocalScript
An object
Two
oP ND abw Invoke means to call something indirectly. JR 11: Client Versus Server
Exercises One problem with the current shop is that the price of the items aren't listed. However, remem- ber that only one function can be bound to a RemoteFunction at a time. Create a second RemoteFunction and use it to display not only the name of items people can purchase, but also the cost. (See Figure 11.13.) Be sure to test your code with multiple items for sale.
FIGURE 11.13 Extend the shop code so that it can retrieve the price for each item in the shop. HOUR 12. Remote Events: One-Way Communication
Hour 11 covers the differences between the local client and the server living on Roblox hardware. It also covers one way of communicating across the divide. This hour covers a second way to send messages.
_ Asa reminder, a RemoteEvent is an object that you can insert instances of into Workspace, typi- cally within ReplicatedStorage where both the client and server can access it (see Figure 12.1).
There are three major ways in which RemoteEvents can be used to send a signal:
FIGURE 12.1 RemoteEvent should be in ReplicatedStorage to be accessed by both the client and server.
On the client side, you set up one or more functions to call when the event gets fired: local function firstFunction (incomingInfo) -- Do stuff end
Vv TRY WT YOURSELF
Quick Countdown Letâs start off with something familiar to demonstrate: a countdown. This is a good example because the server needs to get information to everyone in the server, but it doesnât need any information back. Communicating from the Server to All Clients 163
So far, weâve demonstrated a countdown in two ways. The first was simply in Output where the od i client canât see it. The second was displayed on a 3D GUI in the game space. The problem with that is that people can walk away from it and not see it. If you want to be sure everyone in the experience sees the countdown, as shown in Figure 12.2, using a RemoteEvent is the way to do it.
FIGURE 12.2 A TextLabel displays a countdown until the next round.
FIGURE 12.3 Insert a RemoteEvent into ReplicatedStorage. 164 HOUR 12: Remote Events: One-Way Communication
Create a countdown using a for loop. On every iteration, fire the event and pass back the current countdown: local ReplicatedStorage = game:GetService("ReplicatedStorage") local countdownEvent = ReplicatedStorage:WaitForChild("CountdownEvent")
local secondsRemaining = 20 for count = secondsRemaining, 1, -1 do countdownEvent : FireAllClients (count) wait (1.0) end
Get the client side going; in StarterGui, add a new ScreenGui (See Figure 12.4) anda TextLabel. This is where youâll display the countdown.
FIGURE 12.4 Set up the GUI to be seen by everyone.
5. In ScreenGui, add a LocalScript. This is where you'll put the code that you want to run whenever the event is fired. In the example shown in Figure 12.5, itâs been named DisplayManager.
DisplayManager
FIGURE 12.5 Add a new LocalScript to StarterPlayerScripts. Communicating from the Client to the Server 165
6. Set up your references; then create a new function to be called when the event is fired: local ReplicatedStorage = game : GetService ("ReplicatedStorage") local countdownEvent = ReplicatedStorage:WaitForChild("CountdownEvent")
TIP Checking Your Code The first thing to check is your references. Make sure that the name of your events, instances, and GUI elements matches whatâs being referenced in your code. Itâs okay if they're different from the example code as long as youâre paying attention. Secondly, itâs always a good idea to check all of your code using the network simulator and on a live published game.
Take a look at the example function in the preceding code. The player that triggered the event was passed in automatically. The first parameter will always have to take that into account.
Vv TRY IT YOURSELF
Vadlt )
FIGURE 12.6 This GUI has three different map choices.
Set Up All of the buildings and props of a map can be grouped into a single model. In this section, you set up a few simple models to practice with, and create the GUI buttons:
2. Place three different models into the Maps folder (see Figure 12.7). Make sure each model has a unique name. To group parts into a model, select all desired parts; then right-click and select Group (Cmd+G or Ctrl+G). Communicating from the Client to the Server 167
FIGURE 12.7 Three models holding all the parts for three different maps.
TIP Practice with Simple Models For the sake of this Try It Yourself, itâs OK if the models consist of just a part or two.
FIGURE 12.8 Add a new RemoteEvent within ReplicatedStorage.
4. |In StarterGui, add a newScreenGui, and within that, add a frame named MapSelection. (See Figure 12.9.) Frames allow you to group different GUI elements together.
FIGURE 12.9 A new GUI frame holds the buttons.
5. Select the frame, and insert three TextButtons (see Figure 12.10). Make sure each but- tonâs name matches the maps in ServerStorage (refer to Figure 12.8). You'll use the name to clone the correct map. 168 Remote Events: One-Way Communication
GH ScreenGui thee ~ GH MapSelection
AncientRuinMap MF CityMap GF islandMap
FIGURE 12.10 Three buttons have names matching the map models.
Client Side The buttons appear on the client, which makes it possible for the map to be chosen and a message sent to the server:
2. Set up your references for the RemoteEvent, the button, and the frame: local ReplicatedStorage = game:GetService("ReplicatedStorage") local mapPicked = ReplicatedStorage:WaitForChild("MapPicked")
end
button.Activated: Connect (onButtonActivated)
4. Inside, use FireServer() to send the name of the button that was picked and then make the frame invisible: local ReplicatedStorage = game:GetService("ReplicatedStorage") local mapPicked = ReplicatedStorage:WaitForChild("MapPicked")
button.Activated: Connect (onButtonActivated)
ServerSide On the server side, the selected map will be cloned from ServerStorage:
2. Set up the references for the RemoteEvent, ServerStorage, and the Maps folder: local ReplicatedStorage = game:GetService ("ReplicatedStorage") local mapPicked = ReplicatedStorage:WaitForChild("MapPicked")
Add one more reference that will be used for the current map. Set it to nil for now. You'll use this variable to delete the old map when a new one is made so they arenât stacked on top of each other: local ReplicatedStorage = game:GetService("ReplicatedStorage") local mapPicked = ReplicatedStorage:WaitForChild("MapPicked")
Create a new function and connect it to the RemoteEventâs event named OnServerEvent:
mapPicked.OnServerEvent : Connect (onMapPicked)
Check to make sure the chosen map was found; then destroy the old map and make a copy of the new one: local ReplicatedStorage = game :GetService ("ReplicatedStorage") local mapPicked = ReplicatedStorage:WaitForChild("MapPicked")
mapPicked.OnServerEvent : Connect (onMapPicked)
7. Test it out! If it works, place copies of the LocalScript within the other two buttons.
TIP Troubleshooting Tip If the scripts donât work as expected, make sure youâre testing the correct button. If the buttons are on top of each other, or you try the wrong button, nothing will happen.
Server side:
remoteEvent:FireClient (player, additionalInfo) Workshop 171
Client side: local function onServerEvent (player, additionaliInfo) -- Whatever you want to happen end remoteEvent .OnClientEvent : Connect (onServerEvent)
Even if you donât plan to use the player argument, it still needs to be sent and accounted for on the local side. Itâs just a quirk of the RemoteEvent needing to know specifically who to send a message to.
The client would use FireServer (infoToPass), and from there, the server would pass the information back to one or all of the clients.
Summary RemoteEvents are a really versatile way to send information between the server and the client because you can connect several functions to the same event. Since the information goes only one way, the RemoveEvents donât have to wait for a response, and are generally faster and easier to use than RemoteFunctions whenever a response isnât required.
Workshop Now that you have finished, letâs review what you've learned. Take a moment to answer the fol- lowing questions.
Quiz 1. True or false: RemoteEvents can send information to the server, and return a response to the client.
2. True or false: RemoteEvents can only have one function bound to them at a time.
3. To send a message from a client to a server, you would use the function
4. True or false: The server automatically receives the name of the client that fired the RemoteEvent. 172 4OUR 12: Remote Events: One-Way Communication
Answers 1. False. RemoteEvents can only send information in one direction. They canât wait for a response.
4. True, which is why you need to account for the incoming player argument when connecting functions to the RemoteEvent.
Exercise Once the map is picked, be sure to announce the map choice to everyone in the server, as shown in Figure 12.11.
: ime at
edit tes
FIGURE 12.11 The map choice is announced.
Tips >» Remember that clients canât talk directly to other clients.
Thereâs a good chance your experience will have a lot of things in it: a lot of buttons, a lot of things to touch, a lot of things to pick up. As much as possible, you want to make sure youâre not ending up with lots of duplicate code in your world for handling all of these objects. Having multiple copies of scripts everywhere is hard to manage and hard to update. Imagine making the same change to dozens of item pickup scripts or going trap by individual trap to change how much damage they do.
This hour explains ModuleScripts, another tool in your belt for keeping your code centralized and easy to update. It also covers a little bit more about the general principle of Donât Repeat Yourselfâalso known as DRYâcoding and how to organize your code to make it easier to tweak and update.
Scripts and LocalScripts will still be needed for accessing the module script, but the code within them can be kept to the bare minimum. 174 JR 13: Using ModuleScripts
Where you place ModuleScripts depends on how you plan on using them. If they will only be used by server scripts, you should put them in ServerStorage, where theyâre better protected. If client-side LocalScripts need to use the ModuleScripts, you can put them in ReplicatedStorage. (See Figure 13.1.)
Wa StarterPlayer
FIGURE 13.1 ModuleScripts in ServerStorage (on left) can only be accessed by scripts. ReplicatedStorage (on right) can be used by both scripts and LocalScripts.
i img a WF a ST sYatteatle fe ila | 4 VY, ,» AE wn amaeissliaC fad af 7.)auwsmtbe . Yass \ |ral A aw â Be Understanding How ModuleScripts wWwOTh
These should always be the first and last lines of code for the ModuleScript. Notice the curly brackets? All of the code within the ModuleScript is placed into a table and then returned on the last line. Within the table, all of the moduleâs shared functions and variables are stored.
Naming ModuleScripts Al- . an a at Al - al : | - @& a 7 â | pa
The first thing you'll need to do with the table is update the name to match that of the script, as shown in the following code and Figure 13.2. The name should match the purpose of all the shared functions, such as ShopManager, TrapManager, or PetManager. Adding Functions and Variables 175
TIP Script Naming The word Manager is pretty commonly used to mark a script that tells something else what to do. So ButtonManager can be interpreted as âTells the button what to do.â Se
local TrapManager = {}
return TrapManager
FIGURE 13.2 The name of the ModuleScript and the table name match exactly.
Notice that the names of both the ModuleScript and the internal table shown in Figure 13.2 are in PascalCaseâthe first letter of each word is capitalized. If you want to learn more about com- mon naming conventions in Roblox, some style guide rules can be found in the appendix.
-- Add a variable ModuleName.variableName = 100
-- Adds a function 176 HOUR 13: Using ModuleScripts
Remember, anything added to the module table must be typed between local ModuleName = {} and return ModuleName.
Typing local in front of variables and functions means they are usable only by that code chunk. Usually thatâs what we want, but ModuleScripts are different. The whole point is to make the code shareable: local ScoreManager= {}
However, variables only used by the ModuleScript, like variables within a function, should still be local: local ScoreManager= {}
Within a script or LocalScript, use require () and pass in the ModuleScriptâs location:
Script example: local ServerStorage = game:GetService("ServerStorage") local ModuleName = require (ServerStorage .ModuleName}
The script will load in the module table, making the moduleâs variables and functions available for use.
To use a variable or a function from the module, use the ModuleScriptâs name, followed by the name of what you need. The following code sample has a practice variable with the value of 7 within a ModuleScript. The snippet after that demonstrates that variable being accessed from a normal Script object. Figure 13.3 shows the results in Output.
PracticeModuleScript.practiceVariable = 7
function PracticeModuleScript.practiceFunction () print ("This came from the practice ModuleScript") end
return PracticeModuleScript
The resulting output looks like Figure 13.3. Notice that the source for the printed statement on the first line is the script, whereas the source for the second line is the ModuleScript. 178 )UR 13: Using ModuleScripts
FIGURE 13.3 The printed results are shown in Output.
Make sure you match the names of the ModuleScript, functions, and variables exactly; otherwise, they wonât work. Thereâs nothing wrong with copying and pasting to make sure things are the same.
Also, be sure not to make any changes to the ModuleScript while the experience is running. The table canât be refreshed. Once the module table is loaded, using require () again will only return the same table.
FIGURE 13.4 The blue pad allows people to reach places they couldnât otherwise, like the roof of this house. Using Modules in Other Scripts 179
Set Up hal Set up the script objects and the jump pad; in the next section, youâll work on the code. 1. Add a part or a mesh. The jump pad in Figure 13.4 is just a neon blue base part.
ModuleScript You'll start with setting up the ModuleScript. The ModuleScript will handle how high and how long the player will jump. To do so, you need to get the characterâs HumanoidRootPart, which handles the basic motion of a person. A VectorForce object will be added to the HumanoidRootPart, which will cause the person to shoot up for as long as the VectorForce exists. You may not be very familiar with some of the concepts used yet, but youâll use them more in upcoming hours. All of the heavy lifting for the script will be done here, leaving only a few lines of code to create in the script:
return JumpPadManager
2. Create a local constant for how long the jump will last: local JumpPadManager = {}
return JumpPadManager
3. Create a second local constant for jump direction like the one shown next. VectorForce requires X, Y, Z coordinates to know which way to send things. The middle number, Y, makes things go upward: local JumpPadManager = {}
return JumpPadManager 180 13: Using ModuleScripts
ba TIP Moving and Animating Objects You'll learn more about Vector3, X, Y, Z coordinates, and how to move and animate objects, in the next hour. If youâre feeling brave, you can experiment with the X and Z values for front-and-back and side-to-side force.
return JumpPadManager
This part is similar to making a trap. Find the partâs parent and use that to search for a humanoid. If it finds Humanoid, then search for the HumanoidRootPart: -- Top of ModuleScript
function JumpPadManager.jump (part) local character = part.Parent local humanoid = character:FindFirstChildWhichIsA ("Humanoid")
if humanoid then local humanoidRootPart = character:FindFirstChild("HumanoidRoot Part") end end
return JumpPadManager
Search for a VectorForce instance, even though it won't exist until you add one. You'll need this for the debounce in the next step: -- Top of ModuleScript
function JumpPadManager. jump (part) local character = part.Parent local humanoid = character: FindFirstChildWhichIsA ("Humanoid")
if humanoid then local humanoidRootPart = character: FindFirstChild("HumanoidRoot Part") local vectorForce = humanoidRoot Part: FindFirstChild("vectorForce") Using Modules in Other Scripts 181
end end
return JumpPadManager
7. If thereâs not a VectorForce, add one. This makes sure there is only ever one VectorForce applied: local JumpPadManager = {}
-- Top of ModuleScript
function JumpPadManager. jump (part) local character = part.Parent local humanoid = character: FindFirstChildWhichIsA ("Humanoid")
if humanoid then local humanoidRootPart = character:FindFirstChild("HumanoidRootPart") local vectorForce = humanoidRootPart:FindFirstChild("VectorForce") if not vectorForce then vectorForce = Instance.new("VectorForce") end end end
return JumpPadManager
8. Set the Force property to JUMP_DIRECTION and then attach and parent it to the HumanoidRootPart, as shown here: -- Top of ModuleScript
function JumpPadManager. jump (part) local character = part.Parent local humanoid = character:FindFirstChildWhichIsA ("Humanoid")
if humanoid then local humanoidRootPart = character: FindFirstChild("HumanoidRoot Part") local vectorForce = humanoidRootPart:FindFirstChild("VectorForce") if not vectorForce then vectorForce = Instance.new("VectorForce") vectorForce.Force = JUMP_DIRECTION vectorForce.AttachmentO = humanoidRootPart.RootRigAttachment vectorForce.Parent = humanoidRootPart end end end
return JumpPadManager 182 HOUR 13: Using ModuleScripts
al TIP Keeping VectorForce and HumanoidRootPart Together The attachment makes sure that the VectorForce instance doesnât become separated from the HumanoidRootPart.
if humanoid then local humanoidRootPart = character:FindFirstChild("HumanoidRootPart") local vectorForce = humanoidRootPart : FindFirstChild("VectorForce") if not vectorForce then vectorForce = Instance.new("VectorForce") vectorForce.Force = JUMP DIRECTION vectorForce.AttachmentO = humanoidRootPart .RootRigAttachment vectorForce.Parent = humanoidRootPart wait (JUMP_DURATION) vectorForce: Destroy () end end end
return JumpPadManager
Script On the script side, we want only the bare minimum of code. All it will do is load the ModuleScript and call JumpPadManage . jump r (otherPart) whenever something touches the part:
Connect a function to the Touched event, and inside, pass the touching part to the ModuleScript: local ServerStorage = game:GetService("ServerStorage") local JumpPadManager = require (ServerStorage.JumpPadManager) Dealing in Abstractions 183
Test everything out! If you get nil errors, thereâs a good chance itâs due to a misspelling some- where. Make sure all your naming and capitalization matches exactly.
Think of the resource items like gold and logs in Hour 9. Instead of having different scripts for gold and logs, they both use the same set of scripts, but the code allows for differing information to be processed. This is part of the general practice of DRY coding. DRY stands for Donât Repeat Yourself, and itâs a concept that applies to all coding and coding languagesânot just Lua and Roblox Studio.
The opposite, WET (Write Everything Twice), is generally considered to be a bad thing and is used to mean you have a lot of duplicate code in your scripts.
Dealing in Abstractions A key part of DRY coding is abstractions. Abstraction is the process of pulling out the most impor- tant information, and hiding everything that you donât need to deal with right now.
A lot of things in Roblox Studio are abstracted for you. Think of all the functions or methods where you only have to call the function and pass information in. The functions are reusable abstractions. When called, users get the benefits of the function without having to rewrite or even look at the rest of the code.
A common example in coding languages is print (). Most of its code is hidden, so the coder can focus on what needs to be printed and not on how to get individual pixels to show up on the screen.
ModuleScripts also allow you to set up the abstractions necessary for good DRY coding practices. ModuleScripts can act as a Single Source of Truth (SSOT), meaning that information and func- tions needed by multiple scripts can all be kept in the one ModuleScript. 184 HOUR 13: Using ModuleScripts
A good way to know if you need to set up an abstraction is if you think you'll need to use a variable or function in three or more places. So in the resource game example, there was a good chance that you would want people to collect not just logs and gold but potentially other resource types as wellâmaybe eventually things like iron, berries, or wool, too.
Summary Abstractions provide a simplified representation of something larger by leaving out details. When deciding whether to create an abstraction, look for code that is often reused but with small changes each time. For example, a generic item like a backpack can be abstracted to a reusable function that looks up price and capacity.
Taking time to plan and structure code with abstractions helps coders focus on whatâs important. This investment in time keeps programs better organized and makes updating them easier.
Q&A Q. Why not just always leave out local and make most variables and functions shareable?
A. Making variables local is usually best practice. It makes your code run a bit faster and elim- inates the chances for errors and accidentally overwritten information. Nonlocal variables, such as in ModuleScripts, should always be the exception, not the rule.
A. You can definitely go overboard with creating abstractions. Generally, though, if you can think of a future where you might want to use the same piece of code more than two or three times, abstractions are worth the effort.
Workshop Now that you have finished, letâs review what you've learned. Take a moment to answer the fol- lowing questions.
Quiz 1. My ModuleScript is named RoundManager. What should the first and last lines of my ModuleScript look like?
Answers 1. If your ModuleScript is named RoundManager, the first and last lines of code should be local RoundManager = {} -- Code return RoundManager
In ReplicatedStorage
In ServerStorage
In ReplicatedStorage
PBwR oo Donât Repeat Yourself
Exercise Practice creating a module script by using code youâre familiar with. Make a series of trap parts people need to avoid if they donât want to lose all their health. Figure 13.5 shows an example.
FIGURE 13.5 Red traps in a basement hallway maze ae von ee trou fh u 14 : aop Vees a way
e RAS aR Renatcati : MER ) agg RTs sa mPa Pats | eys : â . = se
In the last hour, you used a module script to create a jump pad that shot people straight up in the air. This hour covers how objects are placed in 3D space and how you can make parts spawn anywhere you want in the world.
If you donât already have it showing, turn on the View Selector (see Figure 14.2) using View > Actions > View Selector to allow you to see which way in the world your camera is facing. 188 HOUR 14: Coding in 3D World Space
FIGURE 14.1 A zoomed-out view of an entire piece of terrain. The world axes are marked with red, green, and blue arrows.
FIGURE 14.2 If the View Selector is enabled, you can see the axes represented there.
When you select an object with the Move tool, you can see the three axes represented with red (X), green (Y), and blue (Z) arrows. Dragging an object within the world space will update the X , Y, and Z position values in the Property window (see Figure 14.3). If you were to place an object at the center of the world, its X, Y, and Z position would be 0, 0, 0. Refining Placement with CFrame Coordinates 189
FIGURE 14.3 When you drag a part around, you can see the X, Y, and Z values updated in the Property window.
CFrame stands for Coordinate Frame. Every object in the 3D space has one. The default value of a CFrame is 0, 0, Oâwhich is why new objects appear at the center of the world. To update an objectâs position, assign a new CFrame value using CFrame.new():
Example: local part = script.Parent part.CFrame = CFrame.new(1, 4, 1)
You can set the X, Y, and Z positions individually, as shown in the preceding code, or you can pass in Vector3 data, as shown here: local vector3 = Vector3.new( 1, 4, 1) part.CFrame = CFrame.new(vector3) 190 HOUR 14: Coding in 3D World Space
WT RY ie YOURSECE
1. Create a part somewhere in your world. Name it something distinct like Marker and create a reference for it: local marker = workspace.Marker
2. Create a new part instance. By default, new parts are unanchored, so make sure to anchor it in place: local marker = workspace.Marker
4. Pass in the markerâs position and then parent the new part to the workspace. Test your code; you should end up with two parts in the same place: local marker = workspace.Marker
You might be asking yourself why you wouldn't always use the Position property? The answer is because Position only works with parts and not with models. We cover that more in a bit. Adding Rotations to CFrames 191
Offsetting CFrames Quite often, you donât want to place something exactly in the same place; instead, you want to place it above or a little to the side. You can combine CFrame and Vector3 values to make that happen. In the following example, a Vector3 adds four studs to the Y value that was passed into CFrame.new:
while wait(0.5) do -- Take spinner's current CFrame and rotate it. spinner.CFrame = spinner.CFrame * ROTATION AMOUNT end
CFrame .Angles also takes in three values for X, Y, and Z. The preceding snippet rotates spin- ner on the Y axis. One thing to note is that it does not operate using degrees. It uses radians. Radians are a math concept used when working with the arc of a circle. Luckily, you donât need to know how to use radians. Instead, math. rad() can convert from degrees to radians for you.
So, if you want a part to rotate 20 degrees on the X axis, it would look like this: local ROTATION AMOUNT = CFrame.Angles (math. rad (20), 0, 0) part.CFrame = part.CFrame * ROTATION_AMOUNT 192 HOUR 14: Coding in 3D World Space
FIGURE 14.4 The cloud model is made from several parts. In Properties, you can see PrimaryPart is set to BigSphere.
TIP Grouping Parts into a Model To group parts into a model, right-click a selection of parts and select Group.
This cloud model cannot be moved using the earlier method. You need to use SetPrimaryPartCFrame() and then pass in the new CFrame: local cloud = workspace.Cloud cloud:SetPrimaryPartCFrame(CFrame.new(0, 20, 0)) Understanding World Coordinates and Local Object Coordinates 193
AIP Setting the Primary Part You can set the PrimaryPart of a model in Properties. Click PrimaryPart; then in Explorer, click the part you want to designate as the main part of the model.
The other is the local object axisâhow an object is positioned and rotated relative to itself. The X, Y, and Z of an individual object might not line up with the world. In Figure 14.5, you see the world axis on the left and the boxâs own local axis shown on the right.
FIGURE 14.5 The image on the left shows the object in relation to the world axis. Each object has its own separate X, Y, and Z that might not line up with the world, as shown on the right.
Think of it this way. The world you walk around in has global compass directions (north, south, east, west) that donât change no matter which way you face. But your personal left, right, for- ward, and back moves and rotates as you do.
You can change your scale and rotate tools to see world or local coordinates by pressing Cmd/ Ctrl + L. You can tell youâre in local mode if you see a little L in the corner along the red X axis, as shown in Figure 14.6. 194 HOUR 14: Coding in 3D World Space
FIGURE 14.6 The Move tool and Rotation tools are in local mode.
Vv TRY Me XYOURSELE
local JumpPadManager = {}
-- Not local because the jump pads need these functions ive function JumpPadManag jump er. (part) local character = part.Parent local humanoid = character: FindFirstChildWhichIsA ("Humanoid") if humanoid then local humanoidRootPart = character: FindFirstChild("HumanoidRootPart") local vectorForce = humanoidRoot Part : FindFirstChild("VectorForce") if not vectorForce then vectorForce = Instance.new("VectorForce" ) vectorForce.Force = JUMP DIRECTION vectorForce.Attachment0O = humanoid Part .RootRigAt Root tachment vectorForce.RelativeTo = Enum.ActuatorRelativeTo.Attachment0 vectorForce.Parent = humanoidRootPart wait (JUMP_DURATION) vectorForce: Destroy () end end end return JumpPadManager
TIP VectorForce Is Relative to an Attachment This will set it so the VectorForce is relative to AttachmentO, which in this case is connected to the HumanoidRootPart.
3. Test it out. Your character should be boosted in the local direction they are facing instead of being boosted along the world axis.
TIP Different Avatars Weigh Different Amounts Just like people in real life, an avatarâs weight depends on its size and type of accessories. How high an avatar is boosted will vary based on the weight.
Summary Congratulations! You now have the power to place objects anywhere in the world. And not just parts, you can also teleport people around. Everything in the 3D world space, including people's characters, has coordinates that can be found along the X (red), Y (green), and Z (blue) of the world. 196 HOUR 14: Coding in 3D World Space
Workshop Now that you have finished, letâs review what you've learned. Take a moment to answer the fol- lowing questions.
Quiz Lr What axis is up in Roblox Studio world space? If you want to create a new Cframe from Vector3 information, use the function
ae 2 To place an object to the side or above an object, you would the position CFrame and a new Vectors.
Answers a Ie The green Y axis is up. CFrame . New ()
CFrame.Angles ()
math. rad () Multiply
Po PB wR 0 Add the desired CFrame position and how much you want to offset it by.
Exercise People can be teleported from place to place within a world by updating their characterâs CFrame information. This might be so that players can cross a canyon like the one shown in Figure 14.7, or it could be teleporting participants from a lobby to an arena. For this exercise, create a part that teleports players to another part. Exercise 197
FIGURE 14.7 People can use the purple parts to teleport across the gap.
Tips > For the sake of practice, you only need to worry about teleporting the player in one direction.
etyaie bigs ..
ai ao HOUR 15 Smoothly Animating Objects
CFrames allow you to suddenly move things from one place to another in the blip of an eye. But what if you donât want things to blip? Maybe you'd instead like them to transition smoothly between two points or change from one color to another. Thatâs where tweens come in. In this hour, you use tweens to smoothly change the position and color of a block, but the same prin- ciples also apply to working with GUIs,
Understanding Tweens Tweens take a starting point, like a position on a map, or a certain color, and smoothly change to an end point over time. To use tweens, you need to get TweenService, as shown here: local TweenService = game:GetService ("TweenService")
TRY-TEcYOURSELF. -„:
2. Within the script, get the tween service and create a variable that points to the target part: local TweenService = game:GetService ("TweenService") local part = script.Parent 200 HOUR 15: Smoothly Animating Objects
Md.. A TweenInfo controls how the transition is handled. Create a new TweenInfo in 5.0 so that the transition to the new color will take 5 seconds: and pass
The TweenService needs a table to hold the goal values for each property to be changedâin this case, the final color of the part: local TweenService = game:GetService ("TweenService") local part = script.Parent local tweenInfo = TweenInfo.new(5.0)
local goal = (} goal .Color = Color3.fromRGB(11, 141, 255)
TIP Use Any RGB Value This color happens to be bright blue.
5. Use TweenService:Create() to bring together the target part, TweenInfo, and the goal table: local goal = {} goal.Color = Color3.fromRGB(11, 141, 255)
Give a smidge of time to let the experience load, and then tell the tween to play: local TweenService = game:GetService ("TweenService") local part = script.Parent
local goal = {} goal.Color = Color3.fromRGB(11, 141, 255) local tweenInfo = TweenInfo.new(5.0) local tween = TweenService:Create (part, tweenInfo, goal)
TIP Transition Time If you donât add the wait (), you'll probably miss the beginning of the transition. If itâs a short tran- sition time or a long load time, you might miss it altogether. If this tween were to be played after an event is fired, wait () wouldnât be needed.
Time [number, seconds] Determines how long it takes for the tween to reach its goals
EasingStyle [Enum] Determines how the tween behaves toward its goal
RepeatCount [number] Establishes the number of times the tween executes after its initial run Reverses [Bool] Designates whether the tween runs the reverse tween fol- lowing its initial run DelayTime [number, seconds] Determines the elapsed time before the tween executes a IE âââââ
This is a rare case where all the arguments are usually put on their own line just to make them easier to read. Not everything needs to be filled in, but you canât skip arguments. You can see a full list of EasingStyles and EasingDirections in the appendix.
Also notice that since TweenInfo is not a table, you donât put a comma after the last argument.
- BS Ea eES RAR SE iHBREDS qqemegne!
FIGURE 15.1 Fancy office buildings might have equally fancy sliding doors on an elevator.
After sliding the door open, the tween will pause and then reverse direction.
Set Up You just need a part for this Try It Yourself. Keep in mind that if you use a model, you need to make sure to account for that in the code later.
Script This script uses the first three parameters of TweenInfo to create a part that moves smoothly over a certain distance.
2. Get the necessary service for the ProximityPrompt and the Tween Service: local ProximityPromptService = game:GetService("ProximityPromptService") local TweenService = game:GetService("TweenService") Setting TweenInfo Parameters 203
3. Set up a new function and connect it to the ProximityPromptâs Prompt Triggered event. Ba Remember to add a check for which ProximityPrompt was triggered: local ProximityPromptService = game :Get Service ("ProximityPromptService") local TweenService = game:GetService("TweenService")
5. Create a table with the goal CFrame values for when the door opens. Your values may differ: if prompt.Name == "SlidingDoorPrompt" then local door = prompt. Parent local goal = {} goal.CFrame = door.CFrame + Vector3.new(0, 0, 5) end
TIP Your Vector3 Information May Be Different For the sake of simplicity, this code is just moving the door along the Z axis. You may need to use a different axis. If you were to use this code with several doors rotated in different directions, you may even want to experiment with relative coordinates. a Ee ee ee 204 HOUR 15: Smoothly Animating Objects
KA 6. Create a new TweenInfo and set the duration to 1 second, the easing style to Linear, and the easing direction to In: if prompt.Name == "SlidingDoorPrompt" then local door = prompt. Parent local goal = {} goal.CFrame = door.CFrame + Vector3.new(0, 0, 5)
end
ue Tweenlnfo Isnât a Table Keep in mind Tweeninfo isnât a table, so it doesnât need a comma after the last argument.
MP Autocomplete As youâre typing the Enums, notice that the autocomplete (see Figure 15.2) and IDE hints (see Figure 15.3) can help you out quite a bit.
FIGURE 15.2 Use the autocomplete to make filling out the TweenInfo easier.
FIGURE 15.3 Also pay attention to the IDE hints to keep track of the parameter order. Chaining Tweens Together 205
ggered: Connect (onPromptTrigg ProximityPromptService.PromptTri ered)
For cases like this, you can use the tweenâs event, Completed. First, wait for the starting tweenâs Completed event to fire, and then use a second wait () to control how long before the door closes, as shown here: local ProximityPromptService = game:GetService("ProximityPromptService") local TweenService = game:GetService ("TweenService")
local closeGoal = {} closeGoal.CFrame = door.CFrame
ProximityPromptService. PromptTriggered: Connect (onPromptTriggered)
Lilie Donât Forget to Debounce The script as it is doesnât include any sort of debounce. If someone were to keep Opening the door, the door could move farther and farther to one side. Donât forget to add a debounce if you actually place this code in your experience. Hee a eh Workshop 207
Summary Tweens allow you to smoothly transition almost any of a targetâs properties to a new value over time. Although the Try It Yourself exercises in this hour only showed one property being changed at a time, you can add as many properties to the goal table as you would like.
1. Get TweenService.
Itâs OK to depend on the IDE or to look up the order of the parameters if you canât remember them off the top of your head. All engineers do that. To revert a tween to the original state, you can enable reverse, or you can chain tweens together by listening for a tweenâs event, Completed.
Workshop Now that you have finished, letâs review what youâve learned. Take a moment to answer the fol- lowing questions.
Quiz rm To use tweens, what service do you need?
True or false: You can simply skip a parameter if you donât want to use it.
DN >abw True or false: Tweens play automatically once they are set up. 208 HOUR 15: Smoothly Animating Objects
Answers 1. TweenService
3. False: While you donât always have to pass an argument for every parameter, you canât skip over parameters.
4. Boolean
5. False: You can change multiple properties with the same tween.
Exercise For this exercise, tween the color of a SpotLight (like the lights in Figure 15.4) so it changes over time and never stops looping.
FIGURE 15.4 Color-changing lights attract people to this fun party place.
This hour introduces a new computer science term to add to your vocabulary: algorithms. You'll get a better idea of how you've already been putting this concept into action and go a step fur- ther with Roblox Studioâs built-in sorting algorithms, which you can use to do things like sort items in a shop from lowest to highest price or rank participants in an FPS according to how many kills they have.
Defining Algorithms Algorithms are precise instructions for solving a problem. Youâve actually created a number of algorithms throughout this book. For example, you've created algorithms that figure out a play- erâs health points after stepping on a trap and algorithms that determine a playerâs total points every time somebody touches a part.
3. A solution is given.
Letâs take a very simple algorithm, one that solves the problem of dividing two numbers. 210 HOUR 16: Solving Problems with Algorithms
local x = 20 local y = 9
Algorithms can be used over and over again with different inputs, much like a function can be used with any two numbers. Most algorithms will have more steps than this, but theyâll never have infinite steps. To be a true algorithm, the code has to give you a solution to your problem.
Sorting an Array A classic place where people need algorithms is when sorting thingsâtaking a list of names, objects, or numbers and putting them in order. In Roblox experiences, this information will most likely be stored in a dictionary or array.
Letâs start with arrays. table.sort (arrayName) uses a sorting algorithm to arrange the values within arrays numerically or alphabetically.
VIR FteyOQUuRSECE
FIGURE 16.1 The triangle indicates a collapsed view of a table.
Click the triangle to expand it and see the whole table, as shown in Figure 16.2.
FIGURE 16.2 The now-sorted table is expanded; notice the index on the left.
The same method can be used to arrange numeric values. The following code snippet sorts the array into ascending order, as shown in Figure 16.3. local testArray = {5, 2, 2, 10} table.sort (testArray) print (testArray) 212 HOUR 16: Solving Problems with Algorithms
FIGURE 16.3 This result shows numeric values after theyâve been sorted.
WARNING Be Careful with Numbers and Strings When Sorting If you try to sort an array of mixed data types, such as numbers and strings, all youâll get is an error: -- Strings and numbers cannot be compared local mixedArray = {5, "Frog", 2, 10}
You could use tostring() to convert from number to string types, but be aware that will cause table.sort to put things in alphabetical order, like so: -- Numbers converted to strings will sort alphabetically local stringArray = {"10", "2", "5", "Frog"}
table.sort () works by going through the whole array two values at a time and comparing the values against each other. By default, the function compares the two values using the less- than operator (<), making the lesser numbers come first.
To customize the sorting algorithm, a new function for comparing two values needs to be created and then passed in along with the array, as shown in the following snippet. Here, the greater- than operator is used, so greater values will appear first: -- First, set up the array local testArray = {5, 2, 2, 10} Sorting a Dictionary 213
-- Second, create a function that shows how two values should be compared local function (a, b) DescendingSort returmoa > b end
-- Third, pass the function into table.sort() along with the array. table.sort (testArray, DescendingSort) print (testArray)
The results of the code snippet will look like Figure 16.4.
FIGURE 16.4 The array is now sorted in descending order.
Sorting a Dictionary A very important thing to remember about dictionaries in Lua is that they do not have a guar- anteed order. They might sometimes do things in order, but you canât depend on it. In other words, you canât actually sort a dictionary.
Instead, what you have to do is convert the dictionary to an array, the results of which might look like the following table, which has an unsorted dictionary on the left and that same diction- ary after itâs been converted to an array on the right. We show you the actual method for con- verting a dictionary to an array in a moment.
Notice that the right-hand column is truly an array, but each value is itself a dictionary. Arrays can hold any valid data type, including dictionaries, and this lets you tag the data to make it easier to sort.
Once sorted by name, the array might look like the following:
local sortingArray = { {name = "healthBerry", amount = 10}, {name = "speedPepper", amount = 1}, {name = "staminaOnion", amount= 5},
WoTRY-IT YOURSELF
1. Set up a dictionary of four or five imaginary players and their scores, similar to the following: local playerScores = { Ariel = 10, Billie = 5, Yichen = 4, Kevin = 14,
} 2. Create a new array to hold the sorted results: local playerScores = { Ariel = 10, Billie = 5, Yichen = 4, Kevin = 14, } local sortedArray = {}
3. Use pairs() to go through the original dictionary and insert each key/value pair into the array as its own mini dictionary: -- Previous code
local sortedArray = {} Sorting a Dictionary 215
4. Set up the comparison function. This time, you want to compare the points and have the greater amounts be first: -- Previous code
TIP Use Dot Notation to Access Dictionary Keys While sorting, the algorithm looks at two values and evaluates them with the comparison function. In this case, each value happens to be a table, so you can use dot notation to navigate to the cor- rect key.
5. Pass the array and function into table.sort() and print the results: local playerScores = { (Neaedbe eae 207 Bilize = 5, Yichen = 4, Kevin = 14,
local sortedArray = {} -- Go through dictionary, and assign each key/value pair to an index for key, value in pairs(playerScores) do table.insert (sortedArray, {playerName = key, points = value}) end
The unsorted information makes it difficult to find what you're looking for. To make it easier to shop, you might want to list the weapons by type and then by price, as shown in Table 16.2.
Since this is already an array, you wouldnât need to convert it, which means the next thing would be to set up the comparison function. First you can compare types, or, if theyâre the same type, compare type and price: -- Sort first by most weapon type, then by price local function sortByTypeAndPrice(a, b)
TIP Keywords Canât Be Key Names On its own, type is a keyword. Thatâs why itâs best to use something like weaponType instead of type.
Finally, pass both the array and comparison function into table.sort () and print the result: table.sort (inventory, sortByTypeAndPrice) print (inventory)
Summary You've used algorithms before, but now you know what theyâre called. Thereâs lots of different sorting algorithms, each with their own strengths and weaknesses. Behind the scenes, Roblox Studio table.sort () uses what's called a quick sort. If youâre interested in learning more about sorting algorithms or creating your own, thereâs a wealth of information about them on the Internet; just search âsorting algorithms.â
Workshop Now that you have finished, letâs review what youâve learned. Take a moment to answer the fol- lowing questions.
Quiz ae What is an algorithm? What are three components of an algorithm?
hart hs heat What's the optional second parameter of table.sort ()? 218 HOUR 16: Solving Problems with Algorithms
5. If you want to use table.sort () to list the players with the quickest times, use the __ operator.
Answers 1. An algorithm is a specific set of steps that can be used to solve a problem.
Exercise A common set of stats in competitive experiences are a personâs number of kills, deaths, and assists (other peopleâs kills theyâve contributed to). Take a sample dictionary such as the one shown here, and sort it according to who has the most kills. If people are tied for kills, prioritize who has assisted other members of the team the most.
Tips >» Include three to five different players and try to make sure some of them are tied for most kills. You can use the following example dictionary if you like: local playerkKDA = { Ana = {kills = 0, deaths = 2, assists = 20}, Beth = {kills = 7, deaths = 5, assists = 0}, Cat = {kills = 7, deaths = 0, assists = 5}, Dani = {kills = 5, deaths = 20, assists = 8}, Ed = {kills = 1, deaths = 1, assists = 8}, } >» You will have to convert the dictionary to an array.
> Printing the array right after itâs created and before itâs sorted can be a good troubleshoot- ing step for making sure the array looks the way you expect.
Without a special mechanism in place to save information, anything that people in your expe- riences earn or accomplish between play sessions is lost. Once a person leaves the experience, points, gold, and purchases are forgotten. This hour covers how data can be saved so that noth- ing is lost between sessions.
Data saved from one session to another is kept in special tables, most typically in Data Stores. Data Stores work like a dictionary in which keys and values can be stored in the cloud with Rob- lox. This hour starts out by creating a box that keeps track of how many times itâs been clicked; then the topic changes to how to minimize the chances that a playerâs data is lost.
1. Make sure the experience is published to Roblox and not just saved locally on your computer.
3. Select Security and turn on Enable Studio Access to API Services. You can then save and exit Game Settings. 220 {OUR 17: Saving Data
GetDataStore("DataStoreName") gets the matching Data Store or creates one with that name if it doesnât already exist.
Keep in mind that SetAsync() will override the value of a key if it already exists. Once itâs overwritten, that information is gone. Thatâs one reason to make sure you always use unique key names.
„. TRY, YOURSELF
FIGURE 17.1 This crate displays how many times it has been clicked.
Set Up You need a part with a TextLabel, so do the following:
2. Insert a SurfaceGui.
4. Select the crate and insert a ProximityPrompt named CratePrompt. Donât worry about set- ting up a HoldDuration. Your hierarchy should look something like Figure 17.2.
FIGURE 17.2 The finished hierarchy for the crate should look like this. 222 HOUR 17: Saving Data
a CrateManager Youâll use two scripts. The first one will manage the ProximityPrompt and update the Data Store. The second script will handle the default display:
4. Add two constants: one for how often players will be allowed to click the prompt and a second for how often the Data Store will be updated: local DISABLED DURATION = 0.1 local SAVE FREQUENCY = 10.0
5. Get the total number of clicks so far from the Data Store; if thereâs no data yet, start at O. local DISABLED DURATION = 0.1 local SAVE FREQUENCY = 10.0
6. Set up everything you'll need for a new function connected to the promptâs PromptTriggered event:
ProximityPromptService. PromptTriggered: Connect (onPromptTriggered)
7. Update totalClicks and the displayed text every time the player clicks: -- Get the current value of TotalClicks, or set to 0 if it doesn't exist local totalClicks = crateData:GetAsync("TotalClicks") or 0
totalClicks = totalGlicks + 4 iv clickDisplay.Text = totalClicks
TIP Search the Childrenâs Children Adding true as a second parameter for FindFirstChild() is one way to go through all of an objectâs children and then go through the childrenâs children to find what youâre looking for.
~OtalClicks = totalClicks + 1 clickDisplay.Text = totalClicks
|W | crateData:SetAsync("TotalClicks", totalClicks)
end
Crate Script This second, shorter, script will be used to update the display text at the beginning of every session, before anyone has clicked on the crate.
4. Add a default value to be used in case the crate has never been clicked: local DEFAULT VALUE = 0
6. Update the TextLabel to display the current count, or the default value if TotalClicks isnât found: local DataStoreService = game:GetService ("DataStoreService") local crateData = DataStoreService:GetDataStore("CrateData")
local DEFAULT_VALUE = 0 local totalClicks = crateData:GetAsync("TotalClicks")
Test your place out. You should be able to stop the play test and then restart with the updated click amount displayed on the crate. Sometimes it might take a second to update values.
TIP Make Sure To Use Unique Key Names Keep in mind that keys within the Data Store need to be unique. If you duplicate the crate, any click on either crate will add to the total. If you want their totals to remain separate, each crate needs its own distinct key. ea ee de Protecting Your Data 225
Each call request gets added to a queue, and thereâs only so many spots in line before the queue fills up and simply wonât accept any more requests. Other good times to update the Data Store are when a player joins, leaves, or the server closes down.
A pcall() takes in a function and returns two values. The first value is a Boolean that states whether the call went through; the second value is for any returned error messages: local setSuccess, errorMessage = pceall (functionName)
pcall() only accepts functions, so if you donât want to create a function beforehand, you can pass in the network call using an anonymous function: local setSuccess, errorMessage = pcall (function () dataStoreName:SetAsync(key, value) end)
You can then test the returned value to make sure it went through. Here, if setSuccess is false, the error message will be printed: if not setSuccess then print (errorMessage) end
If you were to update the while loop in the last section, it might look like this: -- Update the Data Store every so often while wait (SAVE FREQUENCY) do local setSuccess, errorMessage = pcall (function () crateData:SetAsync("TotalClicks", totalClicks) end)
else print ("Current Count:") print (crateData:GetAsync ("TotalClicks") ) end end
Players. PlayerAdded: Connect (onPlayerAdded)
The blue highlight in the code snippet below is the pcall (): local updateSuccess, errorMessage = pcall (function () pointsDataStore:UpdateAsync(playerKey, function(oldValue) local newValue = oldValue or 0 newValue = newValue + GOLD _ON JOIN return newValue end) end) Q&A 227,
Within that, you get the Data Store as normal and use UpdateAsync() to pass in the key as the first parameter: local updateSuccess, errorMessage = pcall (function () pointsDataStore:UpdateAsync(playerKey, function(oldvalue) local newValue = oldValue or 0 newValue = newValue + GOLD ON JOIN return newValue aaa end) end)
Finally, the second parameter takes a function that accepts the old value and returns what the updated value should be. You can create the function beforehand or use an anonymous function as shown: local updateSuccess, errorMessage = pcall (function () pointsDataStore:UpdateAsync(playerKey, function (oldValue) local newValue = oldValue or 0 newValue = newValue + GOLD ON JOIN return newValue end) end)
Summary You can now save data, and that opens you up to opportunities like monetization that werenât available to you before. You can save just about any kind of data that you can think of. In an RPG experience, you can save peopleâs skill level, weapon prowess, and inventory. In competi- tive games, you can save a playerâs rank or average KDA. You can also begin tracking whether people have purchased items in your experience, such as pets, power ups, and weapons.
Data Stores are really powerful; you just need to make sure that you are always using unique keys and verifying that you are both sending and receiving the correct data using pcalls(). The last thing you want is a community of people who are angry that their purchases have been lost to the ether.
Q&A Q. What other ways can you save and update player data?
A. You can search the Roblox Developer hub to find a list of common errors, as well limits for how often requests can be made: https://developer.roblox.com/articles/Datastore-Errors.
Workshop Now that you have finished, letâs review what youâve learned. Take a moment to answer the fol- lowing questions.
Quiz he Whatâs the pattern for retrieving a Data Store (not just a Data Store key)? What does the p in pcall() stand for?
Data Stores take and store two pieces of information; what are they?
Ming ede Pete If thereâs a chance more than one server might be updating a Data Store at a time, should you use SetAsync() or UpdateAsync() ?
Answers 1. local dataStoreName= DataStoreService:GetDataStore ("DataStoreName")
2. Protected
3. You should use a pcall() every time you are reading or updating information from a Data Store.
5. UpdateAsync ()
Exercise Using the information in this hour, you should be able to award people five pieces of gold every time they log in. You can display peopleâs gold on a leaderboard, but for the sake of this exer- cise, you're only being asked to print out how much gold a person has after itâs been updated.
Tips >» Make sure you use the playerâs ID rather than their name.
» Donât forget to check success while reading and updating the Data Store. HOUR 18 Creating a Game Loop
This hour introduces the concept of a game loop, or the pattern of actions that take place in a game. You'll learn how to set up a simple round-based game where players are transported to an arena and back again after a certain amount of time. To do this, you need all of the skills you've learned so far, and add one new skill: using BindableEvents.
The real lesson of this hour is to think about how to organize your world and the scripts within it. The main project focuses on best practices for organizing the workspace as well as your code.
> In harvesting simulators, one pattern is to harvest items, sell items, buy a bigger backpack or a faster shovel, and then harvest even more items.
> In a competitive experience, contestants might be taken from a lobby and then teleported to an arena to fight it out in a 15-minute match. At the end of the match, everyone is returned to the lobby to prepare for the next match.
> In an exploration world, individuals might go through cycles of cooking, mining, and hunting to improve their equipment and skills.
> In an educational experience, students might perform a virtual dissection, record their findings, and then move on to another organism to compare the differences. 230 HOUR 18: Creating a Game Loop
The code you create needs to be able to facilitate the loops that people naturally want to cycle through, giving them periods of both excitement and rest. The loop you'll go through in this hour is a simple round-based game. Participants will be teleported from the lobby to the arena. Once in the arena, they have a certain amount of time to complete a moon-themed obby before theyâre transported back to the lobby.
This game loop is one that can be expanded upon if you later want to introduce elements like competition between players, completing puzzles, or collecting items.
BindableEvents for server use should be placed within ServerStorage. Within ServerStorage, itâs best practice to create folders for different types of objects to keep them organized. Figure 18.1 shows two BindableEvents in a folder and a second folder for ModuleScripts.
FIGURE 18.1 Within ServerStorage, use folders to organize different types of objects.
The event fired by BindableEvents is actually named Event. Functions can then be connected to Event like normal: EventName. Event : Connect (functionName) Working with BindableEvents 931
TRY 1F YOURSELF-„ Make a Simplified Game Loop For the rest of this hour, you'll be working on a moon obby challenge while focusing on cleanly organizing your scripts and assets. The first step you'll take is to set up two distinct areas: a lobby and the arena. You can make them as detailed or functional as you would like. Figure 18.2 shows an elaborate lobby and arena; simpler versions are shown on the right.
FIGURE 18.2 On the left, a lobby sits on a tower above an arena. On the right, simple parts are used to mark the lobby and arena.
After that, you'll set up the events to mark the beginning and end of each round, as well as set up the code for your simplified game loop.
Set Up A big focus of this chapter is organization, which includes organizing the assets for your world. You'll keep all of the assets for the lobby and the arena in their own folders, and theyâll have their own spawn locations.
1. Create the two areas you want to use for your world, such as those shown in Figure 18.2.
2. Separate all the elements for the two areas into unique folders (see Figure 18.3). 232 Creating a Game Loop
Explorer
@ Workspace Âźy Camera A Terrain
> jg Arena
& Players @ Lighting up ReplicatedFirst ug ReplicatedStorage ey ServerScriptService @ ServerStorage Vv fie Events Event
FIGURE 18.3 All of the assets for the lobby and arena should be separated into two folders.
3. In the Lobby folder, add a spawn location named StartSpawn. Then add a second spawn- Location in the Arena folder (see Figure 18.4).
© workspace Gy Camera WA Terrain me Lobby we Environment
i Arena
ronment
wnLocation
Players Lighting
ReplicatedFirst
FIGURE 18.4 The SpawnLocations will be used for moving players back and forth. Working with BindableEvents 233
atP 4 Use Additional Folders as Needed In Figure 18.4, you can see an additional folder named Environment; all of the environment parts have been placed inside. You donât need to do this, but it is helpful. ns
FIGURE 18.5 A new folder named Events is within ServerStorage.
5. Inside of the Events folder, add two new BindableEvents. Name one of the events RoundStart and the other RoundEnd (see Figure 18.6).
ae Theme Your World If you would like to create a moonlike setting, you can change the gravity of the experience within Game Settings > World. Otherwise, you can stay focused on the code if you donât want to create an actual moon obby. 234 HOUF Creating a Game Loop
tedFirst dStorage
RoundStart RoundEnd eM StarterGui aa S
FIGURE 18.6 Within the Events folder, add two new BindableEvents.
RoundSettings ModuleScript The basic settings that control how long each round lasts and how many people are required before starting will be pulled out into its own ModuleScript. This makes updating often-changed settings easier for you and easier for anyone who may end up working with you down the line.
2. Within that folder, create a new ModuleScript named RoundSettings (see Figure 18.7).
FIGURE 18.7 The ModuleScript folder and RoundSettings module have been created. Working with BindableEvents 935
3. Insert values for how long each round should last, the amount of time the players spend ve in the obby, and the minimum number of people needed to start the round. Donât forget to rename the table: local RoundSettings = {}
-- Game Variables RoundSettings.intermissionDuration = 5 RoundSettings.roundDuration = 15 RoundSettings.minimumPeople = 1
return RoundSettings
RoundManager The loop will run within a server-side script. As it runs, it'll fire the events at the appropriate time. A separate ModuleScript will then be listening for those events.
1. In the ModuleScript folder, add a new ModuleScript named PlayerManager (see Figure 18.8).
FIGURE 18.8 ModuleScripts includes a new folder named PlayerManager.
2. In ServerScriptService, add a new regular script named RoundManager (see Figure 18.9). 236 Creating a Game Loop
serverScriptService RoundManager
FIGURE 18.9 You should have three scripts altogether.
Set up references for the ModuleScripts folder and the two ModuleScripts youâll be using: -- Module Scripts local moduleScripts = ServerStorage.ModuleScripts local playerManager = require (moduleScripts.PlayerManager) local roundSettings = require (moduleScripts.RoundSettings)
-- Events local events = ServerStorage.Events local roundStart = events.RoundStart local roundEnd = events.RoundEnd
Create a while-true-do loop. Within it, use the settings from the ModuleScript to wait for the right number of people to join the experience and then fire RoundStart: -- Runs the game loop while true do repeat wait (roundSettings.intermissionDuration) Working with BindableEvents 237
ie Repeat Until a Condition Is Met Before now, weâve often used a while loop, which runs until a condition becomes false. Here, we've used repeat until, which does the opposite; it runs until a condition becomes true. In this case, the condition is when the minimum number of players have joined.
7. Wait for the time specified in RoundSettings for the round duration and then fire RoundEnd. Here is the completed script: -- Services local ServerStorage = game:GetService ("ServerStorage") local Players = game:GetService ("Players")
-- Module Scripts local moduleScripts ServerStorage.ModuleScripts local playerManager require (moduleScripts.PlayerManager) local roundSettings require (moduleScripts.RoundSettings)
=-- Events local events = ServerStorage.Events local roundStart = events.RoundStart local roundEnd = events.RoundEnd
while true do repeat wait (roundSettings. intermissionDuration) until Players.NumPlayers >= roundSettings.minimumPeople roundStart: Fire () wait (roundSettings.roundDuration) roundEnd: Fire () end
This is the loop that will run over and over and over. You can now add functionality to the rounds by listening for the fired events.
PlayerManager This is where the meat of your code will be. Everything that happens to the players as they enter and leave the round goes here. You can give people weapons, assign teams, record scores, or do pretty much anything else you can think of. For now, weâre just going to transfer them to and from the obby. 238 HOUR 18: Creating a Game Loop
-- Services local Players = game:GetService ("Players") local ServerStorage = game:GetService ("ServerStorage")
return PlayerManager
2. Set up variables for the lobby spawn, the arena map, and the arena spawn: local PlayerManager = {}
-- Services local Players = game:GetService ("Players") local ServerStorage = game:GetService ("ServerStorage")
-- Variables local lobbySpawn = workspace.Lobby.StartSpawn local arenaMap = workspace.Arena local arenaSpawn = arenaMap.SpawnLocation
return PlayerManager
-- Services local Players = game:GetService ("Players") local ServerStorage = game:GetService("ServerStorage")
-- Variables local lobbySpawn = workspace.Lobby.StartSpawn local arenaMap = workspace.Arena local arenaSpawn = arenaMap.SpawnLocation
return PlayerManager
4. Start off with what should happen when the player first joins the experience, before they enter the arena. Here, weâre just going to spawn them in the arena: local PlayerManager = {} Working with BindableEvents 939
return PlayerManager
TIP Get Any Saved Data If you decide to add any saved dataâsuch as skill level, appearance upgrades, or total pointsâthis is where you can check for that and update a leaderboard.
5. Say what you want to happen at the beginning of the round. Here, weâre going through the list of players and reloading their character at the arena spawn location: local PlayerManager = {}
return PlayerManager
local PlayerManager = {} -- Previous code not shown
return PlayerManager 240 HOUR 18: Creating a Game Loop
-- Services local Players = game:GetService ("Players") local ServerStorage = game:GetService("ServerStorage")
-- Variables local lobbySpawn = workspace.Lobby.StartSpawn local arenaMap = workspace.Arena local arenaSpawn = arenaMap.SpawnLocation
Players.PlayerAdded: Connect (onPlayerJoin) roundStart .Event: Connect (onRoundStart) roundEnd. Event : Connect (onRoundEnd)
return PlayerManager Workshop 241
Summary A game loop, or an action loop, is the pattern of activities that people take within your experi- ence. The code you write needs to support those actions. In this hour, you created the loop using a literal coded while loop. In other experiences, the loop might still be driven by events such as harvesting, buying, and selling, but those may not be so literally circular. Once you have your basic loop designed, you can continue to add functionality, such as server announcements, team assignments, the ability to add random maps, and updating saved data.
BindableEvents are quite often used with game loops because they allow signals to be sent server to server or client to client.
This hour also talked a lot about keeping the objects in your projects organized, which is just as important as writing clean code. Use folders to organize the scripts, models, events, and every- thing else in your world.
Q&A Q. Why did you use LoadCharacter() instead of updating the HumanoidRootPartâs CFrame?
A. If all you want to do is simply move a player from one location to another, updating their CFrame position is a quick way to do it. However, there are some benefits that can be taken advantage of by forcing a character to reload. SpawnLocations are capable of provid- ing temporary force fields as well as being used for team assignments and checkpoints.
Workshop Now that you have finished, letâs review what you've learned. Take a moment to answer the fol- lowing questions.
Quiz = True or false: BindableEvents can send signals across the server-client divide. A repeat-until loop does what?
od en toat "cadeIf you were to add functionality such as giving the players Superpowers, how would you do that? 242 HOUR 18: Creating a Game Loop
Answers 1. False. If a signal needs to be sent between the server and client, you should use RemoteEvents.
2. A repeat-until loop repeats until a condition is true, which is the opposite of what a while-do loop does.
3. If BindableEvents will be used by the server, they should be stored within their own folder in ServerStorage. If BindableEvents are used by the client, then they are stored within ReplicatedStorage.
4. You can add functionality within roundStart () and remove it within roundEnd (). Or, if itâs a larger chunk of code, you may want a separate ModuleScript to also be required within RoundManager.
Exercise Itâs always good to keep players informed of whatâs about to happen to them. Create a new ModuleScript designed to announce the beginning and end of every match. For now, itâs fine to just practice with BindableEvents and simply print âRound Startingâ and âRound Overâ at the beginning and end of each round. In an actual experience, you would want to set up the UI to make the announcement to the players.
Tips » Create a separate module named Announcements.
>» Within the module, create separate functions for the announcements at the beginning and end of the match.
>» Use print statements to check everything, and then if you want to, move to creating more finalized Ul code. HOUR 19 Monetization: One-Time Purchases
This hour deals with how to allow for users to buy items in your experiences using Robux. You can use your earned Robux in other games or to purchase catalog items, or you can eventually cash out for real-world money using the Developer Exchange Program.
To cash out, you must have an active Roblox Premium membership, be at least 13 years old, and have acquired at least 100,000 Earned Robux. To see the full set of guidelines, visit the Developer Exchange FAQs.
As you go through this hour, you may want to test the functionality of the items youâre selling, which you have to have Robux to do. If you donât have any Robux at this time or donât want to spend them, you can safely continue to the next hour without missing any new coding concepts.
> To give people the ability to visit new areas of your experience.
> To offer people cosmetics such as sparkles, trails, and weapon skins. 244 {OUR 19: Monetization: One-Time Purchases
TIP Fun Comes First The most profitable experiences are the ones in which people have the most fun. If people have been having fun in your experience for a while, theyâre more likely to spend Robux than if you make them pay for things upfront.
To make a new pass, you first need to set up some information on the Roblox site about the pass, and you need an image for the pass itself:
@ htt www.roblox.com
FIGURE 19.1 Click Create in the top navigation.
4. Select Create Pass from the Settings drop-down menu on the right-hand side (see Figure 19 2);
GalacticSpaceHub See start Place: GalacticSpaceHub | Sponsor Edit âuY 3 private get a | Configure | Experience | Configure Start | Spot Place âââ| Configure | Localization |Create Badge Create Pass | Spot Developer Stats P=] :Advertise Shut Down All | Servers
FIGURE 19.2 Select Create Pass.
5. All passes require an image, name, and description. Provide all three and then click the Preview button (see Figure 19.3). Adding Passes to Your Experience 245
Create a Pass Target Experience: PassPractice
FIGURE 19.3 Upload an image for the pass, give the pass a name, and type a description.
6. On the next screen, click Verify Upload (see Figure 19.4) to create the pass and send it through moderation.
Create a Pass Name: Party Crown
Target Experience:PassPractice
=n Verify Upload
FIGURE 19.4 Make sure everything looks correct before you click Verify Upload.
TIP You Can Update the Pass Later Changes can be made to the pass by selecting the pass and then right-clicking and selecting Configure. 246 HOUR 19: Monetization: One-Time Purchases
1. Select Configure from the Settings right-side drop-down menu for the new pass (see Figure 1959).
FIGURE 19.5 Configure the new pass.
2. On the configuration page, click the Sales tab (see Figure 19.6).
FIGURE 19.6 Click the Sales tab.
3. Click the Item for Sale toggle switch to make the pass available to players. Then enter the price (in Robux) players will pay for the item (see Figure 19.7).
Price
FIGURE 19.7 After you enter the price, the percentage of Robux you'll get from the sale is shown under the field. Prompting In-Game Purchases 2A7
TIP
5. Take a moment to copy the ID number of the pass from the URL. Youâll need this number in your code (see Figure 19.8).
ca
FIGURE 19.8 Make a note of the ID number for use in your code.
When youâre checking to see if somebody already owns a pass, use this code: UserOwnsGamePassAsync (player.UserId, gamePassID)
You should always wrap the check within a protected call like this: local success, message = pcall (function () hasPass = MarketplaceService:UserOwnsGamePassAsync(player.UserId, gamePassID)
end) 248 HOUR 19: Monetization: One-Time Purchases
WW TRY iT YOURSELF
FIGURE 19.9 Let people wear this cool party crown if they buy your pass.
1. Follow the steps in the first part of the hour to create a pass. Remember to configure the pass so itâs available to be purchased. You'll need to test purchasing the pass, so you may want to make the price something like 5 or 10 Robux.
2. On the pass page, delete the pass from your inventory (See Figure 19.10). If you closed the page, you can get back to the pass page by going back to the Create Pass drop-down menu and then clicking on the pass. Prompting In-Game Purchases 249
Party Crown Item Owned Delete from Inventory
Description
FIGURE 19.10 Delete the pass from your inventory so you can test what itâs like to purchase it.
3. In StarterGui, add a new ScreenGui and Button named BuyHat. You can use either a TextButton or ImageButton.
2. At the top of the script, get MarketplaceService, Players, and the local player: local MarketplaceService = game:GetService ("MarketplaceService") local Players = game:GetService ("Players") local player = Players.LocalPlayer
end button.Activated:Connect (prompt Purchase)
4. Set up a variable with your pass ID that you copied from the URL. Inside the function, set up a Boolean variable named hasPass. Set hasPass to false: -- Previous code local screenGui = script.Parent local button = screenGui:FindFirstChild("BuyHat") 250 HOUR 19: Monetization: One-Time Purchases
Inside prompt Purchase, use protected calls to check whether the player has the pass: local function promptPurchase() local hasPass = false local success, message = pcall(function() hasPass = MarketplaceService:UserOwnsGamePassAsync (player.UserId, gamePassID) end)
If the player doesnât have a pass, trigger the purchase prompt. The following is the com- pleted script: local MarketplaceService = game:GetService ("MarketplaceService") local Players = game:GetService ("Players") local player = Players.LocalPlayer
else Bal -- Player does NOT own the game pass; prompt them to purchase MarketplaceService : PromptGamePassPurchase (player, gamePassID) end end button .Activated: Connect (prompt Purchase)
Testing Testing properly requires a live server, but first you can do some quick spot checks in Studio to see if youâre on the right track:
1. Quickly test your code. You should see confirmation that you either own the pass or an error that purchases aren't allowed in this environment (see Figure 19.11). The error is a good sign because passes have to be bought in a live server. If you see this, go on to the next step. Otherwise, check your code and make sure you donât already have the pass.
FIGURE 19.11 If your code is correct, this error should appear while testing in Studio.
2. Publish to Roblox. Youâll need the most up-to-date version of your place.
3. Test the code from the live server. The easiest way is to go back to the Create tab and click on the starting place of the experience you want to test. Thatâll take you to the game page.
4. Within the place, test your button. You should see a prompt to purchase the pass like the one shown in Figure 19.12. 252 Monetization: One-Time Purchases
FIGURE 19.12 In the live environment, a prompt to purchase the pass displays.
TIP Test the Live Server Often Itâs always best to test your code on a live server, particularly if you have GUI elements. Different screen sizes and types can affect how people view your experience. If you can, test a variety of devices, such as tablets, phones, PCs, and Macs.
2. Set up the services and your pass ID. Then connect a function to PlayerAdded: local MarketplaceService = game:GetService ("MarketplaceService") local Players = game:GetService ("Players")
end Players . PlayerAdded: Connect (onPlayerAdded) Prompting In-Game Purchases 253
4. lf the player has the pass, you trigger whatever functionality the pass gives. Check for the pass as shown here: -- Previous code local function onPlayerAdded (player) local hasPass = false local success, message = pcall (function () hasPass = MarketplaceService:UserOwnsGamePassAsync (player.UserId, gamePassID) end) if not success then warn ("Error while checking if player has pass: " .. tostring (message) ) return end
5. For this particular pass, we want to override the userâs appearance. Create a new function named onCharacterAdded and call it if the user has the pass:
end 2 19: Monetization: One-Time Purchases
Finally, inside onCharacterAdded, get the current humanoid description and wait for the character to be added to the workspace. Then use ApplyDescription() to add the hat, as shown here: local MarketplaceService = game:GetService ("MarketplaceService") local Players = game:GetService ("Players")
TIP Using Descriptions An alternative way to add a hat is to simply parent it to the userâs head. However, descriptions are more reliable. Descriptions can also be used to update other accessories, such as hair. You can find a full list of description uses on the Developer Hub.
8. When you test in Studio, the print confirmation should appear in Output. Publish to Roblox and then check on the server to verify everything actually works as intended.
Summary Above all else, always concentrate on making your experience somewhere people want to spend time before concentrating on getting your community to spend money.
To create one-time purchasable items, create a pass for the experience on www.roblox.com. Then, within the experience, set up the functionality for the pass.
Purchasing and checking for passes are handled with MarketplaceService. You can use Market- placeService to prompt users to purchase a pass with PromptGamePassPurchase () and check whether they already have the pass with UserOwnsGamePassAsync (player.UserId, game- PassID). Always wrap the check in a peall(). 256 HOUR 19: Monetization: One-Time Purchases
Q&A Q. What if you want to create something that can be purchased over and over again, like in- game currencies or power-ups?
A. To create items that can be purchased repeatedly, you want to create Developer Products. These work similarly to passes. If you search Developer Products on developer.roblox.com, you'll find sample code. With the knowledge you have from this hour, you should be able to figure out how to customize the code.
Q. How do! come up with ideas for pass and developer products?
A. The number one most important thing you can do is figure out what would make people want to stick around in your experience longer. If people are having fun, they'll spend more. One thing you can do is offer special skins, rare items, or consumables like health potions. You can find great workshops about good monetization practices and how to create more engaging experiences by following Roblox Developer Relations and their Level Up series on YouTube.
A. In addition to creating developer products and passes, you can also receive engagement payouts. If users who are members of Roblox Premium spend a lot of time within your experi- ence, you can earn Robux. The more time they spend, the more you earn. Thereâs also oppor- tunities to create and sell avatar items, t-shirts, and plug-ins.
Workshop Now that you have finished, letâs review what you've learned. Take a moment to answer the follow- ing questions.
Quiz = What service allows you to sell items in your experience?
True or false: If you want to create a pass to sell, you need to include an image.
Should confirmation that the user owns a pass be done on the server or client side?
Oy! oar)If you want your experience to do well and make Robux, what should you concentrate on before anything else?
Answers 1. MarketplaceService
True
On the server side. You never want something that important happening on the client side.
Fun. If your experience isnât fun, people wonât spend Robux in it.
KN Bw oaAt least 13.
Exercise Come up with an idea for a cosmetic change that can take place if a user has the correct pass. In this particular case, the code will be very unique to you, but you should be able to make it work if you follow the basic pattern already shown. The cosmetic change can be anything from giv- ing the player sparkles to giving them a permanent speed boost.
Whatever pass you decide to create, it should never give purchasers an unfair advantage within your experience. Thatâs not fun for anyone. Your community will complain, and in the long run, you will most likely make fewer Robux than if you keep the playing field even. > ack Piasti=p haty grat ae
i â , â nia ig ee Wr Asst rernacm e ââ aie > ae ae a ' = = â : i
wine ip af ta om i a â 7 oeâ es =tqgla x ne a ae - 1 Z nmeree te See : pol inth ce ade - a =
a 7 ,ae s > : â _a a. 7 HOUR 20 Object-Oriented Programming
This hour explains how to create custom instances using a concept called object-oriented program- ming, which is more commonly referred to as just OOP. As you practice object-oriented program- ming, you'll begin to think about what objects in your experience have in common and how you can begin to group them and create new categories of objects.
What Is OOP? The core of OOP are the concepts of objects and classes. An object represents an individual thing in your world, such as a house, a car, a tree. Indeed, everything in the Explorer is an object. Parts, models, particle emitters, proximity promptsâall of these are objects.
A class describes what an object is and does. For example, a Part and SurfaceLight are very dif- ferent things even though they are both objects. Their classes are what make them different. A Part is one type of class, whereas a Light is another.
Once you know all the different types of things you need, you can begin thinking about how to code them in a way that allows you to reuse code to create different versions of the same things. For example, one car might be red with a racing stripe, whereas another is yellow. What you 260 HOUR 20: Object-Oriented Programming
donât want is to have to create two different scripts just because the cars need to be two different colors. With OOP, you just have to make one class, car, and then have color be one of the modi- fiable properties.
TIP Use Two Underscores Before index There are actually two underscores before index. It can easily be mistaken for just one. _ index should always be set to the name of the class.
2. A class on its own doesnât do much, though. You need to add a function to the class that describes how to create a new object of that class. These functions are called constructors because they make things. Get it? function NameOfClass.new()
end
3. Inside the constructor, create a new table and return it at the end of the function. This table is the object that gets made whenever a new class instance is asked for: function NameOfClass.new() local self = {}
return self end
MP
4. Use setmetatable () to insert the object self into the original table: function NameOfClass.new() local self = {} setmetatable(self, NameOfClass)
return self end
Finally, you can call the function to create a new class instance: local NameOfClass = {} NameOfClass.__index = NameOfClass
function NameOfClass.new() local self = {} setmetatable(self, NameOfClass)
return self end
function NameOfClass.new(parameterProperty) local self = {} setmetatable(self, NameOfClass)
return self end
Vv TRY IT YOURSELF
local Car = {} Garin) Index =) Car
function Car.new() local self = {} setmetatable(self, Car)
return self end
local Car = {} Car. wandexw= Car
function Car.new() local self = {} setmetatable(selfÂŁ, Car)
self.numberOfWheels = 4
return self end
3. Add the parameter for the color of the car. Remember to add the parameter to the con- structor declaration as well: local Car = {} Car. _index = Car
function Car.new(color) local self = {} setmetatable(self, Car)
self.numberOfWheels = 4 self.color = color
return self end Using Class Functions 263
4. Test your code by creating a new instance of the car and try printing out its properties: tocalâ-Car =} Cares index = Car
function Car.new(color) local self = {} setmetatable(self, Car)
self.numberOfWheels = 4 selft.color = color
return self end
function NameOfClass.new() local self = {} setmetatable(self, NameOfClass) return self end
end
Youâll often find that you will want to reference the object in class functions, particularly to access that objectâs properties. You can use self in functions that represent the object: function NameOfClass:nameOfFunction() local variable = self.nameOfProperty end 264 HOUR 20: Object-Oriented Programming
VTRYAT YOURSELF
Set Up First, you need to have a model to use as the pet. You could use a fancy pet model if you have one. Otherwise, just follow these steps to create a square stand-in:
1. Insert a model into the workspace; rename the model something like Dog. If using just a square part, use the key combination Ctrl+G/Cmd+G to turn it into a model and then rename it.
2. Inside the model, insert a new part. Rename the part HumanoidRootPart.
TIP MoveTo() Needs a HumanoidRootPart Later in this script, the function MoveTo() will be used to make the pet move. In order for it to work, the main part must be named exactly HumanoidRootPart. If itâs named anything else or capitalized wrong, the part won't be able to animate properly.
2. Reference ServerStorage and create a constant for how long the pet will follow a player after someone has patted its head: local ServerStorage = game:GetService ("ServerStorage")
3. Create a new pet class and its constructor. Include a parameter to allow you to pass in what model to use for the pet: local Pet = {} Pet. _index = âPet
function Pet.new(model) local self = {} Using Class Functions 265
setmetatable(self, Pet)
return self end
4. Assign the passed-in parameter to the classâs model and parent it to the workspace, as follows: local Pet = {} Pet... index = Pet
return self end
HIP Pay Attention to the Naming Convention Notice that inside of the constructor, names are preceded by a single underscore.
5. Inside the constructor, create a new ProximityPrompt. Update the promptâs ObjectText and ActionText as shown in the following code, and then parent the prompt to the pet: local Pet = {} Pet. index = Pet
return self end 266 HOUR 20: Object-Oriented Programming
1. Add a new function to the pet class named getPets() with a parameter for the player: -- Previous Code
end
2. Use a for loop to update the location of the pet every 0.25 seconds and then re-enable the prompt: function Pet:getPets (player) self. petPrompt.Enabled = false
end
return Pet end Using Class Functions 267
4. Go back up to the Pet constructor and use an anonymous function to call getPets when yo the prompt has been triggered: local Pet = {} Pete index = Pet
return self end
5. Now, all thatâs left is to make a new instance of the Pet class and pass in the model of the pet you want it to use. Here is the completed script: local ServerStorage = game:GetService("ServerStorage")
local Pet = {} Pet. wndex =) Pet
Lv return self end
return Pet end
TIP Modify the ProximityPrompt if You Canât See It If you have trouble finding the ProximityPrompt while testing, try setting RequiresLineOfSight to False. It may be buried within the model and therefore blocked from view. You can also customize other ProximityPrompt properties, such as UIOffset and Exclusivity, as needed.
Summary An important part of DRY coding is not repeating yourself. Creating classes saves you time and work by creating reusable code. In the next hour, you find out how classes can be further cus- tomized. So maybe instead of just having unique models for pets, you can have different pets, each with its own model, texture, and sound. Workshop 269
Workshop Now that you have finished, letâs review what youâve learned. Take a moment to answer the fol- lowing questions.
Quiz ie What does OOP stand for?
What is a class?
What is a constructor?
eth SeWhatâs wrong with the following code? local NameOfClass = {} NameOfClass. index = NameOfClass
function NameOfClass.new(parameterProperty) local self = {}
return self end
Answers 1. Object-oriented programming
4. Aconstructor is a function that describes how to build a new object of a class. All of the properties of a class go inside the constructor. 270 HOUR 20: Object-Oriented Programming
local NameOfClass = {} NameOfClass. index = NameOfClass
function NameOfClass.new(parameterProperty) local self = {} setmetatable(self, NameOfClass) -- Was missing
return self end
Exercise Create an NPC person class where each NPC person has a parameter to set their name, and has a function to print out their name when called.
Tips v Create the class and constructor first.
Vv You donât need to include a model or other information unless you want to.
>» Create the function outside of the constructor and call it separately.
> What the relationship between parent and child classes (inheritance) is > How to create child classes that inherit properties and functions from a parent class > How to overload functions for polymorphism > How to call parent functions
As your projects get larger and more complex, you might notice some of your classes will overlap and share certain characteristics. In such cases, you can structure your code so that classes pass on their properties and functions to other classes. We call this practice inheritance. The classes that pass on their behaviors are called parent classes, and the classes that inherit these behaviors are called child classes.
Consider the built-in Roblox classes PointLight and SpotLight. Although they illuminate a scene in slightly different ways, they also have a lot in common, as you can see in the properties shown in Figure 20.1. You can turn either of them on and off, and you can adjust the color and brightness for both. Both of these classes inherit these behaviors from their parent class Light. 272 21: Inheritance
FIGURE 21.1 PointLight (left) and SpotLight (right) share a number of properties from their parent class, Light.
Setting Up Inheritance In Lua, inheritance is built on the same class and metatable structure that we used for classes in the previous chapter. The following steps walk you through the basic pattern:
function ParentClass.new() local self = {} setmetatable(self, ParentClass) return self end
function ParentClass.new/() local self = {} setmetatable(self, ParentClass) Setting Up Inheritance 273
return self end
local ChildClass = {} ChildClass. index = ChildClass
To make a child class, use the setmetatable function. This time, pass in the name of the child class, and then the name of the parent class: local ParentClass = {} ParentClass. index = ParentClass
function ParentClass.new() local self = {} setmetatable(self, ParentClass) return self end
local ChildClass = {} ChildClass. index = ChildClass setmetatable(ChildClass, ParentClass)
local ParentClass = {} ParentClass.__index = ParentClass
function ParentClass.new() local self = {} setmetatable(self, ParentClass) return self end
local Childclass = {} ChildClass. index = ChildClass setmetatable(ChildClass, ParentClass)
function ChildClass.new()
end
Inside the child class constructor, create a variable called self. However, instead of setting the value to {} like we have been in constructors, have it set to a new instance of the par- ent class: local ParentClass = {} ParentClass. index = ParentClass
function ParentClass.new() local self = {} 274 HOUR 214: Inheritance
setmetatable(self, ParentClass) return self end
local Childclass = {} ChildClass.__index = ChildClass setmetatable(ChildClass, ParentClass)
function ChildClass.new() local self = ParentClass.new() end
6. Set the metatable for the self variable to the child class and then return self: local ParentClass = {} ParentClass. index = ParentClass
function ParentClass.new() local self = {} setmetatable(self, ParentClass) return self end
local ChildClass = {} ChildClass. index = ChildClass setmetatable(ChildClass, ParentClass)
function ChildClass.new() local self = ParentClass.new/() setmetatable(self, ChildClass) return self end
Inheriting Properties Any properties that a parent class defines will also become properties of children classes. If you remember talking about DRY coding a few hours ago, this is ideal because that means you donât have to repeat yourself by creating the same properties for every class: local ParentClass = {} ParentClass. index = ParentClass
function ParentClass.new() local self = {} setmetatable(self, ParentClass) self.inheritedProperty = "Inherited property" return self end Inheriting Properties 275
TRY 1 YOURSELF „
local Vehicle = {} Vehicle. index = Vehicle
function Vehicle.new() local self = {} setmetatable(self, Vehicle) return self end
function Vehicle.new() local self = {} setmetatable(self, Vehicle) self.numberOfEngines = 1 return self end
local Vehicle = {} Vehicle. index = Vehicle
function Vehicle.new() local self = {} setmetatable(self, Vehicle) self .numberOfEngines = 1 return self end
local Car = {} Car. index = Car setmetatable (Car, Vehicle) 276 HOUR 214: Inheritance
local Vehicle = {} Vehicle. index = Vehicle
function Vehicle.new() local self = {} setmetatable(self, Vehicle) self.numberOfEngines = 1 return self end
local Car = {} Car. âandex, =" Car setmetatable(Car, Vehicle)
function Car.new() local self = Vehicle.new() setmetatable(self, Car) return self end
function Vehicle.new() local self = {} setmetatable(self, Vehicle) self.numberOfEngines = 1 return self end
local Car = {} Car. index = Car setmetatable(Car, Vehicle)
function Car.new() local self = Vehicle.new() setmetatable(self, Car) self.numberOfWheels = 4 return self end Working with Multiple Child Classes Dail
6. Create a new instance of the car and print the number of engines and wheels it has: ve local Vehicle = {} Vehicle. index = Vehicle
function Vehicle.new() local self = {} setmetatable(self, Vehicle) self.numberOfEngines = 1 return self end
local Car = {} Care ndex 5ââ Cale setmetatable(Car, Vehicle)
function Car.new() local self = Vehicle.new/() setmetatable(self, Car) self.numberOfWheels = 4 return self end
function Motorcycle.new() local self = Vehicle.new() setmetatable(selfÂŁ, Motorcycle) self.numberOfWheels = 2 return self end 278 HOUR 21: Inheritance
Inheriting Functions Just like they do with properties, child classes will also inherit all of the functions of their parent class: local ParentClass = {} ParentClass. index = ParentClass
function ParentClass.new() local self = {} setmetatable(self, ParentClass) return self end
function ParentClass:inheritedFunction()
end
Understanding Polymorphism Sometimes child classes need to perform similar actions, but each class executes those functions in specific ways. In these cases, the parent class can define a function that says what the default behavior of child classes that use that function will be. But any child classes that want to break from this and do something special can have a function with the same name that will override the function they inherited from the parent: local ParentClass = {} ParentClass. index = ParentClass
function ParentClass:doSomething() -- Default behavior if child classes do not define their own doSomething end
local ChildClassOne = {} setmetatable(ChildClassOne, ParentClass)
function ChildClassOne:doSomething() -- Does something specific to ChildClassOne end Understanding Polymorphism 279
local ChildClassTwo = {} setmetatable(ChildClassTwo, ParentClass)
function ChildClassTwo:doSomething() -- Does something specific to ChildClassTwo end
local ChildClassThree = {} setmetatable(ChildClassThree, ParentClass) -- ChildClassThree did not define a doSomething function. -- It will call the Parent class doSomething function instead.
TRY YOURSELF V
2. Create the Dog and Cat child classes and their constructors: local Animal = {} Animal. index = Animal
local Dog = {} Dog. index = Dog setmetatable(Dog, Animal) 280 HOUR 21: Inheritance
function Dog.new() local self = Animal.new() setmetatable(self, Dog) return self end
local Cat = {} Cat. index = Cat setmetatable(Cat, Animal)
function Cat.new() local self = Animal.new() setmetatable(self, Cat) return self end
function Animal:speak() print ("The animal makes a noise") end
local Dog = {} Dog. _index = Dog setmetatable (Dog, Animal)
function Dog.new() local self = Animal.new() setmetatable(self, Dog) return self end
local Cat = {} Cat.__index = Cat setmetatable(Cat, Animal)
function Cat.new() local self = Animal.new() Understanding Polymorphism 281
setmetatable(self, Cat) ve return self end
TIP Use Colons for Functions Notice that we are using a colon instead of dot notation like you would with properties.
function Animal:speak() print ("The animal makes a noise") end
local Dog = {} Dog. __index = Dog setmetatable (Dog, Animal)
function Dog.new() local self = Animal.new() setmetatable(self, Dog) return self end
function Dog:speak() print ("Woof") end
local Cat = {} Cat.__index = Cat setmetatable(Cat, Animal)
return self end
function Cat:speak() print ("Meow") end
5. At the bottom of the script, call speak () for both the Cat and Dog classes: Cat : speak () Dog: speak ()
Notice that you are doing something slightly different from normal with this function call. You're not calling the function from a specific object; youâre calling it with the class itself. Youâre also using the dot (.) operator instead of the colon (:) operator to call the function. Last, you pass in the variable self. This variable refers to the actual object you want to call the function with.
The following code sample shows two different jobs people can take on within an experience, warrior and mage. For both jobs, people must use energy to attack. The energy property and the attack function are both set within the parent job class: local Job = {} Job. _index = Job
function Job.new() local self = {} setmetatable(self, Job) self.energy = 1 return self end Calling Parent Functions 283
function Job:attack() if self.energy > 0 then self.energy -= 1 return true end return false end
local Warrior = {} Warrior. index = Warrior setmetatable (Warrior, Job)
function Warrior.new() local self = Job.new/() setmetatable(self, Warrior) return self end
function Warrior:attack() local couldAttack = Job.attack (self) if couldAttack then print ("I swing my weapon!") else Print ("E'"m toe tired to attack!") end end
local Mage = {} Mage. index = Mage setmetatable (Mage, Job)
function Mage.new() local self = Job.new() setmetatable(self, Mage) return self end
function Mage:attack() local couldAttack = Job.attack (self) if couldAttack then print ("I cast a spell!") else print ("I'm out of mana!") end end 284 HOUR 24: Inheritance
Summary Inheritance and polymorphism are more tools in your belt when it comes to good object-oriented programming practices. Once you have a parent class, like a zoo animal class, you can then create as many child classes as you want. Default properties like the animalâs number of heads, tails, and legs can be set within the parent class, and then passed on to the child class.
With polymorphism, the functions and properties of the parent class can be tweaked so that each zoo animal is unique. A zebra and chimpanzee might have the same wandering behaviors but different animations and sounds.
Workshop Now that you have finished, letâs review what youâve learned. Take a moment to answer the fol- lowing questions.
Quiz 1. True or false: A child class needs to include every property from the parent class.
ee sR True or false: Child classes inherit properties, but not functions.
Answers 1. False. If a property exists in the parent class and isnât defined in the child class, the child uses whatâs stated in the parent class.
2. Inheritance is when the functions and properties of one class are passed to another.
3. There is no maximum number of child classes. You can have as many as you want. Exercise 285
4. Sometimes child classes need to perform similar actions but execute them in different ways. The example in the hour was of animals each having a unique sound.
Exercise Try making two jobs for a role-playing game. Make a class for each job. Each job should keep track of how much experience the player has earned and have a function to add to that experi- ence. Each job should also have a special resource that they keep track of, such as energy, stamina, mana, etc. The jobs should have an attack function that depletes this resource. Once you are done, make objects of these classes.
Tips > If you find two classes have the exact same variables or code, see if you can use a parent class to your advantage.
> Printing the values of object properties before and after function calls can be a good way to make sure your function works correctly. oa ra» rein # er = Ci? cop a an =? Gam f < Sa Joe it ⹠aoet Peete ofa eff reeeesy) eeeti th rome
â_ a pr es
eo
cn 3 7
as â rr?
: - HOUR 22 Raycasting
Objects in a Roblox experience can move around a lot. Sometimes you'll need your code to examine the environment to understand where things are. One way to do this is with a tech- nique called raycasting. When you raycast, you tell the engine to start at a given point and draw a line from that point in a certain direction for a certain distance. If that line hits anything as it is drawing, then the raycast function returns what is hit. This technique is used for everything from creating detailed reflections on virtual glass surfaces to tracing bullet paths in competitive games.
1. Define parameters for the origin and direction of the ray using Vector3 coordinates: local origin = Vector3.new(0, 5, 0) local direction = Vector3.new(0, -10, 0)
„V TRY It YOURSELF
Chameleon Material In this Try It Yourself, youâre going to make a part that camouflages itself by copying the material of whatever part it is over, as shown in Figure 22.1.
FIGURE 22.1 Aan the ray is cast (left), the material will be plastic; then the part will update to match the part beneath it right).
1. Create a few tiles of different materials, as shown in Figure 22.1, and one part to act as the chameleon. 3D Math Trick: Getting the Direction 289
3. Use the camo part to define the origin for the ray; then define the direction:
5. If a part was found along the ray, update the material of the camo part to match: local camouflagePart = script.Parent local origin = camouflagePart.Position local direction = Vector3.new(0, -5, QO) local result = game.Workspace:Raycast (origin, direction) if result then camouflagePart.Material = result.Material end
alg Ray Length If the part fails to change materials, itâs possible the ray isnât long enough. Either update the direc- tion or move the camo part.
FIGURE 22.2 The origin and destination are offset from each other, and the direction is unknown.
Fortunately, there is a convenient way to get a direction between two points using vector math. Simply subtract the originâs position from the destinationâs position: local pointA = Vector3.new(0, -4, 0) local pointB = Vector3.new(24, -10, 0) local fromAToB = pointB - pointA
Put objects you want the raycast to ignore in the new table: local origin = Vector3.new(0, 5, 0) local direction = Vector3.new(0, -10, 0) local parameters = RaycastParams.new() parameters.FilterDescendantsInstances = { game .Workspace.IgnorePartl, game .Workspace.IgnorePart2,
} parameters.FilterType = Enum.RaycastFilterType.Blacklist
} parameters.FilterType = Enum.RaycastFilterType.Blacklist local result = (origin, game .Workspace:Raycast direction, parameters) 292 OUR 22: Raycasting
VW TRY IT YOURSELF
1. Create a sphere and a cube on either side of a glass window as shown in Figure 22.3.
FIGURE 22.3 There are two red parts on either side of a clear glass window.
2. In ServerScriptService, add a script and create references for both of the parts and for the window.
4. To find the direction, subtract the position of the sphere from the cube: local origin = sphere.Position local direction = cube.Position - sphere.Position
7. Cast the ray and confirm the window is not returned in the results: local sphere = game.Workspace.Sphere local cube = game.Workspace.Cube local window = game.Workspace.Window
TIP The Absence of Evidence Is Not Evidence of Absence So far, youâve only tested that the window is not being detected by the ray. When coding, try always to think of multiple ways you can test your scripts. In this case, you should also confirm that parts other than the window will be detected.
For example, if you use raycasting to help enemies spot a player, you may not want them to be able to see all the way across the map. Youâd want to put a cap on how far they could see. In 294 HOUR 22: Raycasting
such cases, you can use the unit vector of the direction. The unit vector of a Vector3 is another vector that is in the same direction, but itâs only 1 stud long: local maximumDistance = 10 local pointA = Vector3.new(2.5, 10, 0) local pointB = Vector3.new(16, 5, -9) local fromAToB = pointB - pointA local unit = fromAToB.Unit local fromAToBCapped = unit * maximumDistance
Summary Raycasting draws a line and returns the results of whatever is found on that line. You can use this technique to check for obstacles, draw reflections, create weaponâs fire, or update an object based on its surroundings.
The ray requires an X, Y, Z origin point, and an X, Y, Z direction. If you donât know the direction but you do know the end point for the ray, you can find the direction by subtracting the origin from the destination. So, if you have an origin of (2, 2, 2) and a destination coordinate of (7, 7, 7), your direction would be (5, 5, 5).
Optionally, parameters can be provided for the ray so that it excludes certain objects along its path.
Q&A Q. What if instead of ignoring objects along the ray, | only want to return certain objects?
A. |f you want to search for only certain types of objects instead of blacklisting objects, you can filter using Enum.RaycastFilterType.Whitelist.
Workshop Now that you have finished, letâs review what you've learned. Take a moment to answer the fol- lowing questions.
Quiz r What are the two required parameters for casting a ray? How can you find the direction of a ray when you know the origin and destination?
True or false: The ray will keep searching for things along the path thatâs been drawn.
oN. True hwo or false: You can set a maximum length for the ray to cast. Exercise 295
Answers ats Origin and direction.
2 - Subtract the origin from the destination to find the direction for a ray.
3. False. The ray returns results only once unless you tell it otherwise. 4 . True. You can use the unit vector of the direction to set a maximum length a ray is allowed to draw.
Exercise Build a detector in your game that senses when a player is nearby. The detector should change colors if a player is within a certain distance of the detector. Remember that you can get all of the players in a game using Players:GetPlayers(), and you can get a player character with player.Character.
Tip: Until now, youâve been raycasting only once per script. Keep in mind that the Raycast func- tion only draws its ray at the exact moment when you call it. To build a proper detector, you need to raycast every couple of milliseconds. Twi ey nda
# waite « eh | â> « oot. nut 7 A wes ornare
eRe: Teri f ete | Fi gae) air ~
â. ee i â
vo ae
Sore â . ee : HOUR 23 Plopping Objects in an Experience: Part 1
Allowing your users to control what their environment looks like gives them a chance to express themselves within the world youâve made, and that makes them more engaged and more likely to come back. For the next two hours, you'll work on your capstone project: creating a button that enables people in your experience to place or âplopâ an item wherever they would like. They can use it to decorate their house as in Figure 23.1 or plant flowers in their garden.
Two new concepts will be covered as you work through the project: the ability to update an object or code whenever the game refreshes and to detect player clicks within the 3D environment.
FIGURE 23.1 MeepCity by AlexNewtron allows you to decorate your whole house, even the bathroom. 298 HOUR 23: Plopping Objects in an Experience: Part 1
By the end of this first hour, you'll learn to track a userâs mouse movement to allow them to drag a ghost image that only they can see. To do this, youâll learn about the render step. In the next hour, youâll learn how to listen for user input to finalize the placement of the object on the server.
1. In Workspace, add a new folder named Surfaces to hold all the parts that people can place items onto.
2. Within the folder, create a new part to act as a floor to place the item onto, as shown in Figure 23.2.
FIGURE 23.2 This large part acts as a floor.
3. In ReplicatedStorage, add a new folder named Events and a new RemoteEvent named PlopEvent (see Figure 23.3).
5. Decide on the object you want to place and create a slightly transparent ghost version the user will drag around before finalizing the placement. The model should have a base at the bottom of the model that will be used to line up the object with the floor. Figure 23.4 shows a ghost chair. Setting Up the Object 299
FIGURE 23.3 Add a new RemoteEvent in ReplicatedStorage.
FIGURE 23.4 The slightly transparent ghost model has a base at the bottom. 300 Plopping Objects in an Experience: Part 1
6. Make sure the base is the PrimaryPart and insert the whole model into the GhostObjects folder (see Figure 23.5).
ae Ph fe tee ee 8 ek
wae ReplicatedStorage Ge Events
GhostChair â GhostObjects @ = af gm oe Origin Orientation
Pivot a Diuat Ofcat Daci & FIGURE 23.5 After setting the PrimaryPart (left), move the model to the GhostObjects folder (right).
7. In ServerStorage, add another folder named Ploppables and insert a copy of the same model at normal transparency (see Figure 23.6). It should also have a basepart as the PrimaryPart.
8. In StarterGui, add a ScreenGui and insert a TextButton named PlopButton (see Figure 23.7). Setting Up the Object 301
Replicat Replicate:
iF
StarterPlayer f Tears
FIGURE 23.6 This folder will hold the objects that will actually be placed in the world.
FIGURE 23.7 People will click the button to plop an object. 302 HOUR 23: Plopping Objects in an Experience: Part 1
2. In the LocalScript, create variables for Player service and the local player: local Players = game:GetService ("Players")
end
Inside the function, switch the Visible property of the button to false. This way, the but- ton is hidden while the player is plopping: -- Previous code local function onPlopButtonActivated() plopButton.Visible = false end
plopButton.Activated: Connect (onPlopButtonActivated)
7. Test the code and make sure the button disappears after you click it.
BindToRenderStep Every time your screen refreshes, thereâs a whole host of code happening behind the scene that calculates what should appear on the screen. This is called the render step. If something needs to be smoothly animated, such as a camera, you can add functions to the render step using BindToRenderStep (). However, you want to be careful about adding too many. Giving the render step too much to do will slow down how often the screen refreshes, which makes your experience appear laggy and the motion seem jerky.
BindToRenderStep is part of RunService, and it has three parameters. It looks like this: RunService:BindToRenderStep (bindingName, priority, functionName)
> bindingName: This is not the same as the name of the function; itâs the name of the binding.
> priority: A numeric value stating how soon in the render step the bound function should be calculated.
In the plopping project, youâre going to add code to the renderstep so that a ghost object can be smoothly moved around before finalizing where the permanent object should be placed:
1. In the same LocalScript, create a variable for RunService, the service that owns renderstep: local Players = game:GetService ("Players") local RunService = game:GetService ("RunService")
plopButton.Activated: Connect (onPlopButtonActivated)
end
plopButton.Activated: Connect (onPlopButtonActivated)
TIP Determining Priority Value to Use Rather than trying to state a specific value, like 20, this code finds the value of the player camera and adds one, making the bound code happen right after.
1. Still in the same script, beneath the player variable, add variables for the camera and mouse: -- Previous variables local player = Players.LocalPlayer local camera = game.Workspace.Camera local mouse = player:GetMouse()
TIP Keep Like Variables Together Weâre jumping around a fair amount, but the important thing is to keep like variables together. Services should be with services, objects with objects, and constants with constants.
-- Rest of code
3. Set the filter type to Whitelist and add the surfaces folder you made earlier to the list of instances: -- Previous variables local plopButton = plopScreen:WaitForChild("PlopButton")
4. Make a variable named RAYCAST_DISTANCE. This will control how long a ray the Raycast function will use later: -- Previous variables
5. In the onRenderStepped function, use the ScreenPointToRay function to get the ray starting at the camera and going toward the playerâs mouse: -- Other variables
6. Still within onRenderStepped(), use the Raycast function with the values you got from ScreenPointToRay () in the last step. Note that you have to multiply the direction by your RAYCAST_DISTANCE value because that value is a unit vector by default: local function onRenderStepped() local mouseRay = camera:ScreenPointToRay (mouse.X, mouse.Y, 0) -- Casts the ray using the origin and direction from mouseRay local raycastResults = game.Workspace:Raycast (mouseRay.Origin, mouseRay.Direction * RAYCAST DISTANCE, raycastParameters) end
7. Test your code again. The game won't do anything different from the last test, but make sure there are no errors.
plopButton.Activated: Connect (onPlopButtonActivated)
1. Still in the same script, create variables for ReplicatedStorage and the ghost object stored within: local Players = game:GetService ("Players") local ReplicatedStorage = game:GetService("ReplicatedStorage") local RunService = game:GetService ("RunService")
TIP Missing Code If you see ... in a code sample, assume there is code not being shown at the moment.rtnnt ee inintnttNennNtNtâtnCtanne 308 HOUR 23: Plopping Objects in an Experience: Part 1
2. Create a variable named plopCFrame. This will store the position where the player's mouse is pointing:
3. Inside the onRenderStepped function and after the Raycast, check if the Raycast returned any results:
end end
4. If the Raycast returned a result, that means the mouse is in a valid area, and you should show the object there. Set the value of plopCFrame to the position of the Raycast result:
5. Use the Set PrimaryPartCFrame function of the ghost object to move the object to plopCFrame:
end end
6. Move the object into the workspace. In the else statement, move the object back into ReplicatedStorage:
7. Test your game. Make sure that after you click the Plop Chair button that you see the ghost chair when you move your mouse over the build area, as shown in Figure 23.8.
FIGURE 23.8 , : folder. _ After clicking the button, the ghost object should appear when hovering over objects in the Surfaces 310 HOUR 23: Plopping Objects in an Experience: Part 1
Summary In this hour, you learned how to create ghost objects that only the user can see. You also learned about the render step, the short period of time during which all of the graphics are calculated.
To bind objects to the render step, you use RunService: BindToRenderStep (bindingName, priority, functionName). The parameters work as follows:
> bindingName: This is not the same as the name of the function; itâs the name of the bind- ing. It allows you to connect and disconnect things to the render step.
> priority: A numeric value stating how soon in the render step the bound function should be calculated.
In the next hour, you'll learn how to finalize the placement of the object by listening for the user to click.
Q&A Q. Why do we use a whitelist for the raycast?
A. A whitelist allows you to look for only certain objects. You could in theory blacklist every other object in the experience, but that would be a much longer list and a lot more work. You would also need to update the blacklist every time you add a new object to the experience.
Workshop Now that you have finished, letâs review what you've learned. Take a moment to answer the fol- lowing questions.
Quiz ae What is the render step? What does BindToRenderStep do?
nae eed What is the first parameter of BindToRenderStep and what does it do? Exercise 311
Answers 1. The render step is when all the calculations needed to display an image on screen take place.
2. BindToRenderStep connects a function to the render step, making it happen during that process.
4. The first parameter is for the name of the binding, and that allows you not only to connect functions to the render step but also disconnect things.
Exercise Use the same pattern shown so far to add a second object for users to plop, as shown in Figure 72S).8).
FIGURE 23.9 ; A table is another good option for people to decorate with.
Tip: Remember to make sure the model has a base set as the primary part.
ry taRopee aie EON RE ROS ES Gacy Ose PR VANE ay Be SP
This is the second hour of your two-part capstone project. In the last hour, you learned how to track a playerâs mouse movement and update what they see using the render step. In this hour, you use ContextActionService to listen for a player to click and finalize the placement of the object, such as when a player is decorating a house in Figure 24.1.
"ol Cancel |
FIGURE 24.1 â In Welcome to Bloxburg, users click to decorate their houses and gardens. 314 HOUR 24: Plopping Objects in an Experience: Part 2
ContextActionService allows actions to take place only under certain conditions. A com- mon use is to quickly bind and unbind actions to player input, such as mouse clicks or keyboard presses using BindAction(), as follows: ContextActionService:BindAction(actionName, functionName, addMobileButton, inputTypes)
>» addMobileButton: Adds a button on the screen for this action on mobile devices.
The function that you bind with BindAction should have the following parameters: onInput (actionName, inputState)
> inputState: What state the input was in when this function was called.
If you no longer need to listen for particular input, you can remove a binding with the UnbindAction function: ContextActionService:UnbindAction (actionName)
1. In same script as before, create a variable for ContextActionService and one for the binding name: local ContextActionService = game:GetService("ContextActionService") local Players = game:GetService ("Players")
end
In the onMouseInput function, check if the input state was End because this is when the player has clicked the mouse:
end end
Also in the if statement, unbind the click action and the render actions using UnbindAction and UnbindFromRenderStep:
ghostChair.Parent = ReplicatedStorage RunService:UnbindFromRenderStep (PLOP_MODE) ContextActionService:UnbindAction (PLOP_CLICK) end end
8. Test your game. Confirm that after you click that the object disappears and the plop but- ton appears again.
2. At the end of onMouseInput and in the if statement, fire the remote event passing the plopCFrame variable in as an argument if the plopCFrame exists:
plopButton.Visible = true if plopCFrame then plopEvent : FireServer (plopCFrame) end end end
TIP Dealing with Multiple Objects lf you did the exercise in the last hour and have multiple objects, you need to send over the name of the ghost object as well.
4. Create a function named onPlop that takes a player and CFrame as an argument: local ReplicatedStorage = game:GetService ("ReplicatedStorage") local ServerStorage = game:GetService ("ServerStorage")
end 318 HOUR 24: Plopping Objects in an Experience: Part 2
ie Dealing with Multiple Objects Remember to add another parameter here to take in an object name if you have more than one.
plopEvent .OnServerEvent : Connect (onPlop)
6. Inside the onPlop function, make a copy of the chair using the Clone function: local ReplicatedStorage = game:GetService("ReplicatedStorage") local ServerStorage = game:GetService("ServerStorage")
plopEvent .OnServerEvent : Connect (onPlop)
7. Move the copied chair to the CFrame and change its parent to the workspace: local ReplicatedStorage = game:GetService ("ReplicatedStorage") local ServerStorage = game:GetService ("ServerStorage")
plopEvent .OnServerEvent : Connect (onPlop)
8. Test your game. Confirm that after clicking where you want the object to go that it does get added to the workspace. Also try placing several chairs.
Summary Congratulations! Youâve completed a big project that used all of the knowledge youâve acquired throughout this book. Hereâs a quick summation of the skills you used to create a plopping sys- tem that enables users to decorate and have more control of their environments:
1. You used raycasting to find the location of the mouse and a whitelist to ensure it only returned the names of certain objects.
2. Users could drag a ghost object around using their mouse and the location was updated by binding a function to the render step.
3. Users finalized the placement of the object by clicking. You used ContextActionService to bind a function to the click and place the object.
This is the last hour of this book, but thereâs still lots more to learn. All of the code youâve worked on in this book should be seen as just a starting point. For every project, you can expand the code out and use what you know in new ways. Thereâs a great community of Roblox developers who can help you out on the Roblox Developer Forums and tons of code samples for you to use on developer.roblox.com.
As you expand on your skills, keep in mind the importance of staying organized and testing your code with multiple scenarios. You should always be thinking about how different situations like differing screen sizes and having multiple people in a server affect how your code behaves. Ideally, you should even get other people to try out your experience and make sure everything works for them as expected.
If you're interested in learning more about how to build experiences and get better at things like lighting, sounds, environments, and animations, you can also check out the companion book, Learn Roblox Game Development in 24 Hours. 320 HOUR 24: Plopping Objects in an Experience: Part 2
Q&A Q. How can you make the plop buttons disappear when you donât need them?
A. You could place all of the GUI needed for the plop buttons within a larger frame named something like Decorations or Shop. By default, disable the frame. Use what you know to create another button people can click to enable and disable the frame and see or hide all of their decor choices.
A. You can set up scenarios that enable new controls depending on what a player is doing. For example, if theyâre in a car, you might enable buttons to act as the breaks, gas, and horn. You can then disable these buttons when the player isnât in the car.
Workshop Now that you have finished, letâs review what youâve learned. Take a moment to answer the fol- lowing questions.
Quiz 1. What does ContextActionService allow you to do?
2. What is the function that allows you to enable certain keys under certain circumstances?
3. If you have multiple objects that can be plopped, what property do you need to pass over in addition to the base code?
Answers 1. ContextActionService allows you to make it so that an action can only take place in certain conditions.
2. Use BindAction() if you want to enable certain keys under certain circumstances.
3. You need to send the name of the object over as well if you have multiple objects that can be plopped.
Exercise Try adding a command to rotate objects while you're placing them. Every time a player presses a key, the object should rotate a set amount of degrees.
Tips > Set up a constant for how many degrees the object should rotate.
The following tables list the keys and associated actions when editing in Roblox Studio
Key Movement WASD Move the camera: W: Forward A: Back S: Left D: Right IZ Raise camera -
Q ; ae Lower camera
Key Movement
Q Lower camera Right mouse button (hold and drag mouse) Turn camera
Keywords
Here is a selection of a few additional keywords that perform important actions that are specific to the Roblox platform:
DataType Index
A B Cc Axes BrickColor CatalogSearchParams CFrame Color3 ColorSequence ColorSequenceKeypoint
D E F
| N Pp
R T Random TweentInfo Ray RaycastParams RaycastResult RBXScriptConnection RBXScriptSignal Rect Region3 Region3int16
Vv Vector2 Vector2int16ector2 Vector3 Vector3int16 324 APPENDIX A: Roblox Basics
Operators An operator is a special set of symbols used to perform an operation or conditional evaluation.
Logical The logical operators for conditional statements are and, or, and not. These operators consider both false and nil as âfalseâ and anything else as âtrue.â
Operator Description
Relational Relational operators compare two parameters and return a boolean true or false.
Arithmetic Lua supports the usual binary operators along with exponentiation, modulus, and unary negation.
Miscellaneous Miscellaneous operators include concatenation and length. Operator Description Associated Metamethod
Naming Conventions > Spell out words fully! Abbreviations generally make code easier to write but harder to read.
> Use PascalCase for all Roblox APIs. camelCase APIs are mostly deprecated, but still work for now.
>» Use camelCase names for local variables, member values, and functions.
» For acronyms within names, donât capitalize the whole thingâfor example, a JsonVariable or MakeHttpCall.
> Lua does not have visibility rules, but using a character like an underscore helps make private access stand out.
>» A moduleâs name should match the name of the object it exports.
Animation Easing Animation easing defines a âdirectionâ and style in which a tween will occur. Style Description
Style Description
Quart Similar to Quad but with a more emphasized start and/or finish.
Quint Similar to Quad but with an even more emphasized start and/or finish.
Direction Description
In The tween will have less speed at its beginning and more speed toward its end.
Out The tween will have more speed at its beginning and less speed toward its end.
InOut In and Out on the same tween, with In at the beginning and Out taking effect halfway through.
Hour 1 Create a part that destroys whatever touches it.
Hour 2 Exercise 1 Use code to turn a regular part into an NPC with a greeting and face.
guideNPC.Transparency = 0.25 guideNPC.Dialog.InitialPrompt = message guideNPC.Color = Color3.fromRGB(40, 0, 160) decal.Parent = guideNPC
Exercise 2 Use code to create a new part instance at the center of the world, and give it a face and dialog.
dialog.InitialPrompt = message dialog.Parent = newNPC
decal.Texture = "rbxassetid://494291269" decal.Parent = newNPC
newNPC.Transparency = 0.25 newNPC.Color = Color3.fromRGB(40, 0, 160) newNPC.Anchored = true newNPC.Parent = workspace 328 A {DIX A: Roblox Basics
Hour3 Use a button to activate and deactivate a bridge.
Hour4 Create a part that sets whatever touches it on fire.
bonfire.Touched: Connect (onTouch)
Create a part that checks for a humanoid, and if found, increases their walk speed.
speedBoost .Touched: Connect (onTouch)
Exercise 1 Itâs important to start thinking about the ways in which your code can be improved in the future. Here are a few ways some of the code youâve worked with so far can be improved. Maybe you can think of more:
>» Players might get confused at still being able to use the ProximityPrompt while itâs on cooldown.
>» Some people might have low vision or color blindness. Adding sound while mining to let them know itâs working might make the experience easier for them.
> Itâs a bit hard to tell if gold was received after mining. Maybe a particle or a sound would make it more obvious what happened.
> In the speed part code solution, it only allows for players to go a certain speed. Maybe it can be modified to allow players to go faster and faster each time they touch a part. 330 APPENDIX A: Roblox Basics
Exercise 2 Make anyone who touches a part gigantic (or tiny).
wait (COOLDOWN)
isEnabled = true growthPotion.Color = originalColor end end
growthPotion. Touched: Connect (onTouch)
Hour 7 For this solution, itâll be assumed that players can harvest logs and gold ore, so both will be reflected in the harvesting and leaderboard scripts. Possible Solutions to Exercises 331
» Name: ResourceType
Campfire Script Location and type: ServerScriptService > Script local ProximityPromptService = game:GetService("ProximityPrompt Service")
logs.Value -= 1 local campfire = prompt.Parent local fire = campfire.Fire
Leaderboard Script Location and type: ServerScriptService > Script local Players = game:GetService ("Players")
Players. PlayerAdded: Connect (statsSetup)
Harvesting Script Location and type: ServerScriptService > Script local ProximityPromptService = game:GetService("ProximityPromptService")
prompt.Enabled = true node.Transparency = 0 end end
ProximityPromptService. PromptTriggered: Connect (onPrompt Triggered) Possible Solutions to Exercises 333
Exercise 1 Create a hit box for a campfire, and do damage over time to anyone who touches it.
Exercise 2 Hereâs a few ways loops can be used:
> Day/night cycles: You can use a while loop to cycle through the time of day. There are lots of existing tutorials that can help you if you want to try!
> Seasonal cycles: Similar to day time cycles. Inside of the while loop, you can trigger envi- ronmental changes for each season.
> Applying changes to multiple objects: Loops can be used to go through a bunch of objects and make updates. For example, you can change the appearance of a number of objects to make seasonal cycles like the one previously mentioned.
> Creating rounds and lobbies for games: Some experiences use a round-based structure to control when a game starts and to wait for players. 334 APPENDIX A: Roblox Basics
> Creating a vanishing staircase or bridge: Loops can make objects continuously reappear or disappear.
> Weapon cooldowns: Control how long spells and skills need to be recharged between uses. You could use wait (), but loops give you greater control.
> Making objects move back and forth: It could be an NPC that walks on a set path or a platform that slides from point A to point B.
Create a script that changes the appearance of a number of parts. This example shows a pine tree going from normal green to snowy white.
Hour 10 Assign newcomers to either âredâ or âblueâ as they join and print the resulting teams.
local teamAssigments = {
else teamAssigments [newPlayer] = "Blue" AssignRed = true end
printTeamAssignments() end
Players .PlayerAdded: Connect (assignTeam)
Hour 11 Leaderstat Code for the Try It Yourself The following script can be used to set up a leaderboard for use with the Try It Yourself if you donât have one set up already.
logs.Parent = leaderstats end
Players. PlayerAdded: Connect (statsSetup)
Exercise The goal of this exercise was to retrieve information about the shop item from the server instead of hard coding it as shown in the Try It Yourself earlier in the hour.
FIGURE A.1 Add an additional RemoteFunction.
2. Add attributes to the items you want to sell for StatName, Price, and NumberToGive (see Figure A.2).
FIGURE A.2 Each item should have its own folder. Possible Solutions to Exercises 337
Highlighted code is for the exercise; nonhighlighted was from the earlier Try It Yourself. local ReplicatedStorage = game : GetService ("ReplicatedStorage") local Players = game:GetService ("Players") local ServerStorage = game : Get Service ("ServerStorage")
local serverMessage
ButtonManager
Location and type: StarterGui > ShopGui (ScreenGui) > Buy3Logs (TextButton) > ButtonMan- ager (LocalScript) local ReplicatedStorage = game:GetService ("ReplicatedStorage")
button.Text = defaultText
button.Activated:Connect (onButtonActivated)
Hour 12 For this exercise, you were asked to announce to all clients what map was picked.
yNextMap DisplayMapManager Bisrnlay Kel aPidy
FIGURE A.3 In the complete setup, notice that only one RemoteEvent is needed.
if mapChoice then if currentMap then currentMap: Destroy () end currentMap = mapChoice:Clone() currentMap.Parent = workspace 340 ix A: Roblox Basics
else print ("Map choice not found") end end
mapPicked.OnServerEvent : Connect (announceMap) mapPicked.OnServerEvent : Connect (onMapPicked)
DisplayMapManager
Hour 13 For this exercise, you were asked to take something youâve worked with beforeâtrap partsâand convert them to module scripts. This module could be extended not only to take all of a human- oidâs health, but also to create traps that take partial health or even heal the player.
PickupManager
function PickupManager.modifyHealth (part) local character = part.Parent local humanoid = character:FindFirstChild("Humanoid")
if humanoid then humanoid.Health = 0 end end
return PickupManager Possible Solutions to Exercises 341
OnTouch
trap. Touched: Connect (onTouch)
Hour 14 For this exercise, you were asked to teleport players from one part to another part.
Name: JumpPadManager
if humanoid then local humanoidRootPart = character:FindFirstChild("HumanoidRootPart") local vectorForce = humanoidRootPart:FindFirstChild("VectorForce") if not vectorForce then vectorForce = Instance.new("VectorForce") vectorForce.Force = JUMP_DIRECTION vectorForce.Attachment0O = humanoidRootPart.RootRigAttachment vectorForce.Parent = humanoidRootPart wait (JUMP_DURATION) vectorForce: Destroy () end end end
return JumpPadManager a 342 APPENDIX A: Roblox Basics
Name: OnTouchManager
Exercise For this exercise, you were asked to teleport players from one part to another part.
Set up: Create a part named Origin and a part named Destination.
Hour 15 Create a SpotLight that transitions from one color to another indefinitely. This particular solu- tion goes back and forth between the lightâs original color and the one set in the goal table. The EasingStyle, Bounce, gives a slight flickering effect that would make this same script good for things like campfires. Possible Solutions to Exercises 343
ScriptName: SpotLightManager
true
local goal {} goal. Color = Color3 .fromREB (255, 0, 255)
spotLightTween: Play ()
Hour 16 Take a dictionary storing how many kills, deaths, and assists participants have in a match and sort the dictionary according to who has the most kills. If tied, prioritize how many assists they have.
Hour 17 Give people gold each time they join. This version just prints each personâs current wealth in the Output window, but the code can be expanded to update a leaderboard or GUI.
local GOLD ON JOIN = 5.0
-~- Use UpdateAsync to get the old value, and send an updated value. local updateSuccess, errorMessage = pcall(function () goldDataStore:UpdateAsync(playerKey, function (oldValue) local newValue = oldValue or 0 newValue = newValue + GOLD ON JOIN return newValue end) end)
if getSuccess then print (player.Name .. " has " .. currentGold) end end
Players. PlayerAdded: Connect (onPlayerAdded)
Hour 18 Add an Announcements module that prints âRound Startingâ and âRound Endingâ at the begin- ning and end of the round.
Announcements Module
=- Services local Players = game:GetService ("Players") local ServerStorage = game:GetService ("ServerStorage")
roundStart.Event : Connect (onRoundStart) roundEnd. Event : Connect (onRoundEnd)
return Announcements
Updated RoundManager -- Services local ServerStorage = game:GetService ("ServerStorage" ) local Players = game:GetService ("Players")
=- Module Scripts local moduleScripts = ServerStorage.ModuleScripts local playerManager = require (moduleScripts.PlayerManager) 346 APPENDIX A: Roblox Basics
-- Events local events = ServerStorage.Events local roundStart = events.RoundStart local roundEnd = events.RoundEnd
while true do repeat wait (roundSettings.intermissionDuration) until Players.NumPlayers >= roundSettings.minimumPeople roundStart : Fire () wait (roundSettings.roundDuration) roundEnd: Fire () end
Hour 19 You were asked to create a pass that could be purchased by users. The actual functionality of your pass will be unique, so no solution is given here.
Hour 20 Create an NPC class that prints out its own name. local Person = {} Person. index = Person
self.name = name
return self end
Hour 21 Imagine you're creating an RPG world where you want characters to be able to take on different jobs. Create a parent job class and two different child classes to represent roles people can take on within the game. Hocalngobu= ai Job. index = Job
function Job.new() local self = {} setmetatable(self, Job) self.experience = 0 return self end
function Job:attack() print ("I attack the enemy!") end
local Warrior = {} Warrior. index = Warrior setmetatable (Warrior, Job)
function Warrior.new() local self = Job.new() setmetatable(self, Warrior) self.stamina = 5 return self end
function Warrior:attack() if self.stamina > 0 then print ("I swing my weapon at the enemy!") self.stamina -= 1 else print ("I am too tired to attack") end end
local Mage = {} Mage. index = Mage setmetatable (Mage, Job) 348 APPENDIX A: Roblox Basics
function Mage.new() local self = Job.new() setmetatable(self, Mage) self.mana = 10 return self end
function Mage:attack() if self.mana > 0 then print ("I cast a spell at the enemy!") self.mana -= 1 else print ("I am out of mana") end end
Hour 22 Create a player detector. Use a ray to detect when a player is near a part.
detector.Color = NOT_DETECTED_COLOR while wait (DETECTION INTERVAL) do if checkForPlayers() then detector.Color = DETECTED COLOR else detector. Color NOT_DETECTED_COLOR end end 350 APPENDIX A: Roblox Basics
Hour 23 You were asked to update the existing code for the Try It Yourself and add a second ploppable object. The code only needs to get up to the point where users can drag a ghost object around.
plopChairButton.Activated: Connect (onPlopChairButtonActivated) plopTableButton.Activated: Connect (onPlopTableButtonActivated)
Hour 24 Take the plopping code you have so far and add the ability for users to rotate the objects as they place them.
end end
plopButton.Activated: Connect (onPlopButtonActivated) =e Rip ade nthe ae Senne Reba (3th eet ridin le eee ana ee ae: Aten Iced, ee are index
Symbols 3D Editor, 3
3D space
properties and, 20 == (double equal sign) operator, 58 A = (equal sign), variable values, 22 >= (greater than or equal to) abstractions, 183-184 operator, 59 accessing __index, naming classes, 260 Data Stores, 220 " (quotation marks), in key-value functions, 68 pairs, 128 ModuleScripts, 177-178, 182-183 356 adding
camouflage raycasting example, functions of, 263-268 colors, changing, 25, 199-200, 288-289 inheritance, 271-272 208, 342 car class example of functions, 278 comments, 12
X, Y, Z coordinates, 187-189 358 copying meshes
copying meshes, 78 decals, inserting, 28-29, 327 doubling and halving variables, 85
countdowns, creating with descending sorts, 212-213 DRY coding. See also OOP RemoteEvent object, 163-165 descriptions, 255 abstractions, 183-184 crown sales example, 248-255 destroy() function, 18-19 purpose of, 183 curly brackets ({}) detecting mouse input, 314-316 for arrays, 113 detector exercise, 295, 348 for dictionaries, 128 Developer Exchange Program, custom leaderboards, 87 243 Developer Products, 256 easing in animation, 325 dictionaries elevator doors, creating, 202-205 converting to arrays, 213-215 else keyword, 63 creating, 128 elseif keyword, 62 Damage Over Time (DoT), key-value pairs, 128 embedded objects, finding in 111-112, 333 adding, 130-132 hierarchy, 47 dance floor, creating, 92-93 formatting keys, 128-129 enabling Data Stores, 219 Data Stores removing, 130-131 end value in for loops, 103 accessing, 220 unique keys, 130 engagement payouts, 256 creating, 220 value usage, 129-130 equal sign (=), variable values, 22 enabling, 219 pairs() function, 132-133 error messages, 11-12 limiting network calls, 225 purpose of, 127-128 errors unique key names, 224 sorting, 213-215, 218, 343 list of, 228 updating, 220-228, 344 voting simulator, 133-142 string debugging, 82-84 data types, 22, 27 direction parameter for ray- event connections, order and in Lua, 322 casting, 289-290 placement, 138
accessing, 68 330-332 false conditions, loops for, 98 anonymous, 52-55, 328 creating NPCs, 29, 327 files, saving, 13 arguments debouncing, 88-89, 330 filtering definition of, 43 debugging, 88, 329 lists, 7 mismatched, 51-52 detector with raycasting, 295, 348 objects for raycasting, 294 multiple, 45-49 finding value types, 86 dictionary sorting, 218, 343 all specific array values, 123 BindAction(), 314 DoT (Damage Over Time), 111-412, 333 array indexes from values, BindToRenderStep(), 303-305 Dal calling, 32 inserting decals, 28-29, 327 embedded objects in with events, 33-36 job roles, 285, 347 hierarchy, 47 loops, 112, 333-334 from parent classes, 282 list items, 7 map choice announcement, CFrame.Angles(), 191 fire 172, 338-340 of classes, 263-268 burning, 93-97 NPC person class, 270, 346 connect(), 33 collecting firewood, 100, obstacle course, 14-15, 326 constructors, 260, 265 330-332 pass creation, 257, 346 creating, 31-32 folders, modifying items placing objects, 311, 350 definition of, 314 with for loops, 116-121 player announcements, 242, destroy(), 18-19 with ipairs() function, 116 345 GetAsync(), 220, 225 for loops, 98, 101-102 price lists, 160, 336-338 IncrementAsync(), 227 default increment, 105 rotating objects, 320, 351 inheriting, 278 examples and exercises, solidifying bridges, 42, 328 insert(), 114 105-106, 112, 333-334 solutions to, 326-351 finding and removing all team assignments, 143, 334 specific array values, 123 360 functions
LoadCharacter(), 241 SetAsyne(), 220, 225 gold ore setup (mining simulator), 78-79 as methods, 33 table.sort(), 210-213 graphical user interfaces. See in ModuleScripts tostring(), 212 GUls accessing, 177-178 UnbindAction(), 314 gravity, changing, 233 adding, 175-176 UpdateAsync(), 226-227 greater than or equal to (>=) scope, 176 wait(), 42-43, 201 operator, 59 MoveTo(), 264 default value, 86 grouping parts, 166, 192 multiple in scripts, 41 with while loops, 92-93 GUls (graphical user interfaces) named, 52-55, 328 workspace:Raycast() creating, 106-109, 146-148, naming conventions, 32, 35, camouflage example, Seo 69 288-289 customizing, 147 new(), 26 direction parameter, moving, 154 order and placement, 36-40 289-290 purpose of, 146 paint(), 44-48 limiting distance, 293 script placement, 148 pairs() setup, 287-288
require(), 177 LocalScript object 361
key-value pairs versus, 129 for moving camera, 321 colors, changing via tweens, uniqueness in Data Stores, 208, 342 retrieving specific values, 114-115 224 glowing, 120
in-game purchases. See moneti- key-value pairs, 128 SpotLight objects, 117
job roles exercise, 285, 347 formatting keys, 128-129 network calls, 225
of properties, 274-277 unique keys, 130 load times for scripts, 109
local variables, 22, 184 Lua, 1 data types, 22, 27, 322
logical operators, 62-63, 324 adding items, 114 argument value types, 86
for false conditions, 98 finding and removing all variable order and specific values, 123 placement, 84 for, 98, 101-102 indexes, 113-115, 121 dot operator default increment, 105 printing with ipairs() for dictionary values, examples, 105-106 function, 115 129-130 finding and removing all purpose of, 113 for embedded objects, 47 specific array values, 123 removing items, 122 object hierarchy and,
mouse input, detecting, 314-316 network calls, 225 placing, 297-298, 313. See
player management, services for, with while loops, 93-97 cation, 162-165
Q-R removing scripts, 7-8 all specific array values, 123 polymorphism, 278-282 items from arrays, 122 portals, creating, 63-70 quotation marks ("") in key-value key-value pairs from dic- positioning. See placing pairs, 128 tionaries, 130-131 Position property (CFrames), 190 renaming scripts, 18-19 previewing object placement, raycasting renderstep, 303-305 307-309 camouflage example, 288-289 repeat until loops, 237 price list exercise, 160, 336-338 detector exercise, 295, 348 require() function, 177 PrimaryParts (models), 192 direction parameter, 289-290 print() function, 7-9, 23, 43 reserved names, 322 filtering objects, 294 resources for information, 13, for debugging, 82-84 from mouse, 305-306 319 printing arrays with ipairs() function setup, 287-288 function, 115 retrieving specific array values, limiting distance, 293 114-115 prompting in-game purchases, purpose of, 287 247-250 return keyword, 49-50 setting parameters, 290-293 properties, 20-22 return values through windows, 292-293 changing, 25 definition of, 49 whitelists versus blacklists, of classes, 261-263 multiple, 50, 80 Shille) data types for, 22, 27 nil, 52 reactivating bridges, 38-40 inheriting, 274-277 returning table values, 133 receiving messages on server, variables and, 28 Roblox Premium 317-319 Properties window, 3 engagement payouts, 256 red lines in editor, 11-12 protected calls, 225 monetization and, 247 references, checking, 165 Roblox Studio, 1 relational operators, 324 blocks, anchoring, 10 relative jumps, creating, 194-195 camera controls, 4 scripts 367
inserting into parts, 6-7 seasons, changing, 125-126, 334 creating NPCs, 327
for false conditions, 98 RemoteEvent object, 161-162 DoT (Damage Over Time), 333
mixed data types, 212 removing items, 122 Touched event, 34-35 by multiple pieces of infor- searching part of, 123-124 tracking mouse movements, mation, 216-218, 343 sorting, 210-213, 303-306 numerically, 211-212 2016-2187343 BindToRenderStep() function, dictionaries, 213-215, 218, voting simulator, 133-142 303-305 343 dictionaries raycasting from mouse, SpeedBoost tweaks, 85 converting to arrays, 305-306
testing T buttons, 170 UnbindAction() function, 314
adding, 175-176 WwW writing scripts, 7-9
scope, 176 for multiple player inter- wait() function, 42-43, 201 actions, 70 default value, 86 X=-Z naming conventions, 24 with while loops, 92-93 order and placement, 36-40, WET coding, 183 X coordinates, 187-189 45, 84 while loops, 91-92 organizing, 305 exercises, 112, 333-334 Y coordinates, 187-189 with ProximityPrompts, 93-97 scope, 98 Z coordinates, 187-189 with wait() function, 92-93 The Official - R@BLaX
Roblox Game Development in
P Pearson ours
Roblox Game Development Hours
ji @ Pearson Photo by izusek/getty
Automatically receive a coupon for 35% off your next purchase, valid for 30 days. Look for your code in your InformIT cart or the Manage Codes section of your account page. Download available product updates. Access bonus material if available.â Check the box to hear from us and receive exclusive offers on new editions and related products.
âRegistration benefits vary by product. Benefits will be listed on your account page under Registered Products.
Addison-Wesley + Adobe Press + Cisco Press + Microsoft Press + Pearson IT Certification * Que + Sams = Peachpit Press
@ Pearson PUBLIC LIBRARY DISTRICT OF COLUMBIA Lamond-Riggs Library LAR
If you often open multiple tabs and struggle to keep track of them, Tabs Reminder is the solution you need. Tabs Reminder lets you set reminders for tabs so you can close them and get notified about them later. Never lose track of important tabs again with Tabs Reminder!
Try our Chrome extension today!
Share this article with your
friends and colleagues.
Earn points from views and
referrals who sign up.
Learn more