Unity - Génération procédurale de terrain

Collisions

L'auteur

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

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 - Collisions


III. Résumé

Dans cet épisode, nous ajoutons les collisions au terrain afin de permettre, par exemple, d'avoir un personnage se déplaçant sur le terrain en marchant.

III-A. Implémentation

Dans la classe TerrainChunk du fichier EndlessTerrain.cs, nous ajoutons aux données du terrain un MeshCollider. Dans le constructeur, nous créons le composant :

 
Sélectionnez
meshCollider = meshObject.AddComponent<MeshCollider>();

Et dans la fonction de mise à jour du terrain UpdateTerrainChunk, nous assignons le modèle au MeshCollider :

 
Sélectionnez
meshCollider.sharedMesh = lodMesh.mesh;

III-B. Amélioration du programme

Nous souhaitons cacher automatiquement la prévisualisation du modèle lorsque nous entrons en mode « Jeu ». Pour cela, créez un nouveau script HideOnPlay.cs :

 
Sélectionnez
using UnityEngine;
using System.Collections;

public class HideOnPlay : MonoBehaviour {
    void Start () {
        gameObject.SetActive (false);
    }
    
    void Update () {
    
    }
}

Il ne reste plus qu'à l'assigner au modèle de prévisualisation.

III-C. Test

III-C-1. Importation du personnage

Pour tester les collisions, il faut importer le paquet « Character ».

Tous les composants du paquet ne sont pas importés. Lors de l'importation, désélectionnez :

  • Editor
  • Standard Assets → FirstPersonCharacter → Audio
  • Standard Assets → FirstPersonCharacter → FirstPersonCharacterGuidelines.txt
  • Standard Assets → FirstPersonCharacter → Prefabs → FPSController.prefab
  • Standard Assets → FirstPersonCharacter → Scripts → FirstPersonController.cs
  • Standard Assets → FirstPersonCharacter → Scripts → HeadBob.cs
  • Standard Assets → FirstPersonCharacter → RollerBall
  • Standard Assets → FirstPersonCharacter → ThirdParsonCharacter
  • Standard Assets → CrossPlatformInput
  • Standard Assets → Utility

Ensuite, il est nécessaire d'éditer le script RigidBodyFirstPersonController.cs nouvellement importé et supprimer les références aux autres scripts :

 
Sélectionnez
using UnityStandardAssets.CrossPlatformInput

Et remplacer le code de la fonction Update() :

 
Sélectionnez
private void Update()
{
    RotateView();

    if (Input.GetButtonDown("Jump") && !m_Jump)
    {
        m_Jump = true;
    }
}

Ainsi que dans la fonction GetInput() :

 
Sélectionnez
private Vector2 GetInput()
{
    Vector2 input = new Vector2
        {
            x = Input.GetAxis("Horizontal"),
            y = Input.GetAxis("Vertical")
        }
    movementSettings.UpdateDesiredTargetSpeed(input);
    return input;
}

Il est nécessaire de faire de même dans le script MouseLook.cs en supprimant la ligne :

 
Sélectionnez
using UnityStandardAssets.CrossPlatformInput

Et en remplaçant, dans la fonction LookRotation(), les mentions de CrossPlatformInputManager.

III-C-2. Mettre en pause dans l'éditeur

Nous souhaitons mettre en pause le jeu grâce à une touche du clavier. Pour ce faire, nous modifions la fonction Update() du script RigidBodyFirstPersonController.cs :

 
Sélectionnez
private void Update()
{

    if (Input.GetKeyDown (KeyCode.Escape)) {
        Debug.Break ();
    }

    RotateView();

    if (Input.GetButtonDown("Jump") && !m_Jump)
    {
        m_Jump = true;
    }
}

III-C-3. Configuration

Il ne reste plus qu'à placer le prefab « RigidBodyFPSController » dans la scène. Dans le composant « MainCamera », supprimer le script associé à cet objet.

Si vous souhaitez vous déplacer plus vite, vous pouvez modifier les propriétés offertes par le script de l'objet « RigidBodyFPSController ». Une autre possibilité est de changer le facteur de redimensionnement (« scale ») dans le script EndlessTerrain.cs.

Finalement, désactivez l'objet Viewer et assignez l'objet « RigidBodyFPSController » comme « Viewer » du script EndlessTerrain.cs.

III-C-4. Analyse des performances

