How to Script Anything/Chapter3
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).