The Power of 10: Implement Nasa Rules to Your Roblox Studio Development Workflow
Unlock the secrets to better coding in Roblox Studio with insights inspired by NASA's 10 programming rules.
Introduction
In Roblox Studio, writing a clean, efficient, and maintainable code is crucial. NASA follows 10 programming rules they have developed, originally designed for high-stakes aerospace software, that can be adapted to enhance your coding practices in Roblox Studio using Luau scripting language. In this 10 steps long blog tutorial, you will find out how you can implement these rules in your Roblox projects, along with alternatives where necessary.
1. Avoid Complex Flow Constructs
Avoid looping functions at all cost unless there is no other alternative, as they can lead to stack overflow errors and make your code harder to manage. Instead, stick to simple control structures like for, while, and repeat for looping, which provide clearer logic and better performance in your scripts.
--Limit this:
function SpawnNpc(plr: Player)
local Npc = game.ReplicatedStorage.NPC:Clone()
Npc.Parent = workspace
task.wait(1) --> correct way to use "wait" as it's not deprecated
plr.leaderstats.EnemyCount.Value += 1
SpawnNpc(plr) -->This run the function again
end
SpawnNpc(game.Players:GetPlayers()[1])
--Use this:
while true do
local Npc = game.ReplicatedStorage.NPC:Clone()
Npc.Parent = workspace
task.wait(1) --> correct way to use "wait" as it's not deprecated
plr.leaderstats.EnemyCount.Value += 1
end
2. Set a Loop Limit
When writing loops in Roblox Luau, it's important to ensure that they have defined boundaries. This means specifying the exact number of iterations or providing a clear condition for when the loop should stop. Loops with fixed bounds are easier to debug, and optimize compared to loops with non-limited runs.
You can achieve this by using built-in for and repeat until loops, which have defined limits by default. Occasionally, you can also use a while loop combined with a break statement to stop the loop when a certain condition is met. This approach ensures that your code remains predictable and manageable.
--> This will run 10 times.
for i = 1, 10 do
print(i)
end
--> This will run continuously for 2 seconds.
local CurrentTime = os.clock()
local cooldown = 2
repeat
print(CurrentTime)
task.wait()
until os.clock() - CurrentTime >= cooldown
--> This will run continuously until the random number is 1.
local
while true do
local randomNumber = math.random(1,10)
if randomNumber == 1 then
break
end
task.wait()
end
In some cases, an endless loop may be necessary for certain logic, such as an NPC checking for players, as shown below. Notice that there is still a break statement within an if condition, meaning the loop can end. However, this will only happen when the NPC's health reaches zero. Without the break, the loop could lead to some issues, causing errors and affecting optimization.
Since the code is placed under the NPC model, it will reset and function correctly when the NPC respawns. With that in mind, you can also place the script in ServerScriptService and use functions that respond to NPC death events to manage the workflow more effectively. This approach allows for a more structured and efficient handling of NPC behavior without relying solely on endless loops.
while true do
--> Check if the NPC's health is greater than 0
if humanoid.Health <= 0 then
print("NPC is dead. Stopping movement.")
break -- Exit the loop if the NPC's health is 0
end
if targetPlayer and targetPlayer.Character then
local targetPosition = targetPlayer.Character.HumanoidRootPart.Position
--> Create a path to the target position
local path = PathfindingService:CreatePath({
AgentRadius = 2,
AgentHeight = 5,
AgentCanJump = true,
AgentJumpHeight = 10,
AgentMaxSlope = 45,
})
path:ComputeAsync(NPC.Position, targetPosition)
path:MoveTo(NPC)
--> Wait for the path to finish moving
path:MoveToFinished:Wait()
else
print("Target player not found.")
end
task.wait()
end
end
3. Avoid Creating New Memory During the Run
Instead of dynamically creating objects during gameplay, predefine your objects and reuse them. For example, use existing local variables to manage frequently used items, which can improve performance and reduce memory usage.
--> Predefined variables
local _workspace = workspace
local CoinPos = Vector.new(2,5,2)
--> Code ex
local function SpawnCoin(coin)
coin.Position = CoinPos --> CoinPos variable is reused
coin.Parent = _workspace --> _workspace variable is reused
SpawnCoin(_workspace.Coin:Clone())
To avoid dynamic memory allocation, create a pool of objects that you can reuse throughout your game. When an object is no longer needed, return it to the pool instead of destroying it. This reduces the overhead of constantly creating and destroying objects.
local objectPool = {} -- Initializes an empty table to act as the object pool
-- Function to get an object from the pool or create a new one if the pool is empty
function getObject()
if #objectPool > 0 then
return table.remove(objectPool) -- Returns an object from the pool if available
else
return game.ReplicatedStorage.ObjectTemplate:Clone() -- Creates a new object if the pool is empty
end
end
-- Function to release an object back into the pool
function releaseObject(obj)
obj:SetPrimaryPartCFrame(CFrame.new()) -- Resets the object's position
table.insert(objectPool, obj) -- Inserts the object back into the pool
end
4. Shorten Functions
It is advisable to keep your functions concise. As a general rule, functions should be limited to a few lines of code. If a function is getting too long, consider breaking it into smaller helper functions. This improves readability and makes debugging way easier as you can identify where the issue located.
local function createLeaderstats(player)
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = player
return leaderstats
end
local function createScore(leaderstats)
local score = Instance.new("IntValue")
score.Name = "Score"
score.Value = 0
score.Parent = leaderstats
end
local function createHealth(leaderstats)
local health = Instance.new("IntValue")
health.Name = "Health"
health.Value = 100
health.Parent = leaderstats
end
local function handlePlayerJoin(player)
local leaderstats = createLeaderstats(player)
createScore(leaderstats)
createHealth(leaderstats)
print(player.Name .. " has joined the game with 100 health.")
end
5. Use Assertions for Each Function
Each function should have at least two assertions to ensure it is working correctly. Assertions are a powerful tool for ensuring the correctness and stability of your code. Assertions allow you to specify conditions that must be true at certain points in your code. If an assertion fails, it errors and you can identify and fix issues more quickly.
local function calculateRectangleArea(width, height)
assert(type(width) == "number", "Width must be a number")
assert(type(height) == "number", "Height must be a number")
assert(width > 0, "Width must be greater than 0")
assert(height > 0, "Height must be greater than 0")
return width * height
end
calculateRectangleArea(10, 20)
You can achieve the same using error() whenever a condition is met:
local function calculateRectangleArea(width, height)
if type(width) ~= "number" then
error("Width must be a number")
elseif type(height) ~= "number" then
error("Height must be a number")
elseif width <= 0 then
error("Width must be greater than 0")
elseif height <= 0 then
error("Height must be greater than 0")
end
return width * height
end
calculateRectangleArea(10, 20)
6. Limit the scope of your variables
Managing the scope of your variables is crucial for maintaining clean, efficient, and secure code. Use local variables within functions whenever possible to avoid unintended side effects. Limit the usage of global variables and functions to reduce naming conflicts, especially on clients as it may be also exploitable. You can replace global variables and functions with modules allowing you limit access to some scripts.
--// Module Script \\--
local Module = {}
Module.Number = 4
function Module.AddNumber()
Module.Number = Module.Number + 1
end
return Module
-- // Main Server/Local Script \\--
local Module = require(path_to_module) --> access the module
Module.AddNumber() --> run the function from the module
-- use Module.Number to access the Number value from the module
print(Module.Number) --> Print the Number (5)
Keep your sensitive data and API keys server-sided to prevent leaks. If you want to retrieve information from HTTP requests, you can use Remote Functions on the server side to return it back to the client.
--// Server Sided Script \\--
local API_Key = "B34vaSW2QFZasGFf127"
local Url = "https://marketplace.roblox.com/api/v1/" .. API_Key .. "/" -- Use .. for string concatenation
local HttpService = game:GetService("HttpService")
local remoteFunction = game.ReplicatedStorage:WaitForChild("RemoteFunction")
local function GetData(plr, item)
local response = HttpService:PostAsync(Url .. item) -- Use PostAsync to send a POST request
return response -- Return the response to the client
end
remoteFunction.OnServerInvoke = GetData
--// Client Sided Script \\--
local remoteFunction = game.ReplicatedStorage:WaitForChild("RemoteFunction")
local function RequestData(item)
local response = remoteFunction:InvokeServer(item) -- Invoke the server function and get the response
print("Response from server: " .. response) -- Handle the response
end
RequestData("hat")
7. Check Returned Values
Always check the return values of functions and methods to ensure that your code is handling errors and unexpected situations correctly. Ignoring return values can lead to bugs and issues in your game.
local function GetCharacter(plr : Player)
if plr.Character then
return plr.Character
end
return plr.Character or plr.CharacterAdded:Wait()
end
local Character = GetCharacter(plr) --> Get the returned value
if Character then --> Check if the return value isn't nil
print(Character)
else
warn("Player's character didn't load")
end
You can also use pcall functions as catch any error that may occur, returning the success status and the result of the function.
local success, response = pcall(function()
return HttpService:GetAsync("https://api.example.com/data")
end)
if success then
-- Process the response
local data = HttpService:JSONDecode(response)
else
-- Handle the error
warn("Error fetching data:", response)
end
8. Limit Data Usage
Consider using tables to drive your program's behavior. By storing data in tables and using functions to process that data, you can write more efficient, readable, and maintainable code.
-- Instead of...
local function generateFunctions()
local functions = {}
for i = 1, 5 do
local function func()
return i * 2
end
table.insert(functions, func)
end
return functions
end
-- ...consider...
local data = {
{ x = 1 },
{ x = 2 },
{ x = 3 },
{ x = 4 },
{ x = 5 }
}
local function process(data)
return data.x * 2
end
9. Limit the Use of Non-Function Specified Variables
Limit the use of variables that are not encapsulated within functions. This helps improve code organization, reduces the risk of naming conflicts, and enhances maintainability. By keeping variables local to functions whenever possible, you ensure that their scope is limited. This way, they do not unintentionally affect other parts of your code.
-- Instead of...
food = "chocolate" -- Global variable
local function eat()
print("Player ate " .. food) -- Accessing global variable
end
eat()
-- ...consider...
local function eat(food)
print("Player ate " .. food)
end
eat("chocolate") --> function variable (doesn't affect the rest of the code)
10. Compile with Warnings
Always compile the code with all warning messages turned on. Test your scripts regularly and pay attention to any warnings or errors. Use the output to identify issues and address them promptly. This practice helps maintain code quality and reduces bug risks.
Conclusion
Even though some of these rules may not seem directly relevant to NASA's, applying them to your Roblox development can significantly enhance your coding practices. By applying to these principles, you can create cleaner, more maintainable, and efficient code. These practices foster a better workflow, making it easier for you and your team to collaborate and improve your games.