Good Scripting Practices

Hey guys, so this post is mainly an attempt at improving my overall scripting abilities by applying the best practices. I also hope this post could be used by others who have similar questions to mine.

For Loops

I’ve seen people write for loops in several different ways and am unsure of which of these is the best approach.
for i, v in pairs(model:GetChildren()) do

for _, v in pairs(model:GetChildren()) do

for i, v in (model:GetChildren()) do (I think this one may error the code but am unsure)

Could someone please explain the differences between these and why one of them might be better than the other.

Random Events

I often find myself writing code with the following lines in it:

local chance = math.random(1,2)
    if chance == 1 then
        print("Do Event 1")
    else
        print("Do Event 2")

Is there a more efficient way of doing this? I’ve sometimes had upwards of 15 events and it becomes very tedious to type all of the code out.

Connections

I have never understood connections and still don’t. It seems as though certain lines of code need to be “disconnected” or it could cause issues. I don’t understand this concept though and honestly have no idea how to do it. And how do I know what events need to be disconnected? Like does the .Touched() event need to be disconnected every time I use it or is it only certain events?

Naming Conventions

I’ve never been entirely sure which naming conventions are the most used by professionals.

Functions:

local function BuyItem()
local function buyItem()
local function buy_item()

Variables:

local killPart = script.Parent
local KillPart = script.Parent
local kill_part = script.Parent

Services:

local ReplicatedStorage = ...
local replicatedStorage = ...
local replicated_storage = ...
UpValues/Constants

I’m honestly kind of clueless as to what these are. I’ve heard a couple developers mention them though. Are they something I need to take into account when writing code?

Remotes

I use RemoteEvents anytime I need to communicate between the client and the server, but am aware there is another instance called a “RemoteFunction.” Can someone explain what the difference is and list a couple examples where a RemoteFunction should be utilized instead of a RemoteEvent?

That’s basically all of my questions regarding basic scripting principles. Any help/feedback is much appreciated! And I apologize in advance if some of my sentences are poorly conveyed/written. It’s very late at night and I’m quite exhausted.

7 Likes

For the loops, I’m fairly sure the first and second examples you gave are the same. i or _ being the number and v being the value in the table. I’d recommend using either. Not sure about the third example.

For random events, that’s how I do it as well, you might be able to cram the random in the if statement itself but I’ve never tried.

For connections, that is for events to disconnect what they do basically, I personally don’t disconnect my events much.

For the naming, it’s a personal preference thing. I just name my stuff with like “ExampleVariable” or something. Using an underscore to separate the words is an alternative to a space.

Never heard of UpValues/Constants, also never have used them unless I know them under a different name. (Looked it up and it’s to do with exploiting)

RemoteFunctions are to fire functions from other scripts, and return whatever value that function gives I think. I don’t use them as I prefer modulescripts.

I might be wrong about some of this, but yeah that’s just what I know.

3 Likes

