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 :
meshCollider =
meshObject.
AddComponent<
MeshCollider>(
);
Et dans la fonction de mise à jour du terrain UpdateTerrainChunk, nous assignons le modèle au MeshCollider :
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 :
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 :
using
UnityStandardAssets.
CrossPlatformInput
Et remplacer le code de la fonction Update() :
private
void
Update
(
)
{
RotateView
(
);
if
(
Input.
GetButtonDown
(
"Jump"
) &&
!
m_Jump)
{
m_Jump =
true
;
}
}
Ainsi que dans la fonction GetInput() :
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 :
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 :
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 :
[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 :
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 :
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 :
Vector3[]
bakedNormals;
Ainsi qu'une nouvelle fonction :
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.