¡Hola, homo fabers! Hace un tiempo publiqué este artículo donde expliqué cómo generar mapas de terreno aleatorios con Python y la librería OpenSimplex. A partir de una semilla aleatoria generábamos varios arrays de Ruido Simplex que se sobreponían para formar estos mapas tan bonitos:
Para dibujar los mapas generábamos una imagen de color y la mostrábamos con la librería PIL. Algunos lectores me habéis pedido muy amablemente otro tutorial utilizando Pygame en vez de PIL y así poder integrar este generador de mapas en vuestros juegos.
Por tanto, partiendo de lo que aprendimos en el último tutorial, hoy haremos un pequeño “juego” con Pygame que nos genere mapas aleatorios.
Abrid vuestro editor de texto favorito, id a por galletas, poned música de fondo y… ¡a trabajar!
Librerías
En este tutorial trabajaremos con Python 3. Comprobad que tengáis correctamente instaladas estas librerías:
- OpenSimplex, para generar los mapas de ruido simplex.
- Pygame, para mostrar la imagen.
- Numpy, para generar arrays vacíos.
- Random, para generar numeros aleatorios.
Recordatorio: ¿cómo iba esto de hacer mapas al azar?
Una vez leí no-recuerdo-donde a alguien que decía que el Ruido Simplex era para la generación procedural lo mismo que la sal y la pimienta para los cocineros… ¡y tenía toda la razón! A diferencia del ruido totalmente aleatorio, el Ruido Simplex tiene una apariencia mucho más suave y es idóneo para generar texturas y superficies con un aspecto natural.

Ruido totalmente aleatorio (izq.) vs. Ruido Simplex (derecha)
Para crear un mapa aleatorio empezábamos llenando un array con ruido simplex entre -1 y 1, que representaba la elevación del terreno. Después convertíamos este array en una imagen y pintábamos cada píxel de un color diferente según su elevación. Esto nos permitía crear mapas sencillos pero poco realistas…

Creo que ni los atolones del Pacífico tienen unas playas tan perfectas 😛
Para conseguir un terreno más irregular (y por tanto más natural) sobreponíamos varias capas de ruido simplex a distintas frecuencias a la hora de generar el array de elevación.
El resultado era un mapa de terreno mucho más fiel a la realidad. Los contornos de la costa ya no son tan suaves y las montañas están agrupadas de forma irregular. ¡Genial!
Esto es un buen punto de partida, pero todavía le falta algo muy importante: Biomas. En nuestro planeta, las condiciones del terreno no dependen únicamente de la elevación sobre el nivel del mar; la humedad de la zona es otro factor muy importante.
Para que nuestro mapa tuviera distintos biomas, generábamos un segundo mapa de ruido simplex al que llamábamos mapa de humedad. Sobreponiendo los dos mapas, podíamos saber la altura y nivel de humedad de cada píxel y pintarlo de un color diferente según el bioma que le correspondiera.

