Page 1 of 1

2D level generator (terraria-like)

Unread postPosted: 25 Nov 2014, 19:08
by technolapin
Enfin je finis un programme!
J'ai mis au point une génération aléatoire de map, qui ressemblent un peu à celles de terraria ou autres jeux utilisant des fractales à multiples dimension compliquées pour générer leur mondes (évidement, moi j'ai trouvé plus simple et assez potable quand même).
Je poste ça ici parce que la probabilité que j'en fasse quelque chose est quasi-nulle et parce que cela pourrait être utile à d'autres.

Code: Select all
local rand = math.random
local floor = math.floor
local abs = math.abs
local pow = math.pow
local sqrt = math.sqrt

local biomesList = {
  {.1, 8, 32, 8}
, {1, 4, 8, 4}
}


function on.resize (ww, hh)
width, height = ww, hh
w, h = width, height/2
mat = gen()
map = convertMat (mat)
end

function dist2 (x1, y1, x2, y2)
return pow(x1-x2, 2)+pow(y1-y2, 2)
end


function gen ()
local m = genMat()
local biomes = genBiomes(24, 32)
genSphericalNoise (m, biomes)
genLeveling (m, 255)
genSmoothCorner (m, 2)
return m
end

function genBiomes (min, max)
local biomes = {}
local a = 1
while a < w do
  local r = rand(min, max)
  local b = rand(#biomesList)
  biomes[#biomes+1] = {type=b, x1=a, x2 = a+r, size = r}
  a = a+r
end
biomes[#biomes].x2 = w
return biomes
end




function genLeveling (m, tol)
for a = 1, w do
  local n = 0
  for b = 1, h do
   n=n+m[a][b]
  end
  n = math.floor(n/tol)
  for b = 1, h do
   if n > 0 then m[a][b] = tol
   else m[a][b] = 0 end
   n = n-1
  end
end
end

function genSphericalNoise(m, biomes)
for _, bio in pairs (biomes) do
  local n, min, max, v = unpack (biomesList[bio.type])
  for i = 1, n*bio.size do
   local r = rand (min, max)
   local r2 = r*r
   local x = rand (bio.x1, bio.x2)
   local y = rand (h)
   for a = x-r, x+r do
    for b = y-r, y+r do
     local d = dist2(a, b, x, y)
     if d <=r2 then
      pcall (function () m[a][b] = sqrt(d)*v end)
     end
    end
   end
  end
end
end


function genSmoothCorner (m, tol)
local he = {}
for a = 1, w do
  he[a] = 0
  for b = 1, h do
   he[a] = he[a]+m[a][b]/255
  end
end
for a = 2, w-1 do
  local med = floor((he[a-1]+he[a+1])/2)
  if abs (med-he[a]) > tol then
   for b = 1, h do
    if med > 0 then m[a][b] = 255 else m[a][b] = 0 end
    med = med-1
   end
  end
end
end


function genMat ()
local m = {}
for a = 1, w do
  m[a] = {}
  for b = 1, h do
   m[a][b] = 0
  end
end
return m
end


function convertMat (m)
local l = {}
for a = 1, w do
  l[a] = 0
  for b = 1, h do
   if m[a][b] == 0 then l[a] = l[a]+1 end
  end
end
return l
end



function on.paint (gc)
for a = 1, w do
  gc: fillRect (a, height-map[a], 0, map[a])
end
end
   



La map consiste en une matrice bidimensionnelle dont deux valeurs sont possible: 255 (l'air en blanc) et 0 (le sol).

Au lieu d'utiliser des fractales très louches comme le fait terraria, je fait un bruit aléatoire consistant en des cercles dégradés de tailles différentes. La somme des cases de chaque colonne est ensuite convertie en hauteur, un petit dernier traitement est appliqué pour éliminer les pics moches et incongrus.

J'utilise une fonction qui créé une liste avec les hauteurs pour les afficher avec des rectangles de 1 de large parce que c'est (beaucoup) plus rapide à afficher.

Quelques images ainsi générées:
4113
4112
4111

Encore une fois, si ce code intéresse quelqu'un (tout est possible de nos jours :p ), je donne tous les droits pour l'implémenter dans n'importe quel programme. Ma seule condition est de mettre dans les crédits de ce programme "Gloire à technolapin, qui m'oblige à mettre cette phrase à la noix dans les crédits pour que je puisse utiliser sa génération de map foireuse" (mot pour mot :troll: )

Re: 2D level generator (terraria-like)

Unread postPosted: 25 Nov 2014, 19:33
by Ti64CLi++
Cool car cela me dirais bien de faire un warm.

Re: 2D level generator (terraria-like)

Unread postPosted: 25 Nov 2014, 19:34
by Adriweb
Ca me rappelle ce que Jim avait codé d'apres ma suggestion pour le terrain (enfin, j'avais vu l'algo quelque part et je lui en avais parlé):

Image

Code: Select all
function getMidPoint(line)
    local xdif    = math.abs(line[3]-line[1])
    local ydif    = math.abs(line[4]-line[2])
    local x    = math.min(line[3], line[1]) + xdif/2
    local y    = math.min(line[4], line[2]) + ydif/2
    return x, y
end

function terrain(bline, range, roughness)
    local lines    = {bline}
    local lines2    = {}

    local midX, midY

    for times=1, 10 do
        for index, line in pairs(lines) do
            midX, midY    = getMidPoint(line)
            midY    = midY + math.random(-range, range)

            table.insert(lines2, {line[1], line[2], midX, midY})
            table.insert(lines2, {midX, midY, line[3], line[4]})
        end
        lines    = lines2
        lines2    = {}
        range    = range * (roughness*2^-roughness)
    end

    return lines
end

lines    = terrain({0,120,318,120}, 100, 0.6)
function on.paint(gc)
    for index, line in pairs(lines) do
        gc:drawLine(line[1], line[2], line[3], line[4])
    end
end


Il a ensuite fait quelques essais avec le moteur physique (cf. piece jointe pour le code complet du gif)

Re: 2D level generator (terraria-like)

Unread postPosted: 27 Nov 2014, 10:32
by technolapin
Oui, mais le système ce génération est juste totalement random, et ne donne pas de résultats potables pour des grandes maps. Alors que le mien est potable pour un jeu .
J'ai implémenté la possibilité de générer différents "biomes" avec des reliefs spécifiques, et la taille de la map ne change plus la tête des reliefs (le nombre de sphères random s'adaptant à la taille du biome). Je mettrais cette version en ligne dès que j'aurais accès à mon pc (peux pas avec ceux du lycée).

Re: 2D level generator (terraria-like)

Unread postPosted: 27 Nov 2014, 19:06
by technolapin
J'ai implémenté une différenciation des blocs, qui sont fait en multipart maintenant (c'est juste joli et plus long à afficher :troll: )

Code: Select all
local rand = math.random
local floor = math.floor
local abs = math.abs
local pow = math.pow
local sqrt = math.sqrt
local cx, cy = 0, 0

local biomesList = {
  {.1, 8, 32, 8}
, {1, 4, 8, 4}
}


function on.resize (ww, hh)
width, height = ww, hh
w, h = floor(width/4), floor(height/8)
t = 8
mat = gen()
end

function dist2 (x1, y1, x2, y2)
return pow(x1-x2, 2)+pow(y1-y2, 2)
end


function gen ()
local m = genMat()
local biomes = genBiomes(8, 32)
genSphericalNoise (m, biomes)
genLeveling (m, 255)
genSmoothCorner (m, 2)
he = mapToHeight (m)
m = heightToBlocs (he)
return m
end

function genBiomes (min, max)
local biomes = {}
local a = 1
while a < w do
  local r = rand(min, max)
  local b = rand(#biomesList)
  biomes[#biomes+1] = {type=b, x1=a, x2 = a+r, size = r}
  a = a+r
end
biomes[#biomes].x2 = w
return biomes
end




function genLeveling (m, tol)
for a = 1, w do
  local n = 0
  for b = 1, h do
   n=n+m[a][b]
  end
  n = math.floor(n/tol)
  for b = 1, h do
   if n > 0 then m[a][b] = tol
   else m[a][b] = 0 end
   n = n-1
  end
end
end

function genSphericalNoise(m, biomes)
for _, bio in pairs (biomes) do
  local n, min, max, v = unpack (biomesList[bio.type])
  for i = 1, n*bio.size do
   local r = rand (min, max)
   local r2 = r*r
   local x = rand (bio.x1, bio.x2)
   local y = rand (h)
   for a = x-r, x+r do
    for b = y-r, y+r do
     local d = dist2(a, b, x, y)
     if d <=r2 then
      pcall (function () m[a][b] = sqrt(d)*v end)
     end
    end
   end
  end
end
end


function genSmoothCorner (m, tol)
local he = {}
for a = 1, w do
  he[a] = 0
  for b = 1, h do
   he[a] = he[a]+m[a][b]/255
  end
end
for a = 2, w-1 do
  local med = floor((he[a-1]+he[a+1])/2)
  if abs (med-he[a]) > tol then
   for b = 1, h do
    if med > 0 then m[a][b] = 255 else m[a][b] = 0 end
    med = med-1
   end
  end
end
end


function genMat ()
local m = {}
for a = 1, w do
  m[a] = {}
  for b = 1, h do
   m[a][b] = 0
  end
end
return m
end


function mapToHeight (m)
local l = {}
for a = 1, w do
  l[a] = 0
  for b = 1, h do
   if m[a][b] == 0 then l[a] = l[a]+1 end
  end
end
return l
end

color = {
  [0] = {255, 255, 255}
, [1] = {150, 150, 150}
, [2] = {200, 150, 0}
, [3] = {0, 200, 0}
}
function getBloc(m, x, y)
assert (m[x][y])
return m[x][y]
end

function getSides (m, x, y, e)
local sides = {}
local bool = {}
bool[1], sides[1] = pcall (getBloc, m, x-1, y)
bool[2], sides[2] = pcall (getBloc, m, x, y-1)
bool[3], sides[3] = pcall (getBloc, m, x+1, y)
bool[4], sides[4] = pcall (getBloc, m, x, y+1)
for a = 1, 4 do
  if not bool[a] then
   sides[a] = makeBloc(e,e,e,e)
  end
end
return  makeBloc( sides[1].right or e
                 , sides[2].down or e
                 , sides[3].left or e
                 , sides[4].up or e)
end


function makeBloc (l, u, r, d)
return {left=l, up=u, right=r, down=d}
end

function heightToBlocs (t)
local m = {}
for a = 1, w do
  local he = t[a]
  m[a] = {}
  -- set air
  for b = 1, h-he do
   m[a][b] = makeBloc (0,0,0,0)
  end
end
for a = 1, w do
  local he = t[a]
  -- set grass
  local b = h-he
  local s = getSides (m, a, b, 2, 2)
  for c, d in pairs (s) do
    if d == 0 then s[c]=3
elseif d == 1 then s[c]=2
   end
  end
  m[a][b] = makeBloc (s.left or 0, 3, s.right or 0, s.down or 0)
  -- set dirt
  for b = h-he+1, h-he+1+floor(he/5) do
   m[a][b] = makeBloc (2, 2, 2, 2)
  end
  -- set stone
  for b = h-he+1+floor(he/5), h do
   m[a][b] = makeBloc (1,1,1,1)
  end
end
return m
end




function drawBloc (gc, a, b, x, y)
gc: setColorRGB (unpack(color[mat[a][b].left]))
gc: fillPolygon ({x, y, x, y+t, x+t/2, y+t/2})
gc: setColorRGB (unpack(color[mat[a][b].up]))
gc: fillPolygon ({x, y, x+t, y, x+t/2, y+t/2})
gc: setColorRGB (unpack(color[mat[a][b].right]))
gc: fillPolygon ({x+t, y, x+t, y+t, x+t/2, y+t/2})
gc: setColorRGB (unpack(color[mat[a][b].down]))
gc: fillPolygon ({x, y+t, x+t, y+t, x+t/2, y+t/2})
end

function on.paint (gc)
for a = 1, w do
  for b = 1, h do
   pcall(drawBloc, gc, a, b, (a-1)*t-cx, (b-1-h)*t+height-cy)
  end
end
end

function on.arrowKey (k)
    if k == "left" then
     cx = cx-t
elseif k == "right" then
     cx = cx+t
elseif k == "up" then
     cy = cy-t
elseif k == "down" then
     cy = cy+t
end
platform.window: invalidate ()
end