This is just some simple explanation:
for the first one, you use that when both i and v values are important (for example, you plan on both using the v value to directly reference and item, and i to reference the item in the array which is model:GetChildren().
for the 2nd, its when you dont need the i value, since you dont need its location in the array, and only the reference to the item itself.
(ive never seen the 3rd one, im preeeetty sure that one should error)

2 Likes

It won’t error and is probably the best out of the three.

Edit:

UpValues are not exclusive to exploiting. They’re a bit hard to explain, so I suggest just reading this page from the Lua website.

Constants are exactly what their name suggests: variables which do not change. Luau does some magic behind the scenes to give some performance boosts to constant variables if I remember correctly.

  1. It doesn’t matter much in this case, but it’s usually better to use Random, since it’s more flexible.
  2. For larger amounts of events, is probably better just to have a table do the work, something like
-- you could use anonymous functions
-- but I find this slightly cleaner
function event1()
    ...
end
function event2()
    ...
end
local events = {
    event1, event2
}
...
-- choosing random event
events[math.random(1, #events)()
...
3 Likes

For Loops
the variable _ is generally used as a “ghost variable”, basically, if you do not need to use the index variable, you can mark it as _ to indicate that it isn’t used. I think if you use type checking, it will even put a warning if you do actually use _ lower down in the script
As for the third option, Roblox removed the need to use an iterator function (pairs, ipairs, etc). So that would probably be the “best practice”, however, I don’t like it (and ipairs is still slightly faster, not that it makes a big difference though)
In the example you provided, ipairs should be used instead of pairs, because GetChildren() returns an array. Using pairs is still fine though, it can iterate over any table, this is just some nitpicking

Random Events
One thing I like doing, is using tables for that kind of stuff, it would look something like this

local Events = {
	function(a,b,c)
		print("Event 1")
	end,
	
	function(a,b,c)
		print("Event 2")
	end,
	
	function(a,b,c)
		print("Event 3")
	end,
}

local RandomIndex = math.random(#Events)
Events[RandomIndex](a,b,c) -- This will run the function at RandomIndex

You could store values instead of functions in the table, which could be a better solution if the random elements are similar to each other

Connections
Reading the documentation really helps with understanding events in my opinion

RBXScriptSignal
These are your events, so BasePart.Touched is a RBXScriptSignal, and when you connect to it, you get a RBXScriptConnection


The RBXScriptConnection is the object you use to disable the event

I’m pretty sure when an object is destroyed, its events are also cleaned up for you. If you have an event still connected, you should notice it though, as the connected functions will still run when the event is fired.
Bastically, use Disconnect() when you want to stop the connected function from running when the event is fired, and just make sure that there are no hanging connections

Naming Conventions
I am pretty sure it’s
camelCase - normal variables
PascalCase - functions & services
LOUD_SNAKE_CASE - constants

Idk when the underscore is used. I don’t really follow these conventions lol, I usually use PascalCase and LOUD_SNAKE_CASE

UpValues/Constants
No idea. Constants in other languages are variables that cannot be modified, however, in lua, variables do not have a fixed type
I don’t really know what you a referring to

Remotes
So, remote events are for one way communication, the client sends data to the server, or the server sends data to the client. RemoteFunctions on the other end “request” data, they will/can send data, but it expects a response. You could make it send back nil, but I am not sure why you would do that?
I usually use RemoteEvents when I want the client the get some data from the server, from a method that can only be used on the server, or just getting data on the server, so for example, a GetCapes remote the client fires, and then receives the information. If you want the client to decide when it wants to get that data, then RemoteFunctions are a good idea.
Do not used RemoteFunctions from the server (server → client → server) because RemoteFunctions yeild until data is sent back. If you have an exploiter, he can decide to just never send data back, making the server yeild forever

2 Likes

Nope, it wouldn’t error. This is actually the way I write for loops. Its actually quite newish

If you don’t want to use the event anymore you should disconnect it.

When you want to return something.

2 Likes

Adding on to what everyone else said,

For Loops

The third one uses Luau’s generalised iteration. In most cases, you’ll want to use the third one, since it’s a bit cleaner yet does the same thing. You also don’t need the parenthesis around it

Naming Conventions

It’s really up to you how to name your variables. I would recommend following Roblox’s style guide though
Roblox Lua Style guide

UpValues/Constants

No, they don’t really matter when writing code. An upvalue is a variable that comes from outside the function it’s used in

local a = 1 -- This is a variable

local function add()
    local b = 2 -- This is also a variable
    print(a + b) -- `a` is used here, but it comes from outside the function, so it's an upvalue
end

A constant is a value that doesn’t change, like strings or numbers

print("Hello, World!") -- The constant here is the string "Hello, World!"
Remotes

A RemoteEvent tells the server something. A RemoteFunction asks the server for something, and the server gives something back. And vice versa - the server can tell the client something, or the server can ask the client for something, and the client sends something back.

For example, if you had a shop, you’d use RemoteFunctions to purchase items. The client asks to buy an item, then the server will respond with whether or not the purchase was successful (e.g., the player might not have enough coins, so the purchase failed). It’s important to check everything the client sends to make sure it’s valid.

Roblox provides great documentation for a lot of things like this, you should give this article a read for more about remotes
https://create.roblox.com/docs/en-us/scripting/events/remote

3 Likes

_ is a placeholder, using the first index or not depends on if you need the index or key of the table element. Always name them meaningful things (except i in most cases). The third one works. It’s actually faster than using pairs() or ipairs(), usually if you don’t need ordered access it’s nice to use. Correct me if I’m wrong.

For random events, store them in tables or arrays. For example:

local RandomEventTable = {
    "Do event1",
    "Do event2",
    "Do whatever" -- I assume you want to print strings, you can store functions or whatever as elements.
}

print(RandomEventTable[math.random(1,#RandomEventTable)])

will be the same as you did.

Always use events over while loops! Events are like a signal that will be fired when something happens. For example .Touched will fire when the part associated with it is touched. There are various methods, :Connect() will connect a function to the event and will run it every time the event is fired. For example:

local part = workspace:WaitForChild("TestPart")

part.Touched:Connect(function(hit)
    if hit == workspace.Baseplate then return end
    print("You touched meee")
end
--Do note that some events can parse something into the function

will print You Touched Meeee every time the part is touched by other parts excluding the Baseplate. hit is parsed as the part which touched the part.
There’s also :Once() which unlike connect will only fire once after the connection if the event is fired.

Yes, you will need to disconnect them in cases or the connection will exist forever. To do so you can do:

local connection = part.Touched:Connect(function()
    print("Whatsoever")
end

task.wait(5)
connection:Disconnect()

I would recommend checking out cleanup modules Maids like Janitor to simplify this though.

For naming, I recommend PascalCase, which is just capitalizing the first letter of every word. For example: HumanoidRootPart.

Honestly it’s quite rare we look into them.
I myself after developing for a long time haven’t dealt with them at all and is totally fine. You may search online for videos about them though.

Remote events are one-way communications, remote functions are different, you invoke the remote function on the client for example, the server will catch this invoke if you connected it but different from events, this will yield the local script and wait for a callback from the server, basically after the script finished running on server and may/may not return a value depending on you. This also works for server-client similarly. But this enables exploit vulnerabilities and I usually prefer using remote events to replicate the behaviour of them, mainly because you don’t know if a hacker will return non-sense values from a server-client remote function and break your game.

2 Likes

Thanks for all of your feedback, it has been extremely insightful. I hope others can learn just as much as I did about these topics by viewing all of your replies! :slight_smile:

4 Likes

I swear i’ve used something like that and it errored before lol
idk might’ve been something else
(so then whats the point of pairs lol)

1 Like

Oh, and one thing I forgot to mention in my post:

What is the difference between placing scripts in StarterPlayerScripts and StarterCharacterScripts? (When should you use one or the other)

And what is the difference between ReplicatedStorage and ServerStorage? (When should you use one or the other)

1 Like

I simply write my code like javascript :moyai:

local Humanoid = Character:FindFirstChildWhichIsA("Humanoid");

if (Humanoid.Health > 0) then
    print("Alive!!");
end
4 Likes