Para elegir el Bioma de cada píxel nos servíamos de esta tabla como referencia.
Parte 1. Mapas de ruido
En esta primera parte vamos a escribir el script que generará los mapas de terreno con los biomas. Dividiremos el programa en tres funciones: una para generar los arrays de elevación y humedad, otra para pintar el mapa y una función main para gestionar la ventana de pygame y el bucle principal.
Lo primero será importar las librerías que necesitamos para trabajar:
import pygame from opensimplex import OpenSimplex import numpy as np import random
Ahora definiremos la función generar_mapa_ruido(), que se encargará de generar los dos arrays con la elevación y la humedad del mapa. Esta función recibirá dos enteros correspondientes a las dimensiones del mapa y devolverá un array con el mapa de elevación (con valores entre -1 y 1) y otro con el mapa de humedad (con valores entre 0 y 1)
def generar_mapa_ruido(ancho, alto): #==Esta funcion genera los mapas de elevacion y humedad== #Crear los objetos opensimplex con una semilla aleatoria gen_altura = OpenSimplex(seed = random.randint(1,1000)) gen_humedad = OpenSimplex(seed = random.randint(1,1000)) #Valor de la frecuencia (3 es un valor estandar) #Aumentar este valor hace el mismo efecto que un zoom hacia afuera freq = 3 #Array para guardar la elevacion mapa_altura = np.empty([alto, ancho]) mapa_humedad = np.empty([alto, ancho]) #Generar el mapa de terreno mezclando ruido a diferentes octavas for y in range(alto): for x in range(ancho): #Dividir la posicion por la altura nx = 2*x/ancho ny = 2*y/alto #Generar el valor de la posicion actual mapa_altura[y][x] = gen_altura.noise2d(freq*nx, freq*ny) + 0.5 * gen_altura.noise2d(4*freq*nx, 4*freq*ny)+0.25 * gen_altura.noise2d(8*freq*nx, 8*freq*ny) #La humedad la escalaremos para que el rango vaya de 0 a 1 en vez de -1 a 1 mapa_humedad[y][x] = (gen_humedad.noise2d(freq*nx, freq*ny) + 0.5 * gen_humedad.noise2d(4*freq*nx, 4*freq*ny)+0.25 * gen_humedad.noise2d(8*freq*nx, 8*freq*ny))/2.0+0.5 #Devolver el array de los dos mapas return mapa_altura, mapa_humedad
Como podéis ver, el proceso para generar los mapas es el mismo de siempre: se crean dos objetos OpenSimplex (uno para la elevación y otro para la humedad) y se establece una frecuencia base. Después se crean dos arrays vacíos (mapa_altura y mapa_humedad) y se llena cada uno de sus espacios con ruido simplex a varias frecuencias.
La segunda función que definiremos se llamará pintar_pixel() que, como su nombre indica, pintará un píxel determinado de la ventana del juego según el bioma que le corresponda.
def pintar_pixel(elevacion, humedad, x, y, gameDisplay): #===Esta funcion pinta un pixel situado en las coordenadas (X,Y) de la ventana 'gameDisplay'=== # Nota: pintar un pixel equivale a dibujar un rectangulo de 1x1 #Mar - Azul oscuro if elevacion < -0.2: pygame.draw.rect(gameDisplay,(50,80,160),(x,y,1,1)) #Aguas costeras - Cyan elif elevacion < 0: pygame.draw.rect(gameDisplay,(50, 100, 200),(x,y,1,1)) #Playa (no es un bioma, pero respetaremos la linea de la costa) elif elevacion < 0.07: pygame.draw.rect(gameDisplay,(242,223,152),(x,y,1,1)) #Elevacion baja elif elevacion < 0.25: if humedad > 0.5: pygame.draw.rect(gameDisplay,(59, 136, 64),(x,y,1,1)) #Bosque tropical elif humedad > 0.25: pygame.draw.rect(gameDisplay,(118, 183, 54),(x,y,1,1)) #Praderia else: pygame.draw.rect(gameDisplay,(242,223,152),(x,y,1,1)) #Desierto tropical #Elevacion media elif elevacion < 0.5: if humedad > 0.75: pygame.draw.rect(gameDisplay,(102, 136, 59),(x,y,1,1)) #Bosque templado humedo elif humedad > 0.55: pygame.draw.rect(gameDisplay,(122, 166, 71),(x,y,1,1)) #Bosque templado seco elif humedad > 0.25: pygame.draw.rect(gameDisplay,(118, 183, 54),(x,y,1,1))#Praderia else: pygame.draw.rect(gameDisplay,(208,196,146),(x,y,1,1)) #Desierto templado #Elevacion alta elif elevacion < 0.75: if humedad > 0.66: pygame.draw.rect(gameDisplay,(120, 143, 91),(x,y,1,1)) #Taiga elif humedad > 0.33: pygame.draw.rect(gameDisplay,(157, 167, 117),(x,y,1,1)) #Matorrales else: pygame.draw.rect(gameDisplay,(186, 182, 163),(x,y,1,1)) #Desierto frio #Elevacion muy alta else: if humedad > 0.45: pygame.draw.rect(gameDisplay,(234, 234, 234),(x,y,1,1)) #Nieve elif humedad > 0.33: pygame.draw.rect(gameDisplay,(203, 218, 192),(x,y,1,1)) #Tundra else: pygame.draw.rect(gameDisplay,(146, 137, 127),(x,y,1,1)) #Yermo
Por supuesto los valores que he elegido para los colores/rangos son arbitrarios y podéis cambiarlos por otros que os gusten más 🙂
Ahora vamos a escribir la función principal main(). Esta función se encargará de establecer los parámetros del mapa y generarlo llamando a la función generar_mapa_ruido(). Una vez generado este array, se creará una ventana de pygame y se dibujará todo el contenido del mapa.
def main(): #===Funcion principal del programa=== #Dimensiones del array del mapa alto = 500 ancho = 500 #Generar el mapa aleatorio mapa_altura, mapa_humedad = generar_mapa_ruido(alto, ancho) #Iniciar pygame pygame.init() #Dimensiones de la ventana de pygame donde se dibuja el mapa ancho_ventana = 500 alto_ventana = 500 #Crear la ventana y cambiar su nombre gameDisplay = pygame.display.set_mode((ancho_ventana,alto_ventana)) pygame.display.set_caption('Hic sunt draconis') #Dibujar el mapa for j in range(len(mapa_altura)): for i in range(len(mapa_altura[j])): pintar_pixel(mapa_altura[j][i], mapa_humedad[j][i], i, j, gameDisplay) pygame.display.update() #Bucle principal del "juego". Solo sirve para comprobar si el jugador cierra la ventana. continuar = True while continuar: for event in pygame.event.get(): if event.type == pygame.QUIT: continuar = False pygame.quit()
Por último se llama a la función main() y después la función quit() para salir del script:
main() quit()
Por tanto, el código final para generar mapas de ruido tendría que quedar así:
import pygame from opensimplex import OpenSimplex import numpy as np import random def generar_mapa_ruido(ancho, alto): #==Esta funcion genera los mapas de elevacion y humedad== #Crear los objetos opensimplex con una semilla aleatoria gen_altura = OpenSimplex(seed = random.randint(1,1000)) gen_humedad = OpenSimplex(seed = random.randint(1,1000)) #Valor de la frecuencia (3 es un valor estandar) #Aumentar este valor hace el mismo efecto que un zoom hacia afuera freq = 3 #Array para guardar la elevacion mapa_altura = np.empty([alto, ancho]) mapa_humedad = np.empty([alto, ancho]) #Generar el mapa de terreno mezclando ruido a diferentes octavas for y in range(alto): for x in range(ancho): #Dividir la posicion por la altura nx = 2*x/ancho ny = 2*y/alto #Generar el valor de la posicion actual mapa_altura[y][x] = gen_altura.noise2d(freq*nx, freq*ny) + 0.5 * gen_altura.noise2d(4*freq*nx, 4*freq*ny)+0.25 * gen_altura.noise2d(8*freq*nx, 8*freq*ny) #La humedad la escalaremos para que el rango vaya de 0 a 1 en vez de -1 a 1 mapa_humedad[y][x] = (gen_humedad.noise2d(freq*nx, freq*ny) + 0.5 * gen_humedad.noise2d(4*freq*nx, 4*freq*ny)+0.25 * gen_humedad.noise2d(8*freq*nx, 8*freq*ny))/2.0+0.5 #Devolver el array de los dos mapas return mapa_altura, mapa_humedad def pintar_pixel(elevacion, humedad, x, y, gameDisplay): #===Esta funcion pinta un pixel situado en las coordenadas (X,Y) de la ventana 'gameDisplay'=== # Nota: pintar un pixel equivale a dibujar un rectangulo de 1x1 #Mar - Azul oscuro if elevacion < -0.2: pygame.draw.rect(gameDisplay,(50,80,160),(x,y,1,1)) #Aguas costeras - Cyan elif elevacion < 0: pygame.draw.rect(gameDisplay,(50, 100, 200),(x,y,1,1)) #Playa (no es un bioma, pero respetaremos la linea de la costa) elif elevacion < 0.07: pygame.draw.rect(gameDisplay,(242,223,152),(x,y,1,1)) #Elevacion baja elif elevacion < 0.25: if humedad > 0.5: pygame.draw.rect(gameDisplay,(59, 136, 64),(x,y,1,1)) #Bosque tropical elif humedad > 0.25: pygame.draw.rect(gameDisplay,(118, 183, 54),(x,y,1,1)) #Praderia else: pygame.draw.rect(gameDisplay,(242,223,152),(x,y,1,1)) #Desierto tropical #Elevacion media elif elevacion < 0.5: if humedad > 0.75: pygame.draw.rect(gameDisplay,(102, 136, 59),(x,y,1,1)) #Bosque templado humedo elif humedad > 0.55: pygame.draw.rect(gameDisplay,(122, 166, 71),(x,y,1,1)) #Bosque templado seco elif humedad > 0.25: pygame.draw.rect(gameDisplay,(118, 183, 54),(x,y,1,1))#Praderia else: pygame.draw.rect(gameDisplay,(208,196,146),(x,y,1,1)) #Desierto templado #Elevacion alta elif elevacion < 0.75: if humedad > 0.66: pygame.draw.rect(gameDisplay,(120, 143, 91),(x,y,1,1)) #Taiga elif humedad > 0.33: pygame.draw.rect(gameDisplay,(157, 167, 117),(x,y,1,1)) #Matorrales else: pygame.draw.rect(gameDisplay,(186, 182, 163),(x,y,1,1)) #Desierto frio #Elevacion muy alta else: if humedad > 0.45: pygame.draw.rect(gameDisplay,(234, 234, 234),(x,y,1,1)) #Nieve elif humedad > 0.33: pygame.draw.rect(gameDisplay,(203, 218, 192),(x,y,1,1)) #Tundra else: pygame.draw.rect(gameDisplay,(146, 137, 127),(x,y,1,1)) #Yermo def main(): #===Funcion principal del programa=== #Dimensiones del array del mapa alto = 500 ancho = 500 #Generar el mapa aleatorio mapa_altura, mapa_humedad = generar_mapa_ruido(alto, ancho) #Iniciar pygame pygame.init() #Dimensiones de la ventana de pygame donde se dibuja el mapa display_width = 500 display_height = 500 #Crear la ventana y cambiar su nombre gameDisplay = pygame.display.set_mode((display_width,display_height)) pygame.display.set_caption('Hic sunt draconis') #Dibujar el mapa for j in range(len(mapa_altura)): for i in range(len(mapa_altura[j])): pintar_pixel(mapa_altura[j][i], mapa_humedad[j][i], i, j, gameDisplay) pygame.display.update() #Bucle principal del "juego". Solo sirve para comprobar si el jugador cierra la ventana. continuar = True while continuar: for event in pygame.event.get(): if event.type == pygame.QUIT: continuar = False pygame.quit() main() quit()
Parte 2. Mapa con losetas
Los juegos de estrategia por turnos normalmente utilizan una técnica un poco distinta para crear mapas aleatorios. En vez de generar mapas con tanto detalle como hemos hecho, el mapa se guarda en un array más pequeño donde cada espacio representa una “casilla” del tablero de juego. A la hora de dibujar el mapa por pantalla se utilizan assets prefabricados (imágenes de bosques, agua, montañas…) para mostrar al jugador qué hay en cada casilla.