Si vous naviguez dans le terrain, vous remarquez des latences. Le profileur de performances (« Window → Profiler ») confirme ce ressenti en affichant des pics sur le graphe d'utilisation du CPU.
En appuyant sur « Échap », le jeu se met en pause (grâce à ce codeMettre en pause dans l'éditeur) permettant ainsi d'analyser la raison des pics d'utilisation du CPU. On remarque que cela provient de l'exécution de Mesh.Base PhysX CollisionData. Pour réduire cela, il faut réduire les données fournies au moteur physique. Pour ce faire, nous allons mettre en place deux améliorations :

  • ne générer les données de collision que si le joueur est à côté du morceau de terrain ;
  • générer les données de collision en utilisant un niveau de détails plus faible.

III-C-5. Optimisations des collisions

Première, un booléen est ajouté à la structure LODInfo afin de permettre de configurer le niveau de détail qui doit être utilisé pour la génération des collisions :

EndlessTerrain.cs
Sélectionnez
[System.Serializable]
public struct LODInfo {
    public int lod;
    public float visibleDstThreshold;
    public bool useForCollider;
}

Ensuite, dans le constructeur TerrainChunk, nous assignons une référence vers le niveau de détail à utiliser pour les collisions :

 
Sélectionnez
LODMesh collisionLODMesh;

public TerrainChunk(Vector2 coord, int size, LODInfo[] detailLevels, Transform parent, Material material) {
    this.detailLevels = detailLevels;

    position = coord * size;
    bounds = new Bounds(position,Vector2.one * size);
    Vector3 positionV3 = new Vector3(position.x,0,position.y);

    meshObject = new GameObject("Terrain Chunk");
    meshRenderer = meshObject.AddComponent<MeshRenderer>();
    meshFilter = meshObject.AddComponent<MeshFilter>();
    meshCollider = meshObject.AddComponent<MeshCollider>();
    meshRenderer.material = material;

    meshObject.transform.position = positionV3 * scale;
    meshObject.transform.parent = parent;
    meshObject.transform.localScale = Vector3.one * scale;
    SetVisible(false);

    lodMeshes = new LODMesh[detailLevels.Length];
    for (int i = 0; i < detailLevels.Length; i++) {
        lodMeshes[i] = new LODMesh(detailLevels[i].lod, UpdateTerrainChunk);
        if (detailLevels[i].useForCollider) {
            collisionLODMesh = lodMeshes[i];
        }
    }

    mapGenerator.RequestMapData(position,OnMapDataReceived);
}

Évidemment, il est nécessaire de modifier la fonction UpdateTerrainChunk() pour obtenir le modèle adéquat pour les collisions :

 
Sélectionnez
public void UpdateTerrainChunk() {
    if (mapDataReceived) {
        float viewerDstFromNearestEdge = Mathf.Sqrt (bounds.SqrDistance (viewerPosition));
        bool visible = viewerDstFromNearestEdge <= maxViewDst;

        if (visible) {
            int lodIndex = 0;

            for (int i = 0; i < detailLevels.Length - 1; i++) {
                if (viewerDstFromNearestEdge > detailLevels [i].visibleDstThreshold) {
                    lodIndex = i + 1;
                } else {
                    break;
                }
            }

            if (lodIndex != previousLODIndex) {
                LODMesh lodMesh = lodMeshes [lodIndex];
                if (lodMesh.hasMesh) {
                    previousLODIndex = lodIndex;
                    meshFilter.mesh = lodMesh.mesh;
                } else if (!lodMesh.hasRequestedMesh) {
                    lodMesh.RequestMesh (mapData);
                }
            }

            if (lodIndex == 0) {
                if (collisionLODMesh.hasMesh) {
                    meshCollider.sharedMesh = collisionLODMesh.mesh;
                } else if (!collisionLODMesh.hasRequestedMesh) {
                    collisionLODMesh.RequestMesh (mapData);
                }
            }

            terrainChunksVisibleLastUpdate.Add (this);
        }

        SetVisible (visible);
    }
}

III-C-6. Amélioration de la génération des normales

Le profileur indique aussi une grande consommation de CPU lors de la génération des normales, au travers de la fonction CalculateNormals() mise en place dans l'épisode précédent.
Pour diminuer l'impact sur les performances, ce calcul est déplacé dans un autre thread, permettant ainsi son exécution sans pour autant bloquer le jeu. D'abord, cela nécessite une nouvelle variable dans la classe MeshData :

 
Sélectionnez
Vector3[] bakedNormals;

Ainsi qu'une nouvelle fonction :

 
Sélectionnez
public void BakeNormals() {
    bakedNormals = CalculateNormals ();
}

La fonction est appelée à la fin de la fonction GenerateTerrainMesh(). Quant à la fonction CreateMesh(), elle utilise dorénavant la variable bakedNormals.

IV. Commenter

Vous pouvez commenter et donner vos avis dans la discussion associée sur le forum.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2018 Unity Technologies. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.