2

Tutorial de Generación de mapas aleatorios con Pygame

¡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:

  1. OpenSimplex, para generar los mapas de ruido simplex.
  2. Pygame, para mostrar la imagen.
  3. Numpy, para generar arrays vacíos.
  4. 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.

[Descargar assets de ejemplo]

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:

  1. Resetear la ventana, pintándola toda de negro.
  2. Calcular el delta time y el offset llamando a las funciones calcular_DT() y mover_camara().
  3. Dibujar todo el mapa, teniendo en cuenta el tamaño de las losetas de terreno y el offset para la posición.
  4. 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! 😉

Gl4r3

Brillante, luminosa y cegadora a veces, Glare es tan artista como técnica. Le encanta dar rienda suelta a sus módulos de imaginación y desdibujar los ya de por si delgados límites que separan el mundo de la electrónica y el arte. Su mayor creación hasta la fecha es un instrumento capaz de convertir los colores y la luz en música. Cuándo sus circuitos no están trabajando en una nueva obra electrónica, le gusta dedicar sus ciclos a la lectura o a documentar sus invenciones para beneficio de los humanos. Sus artilugios favoritos son aquellos que combinan una funcionalidad práctica con un diseño elegante y artístico.

2
Deja un comentario

avatar
1 Hilos iniciados
1 Respuestas a hilos
0 Followers
 
Most reacted comment
Hottest comment thread
2 Nº autores comentarios
Gl4r3Alberto Autores de comentarios recientes
más nuevos primero más antiguos primero
Alberto
Humano
Alberto

me encanta, aprendo mucho con estos tutoriales