Juegos como Battle for Wesnoth guardan el mapa en arrays más pequeños (unos 150 x 150 como máx) y utilizan losetas de terreno para dibujarlo.
En este segundo ejemplo os enseñaré como crear un mapa de este tipo, utilizando losetas para el terreno:
Os he preparado algunos assets para que no tengáis que perder tiempo dibujándolos. No están especialmente currados, pero servirán para hacer pruebas. Guardad los assets en la misma carpeta donde tengáis el script de Python y descomprimid el zip.

Mapa de ejemplo con las losetas que os he preparado
Vamos a partir del código que ya tenemos. Las librerías y la función que genera el mapa vamos a dejarlas exactamente igual, sin hacer ningún cambio.
import pygame from opensimplex import OpenSimplex import numpy as np import random def generar_mapa_ruido(ancho, alto): #==Esta funcion genera los mapas de elevacion y humedad== #Crear los objetos opensimplex con una semilla aleatoria gen_altura = OpenSimplex(seed = random.randint(1,500)) gen_humedad = OpenSimplex(seed = random.randint(1,500)) #Valor de la frecuencia (3 es un valor estandar) #Aumentar este valor hace el mismo efecto que un zoom hacia afuera freq = 3 #Array para guardar la elevacion mapa_altura = np.empty([alto, ancho]) mapa_humedad = np.empty([alto, ancho]) #Generar el mapa de terreno mezclando ruido a diferentes octavas for y in range(alto): for x in range(ancho): #Dividir la posicion por la altura nx = 2*x/ancho ny = 2*y/alto #Generar el valor de la posicion actual mapa_altura[y][x] = gen_altura.noise2d(freq*nx, freq*ny) + 0.5 * gen_altura.noise2d(4*freq*nx, 4*freq*ny)+0.25 * gen_altura.noise2d(8*freq*nx, 8*freq*ny) #La humedad la escalaremos para que el rango vaya de 0 a 1 en vez de -1 a 1 mapa_humedad[y][x] = (gen_humedad.noise2d(freq*nx, freq*ny) + 0.5 * gen_humedad.noise2d(4*freq*nx, 4*freq*ny)+0.25 * gen_humedad.noise2d(8*freq*nx, 8*freq*ny))/2.0+0.5 #Devolver el array de los dos mapas return mapa_altura, mapa_humedad
El primer cambio que habrá que hacer es sustituir la función pintar_pixel() por pintar_casilla() que, en vez de dibujar un rectángulo de 1×1, nos pintará una imagen:
def pintar_casilla(elevacion, humedad, x, y, gameDisplay): #===Esta funcion pinta una casilla situada en las coordenadas (X,Y)=== #Mar - Azul oscuro if elevacion < -0.2: tile = pygame.image.load('assets_tutorial_opensimplex/agua2.bmp').convert_alpha() #Aguas costeras - Cyan elif elevacion < 0: tile = pygame.image.load('assets_tutorial_opensimplex/agua1.bmp').convert_alpha() #Playa elif elevacion < 0.10: tile = pygame.image.load('assets_tutorial_opensimplex/desierto_tropical.bmp').convert_alpha() #Elevacion baja elif elevacion < 0.25: if humedad > 0.5: tile = pygame.image.load('assets_tutorial_opensimplex/bosque_tropical.bmp').convert_alpha() #Bosque tropical elif humedad > 0.25: tile = pygame.image.load('assets_tutorial_opensimplex/praderia.bmp').convert_alpha() #Praderia else: tile = pygame.image.load('assets_tutorial_opensimplex/desierto_tropical.bmp').convert_alpha() #Desierto tropical #Elevacion media elif elevacion < 0.5: if humedad > 0.6: tile = pygame.image.load('assets_tutorial_opensimplex/bosque_templado.bmp').convert_alpha() #Bosque templado elif humedad > 0.35: tile = pygame.image.load('assets_tutorial_opensimplex/praderia_templada.bmp').convert_alpha() #Praderia templada elif humedad > 0.25: tile = pygame.image.load('assets_tutorial_opensimplex/praderia.bmp').convert_alpha() #Praderia else: tile = pygame.image.load('assets_tutorial_opensimplex/desierto_templado.bmp').convert_alpha() #Desierto templado #Elevacion alta elif elevacion < 0.75: if humedad > 0.66: tile = pygame.image.load('assets_tutorial_opensimplex/taiga.bmp').convert_alpha() #Taiga elif humedad > 0.33: tile = pygame.image.load('assets_tutorial_opensimplex/matorrales.bmp').convert_alpha() #Matorrales else: tile = pygame.image.load('assets_tutorial_opensimplex/desierto_frio.bmp').convert_alpha() #Desierto frio #Elevacion muy alta else: tile = pygame.image.load('assets_tutorial_opensimplex/nieve.bmp').convert_alpha() #Nieve #Dibujar la baldosa gameDisplay.blit(tile, (x,y))
Como las losetas de terreno son de 32×32 píxeles no nos va a caber todo el mapa en la pantalla de nuestro ordenador. Tenemos que escribir una función para poder desplazarnos por el mapa mediante el ratón.
¿Cómo lo hacemos? Vamos a utilizar un desfase (offset) que se tendrá en cuenta a la hora de pintar las casillas. Al mover el ratón a uno de los bordes de la pantalla el offset aumentará o disminuirá y esto hará que el mapa se mueva hacia la derecha/izquierda según toque (lo mismo con el movimiento arriba/abajo).
def mover_camara(alto, ancho, offx, offy, dt): #===Funcion para calcular el offset de la camara=== margen = 20 #Distancia del raton al borde a partir de la cual se empieza a mover la camara speed = 400 #Velocidad de la camara #Calcular posicion (x,y) del mouse posicion = pygame.mouse.get_pos() #Calcular el offset a partir de la posicion del mouse if(posicion[0] >= 0 and posicion[0] < margen): offx -= speed*dt #Izquierda elif(posicion[0] <= ancho and posicion[0] > ancho-margen): offx += speed*dt #Derecha if(posicion[1] >= 0 and posicion[1] < margen): offy += speed*dt #Abajo elif(posicion[1] <= alto and posicion[1] > alto-margen): offy -= speed*dt #Arriba #Devolver el offset return offx, offy
La función mover_camara() que acabamos de escribir necesita saber el delta time para calcular la velocidad de la cámara. Por tanto necesitamos otra función que lo calcule:
def calcular_DT(t_ant): #===Funcion para calcular el delta time=== #Calcular el tiempo actual t = pygame.time.get_ticks() #Calcular el deltaTime (en segundos) deltaTime = (t - t_ant) / 1000.0 t_ant = t #Devolver el deltaTime y el tiempo anterior return deltaTime, t_ant
La función main() también recibirá cambios importantes. A cada vuelta de bucle hay que hacer estos cuatro pasos:
- Resetear la ventana, pintándola toda de negro.
- Calcular el delta time y el offset llamando a las funciones calcular_DT() y mover_camara().
- Dibujar todo el mapa, teniendo en cuenta el tamaño de las losetas de terreno y el offset para la posición.
- Comprobar si el jugador intenta salir de la ventana.
def main(): #===Funcion principal del programa=== #Dimensiones del array del mapa alto = 100 ancho = 100 #Generar el mapa aleatorio mapa_altura, mapa_humedad = generar_mapa_ruido(alto, ancho) #Iniciar pygame pygame.init() #Dimensiones de la ventana de pygame donde se dibuja el mapa ancho_ventana = 600 alto_ventana = 600 #Crear la ventana y cambiar su nombre gameDisplay = pygame.display.set_mode((ancho_ventana, alto_ventana)) pygame.display.set_caption('Hic sunt draconis') #Dimensiones de las imagenes de las losetas lx = 32 ly = 32 #Inicializar el offset a cero offx = 0 offy = 0 #Delta time y tiempo anterior (necesarios para el desplazamiento) dt = 0 t_ant = 0 #Bucle principal del "juego": continuar = True while continuar: #1. Resetear la ventana pintandola de negro gameDisplay.fill((0,0,0)) #2. Calcular el offset de la camara dt, t_ant = calcular_DT(t_ant) #calcular el delta time (necesario para calc. velocidad) offx, offy = mover_camara(alto_ventana, ancho_ventana, offx, offy, dt) #calcular el offset #3. Dibujar el mapa for j in range(len(mapa_altura)): for i in range(len(mapa_altura[j])): pintar_casilla(mapa_altura[j][i], mapa_humedad[j][i], i*lx-offx, j*ly+offy, gameDisplay) pygame.display.update() #4. Comprobar si el jugador quiere salir for event in pygame.event.get(): if event.type == pygame.QUIT: continuar = False pygame.quit()
Por último se llama la función main() y después la función quit() para salir del script:
main() quit()
Así pues, este segundo código nos quedará así:
import pygame from opensimplex import OpenSimplex import numpy as np import random def generar_mapa_ruido(ancho, alto): #==Esta funcion genera los mapas de elevacion y humedad== #Crear los objetos opensimplex con una semilla aleatoria gen_altura = OpenSimplex(seed = random.randint(1,500)) gen_humedad = OpenSimplex(seed = random.randint(1,500)) #Valor de la frecuencia (3 es un valor estandar) #Aumentar este valor hace el mismo efecto que un zoom hacia afuera freq = 3 #Array para guardar la elevacion mapa_altura = np.empty([alto, ancho]) mapa_humedad = np.empty([alto, ancho]) #Generar el mapa de terreno mezclando ruido a diferentes octavas for y in range(alto): for x in range(ancho): #Dividir la posicion por la altura nx = 2*x/ancho ny = 2*y/alto #Generar el valor de la posicion actual mapa_altura[y][x] = gen_altura.noise2d(freq*nx, freq*ny) + 0.5 * gen_altura.noise2d(4*freq*nx, 4*freq*ny)+0.25 * gen_altura.noise2d(8*freq*nx, 8*freq*ny) #La humedad la escalaremos para que el rango vaya de 0 a 1 en vez de -1 a 1 mapa_humedad[y][x] = (gen_humedad.noise2d(freq*nx, freq*ny) + 0.5 * gen_humedad.noise2d(4*freq*nx, 4*freq*ny)+0.25 * gen_humedad.noise2d(8*freq*nx, 8*freq*ny))/2.0+0.5 #Devolver el array de los dos mapas return mapa_altura, mapa_humedad def pintar_casilla(elevacion, humedad, x, y, gameDisplay): #===Esta funcion pinta una casilla situada en las coordenadas (X,Y)=== #Mar - Azul oscuro if elevacion < -0.2: tile = pygame.image.load('assets_tutorial_opensimplex/agua2.bmp').convert_alpha() #Aguas costeras - Cyan elif elevacion < 0: tile = pygame.image.load('assets_tutorial_opensimplex/agua1.bmp').convert_alpha() #Playa elif elevacion < 0.10: tile = pygame.image.load('assets_tutorial_opensimplex/desierto_tropical.bmp').convert_alpha() #Elevacion baja elif elevacion < 0.25: if humedad > 0.5: tile = pygame.image.load('assets_tutorial_opensimplex/bosque_tropical.bmp').convert_alpha() #Bosque tropical elif humedad > 0.25: tile = pygame.image.load('assets_tutorial_opensimplex/praderia.bmp').convert_alpha() #Praderia else: tile = pygame.image.load('assets_tutorial_opensimplex/desierto_tropical.bmp').convert_alpha() #Desierto tropical #Elevacion media elif elevacion < 0.5: if humedad > 0.6: tile = pygame.image.load('assets_tutorial_opensimplex/bosque_templado.bmp').convert_alpha() #Bosque templado elif humedad > 0.35: tile = pygame.image.load('assets_tutorial_opensimplex/praderia_templada.bmp').convert_alpha() #Praderia templada elif humedad > 0.25: tile = pygame.image.load('assets_tutorial_opensimplex/praderia.bmp').convert_alpha() #Praderia else: tile = pygame.image.load('assets_tutorial_opensimplex/desierto_templado.bmp').convert_alpha() #Desierto templado #Elevacion alta elif elevacion < 0.75: if humedad > 0.66: tile = pygame.image.load('assets_tutorial_opensimplex/taiga.bmp').convert_alpha() #Taiga elif humedad > 0.33: tile = pygame.image.load('assets_tutorial_opensimplex/matorrales.bmp').convert_alpha() #Matorrales else: tile = pygame.image.load('assets_tutorial_opensimplex/desierto_frio.bmp').convert_alpha() #Desierto frio #Elevacion muy alta else: tile = pygame.image.load('assets_tutorial_opensimplex/nieve.bmp').convert_alpha() #Nieve #Dibujar la baldosa gameDisplay.blit(tile, (x,y)) def mover_camara(alto, ancho, offx, offy, dt): #===Funcion para calcular el offset de la camara=== margen = 20 #Distancia del raton al borde a partir de la cual se empieza a mover la camara speed = 400 #Velocidad de la camara #Calcular posicion (x,y) del mouse posicion = pygame.mouse.get_pos() #Calcular el offset a partir de la posicion del mouse if(posicion[0] >= 0 and posicion[0] < margen): offx -= speed*dt #Izquierda elif(posicion[0] <= ancho and posicion[0] > ancho-margen): offx += speed*dt #Derecha if(posicion[1] >= 0 and posicion[1] < margen): offy += speed*dt #Abajo elif(posicion[1] <= alto and posicion[1] > alto-margen): offy -= speed*dt #Arriba #Devolver el offset return offx, offy def calcular_DT(t_ant): #===Funcion para calcular el delta time #Calcular el tiempo actual t = pygame.time.get_ticks() #Calcular el deltaTime (en segundos) deltaTime = (t - t_ant) / 1000.0 t_ant = t #Devolver el deltaTime y el tiempo anterior return deltaTime, t_ant def main(): #===Funcion principal del programa=== #Dimensiones del array del mapa alto = 100 ancho = 100 #Generar el mapa aleatorio mapa_altura, mapa_humedad = generar_mapa_ruido(alto, ancho) #Iniciar pygame pygame.init() #Dimensiones de la ventana de pygame donde se dibuja el mapa ancho_ventana = 600 alto_ventana = 600 #Crear la ventana y cambiar su nombre gameDisplay = pygame.display.set_mode((ancho_ventana, alto_ventana)) pygame.display.set_caption('Hic sunt draconis') #Dimensiones de las imagenes de las losetas lx = 32 ly = 32 #Inicializar el offset a cero offx = 0 offy = 0 #Delta time y tiempo anterior (necesarios para el desplazamiento) dt = 0 t_ant = 0 #Bucle principal del "juego": continuar = True while continuar: #1. Resetear la ventana pintandola de negro gameDisplay.fill((0,0,0)) #2. Calcular el offset de la camara dt, t_ant = calcular_DT(t_ant) #calcular el delta time (necesario para calc. velocidad) offx, offy = mover_camara(alto_ventana, ancho_ventana, offx, offy, dt) #calcular el offset #3. Dibujar el mapa for j in range(len(mapa_altura)): for i in range(len(mapa_altura[j])): pintar_casilla(mapa_altura[j][i], mapa_humedad[j][i], i*lx-offx, j*ly+offy, gameDisplay) pygame.display.update() #4. Comprobar si el jugador quiere salir for event in pygame.event.get(): if event.type == pygame.QUIT: continuar = False pygame.quit() main() quit()
¡Y por hoy ya hemos trabajado bastante! Esta es la forma más básica de crear mapas aleatorios con Pygame y OpenSimplex, pero podríamos complicarlo mucho más y hacer mapas aún más realistas.
Si tenéis alguna duda o hay algo que no ha quedado claro, dejadme un comentario y os contestaré en breve. Y si os ha parecido interesante, no dudéis en seguirnos en nuestra página de Twitter y/o Facebook para estar al día de nuestras publicaciones.
¡Hasta pronto! 😉
Y los rios?
Lo ve mucho mas complicado.
Alguna idea?
Otra pregunta:
¿como puedo hacer para forzar agua en todos los lados?
me encanta, aprendo mucho con estos tutoriales
¡Me alegro de que te gusten! 🙂