Générer une carte aléatoire en Go
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 :
Puis la carte correspondante, qui s’appuie sur quelques niveaux de couleurs :
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 :