I. Introduction▲
Cette série explique comment générer de manière procédurale des continents dans Unity.
Vous pouvez retrouver les autres épisodes de cette série dans le sommaire dédié.
II. Vidéo▲
Unity - Génération procédurale de terrain - Stockage des paramètres de génération
III. Résumé▲
Ce chapitre se concentre sur des améliorations du code afin de pouvoir ajouter de nouvelles fonctionnalités facilement. Aussi, dans cette vidéo, vous verrez comment sauvegarder les valeurs de votre générateur de terrain.
III-A. Implémentation▲
Dans un dossier « Data » (sous dossier du dossier « Script »), créez un nouveau script « NoiseData.cs » qui hérite de ScriptableObject. Un ScriptableObject permet de définir de nouveaux types de ressource que vous pouvez créer facilement par la suite et qui contiendront les données que vous avez définies au travers du script.
Dans notre cas, nous allons définir toutes les variables nécessaires pour la génération du bruit :
using UnityEngine;
using System.Collections;
[CreateAssetMenu()]
public class NoiseData : UpdatableData {
public Noise.NormalizeMode normalizeMode;
public float noiseScale;
public int octaves;
[Range(0,1)]
public float persistance;
public float lacunarity;
public int seed;
public Vector2 offset;
protected override void OnValidate() {
if (lacunarity < 1) {
lacunarity = 1;
}
if (octaves < 0) {
octaves = 0;
}
base.OnValidate ();
}
}La fonction OnValidate() est aussi déplacée, car elle permet la cohérence des données choisies par l'utilisateur.
De même, un deuxième script, TerrainData.cs, est créé pour conserver les données de la génération du terrain :
using UnityEngine;
using System.Collections;
[CreateAssetMenu()]
public class TerrainData : UpdatableData {
public float uniformScale = 2.5f;
public bool useFlatShading;
public bool useFalloff;
public float meshHeightMultiplier;
public AnimationCurve meshHeightCurve;
}Pour utiliser ces nouvelles classes, vous devez ajouter des références à celles-ci dans MapGenerator.cs :
public TerrainData terrainData;
public NoiseData noiseData;Évidemment, toutes les références aux variables qui ont été déplacées doivent être mises à jour afin d'utiliser ces structures. Il est nécessaire de faire de même pour le fichier EndlessTerrain.cs.
Ensuite, il est nécessaire de créer les données NoiseData et TerrainData à assigner à l'objet de la scène. Ces ressources sont placées dans un nouveau dossier « Terrain Assets ».
III-B. Génération à la volée▲
Suite à cette modification des scripts, le terrain n'est plus mis à jour lorsque les données changent. Pour corriger cela, un troisième script est créé : UpdatableData.cs. Voici son contenu :
using UnityEngine;
using System.Collections;
public class UpdatableData : ScriptableObject {
public event System.Action OnValuesUpdated;
public bool autoUpdate;
protected virtual void OnValidate() {
if (autoUpdate) {
NotifyOfUpdatedValues ();
}
}
public void NotifyOfUpdatedValues() {
if (OnValuesUpdated != null) {
OnValuesUpdated ();
}
}
}Cette classe est héritée par NoiseData et TerrainData. Elle définit un événement (« System.Action ») qui est appelé s'il est défini. La fonction OnValidate() est appelée, car une nouvelle valeur est disponible et donc, qu'il faut notifier de ce changement.
Aussi, nous ajoutons un nouveau bouton dans l'éditeur, utile si la mise à jour automatique est désactivée. Cela se fait à travers la création d'un nouveau script, UpdatableDataEditor.cs, héritant de la classe Editor :
using UnityEngine;
using System.Collections;
using UnityEditor;
[CustomEditor (typeof(UpdatableData), true)]
public class UpdatableDataEditor : Editor {
public override void OnInspectorGUI ()
{
base.OnInspectorGUI ();
UpdatableData data = (UpdatableData)target;
if (GUILayout.Button ("Update")) {
data.NotifyOfUpdatedValues ();
}
}
}L'option true de l'attribut CustomEditor permet d'indiquer que ce code doit être appliqué aussi aux classes héritant de UpdatableData. Sans cette option, NoiseData et TerrainData ne pourraient avoir le bouton nouvellement défini.
Finalement, dans MapGenerator.cs, il est nécessaire de connecter l'événement indiquant le changement des valeurs à une fonction :
void OnValuesUpdated() {
if (!Application.isPlaying) {
DrawMapInEditor ();
}
}Ainsi connectée :
void OnValidate() {
if (terrainData != null) {
terrainData.OnValuesUpdated -= OnValuesUpdated;
terrainData.OnValuesUpdated += OnValuesUpdated;
}
if (noiseData != null) {
noiseData.OnValuesUpdated -= OnValuesUpdated;
noiseData.OnValuesUpdated += OnValuesUpdated;
}
falloffMap = FalloffGenerator.GenerateFalloffMap (mapChunkSize);
}Afin de ne pas connecter plusieurs fois la fonction à l'événement, on déconnecte l'ancienne connexion avant de faire la nouvelle connexion.
III-C. Amélioration du code▲
Pour la suite de la série, nous supprimons les données des couleurs et donc, les types de terrains. La structure MapData devient donc :
public struct MapData {
public readonly float[,] heightMap;
public MapData (float[,] heightMap)
{
this.heightMap = heightMap;
}
}Par conséquent, la fonction GenerateMapData est tronquée :
MapData GenerateMapData(Vector2 centre) {
float[,] noiseMap = Noise.GenerateNoiseMap (mapChunkSize + 2, mapChunkSize + 2, noiseData.seed, noiseData.noiseScale, noiseData.octaves, noiseData.persistance, noiseData.lacunarity, centre + noiseData.offset, noiseData.normalizeMode);
for (int y = 0; y < mapChunkSize+2; y++) {
for (int x = 0; x < mapChunkSize+2; x++) {
if (terrainData.useFalloff) {
noiseMap [x, y] = Mathf.Clamp01 (noiseMap [x, y] - falloffMap [x, y]);
}
}
}
}
return new MapData (noiseMap);
}Ainsi que DrawMapInEditor :
public void DrawMapInEditor() {
MapData mapData = GenerateMapData (Vector2.zero);
MapDisplay display = FindObjectOfType<MapDisplay> ();
if (drawMode == DrawMode.NoiseMap) {
display.DrawTexture (TextureGenerator.TextureFromHeightMap (mapData.heightMap));
} else if (drawMode == DrawMode.Mesh) {
display.DrawMesh (MeshGenerator.GenerateTerrainMesh (mapData.heightMap, terrainData.meshHeightMultiplier, terrainData.meshHeightCurve, editorPreviewLOD,terrainData.useFlatShading));
} else if (drawMode == DrawMode.FalloffMap) {
display.DrawTexture(TextureGenerator.TextureFromHeightMap(FalloffGenerator.GenerateFalloffMap(mapChunkSize)));
}
}La classe MapDisplay est aussi impactée :
using UnityEngine;
using System.Collections;
public class MapDisplay : MonoBehaviour {
public Renderer textureRender;
public MeshFilter meshFilter;
public MeshRenderer meshRenderer;
public void DrawTexture(Texture2D texture) {
textureRender.sharedMaterial.mainTexture = texture;
textureRender.transform.localScale = new Vector3 (texture.width, 1, texture.height);
}
public void DrawMesh(MeshData meshData) {
meshFilter.sharedMesh = meshData.CreateMesh ();
meshFilter.transform.localScale = Vector3.one * FindObjectOfType<MapGenerator> ().terrainData.uniformScale;
}
}L'auteur en profite pour dimensionner le modèle à la taille indiquée dans TerrainData.
Finalement, la classe EndlessTerrain.cs doit aussi être modifiée :
void OnMapDataReceived(MapData mapData) {
this.mapData = mapData;
mapDataReceived = true;
UpdateTerrainChunk ();
}III-D. Bogue de bordure▲
Lors de l'utilisation de la carte de diminution, un bogue apparaît en bordure de terrain. Le problème est provoqué par la différence de taille entre la carte de diminution et la carte des bruits.
La méthode MapGenerator::Awake() est supprimée. De même, la génération de la carte de diminution dans la méthode OnValidate() est supprimée. Cela est remplacé en générant la carte dans la fonction GenerateMapData() :
MapData GenerateMapData(Vector2 centre) {
float[,] noiseMap = Noise.GenerateNoiseMap (mapChunkSize + 2, mapChunkSize + 2, noiseData.seed, noiseData.noiseScale, noiseData.octaves, noiseData.persistance, noiseData.lacunarity, centre + noiseData.offset, noiseData.normalizeMode);
if (terrainData.useFalloff) {
if (falloffMap == null) {
falloffMap = FalloffGenerator.GenerateFalloffMap (mapChunkSize + 2);
}
for (int y = 0; y < mapChunkSize+2; y++) {
for (int x = 0; x < mapChunkSize+2; x++) {
if (terrainData.useFalloff) {
noiseMap [x, y] = Mathf.Clamp01 (noiseMap [x, y] - falloffMap [x, y]);
}
}
}
}
return new MapData (noiseMap);
}III-E. Données des textures▲
La modification effectuée précédemment sur les données de bruit et de terrain est aussi appliquée sur les données des textures. Ainsi, un nouveau script TerrainData.cs est créé :
using UnityEngine;
using System.Collections;
public class TextureData : UpdatableData {
public void ApplyToMaterial(Material material) {
//
}
}Évidemment, cette nouvelle classe doit être instanciée et utilisée dans la classe MapGenerator.cs comme cela a été fait pour NoiseData et TerrainData. Aussi, une nouvelle fonction est ajoutée pour la mise à jour des données :
void OnTextureValuesUpdated() {
textureData.ApplyToMaterial (terrainMaterial);
}Cette fonction est connectée à l'événement, dans la fonction OnValidate() :
if (textureData != null) {
textureData.OnValuesUpdated -= OnTextureValuesUpdated;
textureData.OnValuesUpdated += OnTextureValuesUpdated;
}IV. Commenter▲
Vous pouvez commenter et donner vos avis dans la discussion associée sur le forum.



