As someone who started on JavaScript but switched over to Lua for Roblox development, I find it very fun to “Javascriptify” scripts when possible just for the sake of it. Recently, I’ve ported over a few system from JavaScript to Lua (currently done 2 but plan to do more in the future) while trying as much as possible to maintain it’s JavaScript "feel". This includes making use of modified tables so I can have my arrays start at 0 instead of 1, making use of "this" instead of self, and a few more.
Note: My sudden motive to do this was heavily by “The Coding Train” on YouTube which I highly recommend checking out if you’re into JavaScript based coding challenges https://www.youtube.com/c/TheCodingTrain
- Maze Generator
Hierarchy
“CellController” *(Script)*>“Cell” *(Module Script)>“Wall” *(Module Script)*
Demonstration
Shortened Version:
Full Version: (Probably not gonna be clicked cause of “Don’t click random links” but it’s the full mp4 version if you want to see it!)
https://mail.google.com/mail/u/0?ui=2&ik=99c9e41bab&attid=0.1&permmsgid=msg-a:r-1993163550845465856&th=183c2ef438c7f340&view=att&disp=safe&realattid=f_l931q76f0
Source!
- CellController:
local Cell = require(script:WaitForChild("Cell"))
task.wait(10)
Cell:Setup()
task.wait(5)
warn("Initializing")
task.wait(1)
warn("Simulation beginning!")
Cell:Draw()
- Cell:
--Type
type Type_Cell = {i:number,j:number,walls:{boolean},visited:boolean,Base:Part, WallObjs:{}}
type Type_Grid = {Type_Cell}
--Math funcs
local floor,ceil,random = math.floor,math.ceil,math.random
--Module init
local Cell = {}
Cell.__index = Cell
--Setup/settings
local CanvasSize = 100
local cols,rows
local w = CanvasSize/30
local originOffset:Vector3
local grid:Type_Grid = {}
local partHeight = 1
local partNamePrefix = "Cell "
local order = {"Top", "Right", "Bottom", "Left"}
local Colors = {
White = Color3.new(1,1,1),
Black = Color3.new(0,0,0),
Red = Color3.new(1,0,0),
Green = Color3.new(0,1,0),
Blue = Color3.new(0,0,1),
Cyan = Color3.new(0,1,1),
}
--Status vars
local current:Type_Cell
local stack = {}
--Resources
local Wall = require(script:WaitForChild("Wall"))
--Indexing important local funcs
local Connect,Visualize = Wall.Connect, Wall.Visualize
--Local fundamental funcs
local function GetLength(tab:{})
local zero = tab[0]
return zero and #tab+1 or 0
end
local function Insert(tab:{}, ...:any)
local packed = table.pack(...)
for _,obj in ipairs(packed) do
tab[GetLength(tab)] = obj
end
return tab
end
local function CreateTab(tab:{})
return Insert({}, table.unpack(tab))
end
local function Iterate(tab:{}, callback:(i:number, v:any)->())
local tabAmount = #tab
for i = 0, tabAmount do
callback(i, tab[i])
end
end
local function Index(i:number,j:number)
if i < 0 or j < 0 or i > cols- 1 or j > rows-1 then return -1 end
return i + j*cols
end
local function rect(x:number,y:number,width:number,height:number, index:string)
local part = Instance.new("Part")
part.Name = partNamePrefix..index
part.Anchored = true
local partCfr = part.CFrame
local partLookVec = partCfr.LookVector
part.Size = Vector3.new(width, partHeight, height)
local partSize = part.Size
local sizeX = partSize.X
local sizeY = partSize.Y
local sizeZ = partSize.Z
part.Position = Vector3.new(x, sizeY/2, y) + originOffset
partCfr = part.CFrame
partLookVec = partCfr.LookVector
local TopLeft = partCfr:PointToWorldSpace(Vector3.new(-sizeX/2,sizeY/2,-sizeZ/2))
local TopRight = partCfr:PointToWorldSpace(Vector3.new(sizeX/2,sizeY/2,-sizeZ/2))
local BottomRight = partCfr:PointToWorldSpace(Vector3.new(sizeX/2,sizeY/2,sizeZ/2))
local BottomLeft = partCfr:PointToWorldSpace(Vector3.new(-sizeX/2,sizeY/2,sizeZ/2))
local partProperties = {
Color = Colors.White,
}
local Top = Wall:New(TopLeft, TopRight, part, nil, {Name = "Top"})
local Right = Wall:New(TopRight, BottomRight, part, nil, {Name = "Right"})
local Bottom = Wall:New(BottomRight, BottomLeft, part, nil, {Name = "Bottom"})
local Left = Wall:New(BottomLeft, TopLeft, part, nil, {Name = "Left"})
local WallObjs = CreateTab({Top, Right, Bottom, Left})
part.Parent = workspace.Board
for i,v in pairs(partProperties) do
part[i] = v
end
return part,WallObjs
end
local function RemoveWalls(a:Type_Cell, b:Type_Cell)
local x = a.i - b.i
if x == 1 then
a.walls[3] = false
b.walls[1] = false
elseif x == -1 then
a.walls[1] = false
b.walls[3] = false
end
local y = a.j - b.j
if y == 1 then
a.walls[0] = false
b.walls[2] = false
elseif y == -1 then
a.walls[2] = false
b.walls[0] = false
end
end
--Fundamental module methods
function Cell:Setup()
cols = floor(CanvasSize/w)
rows = floor(CanvasSize/w)
originOffset = Vector3.new(-(w*cols /2), 0, -(w*rows /2))
for j = 0, rows-1 do
for i = 0, cols-1 do
local cell = Cell:New(i,j, i..","..j)
Insert(grid, cell)
end
task.wait()
end
current = grid[0]
end
local loops = 0
local waitInterval = 20
function Cell:Draw()
while true do
loops = (loops+1)%waitInterval
if loops == 0 then
task.wait()
end
Iterate(grid, function(i,v)
grid[i]:Show()
end)
current.visited = true
current:Highlight()
--STEP 1
local nextCell:Type_Cell? = current:CheckNeighbors()
if nextCell then
nextCell.visited = true
--STEP 2
table.insert(stack, current)
--STEP 3
RemoveWalls(current, nextCell)
--STEP 4
current = nextCell
elseif #stack > 0 then
local stackAmount = #stack
current = stack[stackAmount]
table.remove(stack, stackAmount)
else
--Iterate(grid, function(i,v)
-- grid[i].Base:Destroy()
--end)
--grid = {}
--Cell:Setup()
break
end
end
end
--Practical module funcs
function Cell:New(i,j, index:string?)
local cell = {}::Type_Cell
cell.i = i
cell.j = j
cell.walls = CreateTab({true,true,true,true})
cell.visited = false
if i == cols-1 and j == rows-1 then
warn("END!")
cell.walls[2] = false
elseif i == 0 and j == 0 then
warn("START!")
cell.walls[0] = false
end
local x = cell.i*w + (w/2)
local y = cell.j*w + (w/2)
cell.Base,cell.WallObjs = rect(x,y,w,w, index or "")
return setmetatable(cell, Cell)
end
function Cell:Show()
local self:Type_Cell = self
local walls = self.walls
local WallObjs = self.WallObjs
local Base = self.Base
self:UpdateWalls()
if self.visited then
Base.Color = Colors.Cyan
else
Base.Color = Colors.White
end
end
function Cell:UpdateWalls()
local self:Type_Cell = self
local walls = self.walls
local WallObjs = self.WallObjs
Iterate(walls, function(i,v)
WallObjs[i]:SetVisibility(v)
end)
end
function Cell:CheckNeighbors()
local self:Type_Cell = self
local Neighbors = {}
local i,j = self.i,self.j
local Candidates:{[string]: Type_Cell} = {
Top = grid[Index(i, j-1)],
Right = grid[Index(i+1, j)],
Bottom = grid[Index(i, j+1)],
Left = grid[Index(i-1, j)]
}
for _,v in ipairs(order) do
local Candidate = Candidates[v]
if Candidate and not Candidate.visited then
table.insert(Neighbors, Candidate)
end
end
local length = #Neighbors
if length > 0 then
local r = random(1, length)
return Neighbors[r]
else
return
end
end
function Cell:Highlight()
local self:Type_Cell = self
self.Base.Color = Colors.Red
end
return Cell
- Wall:
--Types
type Type_WallObj = {Obj:Part, Visible:boolean}
--Module init
local Wall = {}
Wall.__index = Wall
--Visibility funcs
--Local funcs
local function Connect(Point1:Vector3,Point2:Vector3, Properties:{[string]:any}):Part
local p = Instance.new("Part")
p.Anchored = true
local size = 1
local Dist = (Point1-Point2).Magnitude
p.Size = Vector3.new(size,size, Dist)
p.CFrame = CFrame.new(Point1, Point2) * CFrame.new(0, 0, -Dist / 2)
if Properties then
for i,v in pairs(Properties) do
p[i] = v
end
end
return p
end
local function Visualize(pos:Vector3, Properties:{[string]:any})
local p = Instance.new("Part")
p.Shape = Enum.PartType.Ball
p.Anchored = true
local size = 1
p.Size = Vector3.one * size
p.Position = pos
p.Parent = workspace.Visuals
if Properties then
for i,v in pairs(Properties) do
p[i] = v
end
end
return p
end
--Connection local funcs to the module
Wall.Connect = Connect
Wall.Visualize = Visualize
--Main module funcs
function Wall:New(Point1:Vector3,Point2:Vector3, part:Part, Show:boolean?, ExtraProperties:{[string]:any})
local WallProperties = {
Parent = part,
Color = Color3.fromRGB(255, 255, 0),
}
local wall:Type_WallObj = setmetatable({}, Wall)
local Obj = Connect(Point1, Point2, WallProperties)
wall.Obj = Obj
if ExtraProperties then
for i,v in pairs(ExtraProperties) do
Obj[i] = v
end
end
wall.Visible = true
if not Show then
wall:Hide()
end
return wall
end
function Wall:Show()
local self:Type_WallObj = self
self.Visible = true
self.Obj.Transparency = 0.7
end
function Wall:Hide()
local self:Type_WallObj = self
self.Visible = false
self.Obj.Transparency = 1
end
function Wall:SetVisibility(status:boolean)
if status then
self:Show()
else
self:Hide()
end
end
return Wall
- Firework System
Hierarchy
ReplicatedStorage> “Particle” *(Module Script)*
ServerScriptService> “Handler” *(Script)*
Demonstration
Source!
- Handler:
--Math funcs
local floor,ceil,random,cos,sin,round,rad,deg = math.floor,math.ceil,math.random,math.cos,math.sin,math.round,math.rad,math.deg
--Services
local rep = game:GetService("ReplicatedStorage")
--
local Particle = require(rep:WaitForChild("Particle"))
--script env settings
local framerate = 60
local gravity = Vector2.new(0, 0.8)
local particlesAmount = 25
--vars
type Type_Firework = {firework:Particle.Type_Particle,exploded:boolean,particles:{Particle.Type_Particle}, hue:number}
local FIREWORKS = {}
FIREWORKS.__index = FIREWORKS
local fireworks:{Type_Firework} = setmetatable({}, FIREWORKS)
do
function FIREWORKS:New()
local this:Type_Firework = setmetatable({}, FIREWORKS)
local width = Particle.width
local height = Particle.height
this.hue = random(255)
this.firework = Particle:New(random(0, width), height/2, this.hue, true)
this.exploded = false
this.particles = {}
return this
end
function FIREWORKS:Update()
local this:Type_Firework = self
if not this.exploded then
this.firework:ApplyForce(gravity)
this.firework:Update()
if this.firework.vel.Y >= 0 then
this.exploded = true
this:Explode()
end
end
for i = #this.particles, 1, -1 do
this.particles[i]:ApplyForce(gravity)
this.particles[i]:Update()
if this.particles[i]:Done() then
this.particles[i]:Destroy()
table.remove(this.particles, i)
end
end
end
function FIREWORKS:Done()
local this:Type_Firework = self
return this.exploded and #this.particles == 0
end
function FIREWORKS:Explode()
local this:Type_Firework = self
for i = 1, particlesAmount do
local p = Particle:New(this.firework.pos.X, this.firework.pos.Y, this.hue)
table.insert(this.particles, p)
end
end
function FIREWORKS:Show()
local this:Type_Firework = self
this.firework:SetVisibility(not this.exploded)
this.firework:Show()
for i = 1, #this.particles do
this.particles[i]:Show()
end
end
end
--Fundamental funcs
local function Setup()
Particle:Setup()
local width = Particle.width
local height = Particle.height
end
local function Draw()
if random() < 0.3 then
table.insert(fireworks, fireworks:New())
end
for i = #fireworks, 1, -1 do
fireworks[i]:Update()
fireworks[i]:Show()
if fireworks[i]:Done() then
fireworks[i].firework:Destroy()
table.remove(fireworks, i)
end
end
end
--
Setup()
while true do
task.wait(1/framerate)
Draw()
end
- Particle:
--Types
export type Type_Particle = {pos:Vector2, vel:Vector2, acc:Vector2, obj:Part, firework:boolean?, lifespan:number, hue:number}
--Math funcs
local floor,ceil,random,cos,sin,round,rad,deg = math.floor,math.ceil,math.random,math.cos,math.sin,math.round,math.rad,math.deg
--Module init
local Particle = {}
Particle.__index = Particle
--Settings
local Canvas:Part
local CanvasSize = Vector2.new(200,200)
local width:number
local height:number
local offset:Vector2
local offset3D:Vector3
local Parts = {}
local PartsPos = Vector3.new(0,-100,0)
--Obj settings
local fireworkSize = 4
local particleSize = 1.5
--Resources
local rep = game:GetService("ReplicatedStorage")
--local funcs
local function Random2D()
local angle = random(1,360)
return Vector2.new(cos(angle), sin(angle))
end
local function AddPart()
local p = Instance.new("Part")
p.CanCollide = false
p.CanQuery = false
p.CastShadow = false
p.Anchored = true
p.Material = Enum.Material.Neon
p.Shape = Enum.PartType.Ball
p.Position = PartsPos
p.Parent = workspace.Visuals
table.insert(Parts, p)
end
local function GetPart()
if #Parts == 0 then
AddPart()
end
local p = Parts[#Parts]
table.remove(Parts, #Parts)
return p
end
local function ReturnPart(p:Part)
p.Position = PartsPos
table.insert(Parts, p)
end
--Fundamental methods
function Particle:Setup()
width = CanvasSize.X
height = CanvasSize.Y
offset = Vector2.new(-width/2, -height/2)
offset3D = Vector3.new(offset.x, 0, offset.Y)
Particle.width = width
Particle.height = height
Particle.offset = offset
Particle.offset3D = offset3D
Canvas = Instance.new("Part")
Canvas.Anchored = true
Canvas.CanCollide = false
Canvas.Name = "Canvas"
Canvas.Size = Vector3.new(width, 1, height)
Canvas.Position = Vector3.new(0, -Canvas.Size.Y/2 + 0.1, 0)
Canvas.Color = Color3.new(0,0,0)
Canvas.Parent = workspace
for i = 1, 1000 do
AddPart()
end
end
function Particle:New(x:number,y:number, hue:number, firework:boolean?)
local this:Type_Particle = setmetatable({}, Particle)
this.pos = Vector2.new(x,y)
this.firework = firework
this.lifespan = 0
this.hue = hue
if this.firework then
this.vel = Vector2.new(0,random(-16,-8))
else
this.vel = Random2D() * random(2, 10)
end
this.acc = Vector2.new(0,0)
local obj = GetPart()
if this.firework then
obj.Size = Vector3.one * fireworkSize
else
obj.Size = Vector3.one * particleSize
end
obj.Color = Color3.fromHSV(this.hue/255, 1, 1)
this.obj = obj
this:Show()
return this
end
--Practical methods
function Particle:ApplyForce(force:Vector2)
local this:Type_Particle = self
this.acc += force
end
function Particle:Update()
local this:Type_Particle = self
if not this.firework then
this.vel *= 0.95
this.lifespan += 0.025
end
this.vel += this.acc
this.pos += this.vel / 2.5
this.acc *= 0
end
function Particle:Show()
local this:Type_Particle = self
local obj = this.obj
local objSize = obj.Size
local sizeY = objSize.Y
if not this.firework then
obj.Transparency = this.lifespan
end
obj.Position = Vector3.new(this.pos.X, sizeY/2, this.pos.Y) + offset3D
end
function Particle:Destroy()
local this:Type_Particle = self
ReturnPart(this.obj)
end
function Particle:Done()
local this:Type_Particle = self
return this.lifespan >= 1
end
function Particle:SetVisibility(status:boolean?)
local this:Type_Particle = self
local obj = this.obj
if status then
obj.Transparency = 0
else
obj.Transparency = 1
end
end
return Particle
Source Files:
-
Maze Generator:
Cell Implementation.rbxl (41.7 KB) -
Firework System:
Fireworks implementation.rbxl (39.6 KB)
What is the point of this you might ask? Well I’m hoping someone can feel inspired to create systems similar to this and share it or simply take some of the code and make it more awesome!
Edit: It seems that there isn’t any JavaScript “feel” in the first example but I hope the second one does to you