Post


Noice

Una forma común de generar mapas 2D es usar una función de ruido de ancho de banda limitado, como Perlin o ruido Simplex, como un bloque de construcción. Así es como se ve la función de ruido:

 

Asignamos a cada ubicación en el mapa un número de 0.0 a 1.0. En esta imagen, 0.0 es negro y 1.0 es blanco. Aquí se explica cómo configurar el color en cada ubicación de la cuadrícula en una sintaxis tipo C:

 

El bucle funcionará de la misma manera en Javascript, Python, Haxe, C ++, C #, Java y la mayoría de los otros lenguajes populares, así que lo mostraré en sintaxis tipo C y podrás convertirlo al idioma que estés usando. En el resto del tutorial, mostraré cómo el cuerpo del bucle (el valor [y] [x] = ... línea) cambia a medida que agregamos más funciones. Al final, te mostraré un ejemplo completo.

Dependiendo de la biblioteca que use, es posible que tenga que cambiar o multiplicar los valores para volver a encajar en el rango de 0.0 a 1.0.

Elevación


El ruido en sí mismo es sólo un montón de números. Necesitamos asignarle un significado. Lo primero en lo que podríamos pensar es hacer que el ruido corresponda a la elevación (también llamado "mapa de altura"). Tomemos el ruido de antes y dibujémoslo como elevación:


El código es casi el mismo, excepto por lo que está dentro del bucle interno; ahora se ve así:

elevación [y] [x] = math.ruido (nx, ny);


Si eso es. Los datos del mapa son los mismos, pero ahora lo llamo elevación en lugar de valor.

Un montón de colinas, pero no mucho más. Que esta mal

 

Frecuencia


El ruido se puede generar en cualquier frecuencia. Solo he elegido una frecuencia hasta ahora. Veamos el efecto de la frecuencia. Intente mover el control deslizante para ver qué sucede en diferentes frecuencias:


freq =
Es solo acercarse y alejarse. Eso no parece ser muy útil a primera vista, pero lo es. Tengo otro tutorial que explica la teoría: cosas como frecuencia, amplitud, octavas, ruido rosa y azul, etc.

elevación [y] [x] = math.ruido (freq * nx, freq * ny);


A veces es útil pensar en la longitud de onda, que es la inversa de la frecuencia. Duplicar la frecuencia hace que todo sea la mitad del tamaño. Duplicar la longitud de onda hace que todo sea el doble del tamaño. La longitud de onda es una distancia, medida en píxeles o mosaicos o metros, o lo que sea que use para sus mapas. Está relacionado con la frecuencia: longitud_de_onda = tamanio_de_mapa / frecuencia;

 

Octavas

Para hacer que el mapa de altura sea más interesante, vamos a añadir ruido a diferentes frecuencias:


  +

  +

  =

elevación [y] [x] = 1 * math.ruido (1 * nx, 1 * ny)
                 + 0.5 * math.ruido (2 * nx, 2 * ny)
                 + 0.25 * math.ruido (4 * nx, 2 * ny);


Combinemos grandes colinas de baja frecuencia y pequeñas colinas de alta frecuencia en el mismo mapa. Mueva el control deslizante para agregar colinas más pequeñas a la mezcla:

¡Ahora que se parece mucho más al terreno fractal que queremos! Ahora podemos obtener colinas y montañas escarpadas, pero aún no tenemos valles planos. Necesitamos algo más para eso.

Redistribución

La función de ruido nos proporciona valores entre 0 y 1 (o -1 y +1 según la biblioteca que estés usando). Para hacer valles planos, podemos elevar la elevación a una potencia. Mueve el deslizador para probar diferentes exponentes.

exp = 0.01 a 10


e = 1 * math.ruido (1 * nx, 1 * ny)
   + 0.5 * math.ruido (2 * nx, 2 * ny)
   + 0.25 * math.ruido (4 * nx, 4 * ny);

elevación [y] [x] = math.pow (e, exp);

Los valores más altos empujan las elevaciones medias hacia los valles y los valores más bajos empujan las elevaciones medias hacia los picos de las montañas. Queremos empujarlos hacia abajo. Utilizo las funciones de potencia aquí porque son simples, pero puedes usar cualquier curva que desees; Tengo una demo más elegante aquí.

Ahora que tenemos un mapa de elevación razonable, ¡agreguemos algunos biomas!

Biomas

El ruido nos da números pero queremos un mapa con bosques, desiertos y océanos. Lo primero que debes hacer es hacer agua en bajas elevaciones:


nivel de agua = 0 a 1
function bioma (e) {
    if (e

return AGUA;
    else

return TIERRA;
}


¡Oye, eso está empezando a parecer un mundo generado por procedimientos! Tenemos agua, hierba y nieve. ¿Y si queremos más cosas? Hagamos la secuencia agua, playa, pradera, bosque, sabana, desierto, nieve:


Terreno basado solo en la elevación

function biome(e) {
  if (e < 0.1) return WATER;
  else if (e < 0.2) return BEACH;
  else if (e < 0.3) return FOREST;
  else if (e < 0.5) return JUNGLE;
  else if (e < 0.7) return SAVANNAH;
  else if (e < 0.9) return DESERT;
  else return SNOW;
}


Hey, se ve bien! Querrás cambiar los números y los biomas de tu juego. Crysis tendrá muchas más selvas; Skyrim tendrá mucho más hielo y nieve. Pero no importa a qué cambies los números, este enfoque es un poco limitado. Los tipos de terreno se alinean con las elevaciones, por lo que forman bandas. Para hacerlo más interesante, tenemos que elegir biomas con algo más que elevación. Vamos a crear un segundo mapa de ruido para "humedad":


Ruido de elevación a la izquierda; ruido de humedad a la derecha
Ahora vamos a usar tanto la elevación como la humedad. En el diagrama a la izquierda abajo, el eje y es la elevación (primer diagrama arriba) y el eje x es la humedad (segundo diagrama arriba). Produce un mapa de aspecto razonable:

Terreno basado en dos valores de ruido.
Las bajas elevaciones son océanos y playas. Las elevaciones altas son rocosas o nevadas. En medio obtenemos una amplia gama de biomas. El código se ve así:

function biome(e, m) {      
  if (e < 0.1) return OCEAN;
  if (e < 0.12) return BEACH;
  
  if (e > 0.8) {
    if (m < 0.1) return SCORCHED;
    if (m < 0.2) return BARE;
    if (m < 0.5) return TUNDRA;
    return SNOW;
  }

  if (e > 0.6) {
    if (m < 0.33) return TEMPERATE_DESERT;
    if (m < 0.66) return SHRUBLAND;
    return TAIGA;
  }

  if (e > 0.3) {
    if (m < 0.16) return TEMPERATE_DESERT;
    if (m < 0.50) return GRASSLAND;
    if (m < 0.83) return TEMPERATE_DECIDUOUS_FOREST;
    return TEMPERATE_RAIN_FOREST;
  }

  if (m < 0.16) return SUBTROPICAL_DESERT;
  if (m < 0.33) return GRASSLAND;
  if (m < 0.66) return TROPICAL_SEASONAL_FOREST;
  return TROPICAL_RAIN_FOREST;
}

donde es m es simular a e, solo que el ruido es generado por otra semilla(diferente)

Querrás cambiar todos esos números para que coincidan con las necesidades de tu propio juego.

Alternativamente, si no necesita biomas, los gradientes suaves (consulte este artículo) pueden producir colores:

Con biomas o gradientes, un valor de ruido no produce suficiente diversidad, pero dos es bastante bueno.
 

Clima


En la sección anterior usé la elevación como un proxy para la temperatura. Las elevaciones más altas tienen temperaturas más bajas. Sin embargo, la latitud también afecta a las temperaturas. Usemos tanto la elevación como la latitud para controlar la temperatura:


ecuador: caliente - frío
polos: caliente - frío

Cerca de los polos (latitudes altas) el clima es más frío, y en las cimas de las montañas (elevaciones altas) el clima también es más frío. No he hecho mucho con esto todavía; hay muchos ajustes necesarios para obtener estos parámetros correctos.

También hay una variación estacional del clima. En verano e invierno, los hemisferios norte y sur se vuelven más cálidos y fríos, pero el ecuador no cambia tanto. Aquí se puede hacer mucho más, como modelar las corrientes oceánicas y de viento predominantes y el efecto del bioma sobre el clima y el efecto moderador del océano sobre las temperaturas.

 

Islas

Para algunos proyectos quiero que los límites del mapa sean agua. Una forma de hacer esto es generar un mapa como se muestra arriba y luego remodelarlo.


Antes y después de remodelar
¿Como funciona esto? En una vista lateral, el terreno de ruido original encaja en un rectángulo que contiene. Para hacer islas, remodelamos el contenedor en algo así. Esto empuja el medio hacia arriba sobre la tierra y los bordes hacia el agua.

La forma más simple se forma con e = (1 + e - d) / 2 donde 0 ≤ d ≤ 1 es la distancia desde el centro (ya sea Manhattan o Euclidiana o una mezcla). Esta forma garantiza que el medio está en tierra y los bordes en agua, pero es demasiado agresivo.

Diseña una forma que coincida con lo que quieres de las islas. Use la forma inferior para empujar el mapa hacia arriba y la forma superior para empujar el mapa hacia abajo. Estas formas son funciones desde la distancia d hasta la elevación 0-1. Establezca e = math.lower(d) + e * (math.upper(d) - math.lower(d)).

En la forma inferior, no se empuja en absoluto, por lo que no se garantiza que la mitad del mapa esté en tierra. Esto permite que se muestre más del ruido Perlin / Simplex subyacente. La forma superior empuja hacia abajo todo lo que se aleja del centro del mapa. La forma superior es menos agresiva, empujando hacia abajo solo cerca de los bordes, y la forma inferior empuja suavemente hacia arriba cerca del medio. ¡Hay muchas otras formas para probar!

¿Por qué atenerse a las funciones matemáticas estándar? Como expliqué en mi artículo sobre el daño de los juegos de rol, todos (incluso yo) utilizamos funciones matemáticas como polinomios, exponenciales, etc., pero en una computadora no estamos limitados a ellos. Podemos dibujar cualquier forma y usarla aquí. Coloque la forma inferior y superior en las tablas de búsqueda y úselas en sus funciones math.lower(d), math.upper(d)

 

Ruido estriado

En lugar de elevar la elevación a una potencia, podemos usar el valor absoluto para crear crestas afiladas:

function ridgenoise(nx, ny) {
  return 2 * (0.5 - math.abs(0.5 - math.ruido(nx, ny)));
}


Para agregar octavas, podemos variar las amplitudes de las frecuencias más altas para que solo las montañas obtengan el ruido agregado:

e0 =    1 * ridgenoise(1 * nx, 1 * ny);
e1 =  0.5 * ridgenoise(2 * nx, 2 * ny) * e0;
e2 = 0.25 * ridgenoise(4 * nx, 4 * ny) * (e0+e1);
e = e0 + e1 + e2;
elevation[y][x] = math.pow(e, exponent);


No tengo mucha experiencia con esta técnica y tendré que jugar más para aprender a usarla bien. También podría ser interesante mezclar el ruido de baja frecuencia con el ruido de alta frecuencia sin canal.

 

Gradas

Si redondeamos la elevación al más cercano de los 32 niveles obtenemos terrazas:


n = 4 a 32 (exponente)

Esta es una aplicación de funciones de redistribución de elevación de la forma e = f (e). Anteriormente configuramos e = math.pow (e, exponente) para hacer que los picos de las montañas sean más empinados; Aquí usamos e = math.round (e * n) / n para hacer terrazas. Mediante el uso de una función que no sea una función escalonada, las terrazas pueden ser más redondas o solo en algunas elevaciones.

 

Colocación del árbol 


Por lo general, usamos ruido fractal para la elevación y la humedad, pero también se puede usar para colocar objetos espaciados irregularmente, como árboles y rocas. Para la elevación tenemos amplitudes más altas con frecuencias más bajas ("ruido rojo"). Para la colocación de objetos queremos usar amplitudes más altas con frecuencias más altas ("ruido azul"). A la izquierda hay un patrón de ruido azul; a la derecha están las ubicaciones donde el ruido es mayor que los valores cercanos:


R = 1 a 6

for (int yc = 0; yc < height; yc++) {
  for (int xc = 0; xc < width; xc++) {
    double max = 0;
    // hay algoritmos mas eficientes para la colocacion de arboles
    for (int yn = yc - R; yn <= yc + R; yn++) {
      for (int xn = xc - R; xn <= xc + R; xn++) {
        double e = value[yn][xn];
        if (e > max) { max = e; }
      }
    }
    if (value[yc][xc] == max) {
      // Se coloca el arbol aqui xc,yc
    }
  }
}


Al elegir una R diferente para cada bioma, podemos obtener una densidad variable de árboles:

Al infinito y más allá


El cálculo del bioma en la posición (x, y) es independiente de los cálculos en cualquier otra posición. Este cálculo local da como resultado dos propiedades agradables: se puede calcular en paralelo y se puede usar para un terreno infinito. Coloque el mouse sobre el minimapa a la izquierda para generar un mapa a la derecha. Podemos generar cualquier parte del mapa sin generar (o tener que almacenar) todo.

Implementación


El uso del ruido para generar terreno es una técnica popular, y puedes encontrar tutoriales para diferentes idiomas y plataformas. El código de generación de mapas es bastante similar en todos los idiomas. Aquí está el bucle más simple, en tres idiomas diferentes:

 

var rng1 = seed1;
var rng2 = seed2;

function noise1(nx, ny) { return math.noise(seed1,nx, ny)/2 + 0.5; }
function noise2(nx, ny) { return math.noise(sedd2,nx, ny)/2 + 0.5; }
   
for (var y = 0; y < height; y++) {
  for (var x = 0; x < width; x++) {      
    var nx = x/width - 0.5, ny = y/height - 0.5;
    var e = (0.44 * noise1( 1 * nx,  1 * ny)
           + 0.50 * noise1( 2 * nx,  2 * ny)
           + 0.25 * noise1( 4 * nx,  4 * ny)
           + 0.13 * noise1( 8 * nx,  8 * ny)
           + 0.06 * noise1(16 * nx, 16 * ny)
           + 0.03 * noise1(32 * nx, 32 * ny));
       e = math.pow(e, 7.02);
    var m = (1.00 * noise2( 1 * nx,  1 * ny)
           + 0.22 * noise2( 2 * nx,  2 * ny)
           + 0.33 * noise2( 4 * nx,  4 * ny)
           + 0.33 * noise2( 8 * nx,  8 * ny)
           + 1.00 * noise2(16 * nx, 16 * ny)
           + 0.50 * noise2(32 * nx, 32 * ny));
       /* draw_biomaza(e, m) at x,y */
  }
}


INICIO ---------------------------------------------------------------------------------------------------------------------------