Yesterday I created this Generator which creates infinite landscapes of Candyland!
The landscape is made of cake, cookies, donuts, pie, the trees are lollipops, candy canes, milkshakes and cotton candy, the rocks are made of cookies, pie donuts and hard candy, and the plants are similar nature!
What do you think?
[
]
Check out this code if you’re interested in procedural generation! The following demonstrates how to place assets that do not overlap by using the Fisher-Yates Algorithm and a bunch of other custom noise algorithms I wrote.
local Candyland={}
local RNG=Random.new()
local noise=3
local element=4
local library=game.ServerStorage.DungeonGenerator.Candyland
--libraries
local platforms=library["Candy platform"]
local plants=library["Candy Plants"]
local rubble=library["Candy Rubble"]
local trees=library["Candy Trees"]
--arrays
local plantar=plants:GetChildren()
local rubblear=rubble:GetChildren()
local treear=trees:GetChildren()
local platformar=platforms:GetChildren()
--count
local nplan=#plantar
local ntree=#treear
local nrubb=#rubblear
local nplat=#platformar
local resizelib=require(script.ScaleModule)
local resize=resizelib.Resize
local function nointeract(t)
t.CanTouch=false t.CanCollide=false t.Transparency=1 t.CanQuery=false t.Massless=true t.CastShadow=false t.Anchored=true
end
local function mr(min,max)--mathrandom
return RNG:NextInteger(min,max)
end
function mrnr(min, max,amnt)--mathrandom no repeat returns array
-- Check if the input is valid
if min > max or max - min < 0 then
return nil, "Invalid range"
end
-- Create an array of numbers from min to max
local numbers = {}
for i = min, max do
table.insert(numbers, i)
end
-- Shuffle the array using Fisher-Yates algorithm[^1^][1]
for i = #numbers, 2, -1 do
local j = math.random(i)
numbers[i], numbers[j] = numbers[j], numbers[i]
end
--print(numbers)
-- Return the first 100 elements of the shuffled array
return {table.unpack(numbers, 1, amnt)}
end
function Candyland.New(pos,size)
local f=Instance.new("Model") f.Name="Candyland" local t=Instance.new("Part")
t.Size=size nointeract(t) t.CFrame=CFrame.new(pos)
t.Parent=f f.PrimaryPart=t
local s=Instance.new("Folder") s.Name="Trees" s.Parent=f
local s=Instance.new("Folder") s.Name="Rubble" s.Parent=f
local s=Instance.new("Folder") s.Name="Plants" s.Parent=f
local s=Instance.new("Folder") s.Name="Platforms" s.Parent=f
return f
end
local function standardresize(obj)
return resize(obj,mr(50,200)/100)
end
local elementnoise={}
local function rollelement(dir,numdir,element)
if elementnoise[dir]==nil then
elementnoise[dir]={}
elementnoise[dir][1]=mr(1,numdir)
elementnoise[dir][2]=elementnoise[dir][1]
else
elementnoise[dir][1]=math.max(1,math.min(numdir,elementnoise[dir][1]+ mr(-element,element))) --max value between 1 and the minimum number between the numdir and the noise
if (elementnoise[dir][1]==1 and elementnoise[dir][2]==1) or (elementnoise[dir][1]==numdir and elementnoise[dir][2]==numdir) then
elementnoise[dir][1]=mr(1,numdir)
end
if elementnoise[dir][1]==elementnoise[dir][2] then
return rollelement(dir,numdir,element)
end
end
elementnoise[dir][2]=elementnoise[dir][1]
return elementnoise[dir][1]
end
local function positionnoise(scale,vectors)
if vectors==nil or vectors=="xyz" then
return Vector3.new(mr(-noise*scale,noise*scale),mr((-noise*scale)/2,(noise*scale)/2),mr(-noise*scale,noise*scale))
elseif vectors=="pxyz" then
return Vector3.new(mr(-noise*scale,noise*scale),mr(0,(noise*scale)),mr(-noise*scale,noise*scale))
elseif vectors=="pnxyz" then
return Vector3.new(mr(-noise*scale,noise*scale),mr(0,(noise*scale))/2,mr(-noise*scale,noise*scale))
elseif vectors=="y" then
return Vector3.new(0,mr(-noise*scale,noise*scale),0)
elseif vectors=="xz" then
return Vector3.new(mr(-noise*scale,noise*scale),0,mr(-noise*scale,noise*scale))
elseif vectors==nil or vectors=="nxyz" then
return Vector3.new(mr(-noise*scale,noise*scale),mr((-noise*scale),0),mr(-noise*scale,noise*scale))
elseif vectors=="py" then
return Vector3.new(0,mr(0,noise*scale),0)
elseif vectors=="ny" then
return Vector3.new(0,mr(-noise*scale,0),0)
end
end
local function towardscenter(cf1,position,scale)
local cf2 = CFrame.lookAt(cf1.Position, position)
return cf1:lerp(cf2,scale)
end
local function orientationnoise(scale,vectors)
if vectors==nil or vectors=="xyz" then
return CFrame.Angles(math.rad(mr(-15*scale,15*scale)),math.rad(mr(-90*scale,90*scale)),math.rad(mr(-15*scale,15*scale)))
elseif vectors=="y" then
return CFrame.Angles(0,math.rad(mr(-15*scale,15*scale)),0)
elseif vectors=="xz" then
return CFrame.Angles(math.rad(mr(-15*scale,15*scale)),math.rad(mr(-90*scale,90*scale)),math.rad(mr(-15*scale,15*scale)))
end
end
local function standardpositionsettings(obj,position)
obj=standardresize(obj)
obj.CFrame=(CFrame.new(position+Vector3.new(0,obj.Size.Y/2,0)+positionnoise(1,"pxyz"))*orientationnoise(1,"xyz"))
end
local function placeobj(obj,position)
standardpositionsettings(obj,position)
return obj
end
local function placeplatform(obj,position)
obj=resize(obj,mr(100,150)/100)
obj:SetPrimaryPartCFrame(CFrame.new(position+positionnoise(.5,"py"))*orientationnoise(.25,"xyz"))
--for i,v in obj:GetDescendants() do
-- if v:IsA("BasePart") then v.Anchored=true
-- end
-- end
return obj
end
local function placetree(obj,position)
obj=resize(obj,mr(25,150)/100)
obj:SetPrimaryPartCFrame(CFrame.new(position+positionnoise(2,"pnxyz"))*orientationnoise(1,"xyz"))
--for i,v in obj:GetDescendants() do
-- if v:IsA("BasePart") then v.Anchored=true
-- end
-- end
return obj
end
function Candyland.FormatDirectory(dir)
local ar= workspace.Castle7["Candy platform"]:GetChildren()
local primposition=workspace.Baseplate.Position.Y
local primtemp=Instance.new("Part")
primtemp.Anchored=true
primtemp.Transparency=1
primtemp.Size=Vector3.new(1,1,1)
for i,v in ar do
if v:IsA("BasePart") then
local newprim= primtemp:Clone()
newprim.CFrame=CFrame.new(v.Position.X,primposition,v.Position.Z)
local m=Instance.new("Model")
m.Name=v.Name
newprim.Parent=m
m.PrimaryPart=newprim
m.Parent=v.Parent
v.Parent=m
elseif v:IsA("Model") then
local basepart=v:FindFirstChildOfClass("BasePart")
if basepart==nil then basepart=v:FindFirstChildOfClass("MeshPart") end
if basepart==nil then basepart=v:FindFirstChildOfClass("Part") end
if basepart then
local newprim= primtemp:Clone()
newprim.CFrame=CFrame.new(basepart.Position.X,primposition,basepart.Position.Z)
newprim.Parent=v
v.PrimaryPart=newprim
end
end
end
end
local function occupyspace(candylandmodel,dirname,xcur,ypos,zcur,numarray)
local density=math.floor(mr(10,25)/(mr(2,5)))
local apx=mrnr(-4,4,density)
local apz=mrnr(-4,4,density)
-- local ap=mrnr(-4,4,density)
local currentplots=0
local origin=Vector3.new(xcur,ypos,zcur)
for i,v in apx do
local obj
if dirname=="plants" then obj=plantar[rollelement("plants",nplan,element)]:Clone()
elseif dirname=="rubble" then obj=rubblear[rollelement("rubble",nrubb,element)]:Clone()
elseif dirname=="tree" then obj=treear[rollelement("trees",ntree,element)]:Clone()
end
local z=apz[i]*2
local position=Vector3.new(xcur+v*2,ypos,zcur+z)
if dirname~="tree" then obj=placeobj(obj,position) -- obj.CFrame =towardscenter(obj.CFrame,origin)
else obj=placetree(obj,position) obj:SetPrimaryPartCFrame(towardscenter(obj.PrimaryPart.CFrame,origin,.25))
end
if dirname=="plants" then obj.Parent=workspace.Plants--candylandmodel.Plants
task.delay(3,function()
obj.Anchored=true
end)
elseif dirname=="rubble" then
local p=library.RubbleTemplate:Clone()
task.delay(3,function()
obj.Anchored=true
end)
local d=Instance.new("Motor6D")
d.Part0=p
d.Part1=obj
d.Parent=p p.CFrame=obj.CFrame p.Parent=workspace.Rubble obj.Parent=workspace.Crystals--candylandmodel.Rubble
elseif dirname=="tree" then obj.Parent=workspace.Trees--candylandmodel.Trees
end
end
end
local function getdensity()
return 1--math.floor(mr(10,25)/(mr(2,5)))
end
local function occupyspace2(candylandmodel,xcur,ypos,zcur)
local density=math.floor(mr(10,30)/2)
local apx=mrnr(-8,8,density)
local apz=mrnr(-8,8,density)
local currentplots=0
local origin=Vector3.new(xcur,ypos,zcur)
local itemar={"plants","rubble","tree"}
for i,v in apx do
local dirname=itemar[mr(1,3)]
local obj
if dirname=="plants" then obj=plantar[rollelement("plants",nplan,element)]:Clone()
elseif dirname=="rubble" then obj=rubblear[rollelement("rubble",nrubb,element)]:Clone()
elseif dirname=="tree" then obj=treear[rollelement("trees",ntree,element)]:Clone()
end
local z=apz[i]*2
local position=Vector3.new(xcur+v*2,ypos,zcur+z)
if dirname~="tree" then obj=placeobj(obj,position) -- obj.CFrame =towardscenter(obj.CFrame,origin)
else obj=placetree(obj,position) obj:SetPrimaryPartCFrame(towardscenter(obj.PrimaryPart.CFrame,origin,.25))
end
if dirname=="plants" then obj.Parent=workspace.Plants--candylandmodel.Plants
task.delay(3,function()
obj.Anchored=true
end)
elseif dirname=="rubble" then
local p=library.RubbleTemplate:Clone()
task.delay(3,function()
obj.Anchored=true
end)
local d=Instance.new("Motor6D")
d.Part0=p
d.Part1=obj
d.Parent=p p.CFrame=obj.CFrame p.Parent=workspace.Rubble obj.Parent=workspace.Crystals--candylandmodel.Rubble
elseif dirname=="tree" then obj.Parent=workspace.Trees--candylandmodel.Trees
end
end
end
function Candyland.Generate(size,position,Settings)--increment of 20x20x20 grid of platforms
print("Candyland Started")
local xstart=position.X-size.X/2+10
local zstart=position.Z-size.Z/2+10
local zend=position.Z+size.Z/2-10
local xend=position.X+size.X/2-10
local zcur=zstart
local xcur=xstart
local ypos=position.Y
local candylandmodel=Candyland.New(position,size)
local oypos=ypos
repeat
repeat
local position= Vector3.new(xcur,ypos,zcur)
local obj=platformar[rollelement("platforms",nplat,element)]:Clone() placeplatform(obj,position)
ypos=obj.PrimaryPart.Position.Y
local dif=math.abs(oypos-ypos)
obj.Parent=candylandmodel.Platforms
if dif>=10 then
local obj2=platformar[rollelement("platforms",nplat,element)]:Clone() placeplatform(obj2,position-Vector3.new(0,10,0))
obj2:SetPrimaryPartCFrame(CFrame.new(position-Vector3.new(0,10,0))*orientationnoise(1,"y"))
obj2.Parent=candylandmodel.Platforms
if dif>=20 then
local obj2=platformar[rollelement("platforms",nplat,element)]:Clone()placeplatform(obj2,position-Vector3.new(0,20,0))
obj2:SetPrimaryPartCFrame(CFrame.new(position-Vector3.new(0,20,0))*orientationnoise(1,"y"))
obj2.Parent=candylandmodel.Platforms
if dif>=30 then
local obj2=platformar[rollelement("platforms",nplat,element)]:Clone()placeplatform(obj2,position-Vector3.new(0,30,0))
obj2:SetPrimaryPartCFrame(CFrame.new(position-Vector3.new(0,30,0))*orientationnoise(1,"y"))
obj2.Parent=candylandmodel.Platforms
end
end
end
occupyspace(candylandmodel,xcur,ypos,zcur)
xcur=xcur+20
until xcur>xend+1
xcur=xstart
zcur=zcur+20
until zcur>zend+1
return candylandmodel
end
return Candyland
I like to share code that may be useful to others! This one in particular is a good start for a object orientated generation algorithm.