CluelessDev

Procedural map generation script

Jun 6th, 2022 (edited)
523
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 6.11 KB | None | 0 0
  1. --# <|=============== SERVICES ===============|>
  2. local ReplicatedStorage = game:GetService("ReplicatedStorage")
  3. local CollectionService = game:GetService("CollectionService")
  4.  
  5. --# <|=============== DEPENDENCIES ===============|>
  6. local Knit = require(ReplicatedStorage.Packages.Knit)
  7.  
  8.  
  9. --- <|=============== AUX FUNCTIONS ===============|>
  10. -- Fractal Brownian Motion noise function (full credit to Stephen Leitnick a.k.a sleitnick, src: https://github.com/Sleitnick/RDC2019-Procedural-Generation)
  11. local function FBM(x, z, seed, amplitude, octaves, persistence, frequency, lacunarity, gain, resultScale)
  12.     local result = 0
  13.     for _ = 1,octaves do
  14.         result = (result + (amplitude * math.noise(((x + seed)/frequency) * persistence, ((z + seed)/frequency) * persistence)))
  15.         frequency = (frequency * lacunarity)
  16.         amplitude = (amplitude * gain)
  17.     end
  18.     return result/resultScale
  19. end
  20.  
  21. -- S shaped function to generate square filter that'll remove edges of the map
  22. local function GenerateSquareFallOff(x, z, mapSize, offset, smoothness)
  23.     -- Normalization of values
  24.     local widthFallOff = math.abs(x/mapSize * 2 - 1)
  25.     local lengthFallOff = math.abs(z/mapSize * 2 - 1)
  26.    
  27.     -- Get the closes to one
  28.     local fallOffResult = math.clamp(math.max(widthFallOff, lengthFallOff), 0, 1)
  29.  
  30.     local a = smoothness
  31.     local b = offset
  32.     local value = fallOffResult
  33.     return math.pow(value, a)/(math.pow(value, a)+math.pow(b - b * value, a))
  34.  
  35. end
  36.  
  37. --? <|=============== KNIT LIFECYCLE ===============|>
  38. local MapGenerationService = Knit.CreateService {
  39.     Name = "MapGenerationService",
  40.     Client = {},
  41. }
  42.  
  43. --# Initial setup
  44. function MapGenerationService:KnitInit()
  45.     self.Terrains = {  --# Biomes
  46.         [1] = {
  47.             Name       = "Ocean",
  48.             Threshold  = 0,
  49.             BrickColor = BrickColor.new("Deep blue"),
  50.             Elevation  = 0
  51.         },
  52.  
  53.         [2] = {
  54.             Name       = "Coast",
  55.             Threshold  = 0.01,
  56.             BrickColor = BrickColor.new("Steel blue"),
  57.             Elevation  = 0
  58.         },
  59.  
  60.        
  61.  
  62.         [3] = {
  63.             Name       = "Beach",
  64.             Threshold  = 0.2,
  65.             BrickColor = BrickColor.new("Daisy orange"),
  66.             Elevation  = 0.25
  67.         },
  68.  
  69.         [4] = {
  70.             Name       = "Plains",
  71.             Threshold  = 0.30,
  72.             BrickColor = BrickColor.new("Bright green"),
  73.             Elevation  = 0.25
  74.         },
  75.  
  76.        
  77.         [5] = {
  78.             Name       = "Hills",
  79.             Threshold  = 0.60,
  80.             BrickColor = BrickColor.new("Dark green"),
  81.             Elevation  = 0.50
  82.         },
  83.  
  84.         [6] = {
  85.             Name       = "Mountains",
  86.             Threshold  = 0.88,
  87.             BrickColor = BrickColor.new("Medium stone grey"),
  88.             Elevation  = 0.75
  89.         },
  90.  
  91.         [7] = {
  92.             Name       = "Impassable",
  93.             Threshold  = 0.99,
  94.             BrickColor = BrickColor.new("Dark stone grey"),
  95.             Elevation  = 1.25
  96.         },
  97.  
  98.         [8] = {  --! needed placeholder due to the way appearance is set!
  99.             Name       = "Placeholder",
  100.             Threshold  = 1.01,
  101.             BrickColor = BrickColor.new("Daisy orange"),
  102.         }
  103.     }
  104. end
  105.  
  106. --# Start process
  107. function MapGenerationService:KnitStart()
  108.     --[[ source of these comments:
  109.         - http://libnoise.sourceforge.net/glossary/#octave
  110.         - https://medium.com/@yvanscher/playing-with-perlin-noise-generating-realistic-archipelagos-b59f004d8401
  111.         - https://thebookofshaders.com/13/
  112.     -- ]]
  113.     --# Generation Config
  114.     local seed = math.random(-100_000, 100_000)    -- Determines the output of the noise result
  115.  
  116.     local amplitude   = 42     -- Maximum displacement of the FBR Sine wave
  117.     local octaves     = 6      -- These are added together to Form noise, more =  = higher detail noise [1, n]
  118.     local persistence = 0.4    -- Modifies the amplitude of each octave the rate each octave diminshes [0.0, 1.0]
  119.     local frequency   = 72     -- Determines
  120.     local lacunarity  = 0.40   -- Determines how much each octave contributes to the final result  [0.0, 1.0]
  121.     local gain        = 0.52   -- Scales the amplitude between each octave [0.0, 1.0]
  122.    
  123.     local terrainScale = 27     -- outer amplitude [1, n]
  124.     local mapSize      = 325    -- Determines the area of the map, mapsize^2  [1, n]
  125.  
  126.     local fallOffSmoothness = 4  -- Detemines how smooth is the transition of biomes from the outermost to the innermost
  127.     local fallOffOffset     = 8  -- determines the offset between the edge of the filter and land (literally padding)
  128.    
  129.     --# Tilemap generations
  130.     for x = 1, mapSize do
  131.         for z = 1, mapSize do
  132.  
  133.             local noiseVal: number = FBM(x, z, seed, amplitude, octaves, persistence, frequency, lacunarity, gain, terrainScale)
  134.             noiseVal -= GenerateSquareFallOff(x, z, mapSize, fallOffOffset, fallOffSmoothness)
  135.             noiseVal = math.clamp(noiseVal, 0, 1)
  136.  
  137.  
  138.             local Tile: Part = Instance.new("Part")
  139.             Tile.Anchored   = true
  140.             Tile.CanCollide = true
  141.             Tile.Size       = Vector3.new(1,1,1)
  142.             Tile.Material   = Enum.Material.SmoothPlastic
  143.            
  144.             --# Tile appearance
  145.             for index, terrain in ipairs(self.Terrains) do
  146.                 local nextTerrain = self.Terrains[index + 1]
  147.                
  148.                 if not nextTerrain then break end
  149.                 if terrain.Threshold <= noiseVal and  nextTerrain.Threshold >= noiseVal   then
  150.                     Tile.BrickColor = terrain.BrickColor
  151.                     Tile.Position   = Vector3.new(x * Tile.Size.X, 20 + terrain.Elevation, z * Tile.Size.Z)
  152.  
  153.                     CollectionService:AddTag(Tile, terrain.Name)
  154.                     break
  155.                 end
  156.             end
  157.  
  158.             Tile.Parent     = workspace
  159.         end
  160.     end
  161.  
  162.  
  163.     --# output each terrain tile count
  164.     for _, t in ipairs(self.Terrains) do
  165.         print("ammount of", t.Name, #CollectionService:GetTagged(t.Name))
  166.         print("Seed is:", seed)
  167.     end
  168. end
  169.  
  170.  
  171. return MapGenerationService
  172.  
Add Comment
Please, Sign In to add comment