INCAYA

Générer une carte aléatoire en Go

Golang est performant, c'est certainement ce qui peut le faire préférer à Python pour certaines tâches un peu gourmandes.

Et c’est le cas de la manipulation d’images. Pour se faire la main, voyons comment générer une image PNG à base de bruit de Perlin, puis comment la transformer en carte aléatoire pour notre prochain jeu vidéo en 2D.

Les notions et ressources importantes pour ce projet

  • savoir comment se présente une image matricielle : un tableau de pixels dont la couleur est codifiée.
  • comprendre le principe du bruit de Perlin : “une texture procédurale utilisée comme effet visuel pour augmenter le réalisme apparent dans la synthèse d’image
  • la bibliothèque image de Go
  • le module go-perlin, une implémentation en Go d’un générateur de bruit de Perlin

Les étapes

  • générer une image d’une certaine dimension (longueur/largeur) ;
  • attribuer aux pixels de cette image des valeurs de gris tirées du générateur de bruit de Perlin : c’est un bruit de gradient, la couleur de chaque pixel varie donc progressivement en fonction de ceux qui l’entourent ;
  • convertir chaque niveau de gris (256 classes) en une couleur choisie parmi 6 classes discrètes réparties équitablement entre 0 et 255 ;
  • enregistrer l’image ainsi générée.

Le code

package main

import (
	"fmt"
	"os"
	"image"
	"image/png"
	"image/color"
	"math/rand"
	"github.com/aquilax/go-perlin"
)

// Retourne un entier pseudo-aléatoire entre min et max
func randInt(min int, max int) int {
    return min + rand.Intn(max-min)
}

// Enregistre une image RGBA au format PNG
func savePNGImage(img *image.RGBA, path string)(error){
	fg,err:= os.Create(path)
	if err!=nil{
		fmt.Println("Error creating file:",err)
	}
	defer fg.Close()
	err = png.Encode(fg, img)
	if err != nil {
		fmt.Println("Error handling png:",err)
	}
	return err
}

// Transforme une image RGBA en carte
func imageToTerrain(img *image.RGBA)(*image.RGBA){
	levels := []color.RGBA{{0, 156, 196, 255}, {128, 229, 255, 255}, {222, 205, 135, 255}, {154, 132, 42, 255}, {250, 250, 250, 255}, {255,255,255, 255}}
	for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
        for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
            c := color.GrayModel.Convert(img.At(x, y)).(color.Gray)
            level := c.Y / 51
			var terrain_type color.RGBA = levels[level]
        	img.Set(x, y, terrain_type)
        }
    }
	return img
}

// Génére une image à base de bruit de Perlin
func generateImage(img_width int, img_height int)(*image.RGBA){
	genImage := image.NewRGBA(image.Rect(0, 0, img_width, img_height))
	randSeed := int64(randInt(1, 2000))
	const (
		alpha       = 2.
		beta        = 2.
		n           = 3
	)

	p := perlin.NewPerlin(alpha, beta, n, randSeed)

	for x:=0; x<((img_width)*4); x+= 4 {
		for y:=0; y<((img_height)); y++ {
			noise := p.Noise2D(float64(x)/400,float64(y)/100)
			level := (noise+1)/2*255
			genImage.Pix[(y*img_width*4)+x] = uint8(level)
			genImage.Pix[(y*img_width*4)+x+1] = uint8(level)
			genImage.Pix[(y*img_width*4)+x+2] = uint8(level)
			genImage.Pix[(y*img_width*4)+x+3] = 255
		}
    }  
	return genImage
}

func main() {
	generated_img := generateImage(400, 400)
	savePNGImage(generated_img, "./perlin_generated.png")
	generated_terrain := imageToTerrain(generated_img)
	savePNGImage(generated_terrain, "./perlin_terrain_generated.png")
}

N.B. : si ce code vous inspire, pensez à implémenter une gestion sérieuse des erreurs !

Le résultat

Voici d’abord le bruit de Perlin “brut”, qui sert de base à la carte :

bruit de perlin

Puis la carte correspondante, qui s’appuie sur quelques niveaux de couleurs :

carte aléatoire

Conclusion

Golang 🐭, une sorte de Python 🐍 performant et rigoureux ? C’est un peu la sensation qu’on a quand on s’attaque à des traitements exigeants.

Plus sérieusement, il semble que ces langages puissent constituer ensemble un outillage tout-terrain assez intéressant. A Python 🐍, l’écosystème foisonnant et la preuve de concept ultra rapide. A Golang 🐭, l’exigence acceptable et la performance.

Prochaine étape : les faire travailler ensemble. Et nous ne sommes pas les premiers à y penser :