Reusing code

From Legacy Roblox Wiki
Jump to navigationJump to search

Tired of copy-and-pasting tons of Lua? Wish you had that cool function in all the scripts in a place? Scared to update a segment of script, lest you have to update every occurance of it? Trying to use good OO concepts you learned from a language like C#, C++, or Java? This article is for you.

Prerequisites

This article assumes that you not only know how to script, but that you can script well, and use scripts extensively in your places. If you wish to follow along, the article is written in tutorial format. (It would be a full-blown tutorial, but I don't want to contribute to the deficit of building tutorials) Simply open up a blank place in ROBLOX Studio, plop a baseplate in (and a spawnpoint if you want), and enjoy the show.

Exposing public functions

The easiest form of re-using code is putting a function somewhere every script in your place can find it. This works because a function is actually a variable.

A function's a WHAT!?

"Wait, what!?", you say. That's right. A function is a variable. If you don't believe me, try this script:

function function1()
	print("Hello world from function1!")
end

--declare a function as a variable
local function2 = function()
	print("Hello world from function2!")
end

function1()
function2()
function1 = function()
	print("I'm not function1 anymore!")
end
function1()

--This function takes a function as an argument
function execute(func)
	func()
end

execute(function2) --Note there are no parenthesis. 
execute(function() print("This function was never named!!") end)

Hello world from function1! Hello world from function2! I'm not function1 anymore! Hello world from function2!

This function was never named!!

As you can see, if we can stash find a place to stash a variable that every script can access, we can use one function across the whole place!

Sharing is caring

The trouble with this is that scripts are selfish little things. They keep all their functions and variables to themselves. There are only two things that scripts share. One of them you're probably very familiar with: game.

The game object contains everything in ROBLOX. The Workspace, the Lighting, the Players, they all exist in the game object. Unfortunately, the game object is very particular about what's inside it, as is everything in it. They only allow ROBLOX objects such as Parts, Models, and the like inside of them, and for good reason. A function, however, is not an approved object, and you'll be hard-pressed to find somewhere where the game will allow a function.

The other object is the global table, called _G. If you're unfamiliar with a table, suffice it to say that a table is a group of variables, each with its own name. When you use a for loop to iterate over a collection of parts (and I hope you do, it's a critical part of ROBLOX scripting), you're actually just looping through everything in a table. The _G global table is a special table that any script can access. It's in here that we can stash a variable, or a function, so that anyone can see them.

Enough gab, let's code!

Add a button to the place. It can be a tycoon-style button, with a label, or just a brick that you step on. It doesn't matter, as long as you can activate it by touching it. Add a basic onTouched script. If you don't know how to do that, you need to read some more basic tutorials. Make it a generic kill-block for now.

Add a script to the root of Workspace, and call it "GlobalScript". Type this code into GlobalScript:

_G.GlobalFunction = function()
	print("Hello World")
end

Let's walk through this for a second.

Note that when we say "_G.GlobalFunction", we're not setting a pre-existing variable. Tables allow you to create a sub-variable just by mentioning it!

"function()" is the way to create a function without naming it. Think of it like skipping the name of the function.

Now in the killblock, replace your onTouched function with the following:

function onTouched(part)
	_G.GlobalFunction() -- Use a dot (.), NOT A COLON (:)
end

