I have never worked in a big studio, and never seen how other professional programmers code. Is there some untold rule about luau syntax?
I have another script which I do not wish to share, which basically has all of the main functions and handlers which need to interact to the same gui, and I figured the best way is to cram everything into one script and using coroutines to break the big script into smaller ones. Is there a better way without using extra scripts?
This script which I want to be reviewed is open source, and you’re free to check out the full game yourself and download the .rblx file. It is a raytracer, albeit a slow one.
I tried my best to clean the code and adding comments where necessary.
READ ONLY IF YOU HAVE TIME!
-- Services
local RunService = game:GetService("RunService")
-- Variables
local LocalPlayer = game.Players.LocalPlayer
local Character = LocalPlayer.Character or LocalPlayer.CharacterAdded:Wait()
local Camera = workspace.Camera
local MainGUI = LocalPlayer.PlayerGui.Main
local FramesPerSecondUI = MainGUI.FramesPerSecond; local FPSupdateSpeed = 0.2
local Display = Instance.new("Frame", MainGUI)
Display.Size = UDim2.new(0, MainGUI.AbsoluteSize.X, 0, MainGUI.AbsoluteSize.Y)
Display.Transparency = 1
Display.BackgroundColor3 = Color3.fromRGB(70, 70, 70)
Display.ZIndex = 0
Display.Name = "Display"
;
-- Constants
local RayDistance = 500
local PixelScale = script.PixelScale
local Bounces = script.Bounces
local Samples = script.Samples
local Paused = script.Paused
local RenderSignal = script.RenderSignal
local Colors = {
["Button"] = Color3.fromRGB(255, 255, 255),
["Green"] = Color3.fromRGB(160, 255, 160),
["Red"] = Color3.fromRGB(255, 100, 100),
["Yellow"] = Color3.fromRGB(255, 255, 110)
}
-- Functions (Library)
local function Lerp(a, b, t)
return a * (1 - t) + b * t
end
local function Reflect(vector, normal)
local r = vector - 2 * (vector:Dot(normal) * normal)
return r
end
local function RandomValueToNormalDistribution()
local theta = 2 * math.pi * math.random()
local rho = math.sqrt(-2 * math.log(math.random()))
return rho * math.cos(theta)
end
local function RandomDirection()
local x = RandomValueToNormalDistribution()
local y = RandomValueToNormalDistribution()
local z = RandomValueToNormalDistribution()
local length = math.sqrt(x^2 + y^2 + z^2)
return Vector3.new(x/length, y/length, z/length)
end
local function RandomHemisphereDirection(Normal)
local Direction = RandomDirection()
return Direction * math.sign(Normal:Dot(Direction))
end
local function MultiplyColorWithColor(ColorA, ColorB)
return Color3.new(ColorA.R * ColorB.R, ColorA.G * ColorB.G, ColorA.B * ColorB.B)
end
local function MultiplyColorWithNumber(Color, Number)
return Color3.new(Color.R * Number, Color.G * Number, Color.B * Number)
end
local function AddColorWithColor(ColorA, ColorB)
return Color3.new(ColorA.R + ColorB.R, ColorA.G + ColorB.G, ColorA.B + ColorB.B)
end
local function DivideColourWithNumber(Color, Number)
return Color3.new(Color.R / Number, Color.G / Number, Color.B / Number)
end
-- Init
local PixelGrid = {}
local PixelInit = true -- By default, should be false because pixels haven't initialized. Set to true because the initializepixels function will automatically set it to false.
local function InitializePixels()
if PixelInit == true then
PixelInit = false
Display:ClearAllChildren()
for x = 1, math.floor(Display.Size.X.Offset/PixelScale.Value) do
PixelGrid[x] = {}
for y = 1, math.floor(Display.Size.Y.Offset/PixelScale.Value) do
local PixelFrame = Instance.new("Frame", Display)
PixelFrame.Size = UDim2.new(0, PixelScale.Value, 0, PixelScale.Value)
PixelFrame.Position = UDim2.new(0, x * PixelScale.Value - PixelScale.Value, 0, y * PixelScale.Value - PixelScale.Value) -- Since x and y start from 1, subtraction by 1 (* PixelSize) is needed to set the first pixel position to 0 as an offset.
PixelFrame.BackgroundColor3 = Colors.Red
PixelFrame.BorderSizePixel = 0.1
PixelGrid[x][y] = PixelFrame -- Store the pixel frame and coordinates in the grid
end
-- Insert a wait() so potato phone doesn't explode
if x % 2 == 0 then task.wait() end
end
for i, v in Display:GetChildren() do
v.BackgroundColor3 = Colors.Green
end
PixelInit = true
end
end
InitializePixels()
PixelScale:GetPropertyChangedSignal("Value"):Connect(function()
InitializePixels()
end)
MainGUI:GetPropertyChangedSignal("AbsoluteSize"):Connect(function()
Display.Size = UDim2.new(0, MainGUI.AbsoluteSize.X, 0, MainGUI.AbsoluteSize.Y)
InitializePixels()
end)
-- Functions
local function RenderScene(Slow : boolean?)
if Slow then
Camera.CameraType = Enum.CameraType.Scriptable
Character.HumanoidRootPart.Anchored = true
end
--Run for every pixel in the display (the for loop(s))
for x = 1, math.floor(Display.Size.X.Offset/PixelScale.Value) do
for y = 1, math.floor(Display.Size.Y.Offset/PixelScale.Value) do
local PixelFrame = PixelGrid[x][y]
-- Get the pixel coordinates
local PixelX = PixelFrame.Position.X.Offset
local PixelY = PixelFrame.Position.Y.Offset
local UnitRay = Camera:ScreenPointToRay(PixelX, PixelY) -- Where all the magic happens
-- Ray Calculation
local function Trace()
-- Convert origin and direction to vector3s so we can edit them (UnitRay.Origin is immutable. UnitRayOrigin is mutable)
local UnitRayOrigin = Vector3.new(UnitRay.Origin.X, UnitRay.Origin.Y, UnitRay.Origin.Z)
local UnitRayDirection = Vector3.new(UnitRay.Direction.X, UnitRay.Direction.Y, UnitRay.Direction.Z)
local IncomingLight = 0.5 -- Default is 0 and light the scene by default with objects. That would take too long though
local RayColor = Color3.new(1, 1, 1) -- Default skybox colour
for i = 0, Bounces.Value do
local RayResult = workspace:Raycast(UnitRayOrigin, UnitRayDirection * RayDistance)
if RayResult then
local DiffusedDirection = (RayResult.Normal + RandomDirection()).Unit
local SpecularDirection = Reflect(UnitRayDirection, RayResult.Normal)
UnitRayDirection = Lerp(DiffusedDirection, SpecularDirection, RayResult.Instance.Reflectance)
UnitRayOrigin = RayResult.Position
if RayResult.Instance:FindFirstChild("Emission") then
IncomingLight += RayResult.Instance.Emission.Value
end
RayColor = MultiplyColorWithColor(RayResult.Instance.Color, RayColor)
else
break
end
end
return MultiplyColorWithNumber(RayColor, IncomingLight)
end
local TotalColours = Color3.new(0, 0, 0)
local AmountOfSamplesDone = 0
for i = 1, Samples.Value do
AmountOfSamplesDone += 1
local TracedColour = Trace()
TotalColours = AddColorWithColor(TotalColours, TracedColour)
end
PixelFrame.BackgroundColor3 = DivideColourWithNumber(TotalColours, AmountOfSamplesDone)
end
if Slow then task.wait() end -- Halt every coloumn. So roblox doesn't try rendering everything in a single frame and take too long and crash
end
Camera.CameraType = Enum.CameraType.Custom
Character.HumanoidRootPart.Anchored = false
end
local deltaTimeSamples = {}
RunService.RenderStepped:Connect(function(deltaTime)
-- FPS stuff
table.insert(deltaTimeSamples, math.floor(1 / deltaTime))
if #deltaTimeSamples > 20 then
table.remove(deltaTimeSamples, 1)
end
local TotalValues = 0
for i, v in deltaTimeSamples do
TotalValues += v
end
local FPS = math.floor(TotalValues / #deltaTimeSamples)
FramesPerSecondUI.Text = FPS
if (FPS >= 0) and (FPS < 15) then
FramesPerSecondUI.TextColor3 = Colors.Red
elseif (FPS >= 15) and (FPS < 30) then
FramesPerSecondUI.TextColor3 = Colors.Yellow
elseif (FPS >= 30) then
FramesPerSecondUI.TextColor3 = Colors.Green
end
-- Render!
if not Paused.Value then
RenderScene()
end
end)
RenderSignal:GetPropertyChangedSignal("Value"):Connect(function()
if Paused.Value == true then
RenderScene(true)
end
end)
The game link and the rblx file:
RobloxRaytracing [13].rbxl (88.5 KB)