How to Script Anything: Difference between revisions
>Chess123mate →Try it out!: Added a few practice questions. |
>Chess123mate →Try it out!: Added a few practice questions. |
(No difference)
|
Revision as of 04:40, 19 December 2008
Introduction
Welcome to How to Script Anything! This miniature scripting "book" will teach you:
-Scripting in general (variables, syntax, etc.)
-The basic commands and details
-Advanced commands
-Logical thinking (and planning)
-How to script almost anything you want
Preliminary knowledge: How to navigate through Roblox; the basics of playing in Roblox.
You will need to do independent research on the links provided.
NOTE: Feel free to request that more be added to this page by PMing chess123mate. I am writing this page because a lot of people want to know how to script, but are confused by other wiki articles (there are many!). Hopefully this will help by laying down the basics and getting more advanced slowly. Much of the information can be found on other websites, this isn't exactly new.
Also, feel free to add in more advanced scripting techniques if you are experienced in them, or format this page better, or clarify something that doesn't make sense (or request clarification).
Be warned...
You must be aware that scripting is not easy to get good at. Read as much in the wiki as you can, test it out in the Command bar and output, and try simple scripts to start with. Believe it or not, but tools that allow cars and airplanes to move are very advanced.
It may take anywhere from weeks to years to get really good at scripting. However, this skill is very valuable in making pretty much anything you dream. That's what Roblox is all about.
It is a good idea to try editing advanced scripts and editing parts of it. Edit one piece at a time, cut and paste the script from the Explorer Window in Roblox Studio (this restarts the script and is an important debugging technique). After reading this entire page and other wiki pages, you should have a general idea of what it does anyway, but sometimes it is difficult to figure out what another scripter has done (or is trying to do).
Chapter 1: Scripting Basics
On Roblox, scripting can help you make things happen. Without it, nothing would occur. Scripts allow your character to move and blox others, regen things, making things fly (potentially on their own), and much more. However, it takes many precise commands for it to all work. Without precise commands, Roblox may end up doing something you didn't want it to do.
However, the skill of choosing the correct commands and putting them in the correct order is not easily learned. It will take hard work, time, and a lot of practice. If you don't get something the first time, you will need to continue to search for errors, bugs, syntax errors, and continue trying. However, the reward of this is great, for you will be able to script almost anything.
Helpful Links
You may wish to visit these links. These are helpful for both beginners and experienced scripters alike, usually as a reference for various commands or classes.
General Scripting Wiki Page
Class Reference Page
Official Lua 5.1 Documentation
Variables
Variables allow you to hold a piece of information. This can be one of many things, including a number, string, boolean value, functions, tables (arrays), or any other type of object.
Definitions: A string is a bunch of letters, always put in quotation marks. Ex. "! A string!" is a string, but ! A string! is not.
A boolean value holds either true or false. Objects will be looked at later, but the most basic example is a brick in Roblox. Every brick is an object of the class name "Part".
The name of a variable can be anything other than reserved names. Reserved names are all the names dedicated to commands, and can be found on the official Lua site. They will show up in blue when scripting in Roblox. Note that you generally shouldn't make variable names of standard commands.
Ex. "wait()" is a command, so you shouldn't have a variable named "wait" (although you can)
Variables must start with a letter or an underscore.
When naming a variable, it is best to consider it's purpose. If the variable is supposed to represent the amount of money you have, name it so that it will easily remind you of its purpose. Ex. myMoney, moneyAmount, theMoneyIHave, money, etc.
It is not good practice to use single letters as the names for important variables, since these are usually used in loops (which will be discussed later). Also note that some styles will expect that the first letter of the variable is a small letter and the first letter of the beginning of each word is capital.
Ex: firstLetterOfEachWordIsCapitilizedExceptTheFirst follows the proper style, but is way too long, and doesn't indicate what value it contains.
Variables are used in the following way:
variableName=value
Ex. colour=game.Workspace.Base.BrickColor
"colour" is our variable, and "game.Workspace.Base.BrickColor" is the value.
There are other things you can do with variables, such as switching their values: a, b = b, a, but it is not essential to know.
Unlike other languages, declaring a variable is not necessary. However, until you assign a value to the variable, its value will be nil. This can be a major source of error if you have a lot of variables and you're not careful in declaring them. However, you can declare a variable local, and must do so if you want it to be private to the function you are declaring the function in. More will be explained on this later.
Properties
Properties are variables with special names in an object (such as a brick) and are essential to understand. A property name can be almost anything, just like a variable.
In the above example (colour=game.Workspace.Base.BrickColor), BrickColor is a property of the Base, which is a child of the Workspace, which is a child of the game.
Properties Explained in Detail
Note that properties always have a period . while functions have a colon :
Example of a function is object:Remove()
Example of a property is object.className
Syntax and Help Symbols
Syntax is much like the grammar and spelling of a language (like in English, you cannot say "is sentence this a.", you should say "This is a sentence." In the same way, computer languages such as Lua expect a specific syntax that must be followed.
Help symbols refer to the explaining of syntax. Different symbols indicate different meanings. The main two that are used on this page are [ ], indicating an optional piece of code, and italics, which indicates that code (usually a variable) should be placed there. The next section deals with a preliminary on functions and explains this.
Also note the comment symbol, --, is often used in code to explain why a certain command is being used, or explaining what its purpose is. It can be used as a text block, which can be inserted anywhere and stopped in the middle of a line and other commands can be put after it. Example:
print("Beginning of this line") --[[Normal comments will comment out the entire line]] print("End of line still executes because comment was ended") --The comment block starts with --[[ and ends with ]]. This is useful for commenting out entire parts of a script that you do not wish to delete, but do not wish to execute either.
Lua commands can be put onto a single line and still function. This means that instead of having five commands, each on its own line, they could all be put one right after each other. Naturally, they all need spaces between them so that Lua doesn't think that you're accessing a very large variable name or function name. Example:
doSomething()
a=b*c --note that it does not matter if you put spaces between the operators or not. It could also be: a = b * c.
print(b+a*c)
Would work just as well as:
doSomething() a=b*c print(b+a*c)
Do not worry if the commands do not make sense to you at this point, the basics will be laid out.
Also note that all variables and functions are case-sensitive. This means that "variable" is not the same thing as "varIable".
Functions, Parameters, and Return Values
Since functions make up the easiest commands, it is a good idea to understand what a function actually is. A program (or script) is made up of a series of functions (and in other languages, subroutines) that carry out a purpose. For example, in Roblox itself there will be many subroutines that draw the graphics on your computer screen, and functions that do calculations. In Lua, only functions are used, and have the syntax:
function name([param1[,param2[,...]]])
[Commands here]
[return [value]]
end
Example:
function add(a, b)
print(a+b) --prints the sum of a and b to
return a+b
end
The beginning of the function has the word "function", to indicate to Lua that you wish to define a function. The next part is on the same line, and is the name "add". This is not a command, therefore it is fine to use as a function name. The next part is the list of parameters in a pair of brackets (). Each parameter is a variable, and must follow the same rules as before. Note that all parameters are declared local (this will be looked at in chapter 3).
Next there are two commands: "print" and "return". The "return" statement indicates to return the value of the variables after the statement. In this case, that means that this function will return the value of a+b. Then the function ends.
Note what the syntax explanation actually means:
- The first word is "function"
- You must provide a name after the word "function"
- You may have parameters (as many as you want), but you do not need them.
- You may put commands in your function, but do not have to.
- You may return a value, but do not have to. You may wish to return without providing a value to simply exit the function. "nil" is returned by default.
- You must end the function with the "end" statement.
wait() and print()
wait() and print() are two very useful, but simple commands, which do exactly as they say. print("Text") will print out information to the Output box, while wait(x) will wait x seconds. Both of these functions are standard commands. These are very useful in debugging. Note that you will often need to use the "tostring(variable)" to convert an object to a string. You may combine strings (concatenate) by using two periods: "123" .. "456" == "123456"
Operators and Boolean Logic
Operators are essential in scripting. Without them, no variables could change, and no calculations could be done. Operators are simple mathematical symbols, including: +, -, /, *, %, and ^. Although you are likely familiar with all of these, here are some examples:
- 4 + 2 = 6 --addition
- 4 - 2 = 2 --subtraction
- 4 * 2 = 8 --multiplication
- 4 / 2 = 2 --division
- 4 ^ 2 = 16 --4 to the exponent 2 (which is the same as 4*4), or 4 squared.
- 4 ^ 0.5 = 2 --the square root of 4
- 4 % 2 = 0 --the remainder of 4 divided by 2 is 0. This is the modulus operator.
- 4 % 3 = 1 --the remainder of 4 divided by 3 is 1
These operators can be applied to all numerical values and printed out in the Output.
Example:
number=3
print(number)
number=number+4*3
print(number)
Note that you will want to search BEDMAS, the order of operations, if you are confused that the 2nd number to be printed out is 15 instead of 21.
A boolean value can only be true or false. Boolean logic is just a combination of true or false values that produce another true/false value. These are useful later on in loops and conditions.
The main operators that allow you to evaluate boolean logic include: and, or, and not. These are used in every day English, and mean the same thing in Lua. The results are:
true and true == true
true and false == false
false and true == false
false or true == true
true or false == true
true or true == true
false or false == false
The "not" is not as important. It simply negates the value, turning true into false and false into true. This means
not false == true
not true == false
Other operators that are important are:
== means "is equal to" = means "assign the value on the right to the variable(s) on the left" ~= means "is not equal to" < means "is less than" <= means "is less than or equal to" > means "is greater than" >= means "is greater than or equal to" For example, if a=1 and b=2, then using the above operators will give: a==b -->false a~=b -->true a<b -->true a<=b -->true a>b -->false a>=b -->false
Note that you can apply this to boolean logic. For example:
a=1 b=2 c=2 print(a==1 and b==2) -->true print(a==b or a==c) -->false. A does not equal b, and it also does not equal c. print(a==b or b==c) -->true. Although a does not equal b, b does equal c. print(2*a==b) -->true print(not a==b) -->true because a does not equal b. This translates to "not" false, which is true.
Try it out!
Open up Roblox Studio, and make sure that the command bar and output windows are open. Try declaring variables, adding to them, and printing them out. Try these exercises:
- Make a variable and assign it a value of 10
- Make another variable and multiply it to the first, printing out the results
- Try printing out some comparison operators, with or without boolean logic.
- Question for practice: What is the result of " 1+1*2 == 3 and 3*3==9 " ?
Chapter 2: Flow Control
Especially in Roblox, if there was no way of controlling what happens, there would be very little that could be done. One way of controlling the "flow", or what the script executes next, is by using events (in Chapter 3).
While Loops
A while loop uses the syntax
while condition is true do
[Statements]
[break]
end
Using the "break" command is optional. It will exit any loop that it is in. This applies to all loop commands.
The "condition" is an expression that can be evaluated to "true" or "false"; boolean values.
What does the loop do? If the condition is true, it evaluates all the statements in the loop's block until a "break" command is executed or the condition is true when the loop restarts.
For Loops
There are a few more advanced for loops, but the most basic one looks like this:
for variable = startValue, endValue[,{increment}] do
Statements
[break]
end
The "step" parameter is optional. Examples:
for i = 1, 10 do
print(i) --prints 1 through 10
end
for i = 3, 1, -0.5 do
print(i) --prints 3 through 1 in increments of -0.5: 3, 2.5, 2, 1.5, 1
end
In other words, a for loop will increase the variable you specify starting at the startValue and continuing to add the increment to the variable until the variable is equal to (or past) the endValue.
NOTE: Clarification should be used on exactly when the for loop exits in something like "for i = 1, 5, 2". Does it stop when i = 4, or i = 6 ?
If/Then Statement
If / then statements are very important. Without them, computers couldn't do anything. The syntax is:
if condition is true then
Statements
[elseif condition then
Statements
[elseif...]]
[else Statements]
end
What this means is that you start with a boolean condition. If it is true, the statements between the word "then" and the next "else", "elseif", or "end" are executed. Then all other statements in that block are skipped. If the condition is false and there is an elseif statement, its condition will be checked. This process continues until an 'else' or 'end' command is encountered. "else" is run if none of the conditions checked are true. Note that elseif and else are optional.
An example helps
myNum=6 --your guess, try editing it realNum=10 --the number you are supposed to "guess". Try editing it if realNum==myNum then print("You guessed it!" elseif myNum < 0 or myNum > 100 then print("The number should be between 0 and 100.") elseif realNum > myNum then print("Your guess is too small.") elseif realNum < myNum then print("Your guess is too large.") else print("BUG! This is impossible!") end
Note that the line that prints out "BUG!" will never run, and should be deleted. This could be used for debugging purposes to make sure that you have covered all the logical possibilities. Here a flow chart could be used to visually show that all possibilities are covered. However, reasoning works just as well. Note that if you assign -5 to "myNum", making the line read "myNum=-5", you might wonder what the result is. The logic is as follows:
"realNum==myNum becomes 10==-5". This is not true, so Lua advances to:
"myNum < 0 or myNum > 100", which becomes "-5 < 0 or -5 > 100", which results in "true or false", which is true. This means that the line "print("The number should be between 0 and 100.")" will run. Note that Lua will skip all the remaining checks and go straight to the end, even though "realNum>myNum" (10 > -5) is true. Remember that order counts when making your if/then/elseif structure.
Try it out!
Open up a blank script and try making a few simple scripts:
- A script that prints out all the numbers between 1 and 20 and at the same time tells you if the number is even or odd:
Output would look like this:
1 odd
2 even
3 odd
4 even
- A script that takes 2 variables, as seen in the above if/then example, and tests to see if 2 times one of the variables is larger than the other.
- A script that prints out all the numbers from 3 to 13, skipping 5 and 8.
- A script that prints out all the numbers from 5 to 1 in increments of -0.2.
- A script that tests to see how many times 2 variables are equal to eachother. You will need the command:
math.random(min, max) --returns a "random" integer between min and max. It is not a truly random number, but it works well enough for this
Once you get your script to test to see if 2 different random numbers are equal, ensure that you record whether they are equal or not and run the test 10 times before printing out the results. Output might be:
"Number of times they matched: 3
Number of times they didn't match: 17"
Try different min and max values for the random number generator. Also try making them different for each variable.
Chapter 3: Functions, Data Types, and Coroutines
A closer look at making your own functions is necessary before any significant scripting can be done. Events, data types, coroutines all require functions, and very few scripts can do anything on Roblox unless they have at least one function.
Custom Functions
To make your own custom function, you must answer the question "What do I want it to do?", or "Why am I writing it?" This will influence what you name it. Sometimes functions are useful for code that you repeat a lot, or that you repeat with slight variations. Functions can efficiently reduce the amount of time it takes for you to script something. Another advantage to using functions is that you can split up a script into smaller tasks. Since you may find out that some tasks should be used more than once, you are at an advantage of having already made the function. You need only call it.
A simple function to start with is one that performs a calculation and returns a value. Pretend that you want a function that will add, subtract, multiply, and then divide a given number using random numbers. First, declare the function:
function calculate(num)
It could be named something other than calculate, but it should be related to what it does for clarity. It is not smart to name a function "subtract" if it doesn't subtract in some way.
The above line also shows you that there is a parameter with the name "num". This could also be named anything, but it is best to let it be named what it represents. In this case it represents a number, and so is named "num" for "number".
The next part is to take the number and perform the calculations on it. If you are good with math, you can do it in one line:
num=(num+math.random(1,10)-math.random(1,10))*math.random(1,10)/math.random(1,10)
Note that there are some redundant calculations performed here. Consider the first math.random() statement. It will return a number between 1 and 10. However, the subtraction does the same thing, in reverse. Look at the extremes to figure out what might happen:
- Add 1, subtract 10; this equals -9
- Add 10, subtract 1; this equals 9
Therefore instead of wasting processing time with multiple math.random() statements, it is slightly faster to just call one math.random statement. As we can see through the extremes, the smallest number that will be added is -9, while the largest is 9. So instead of using "num + math.random(1,10) - math.random(1,10)", we can shorten this to "num + math.random(-9, 9)".
The same can be done for the multiplication and division:
- Multiply by 1, divide by 10; this equals 0.1
- Multiply by 10, divide by 1; this equals 10.
This might look like you could shorten it by using "num * math.random(0.1, 10)". However, this will not work. Consider a few more possibilities:
- Multiply by 2, divide by 10; this equals 0.2
- Multiply by 3, divide by 10; this equals 0.3
Clearly when dividing by 10, the pattern increases by 0.1. This means that the next few numbers would be 0.4 and 0.5. However, what if you only divide by 2?
- num * 1 / 2 = num * 0.5
- num * 2 / 2 = num * 1
- num * 3 / 2 = num * 1.5
- num * 4 / 2 = num * 2
- num * 5 / 2 = num * 2.5
Note that the first value is 0.5, a repeat of multiplying by 5 and dividing by 10. In other words, the formula we have started with will multiply by some values more than others. Also note that it is very difficult, if not impossible to get a single math.random() statement to cover all the possible values shown. It would have to include 0.1, 0.2, 0.3, 0.4, ... 1, 1.2, 1.4, 1.5, 1.6, and many more to cover all the different values. In conclusion, you cannot shorten the multiplying and dividing in this case.
However, there is one more thing we can do to make this function more efficient. At current the script is:
function calculate(num) num=(num+math.random(-9,9))*math.random(1,10)/math.random(1,10) return num end
The final two lines, "return num" and "end", return the value of "num" and ends the function. However, we can simplify the two body lines to be:
function calculate(num) return (num+math.random(-9,9))*math.random(1,10)/math.random(1,10) end
If we are just returning the value of num, there is no point in changing num; just read from it and return the value.
Alternatively, this could be put into less efficient steps, but it is wise to do so if you are unsure of the math:
num=num+math.random(1,10) num=num-math.random(1,10) num=num*math.random(1,10) num=num/math.random(1,10) return num
Note that, just like before, once you have figured out a piece of code that works, you can simplify it just as was done as before.
To call this function, simply write out the name of the function and provide the parameters. Some examples are:
calculate(1) print(calculate(20)) for i = 1, 10 do print(calculate(10)*calculate(10)) end --will print out 10 different numbers, because each time you call "calculate" math.random() will produce new numbers. It is rare for any two sets of numbers to be the same, but will happen if you run this line enough (ex. 1000 times).
Recursion: Another Loop
Sometimes it is useful to use recursion, the act of calling a function within itself. This technique allows another form of a loop. The most basic purpose can be demonstrated in a simple addition loop. This could be done in a for loop:
sum=0 for i = 10, 1, -1 do sum=sum+i end
However, it could also be done in recursion. Normally a straight loop is preferable, but just for demonstration, here it is:
function addSum(num, min) if num<=min then return num end return num+addSum(num-1, min) end
The first line is the declaration. There are two parameters here, "num" and "min". In the for loop shown above, num would be 10 and min would be 1. The next line checks to see if num is smaller than or equal to "min". If so, it simply returns min. Otherwise, it uses recursion. It calls itself with the line "return num+addSum(num-1, min)". Note that recursion, like all loops, has the potential to crash Roblox if the loop does not terminate. For example, pretend the line was "return num+addSum(num-1,min-1)". This would decrease num AND min. Num would never get any closer to min, therefore addSum would be called, theoretically, forever. It would be similar to having a loop like this:
a=5 b=3 while b<=a do a=a-1 b=b-1 end
Although in many forms of recursion, Lua will simply raise an error (and the script will stop working) due to lack of stack space, but in a loop like the while loop above, Roblox would freeze, waiting for the loop to finish (which it never does).
Unnamed Functions
Unnamed functions look like this:
function([param1 [,param2 [, ...]]]) {Statements} end
Normally, a function has a name and can be called at any time. However, an unnamed function cannot be called unless it is assigned to a variable. Usually these functions are used in connections to pass extra parameters to a specific, named function. See Chapter 4 for details on events.
Tables
Tables, also known as arrays, are very useful in Roblox. The basic idea of a table is to hold a series of variables under one name. In lua, the variables do not even have to be of the same type. A table can hold several numbers, strings, and even objects under one variable name.
Tables are declared by either assigning a table to a variable or by using {}. To initialize a table with values, you can create a list: {1, 2, 5, 6, 10, 11}. You can also have tables of tables: {{1, 2, 3}, {2, 3, 4}, {"First Number", "Second Num", "Third #"}}. Tables are otherwise exactly like variables. They also have a special property accessed by using the # symbol, which will return the highest integer index of the table. Use it like this: #tableName
1 Dimensional
A 1 dimensional table is simply a table with values in it, where none of the values are tables.
Table Manipulation
It is suggested to visit the above link and read it over. Other functions can be found on the Functions link on the left side bar: Functions
A 1 dimensional table can be declared with:
name = {}
or
name = {[value1[, value2[, value3[, ...]]]]}
In Roblox, a very common table is using the function "obj:GetChildren()", such as when you want to change all the parts (bricks) to a certain colour.
To access an index in a table, use [ and ]. If you wanted the first index of a table, you would normally access it using: tableName[1]. However, you can also use strings, objects, and other variable types to create the index of a table. However, this is not suggested. Example: tableName["This is an index"], tableName[game.Workspace], tableName[Vector3.new(1,-8,27)]. Reading up on the table manipulation page to find out more on how to access these indexes when you do not know what they are.
ch = game.Workspace.MyModel:GetChildren() --ch stands for children. ":GetChildren" can also be written as ":getChildren" for i = 1, #ch do if ch[i].className=="Part" then --"className" is a property of every Roblox object. "Part" means that it is a brick. ch[i].BrickColor=BrickColor.new("Black") --Every "Part" object has a "BrickColor" property. Trying to access the BrickColor property of a model would raise an error. end end
Multi-Dimensional
Multi-dimensional tables are similar. They can be declared in the same way:
''tableName''={} for i = 1, 100 do tableName[i]={} end
As you can see, each index from 1 to 100 must also be initialized. If the table only needed to have 3 tables, you might do a simple declaration: tableName={{},{},{}} To access an index from this table, use: tableName[1][2]. However, the first method shown is preferable since little scripting is required to make an entire table. If a third dimension is desired, another nested for loop can be inserted. (Nesting a loop means putting a loop inside of another loop. You can do this to if statements as well).
myTable={} for i = 1, 100 do myTable[i]={} for j = 1, 100 do myTable[i][j]={} for k = 1, 100 do --this loop puts information into the table, but is not necessary for declaring it myTable[i][j][k]=i+j+k end end end
This process can be continued indefinitely.
Be warned when using tables in Roblox, if you have a table are removing some of the information in the table, always count in reverse:
for i = #ch, 1, -1 do --code here end
The reason for this is because if at any time you remove something from the table, say at index 5, this shifts all the information in indexes 6, 7, 8, etc. left one. The "#ch" in the for loop has already been saved, and so it will not be modified. Here is the logic:
ch={1, 2, 3, 4} for i = 1, #ch do if ch[i]%2==1 then table.remove(ch, i) end end Stage 1 #ch = 4, so the loop will run 4 times. i=1 ch[i]%2==1, so "table.remove(ch,i)" will run. The table now looks like this: {2, 3, 4} Stage 2: i=2 ch[i]%2==1, since ch[i] is 3, so the if statement runs. The table now looks like this: {2, 4}. Note that the if statement never even checked number "2" in the table. Stage 3: i=3 ch[i] does not exist, since there are currently only 2 indexes to the table. An error will occur.
Try doing the same logic path for the other method:
ch={1, 2, 3, 4} for i = #ch, 1, -1 do if ch[i]%2==1 then table.remove(ch, i) end end
You will find that every index is checked and that removing an index does not influence the rest of the loop.
Classes
A class is a bunch of functions, variables, and properties that work together to do something. The most obvious class in Roblox i a brick, the "Part" class. It has functions such as ":Remove()" and ":Clone()", and properties such as ".BrickColor" and ".Anchored". All these properties and functions can be found in the Object Browser in Roblox Studio (under Help).
Classes allow a script or a series of scripts to keep information together instead of having several variables representing different pieces of the same idea. Without classes, scripting several bricks would become extremely difficult, since there would have to be a table for each property; adding or removing bricks would become complicated since all tables would have to be modified at the same time.
Making Your Own Objects and Classes (not finished)
TODO Make instructions on how to do this with examples.
Coroutines
Each script is run on its own coroutine. Each coroutine is run until it stops running (there is no more code to run), or until Lua encounters a wait() command. This is why it is essential to be careful when making loops; if they never exit and never call a wait command, Roblox will freeze while waiting for this coroutine to stop operating.
Making your own coroutines is not difficult. To make a coroutine, you need a function. The syntax is:
variable = coroutine.create(functionName)
coroutine.resume(variable[, param1 [, param2 [, ...]]])
You can combine these lines to: coroutine.resume(coroutine.create(functionName)[, param1 [, param2 [, ...]]])
Using coroutines is useful if you want several things to happen simultaneously over a period of time, or if you want something to be constantly updated while doing other calculations. Note that every time an event is called, the function is placed in another coroutine.
Delay()
An alternative to coroutines is using the delay() function, which creates the coroutine automatically. Use it like this:
delay(time, functionName)
Note that you must use unnamed functions if you wish to send parameters to the function. Example:
delay(time, function() functionName(param1, param2, ...) end)
Try it out!
- Try making a coroutine that prints a random number every second. On the main script, after a 0.5 second delay, print out a string every second. In the output, it will look like this:
8
Hello!
3
Hello!
etc. Each new line should appear 0.5 seconds after the previous one. Make the script stop after 5 seconds.
- Make a recursive function that counts all the even numbers between 1 and a given number (it should be a parameter).
- Make a class that allows you to store a Roblox profile with KOs, WOs, Profile views, etc. Then put in your information and a few friends' information, then print it out in an organized fashion (print out everyone's user name, then KOs, etc. Use a for loop).
Chapter 4: How to Interact with Roblox
Most of the basic features of Lua have been introduced by now. However, few of the scripts have done anything useful in Roblox.
The script Variable
There are very few properties of a script that are important to look at, and for the most part should be researched in the rest of the wiki. However, the basic ones are:
In a script, you can access the script object itself with the variable "script". Accessing its parent is therefore "script.Parent".
Events
Events are essential in almost every script. The most basic event can be used to make a door. The syntax of an event is:
object.EventName:connect(functionName)
Note that functionName can be an unnamed function. This is useful if you have extra parameters that you wish to pass to your function handler (usually this might be referencing the door itself). Each event will pass its own parameters; the onTouch event used for a door passes only what touches the object.
This example wil use onTouch for a door. The door script will look like this:
function onTouch(obj) script.Parent.Transparency=0.8 script.Parent.CanCollide=false wait(2) script.Parent.Transparency=0 script.Parent.CanCollide=true end script.Parent.Touched:connect(onTouch)
If anything touches the door, it will turn mostly transparent for 2 seconds and allow anything to pass through it. After 2 seconds, it becomes solid and completely opaque (non-transparent).
Debounce
To expand on the above script, a debounce system could be implemented. A debounce system ensures that a function is not running on multiple coroutines. In some cases, a debounce system is essential to allow the script to operate properly. In the above script, imagine that the door is touched, and then 1.5 seconds later, is touched again, and then 1 second after the second touch, a 3rd touch to the door is applied. Here is what would happen:
t (time) = 0, 1st touch. Door is "open" t = 1.5, 2nd touch. Door is re-opened. t= 2, 1st touch closes the door. Door is "closed", even though the coroutine handling the 2nd touch event still thinks the door is open. t = 2.5, 3rd touch. Door is opened. t = 3.5, 2nd touch closes the door. Note that this cuts the time that the door is open for the 3rd touch by 0.5 seconds, an undesired event. t = 4, 3rd touch re-closes the already closed door.
It is clear that the 2nd touch is undesired. The way to solve this problem is using debounce, a technique in which a variable is used to check if the function is already running. The script looks like:
db=false --short for 'DeBounce' function onTouch(obj) if db then return end --exits the function early if "db" is true. db=true --sets "db" to true so that any future events will quit early. script.Parent.Transparency=0.8 script.Parent.CanCollide=false wait(2) script.Parent.Transparency=0 script.Parent.CanCollide=true db=false --because we are about to exit the function, "db" is set to false so that future events will not exit early. end script.Parent.Touched:connect(onTouch)
In this case, any time something touches the door while the door is open will not be registered. Note that a debounce system is useless if there are no wait() commands, and can get complicated when handling multiple objects at once. This will be examined in Chapter 5.
Checking Conditions before Running an Event
In the above example, the door will open for both players and any bricks that happen to fly and touch it. If the door was meant to only open for a player, the condition must be checked before running, just like the debounce system checked to make sure the door wasn't already open. The script will check to see if there is a player that exists whose character is the model that touched the door.
db=false --short for 'DeBounce' function onTouch(obj) if db then return end --exits the function early if "db" is true. if obj.Parent==nil then return end --this is important. Without it, weapons will break the script since they may disappear when they touch something, causing their parent to be nil. Attempting to access a nil object's properties will result in an error. if game.Players:playerFromCharacter(obj.Parent)==nil then return end --this is not a player touching the door db=true --Note that the db statement is AFTER all checks to exit the function. If this was set to true and then it was found that the obj's parent was nil, db would never be reset to false. script.Parent.Transparency=0.8 script.Parent.CanCollide=false wait(2) script.Parent.Transparency=0 script.Parent.CanCollide=true db=false end script.Parent.Touched:connect(onTouch)
Now the script will only open the door if a player touches the door.
Instance.new()
Instance.new() is a very simple function that creates another object and returns the object. If, in a script, another brick was required, one would use:
variable=Instance.new("Part")
Note that instead of "Part", the name of any other class/object could be place there. For example, "BodyGyro" or "BodyPosition" are also useful. Occasionally you may not need to do anything with the object, such as if you want to place a ForceField somewhere and leave it there indefinitely. For this, you can use the object directly:
Instance.new("ForceField").Parent=location
An alternative method to making your own brick in a script is to make a backup brick with all the properties you desire and store it in game.Lighting. Name it something special and you can clone it instead of generating a new brick:
variable=game.Lighting.SpecialBrick:clone()
However, it is not suggested to use this method, since the script can no longer be put into any place and function properly until the special brick is cloned into the new place's Lighting.
Using the Object Browser
It is very helpful to use the object browser, located under "Help" in Roblox Studio. Here a list of all classes for Roblox exists, allowing you to find the names of properties and functions for any object including cameras, bricks, teams, players, sparkles, etc.
_G Variable
Roblox has provided scripters with a way of allowing all scripts in the Workspace to communicate with eachother using the _G variable. Note that scripts in hopperbins do not have access to this variable in online mode, although tools might (edit required here to confirm whether tools can or not; I think they can access it) .
The _G variable is used as a table. If you wish to add a global variable to it, simply use:
_G.variableName = value
Note that variableName can be absolutely anything, from classes to tables to single values (like strings and numbers). Aside from hopperbins, scripts will all access the same _G variable.
Alternate Method of Script Communication
Another way of allowing scripts to communicate is using objects such as NumberValue, BooleanValue, Vector3Value, StringValue, etc. Coming up with a system that all your scripts can understand will enable you to work around the _G hopperbin limitation, although lag may increase since each value will have at least 2 other variables attached to it (such as className and Parent). However, working the name and value may work to your advantage to make use of every variable. Example:
A script system might use an ObjectValue to represent a reference to a player. The value is of course, the player from game.Players, and the name might be a code for a different script that says what action must be taken on this person. It might be named "Bonus" to indicate that the action that should be taken on this person is a good thing, or possibly named "11a6" for a completely unique code that a script decodes (much like a save/load script does, only more simplistic).
As a child of the ObjectValue, there may be StringValues to represent more details, such as what type of bonuses to receive, or what other actions must take place. It depends on what you need and how you decide to get your scripts communicating.
Try it out!
- Write a script that opens a door and changes its colours while its open, then returns to its original state after 5 seconds.
- Write a script for a button that, when pressed, will open up a trap door that will stay open until either 30 seconds have passed or someone goes through the trap door.
Chapter 5: How to Script Anything
In this chapter, an example will be used on how you might apply all of the above concepts in a variety of contexts.
Two examples will be looked at simultaneously. The first is if you had ten doors and wanted them to open or close randomly every time someone touches the door. Instead of using ten separate scripts, one script will be used for all of them to save time if the script needs to be edited and processing time, since less scripts will need to be transmitted to each player using the place on load-up.
The second thing is much more complicated: Make a brick fly and attack players.
Logical Thinking
It is important to think logically. To improve your logical thinking skills, it is useful to solve logic problems. This skill is also improved as you continue to practice scripting. This is why it is not suggested to try very large scripting projects until you've done a few minor ones. However, feel free to challenge yourself; if you find scripting something boring, that is a clear sign that you have mastered it, even if you make a few syntax errors.
Logical thinking simply allows you to think about what the computer is doing when it reads your commands. With no logic, one might think that scripting in "Do what I tell you to" as a command might work, meanwhile someone else with a lot of logic will know which commands must be used and how to use them.
Get an Idea
The first step in scripting anything you want is to first come up with an idea. In this example, we already have two ideas, but you need to come up with your own when you script. Rescripting the same ideas does not accomplish much, with the exception of if you are trying to make the script more efficient (reducing lag). Creativity is a gift and should be used here. If you are finding it difficult to come up with something, get a piece of paper and write out a few ideas.
Our ideas here are:
1. Controlling 10 doors simultaneously: each door may or may not open when you touch it
2. Controlling a brick that will fly around and dive on people, reducing their health.
After you get your main idea, you may find more features that you wish to include. The only danger of these features is coming up with too many; be careful to ensure that all your ideas will work together. Also be aware of your own skill, if you can barely script a dance floor, it is unwise to try and script a plane.
Write it Out on Paper
The next step is to write out your ideas in English (instead of Lua). This is called an algorithm. If you are very skilled in Lua, you may not need to write out each step, and your algorithm may look like this (for idea #1):
1. Connect all 10 doors to the onTouch function 2. Check the debounce for the specific door 3. Randomly open/close the door 4. Undo the debounce for the door
However, for someone with less practice in scripting, it is important to write out the specifics:
1. Let "door" be the table of doors. 2. Let "db" be the table of debounce, each index working with the door number. 3. Get all the 10 doors from the children of game.Workspace.Doors: * Let ch be the children * Use a for loop * Use the table.insert command for the "door" table AND the "db" variable. The function onTouch: 3. It needs the parameters "obj" and "doorNumber" 4. If db for the door is true, exit the function 5. Make db for the doorNumber true 6. If a random number between 1 and 100 is less than or equal to 25, open the door, otherwise close it: Open the Door * Set transparency to 0.6 * Set CanCollide to false or, Close the Door * Set transparency to 0 * Set CanCollide to true 7. After 1 second, set db for this door to false. End the function. 8. Connect all 10 doors to function: * For loop * Unnamed function to pass both parameters
Do note that everyone's algorithm will be slightly different. It will depend on your idea and how you decide to write out the function. There is usually more than one way to script something well.
Script it
Now, script it. TODO: Show scripts.
Debugging and Testing
The next stage of writing your script is to debug it and test it. If you are in edit mode, open the place in solo mode and test the script this way. Ensure your output is open, since it will likely have syntax errors. Continue editing the script until there are no more syntax errors (and no more red text appears in the output). Now test its functionality. This may take several tests to ensure that it will work no matter what; another thing to try and do is if the script uses functions like 'onTouch', try hitting the trigger with weapons, other bricks, etc. to ensure that no matter what happens, the script will work the way you want it to.
If Lua continues to not do what you desire, there are a few possibilities:
- You have made a logic error, in which case you should use Method A (below)
- Roblox is bugged, in which case you should use Method B (below)
Method A
Write out all the variables that are relevant to the code that is not working. Now, at each relevant event or stage where the variables change, write out their values and, step by step, follow the path that your script is supposed to be following. If you encounter a line in your script such as:
if a*b == c then doA() else doB() end
Sub in the values that you've recorded for a, b, and c, and see if they are equal. If they are, examine the function doA(), otherwise look at function doB() to see what Lua would do next. Eventually, you will have either found the error and fixed it, or missed it. If you are unable to find the error, you have a few choices: 1. Repeat the process, 2. Look for other factors you aren't considering, 3. Look for common variable names when you don't declare them local to a function 4. Post it on Scripting Help, 5. Consider the possibility that Lua is not working properly (this should be a last resort), and proceed to Method B.
Method B
You should first ensure that you have checked all ways to solve your problem from Method A before contacting the admin. Especially posting it on Scripting Help to see if anyone else is having the problem. However, administrators cannot always solve everything, and sometimes a "bug" is not really a bug but a limitation of functionality (such as Hopperbins having scripts that are local only, or re-positioning bricks has a different effect than changing the CFrame of a brick, etc.). Occasionally you will find a bug with a new update, in which contacting the administrators is highly suggested.
Try it out!
Now you have the ability to script almost anything you want. With a lot of practice and a few questions, what you can script will increase dramatically.
Here are a few short sample scripts with many bugs in them. See if you can find where they are, determine what the code should have been, and fix the errors.
--Sample 1 function fadeBrick(obj) for i = 100, 0: obj.Transparency=i obj.Remove() end fadeBrick(game.Workspace.FadeThisBrick) --assume that this brick does exist
--Sample 2; a little harder... function flicker(brick) original=brick brick.Parent=nil wait() brick.Parent=original.Parent end while i not 10 do flicker(game.Workspace.CoolModel.Children) --assume "CoolModel" is a model in the Workspace that contains several bricks
--Sample 3. Whoever wrote this must be really tired... or maybe just didn't read "How to Script Anything" function explode(model) ch=model:GetChildren() for i = 1, ch: Explosion.new()=ch(i) ch(i).Remove end wait(60) explode(game.Workpace)
Solutions to Practice Questions
Remember that your solutions to these questions is likely to be slightly different than the ones presented here, and that's perfectly okay. However, even if they are very different, your answer may be just fine or even better than the ones here.
Chapter 1
- myVar = 10
- myVar2 = 5
myVar = myVar * myVar2
print("myVar's value: " .. myVar)
- print(1==3) --will print false
print(8<8) --will print false
print(8<9) --will print true
print(8<8 or 0>-5) --will print true
print(8<8 and 0>-5) --will print false
print(true and true) --will print true
etc.
- 1+1*2 == 3 and 3*3==9
Simplifies to: 1+1 == 3 and 3*3==9
2 == 3 and 9==9
false and true
false
Chapter 2
- for i = 1, 20 do
if i % 2 == 0 then
print(i .. " even")
else
print(i .. " odd")
end
end
- var1 = 5 --editable
var2=11 --editable
if var1*2>var2 then
print("Two times var1 is greater than var2.")
elseif var1*2==var2 then
print("Two times var1 equals var2.")
else
print("Two times var1 is less than var2.")
end
- for i = 3, 13 do
if i~=5 and i~=8 then
print(i)
end
- for i = 5, 1, -0.2 do print(i) end
- min1=1
max1=1
max1=10
max2=20
trials=100
match=0
notMatch=0 --Note that you could also calculate this by subtracting "match" from "trials", making the script more efficient since the script wouldn't have to run as many lines.
for i = 1, trials do
if math.random(min1, max1)==math.random(min2,max2) then
match=match+1
else
notMatch=notMatch+1
end
end
print("Number of times they matched: " .. match)
print("Number of times they didn't match: " .. notMatch) --or, if notMatch was eliminated, it would be ..."didn't match: " .. (trials-match)).
Chapter 3
- function printOut()
for j = 1, 5 do
print(math.random(1,10))
wait(1)
end
end
--Alternate method; use: delay(0, printOut)
coroutine.resume(coroutine.create(printOut))
wait(0.5)
for i = 1, 4 do
print("Hello!")
end
Alternatively, the printOut function could be run indefinitely and then the main coroutine would stop it using coroutine.yield(co), but it would have had to declare co earlier on.
- function recursionSum(num)
if num%2==1 then max=max-1 end --make "num" an even number
if num>1 then
return num+recursionSum(num-2)
end
return num
end
print(recursionSum(10)) --will print out 30
- TODO Finish classes
Chapter 4
- db=false
d=script.Parent
function onTouch(obj) --assumes that the door starts out with CanCollide==true
if db then return end
db=true
trans=d.Transparency
d.Transparency=0.5
d.CanCollide=false
colour=d.BrickColor
for i = 1, 5, 0.1 do
d.BrickColor=BrickColor.Random()
wait(0.1)
end
d.BrickColor=colour
d.Transparency=trans
d.CanCollide=true
db=false
end
d.Touched:connect(onTouch)
- Note: There are many ways of writing this script.
door=game.Workspace.TrapDoor button=game.Workspace.Button open=false --actual state of trapDoor id=0 --touch id. Incremented every time the trapdoor is opened to ensure that the "check" function does not close the door if the door was closed and opened within the 30 seconds. db=false function check(value) if value==id and open then closeDoor() open=false end end function closeDoor() button.BrickColor=BrickColor.new("Bright green") door.Transparency=0 door.CanCollide=true db=false --only make db false here to ensure that 'closeDoor' isn't called with a 1 second delay and the button is touched within that second. end function onDoorTouch(obj) if not open then return end --already closed if obj.Parent==nil then return end --a weapon touched the door if game.Players:playerFromCharacter(obj.Parent)==nil then return end --not a player open=false delay(1, closeDoor) --provide time for the person to get through end function onButtonTouch(obj) if open then return end --trapdoor already open if obj.Parent==nil then return end --a weapon touched the button if game.Players:playerFromCharacter(obj.Parent)==nil then return end --not a player open=true db=true id=id+1 button.BrickColor=BrickColor.new("Bright red") door.Transparency=0.6 door.CanCollide=false delay(30, function() check(id) end) end