Test the place through Tools > Test > Play Solo, open the Output Window (View > Output if it's not already there), and step on the brick. "Hello World" should appear in the window.

Organizing a bit

Starting to see the possibilities? Try making more global functions. Try calling them from more than one button! An immediate problem with this method is that it is very unorganized. To illustrate, image that we have two scripts, and they both have a global function. These functions are named the same, but have very different uses. The obvious solution is to give the functions more descriptive names (i.e. instead of Kill, have KillPlayer and KillZombie), but what if you import a script with a global function from Free Models? This could get out of control very quickly.

The other solution is to create a sub-table within _G, to sort our functions. Let's try this. Modify GlobalScript to the following:

_G.NaturalDisasters = {}
_G.NaturalDisasters.GlobalFunction = function()
	print("Hello World")
end

Make sure to change the brick's touch script to read:

_G.NaturalDisasters.GlobalFunction()

so it can call the method.

In the first line, we create a subtable of _G called NaturalDisasters. Then, instead of added the function directly to _G, we add it to the new subtable.

The disadvantage to this method is that for hierarchical sorting (example: _G.JediKnightKrazysPlaces.BuildToSurvive.NaturalDisasters), we'd have to create each subtable individually.

Organization is key

A subtable doesn't have to have a name. Instead, it can have a key, which is an object used to identify the subtable. It could be a string, an Integer, a Vector3, a Part... even a Script.

Modify the GlobalScript once again:

_G[script].GlobalFunction = function()
	print("Hello World")
end

In this version, the functions are grouped by their parent script! This may not be the best way to organize public functions, but it certainly is easy to use, and the syntax to use it is actually very close to the ideal:

game.Workspace.GlobalScript:GlobalFunction() -- The ideal way to call a public function. THIS DOESN'T WORK.
_G[game.Workspace.GlobalScript].GlobalFunction() -- The actual way to call a global function. Note that I use a period.(.)

Modify the onTouched script:

function onTouched(part)
	_G[game.Workspace.GlobalScript].GlobalFunction()
end

script.Parent.Touched:connect(onTouched)

Now we can access the function from anywhere, just by knowing the name of the script.

A practical application

Let's make an obstacle course with this. Add a new script to the workspace. Call it KillManager. To it, add the following script:

_G[script] = {} --Prep the subtable
public = _G[script] --Set up a shortcut

public.Kill = function(humanoid)
	humanoid:TakeDamage(100)
end

Now add a lava brick, but don't make it deadly just yet. Add the following script to it:

local KillManager = _G[game.Workspace.KillManager]

function onTouched(part)
	h = part.Parent:findFirstChild("Humanoid")
	if (h ~= nil) then
		KillManager.Kill(h)
	end
end

script.Parent.Touched:connect(onTouched)

Copy the lava brick and set up a basic lava jump with it.

Playtest it a bit, you should die when you hit the lava, just like if it was the lavascript itself killing.

You may notice that the lava will not hurt you if you still have your forcefield. This is because we use the TakeDamage function of the Humanoid, which is great for weapons, but lousy for obstacle courses. Change the KillManager to read:

_G[script] = {} --Prep the subtable
public = _G[script] --Set up a shortcut

public.Kill = function(humanoid)
	humanoid.Health = 0 -- Guaranteed death!
end

Now test it. The lava should kill you, even after you just spawned. Note that the behavior of multiple scripts was changed, just by changing one line in one script!

Play around with the script a bit. Maybe change the Kill() function to blow you up! Note that ForceFields are also immune to explosions. You might have to check for a ForceField and remove it.

Items of interest

Global functions can take arguments. (arguments are the variables in parenthesis) If you create a global function that takes arguments, MAKE SURE TO CALL IT WITH A DOT (.). The colon (:) is actually a code shortcut:

h.TakeDamage(h, 100) -- You have to pass the "h" as the first argument
h:TakeDamage(100) -- The ":" automatically passes h as the first argument.

This can be a disastrous side-effect when calling global functions that don't need that first argument:

_G[script].Print = function(stuffToPrint)
     print(stuffToPrint)
end

--In another script...--

--Passes _G[game.Workspace.SomeScript] as the first argument
--"Hello World!" as the second.
--The function will attempt to print a string-based description of the table.
_G[game.Workspace.SomeScript]:Print("Hello World!") -- colon (:)

--Operates correctly
_G[game.Workspace.SomeScript].Print("Hello World!") -- period (.)

Global functions CANNOT be called from a local script, such as a HopperBin. I may cover this in a later part of the tutorial.