Advertisement
CluelessDev

Procedural generation 2.0

Jun 9th, 2022
905
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 8.93 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. --# <|=============== RUN TIME VALUES ===============|>
  9. local Configuration: Folder = script.Parent.Configuration
  10.  
  11.  
  12. --? <|=============== KNIT LIFECYCLE (ENTRY POINT) ===============|>
  13. local MapGenerationService = Knit.CreateService {
  14.     Name = "MapGenerationService",
  15.     Client = {},
  16. }
  17.  
  18. --# Initial setup
  19. function MapGenerationService:KnitInit()
  20.    
  21.     --# Biomes
  22.     self.Terrains = {  
  23.         [1] = {
  24.             Name       = "Ocean",
  25.             Threshold  = 0,
  26.             BrickColor = BrickColor.new("Deep blue"),
  27.             Elevation  = 0
  28.         },
  29.  
  30.         [2] = {
  31.             Name       = "Coast",
  32.             Threshold  = 0.0001,
  33.             BrickColor = BrickColor.new("Steel blue"),
  34.             Elevation  = 0
  35.         },
  36.  
  37.         [3] = {
  38.             Name       = "Beach",
  39.             Threshold  = 0.20,
  40.             BrickColor = BrickColor.new("Daisy orange"),
  41.             Elevation  = 0.25
  42.         },
  43.  
  44.         [4] = {
  45.             Name       = "Plains",
  46.             Threshold  = 0.30,
  47.             BrickColor = BrickColor.new("Bright green"),
  48.             Elevation  = 0.25
  49.         },
  50.  
  51.        
  52.         [5] = {
  53.             Name       = "Hills",
  54.             Threshold  = 0.60,
  55.             BrickColor = BrickColor.new("Dark green"),
  56.             Elevation  = 0.50
  57.         },
  58.  
  59.         [6] = {
  60.             Name       = "Mountains",
  61.             Threshold  = 0.88,
  62.             BrickColor = BrickColor.new("Medium stone grey"),
  63.             Elevation  = 0.75
  64.         },
  65.  
  66.         [7] = {
  67.             Name       = "Impassable",
  68.             Threshold  = 0.99,
  69.             BrickColor = BrickColor.new("Dark stone grey"),
  70.             Elevation  = 1.25
  71.         },
  72.  
  73.         [8] = {  --! needed placeholder due to the way appearance is set!
  74.             Name       = "Placeholder",
  75.             Threshold  = 1.01,
  76.             BrickColor = BrickColor.new("Daisy orange"),
  77.         }
  78.     }
  79.         --[[ source of these comments:
  80.         - http://libnoise.sourceforge.net/glossary/#octave
  81.         - https://medium.com/@yvanscher/playing-with-perlin-noise-generating-realistic-archipelagos-b59f004d8401
  82.         - https://thebookofshaders.com/13/
  83.         - https://www.youtube.com/watch?v=wbpMiKiSKm8&list=PLFt_AvWsXl0eBW2EiBtl_sxmDtSgZBxB3
  84.  
  85.         I did my best to interpret these, to be able to define these properties. I'm utterly ignorant when it comes to math...
  86.     -- ]]
  87.  
  88.     --# Generation Config
  89.    
  90.     self.GenerationParams = {
  91.         --# Map dimensions config
  92.         MapSize       = Configuration.MapSize.Value,          -- Determines the area of the map
  93.         TileSize      = Configuration.TileSize.Value,         -- Determines the area of the tile
  94.         TileThickness = Configuration.TileThickness.Value,    -- Determines how thick a tile is
  95.  
  96.         --# Map generation config
  97.         Seed         = Configuration.Seed.Value,          -- Determines the output of the noise result
  98.         Amplitude    = Configuration.Amplitude.Value,     -- Determines Maximum Height of the Noise Sine
  99.         Frequency    = Configuration.Frequency.Value,     -- Determines frequency of... Not sure it has to do with the co sine of the wave tho (just like amp is the sine of the wave)
  100.         Octaves      = Configuration.Octaves.Value,       -- Determines level of detail, These are added together to Form noise more detailed noise [1, n]
  101.         Persistance  = Configuration.Persistance.Value,   -- Determines the amplitude of each octave the rate each octave diminshes [0.0, 1.0]
  102.         Lacunarity   = Configuration.Lacunarity.Value,    -- Determines Increase of frequency of octaves  [0.0, 1.0]
  103.         Gain         = Configuration.Gain.Value,          -- Scales the amplitude between each octave [0.0, 1.0]
  104.         TerrainScale = Configuration.TerrainScale.Value,  -- Determines the amplitude of the final noise result (how hilly or flat terrain is) [0.0, 1.0]c
  105.  
  106.         --# Fall off filter Config
  107.         FallOffOffset     = Configuration.FallOffOffset.Value,    -- Detemines how smooth is the transition of biomes from the outermost to the innermost
  108.         FallOffSmoothness = Configuration.FallOffSmoothness.Value -- Detemines how smooth is the transition of biomes from the outermost to the innermost
  109.     }
  110.  
  111.     self.TileSet = {}
  112. end
  113.  
  114. --# Start process
  115. function MapGenerationService:KnitStart()
  116.     local Map: Model = Instance.new("Model")
  117.     Map.Name = "Map"            
  118.     Map.Parent = workspace
  119.  
  120.     self:GenerateTileSet()
  121.     self:GenerateHeightMap()
  122.  
  123.     for _, setting: NumberValue in pairs(Configuration:GetChildren()) do
  124.         setting.Changed:Connect(function(newValue)
  125.             self.GenerationParams[setting.Name] = newValue
  126.             self:GenerateHeightMap()
  127.         end)
  128.     end
  129.    
  130.     for i = 1, 200_000 do
  131.         self:GenerateHeightMap()
  132.         Configuration.Seed.Value = i * 3
  133.         task.wait(1)
  134.     end
  135. end
  136.  
  137.  
  138. --- <|=============== AUX FUNCTIONS ===============|>
  139. -- Fractal Brownian Motion noise function (full credit to Stephen Leitnick a.k.a sleitnick, src: https://github.com/Sleitnick/RDC2019-Procedural-Generation)
  140. local function FBM(x, z, seed, amplitude, octaves, persistence, frequency, lacunarity, gain, resultScale)
  141.     local result = 0
  142.     for _ = 1,octaves do
  143.         result = (result + (amplitude * math.noise(((x + seed)/frequency) * persistence, ((z + seed)/frequency) * persistence)))
  144.         frequency = (frequency * lacunarity)
  145.         amplitude = (amplitude * gain)
  146.     end
  147.     return result*resultScale
  148. end
  149.  
  150.  
  151.  
  152. -- S shaped function to generate square filter that'll remove edges of the map
  153. local function GenerateSquareFallOff(x, z, mapSize, offset, smoothness)
  154.     -- Normalization of values
  155.     local widthFallOff = math.abs(x/mapSize * 2 - 1)
  156.     local lengthFallOff = math.abs(z/mapSize * 2 - 1)
  157.    
  158.     -- Get the closest to one
  159.     local fallOffResult = math.clamp(math.max(widthFallOff, lengthFallOff), 0, 1)
  160.  
  161.     local a = smoothness
  162.     local b = offset
  163.     local value = fallOffResult
  164.     return math.pow(value, a)/(math.pow(value, a)+math.pow(b - b * value, a))
  165. end
  166.  
  167.  
  168. --+ <|===============  PUBLIC FUNCTIONS  ===============|>
  169. --!//TODO ADD SET GENERATION PARAMS METHOD!
  170.  
  171. function MapGenerationService:GenerateTileSet()
  172.     local params: table = self.GenerationParams
  173.  
  174.     for x = 1, params.MapSize do
  175.         for z = 1, params.MapSize do
  176.  
  177.             local Tile: Part = Instance.new("Part")
  178.             table.insert(self.TileSet, Tile)
  179.  
  180.             Tile.Anchored   = true
  181.             Tile.CanCollide = true
  182.             Tile.Size       = Vector3.new(params.TileSize, params.TileThickness, params.TileSize)
  183.             Tile.Position   = Vector3.new(x * Tile.Size.X, 20, z * Tile.Size.Z)
  184.             Tile.Material   = Enum.Material.SmoothPlastic
  185.  
  186.             Tile:SetAttribute("XPos", x)
  187.             Tile:SetAttribute("ZPos", z)
  188.            
  189.             Tile.Parent = workspace.Map
  190.         end
  191.     end
  192. end
  193.  
  194.  
  195. function MapGenerationService:GenerateHeightMap()
  196.     for _, tile: BasePart in ipairs(self.TileSet) do
  197.        
  198.         local x: number = tile:GetAttribute("XPos")
  199.         local z: number = tile:GetAttribute("ZPos")
  200.  
  201.         --# Generate noise value
  202.         local noiseVal: number = FBM(x, z,
  203.             self.GenerationParams.Seed,
  204.             self.GenerationParams.Amplitude,
  205.             self.GenerationParams.Octaves,
  206.             self.GenerationParams.Persistance,
  207.             self.GenerationParams.Frequency,
  208.             self.GenerationParams.Lacunarity,
  209.             self.GenerationParams.Gain,
  210.             self.GenerationParams.TerrainScale
  211.         )
  212.  
  213.         noiseVal -= GenerateSquareFallOff(x, z, self.GenerationParams.MapSize, self.GenerationParams.FallOffOffset, self.GenerationParams.FallOffSmoothness)
  214.         noiseVal = math.clamp(noiseVal, 0, 1)
  215.        
  216.         --# Tile biome appearance setting
  217.         for index, terrain in ipairs(self.Terrains) do
  218.  
  219.             local nextTerrain = self.Terrains[index + 1]
  220.            
  221.             --# Since index + 1 can be nil when in the last index
  222.             --# Break from the loop and
  223.             if not nextTerrain then break end
  224.  
  225.             --# Compare the noiseValue with the current and next terrain
  226.             --# Threshold, if it's within the range apply terrain data
  227.             if terrain.Threshold <= noiseVal and  nextTerrain.Threshold >= noiseVal   then
  228.                 tile.BrickColor = terrain.BrickColor
  229.                 tile.Position   = Vector3.new(x * tile.Size.X, 20 + terrain.Elevation, z * tile.Size.Z)
  230.                 CollectionService:AddTag(tile, terrain.Name)
  231.                 break
  232.             end
  233.         end
  234.     end
  235. end
  236.  
  237. return MapGenerationService
  238.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement