Plus....

Beginning Game Development

Partie VII –Terrain et détection de collisions

Traduit par Valentin BILLOTTE

Consultez cet article en anglais  

Cet article n’a pas été traduit par Microsoft. Il n’a pas été relu ou vérifié par Microsoft.

Sur cette page

Introduction Introduction
nettoyage de code nettoyage de code
Répondre aux différents événements liés au Device Répondre aux différents événements liés au Device
Textures Textures
Procedural Terrain Generation Procedural Terrain Generation
Nouvelle Caméra Nouvelle Caméra
Terrain Elevation Terrain Elevation
Conclusion Conclusion

Introduction

Bienvenue dans le septième article consacré à l’apprentissage de la programmation de jeux. Dans le précédent article nous avons vu comment les lumières et les materials peuvent ajouter du réalisme à la scène. Nous avons vu aussi comment créer un terrain à partir d’une heightmap. Enfin nous avons appris a ajouter du texte dans l’application. Nous allons maintenant voir comment modifier notre terrain et l’incorporer pleinement à notre jeu.

Avant de commencer, faisons un léger nettoyage de code.

Haut de page Haut de page

Nettoyage de code

Le nettoyage pour cet article consiste principalement en de légères modifications de code afin de le garder propre. Nous avons aussi fait quelques modifications aux lumières et aux normales pour l’éclairage.

  • Nous répondons à l’événement DeviceResizing.
  • Nous gérons les events du Device (reportez vous à la prochaine section).
  • J’ai change la lumière ambiante et diffuse, pour une lumière plus sombre. Utiliser une couleur blanche en lumière ambiante éclaire à 100% la scène. Ceci empêche les autres couleurs d’être visibles. J’utilise à la place un Color.FromArgb ( 64, 64, 64 ) pour la couleur ambiante et un Color.FromArgb ( 128, 128, 128 ) pour la couleur diffuse.
  • Ajout d’un device.RenderState.NormalizeNormals = true; de manière à ce que les normales soient recalculées lorsque la taille du terrain est modifiées. Vous n’avez pas besoin d’ajouter cette instruction si vous ne modifiez pas la taille du terrain.
  • Dans le dernier article j’ai indiqué que presentParams.AutoDepthStencilFormat = DepthFormat.D16; spécifiait un DepthStencil, cela est faut, cette instruction spécifie un DepthBuffer. Pour plus de détails reportez vous aux liens suivants :
Haut de page Haut de page

Répondre aux différents événements liés au Device

Jusqu’à aujourd’hui nous pensions qu’une fois le device initialisé, nous n’avions plus à y toucher. En vérité cela est faux. Le device est particulièrement couplé à la vie de la fenêtre à laquelle il est lié, et nous pouvons le perdre très facilement face à un Alt-tab d’une application à une autre, ou en réduisant la fenêtre de jeu.

Une fois que le device est perdu, toutes les méthodes de render vont échouer sans provoquer d’erreur jusqu’à ce que le device soit réinitialisé. Par contre, tout appel à la méthode Device.Present provoquera une exception.

On trouve deux exceptions en rapport avec la perte d’un device :

DeviceLostException: Le device est perdu et ne peut pas être reinitialisé.

DeviceNotResetException: Le device est perdu et peut être reinitialisé.

Pour détecter la perte d’un device, DirectX fournit l’événement DeviceLost dans la classe Device Une fois que le device est perdu il doit être réinitialisé pour qu’il fonctionne à nouveau de manière correcte. Vous pouvez savoir si un device est dans un état perdu en utilisant la propriété CheckCopperativeLevel du device.

Une fois que le device est perdu, l’application va déterminer s’il peut être restauré. Si oui, toute la mémoire vidéo placé dans le Pool.Default et la chaine de swap sont supprimées et la méthode Reset appelée. Les ressources créées dans le Pool.Managed sont sauvegardée en mémoire système et ne nécessite pas d’être recréées. Si le device ne peut être réinitialisé, alors l’application attend qu’il soit restauré.

La méthode Reset est la seule méthode qui peut être appelée lorsque le device est perdu. Elle est aussi la seule méthode qui peut changer l’état d’un device de perdu, à opérationnel. La méthode Reset échouera jusqu’à ce que les ressources allouées soient libérées. Ainsi les ressources créées dans l’événement DeviceReset doivent être celles qui sont détruite dans l’événement DeviceLost.

Assez de théorie, nous devons mettre à jour BattleTank2005 pour détecter les événements reset et last du device et leur donner une répondre.

A la fin de la méthode ConfigureDevice de la classe GameEngine ajoutez le code suivant :

Visual C#

_device.DeviceReset+=new EventHandler(OnDeviceReset);
_device.DeviceLost+=new EventHandler(OnDeviceLost);
_device.Disposing+=new EventHandler(OnDeviceDisposing);
OnDeviceReset ( this, null );

Visual Basic

AddHandler m_device.DeviceLost, AddressOf OnDeviceLost AddHandler m_device.DeviceReset,
AddressOf OnDeviceReset AddHandler m_device.Disposing,
AddressOf OnDeviceDisposing OnDeviceReset(Me, Nothing)

Ajoutez maintenant les trois méthodes suivantes à la classe GameEngine:

Visual C#

private void OnDeviceReset(object sender, EventArgs e)
{
if ( _font != null ) _font.OnResetDevice ( );
// Turn on some low level ambient light
_device.RenderState.Ambient = Color.LightGray;
}
private void OnDeviceLost(object sender, EventArgs e)
{
if ( _font != null && _font.Disposed == false ) _font.OnLostDevice ( );
}
private void OnDeviceDisposing(object sender, EventArgs e)
{
if ( _mouse != null ) _mouse.Dispose ( );
if ( _keyboard !=null ) _keyboard.Dispose ( );
if ( _terrain != null ) terrain.Dispose ( );
}

Visual Basic

Private Sub OnDeviceReset(ByVal sender As Object, ByVal e As EventArgs)
If (Not (m_font) Is Nothing) Then m_font.OnResetDevice() End If
m_device.Transform.Projection = m_camera.Projection
' Turn on some low level ambient light
m_device.RenderState.Ambient = Color.FromArgb(64, 64, 64)
m_device.RenderState.NormalizeNormals = True
End Sub
Private Sub OnDeviceLost(ByVal sender As Object,ByVal e As EventArgs)
If Not (m_font Is Nothing)
AndAlso m_font.Disposed = False
Then m_font.OnLostDevice()
End If
End Sub
Private Sub OnDeviceDisposing(ByVal sender As Object, ByVal e As EventArgs)
If (Not (m_mouse) Is Nothing)
Then m_mouse.Dispose() End If
If (Not (m_keyboard) Is Nothing)
Then m_keyboard.Dispose() End If
If (Not (m_terrain) Is Nothing)
Then m_terrain.Dispose()
End If
End Sub
Haut de page Haut de page

Textures

A la fin du précédent article, nous avions utilise une seule texture et étiré celle-ci sur tout le terrain. Les texture nous donnent la possibilité de plaquer une image sur n’importe quelle surface/forme 3D.

Lorsqu’on applique une texture 2D à une forme 3D, nous devons savoir comment la texture va être plaquée. Nous devons donc déterminer comment les coordonnées de la texture vont correspondre aux coordonnées de la forme. Nous faisons ceci en associant à chaque vertex de la forme les coordonnées d’un point sur la texture. Les valeurs Tu et Tv ne sont rien d’autre que les coordonnées X/Y de la texture (une paire u.v est appelée un texel). Les coordonnées d’une texture sont en deux dimensions et sont comprises entre 0.0f et 1.0f.

Illustration 1

Dans le code, nous associons les coordonnées de la texture aux coordonnées du mesh en divisant les coordonnées de celui-ci par le nombre de cases du terrain. Par ce fait nous avions un texel unique associé à chaque case du terrain.

Visual C#

vertex.Tu = (float)x / _numberOfQuadsX; vertex.Tv = (float)z / _numberOfQuadsZ

Visual Basic

vertex.Tu = CType(x, Single) / _numberOfQuadsX vertex.Tv = CType(z, Single) / _numberOfQuadsZ

Nous aurions pu aussi utiliser plusieurs textures en même temps. C’est ce qu’on appelle le multitexturing. Il s’agit là d’un sujet vaste et intéressant pour quiconque veut se lancer dans la création de terrains réalistes. Nous n’utilisons qu’une seule texture pour l’heure, mais le multitexturing explique la valeur 0 que nous passons à la méthode SetTexture dans la méthode Render de la classe Terrain. La valeur 0 représente nous l’aurons compris la première texture.

Visual C#

_device.SetTexture ( 0, _terrainTexture );

Visual Basic

_device.SetTexture(0, _terrainTexture)

DirectX utilise la technique dite du “Filtering” pour éviter tout effet de distorsion néfaste lorsqu’une texture est agrandie ou réduite lors de son placage sur une forme 3D. Vous pouvez expérimenter cela en changeant la valeur de SamplerStageStates passée à la méthode Device.SetSamplerState. Là encore le choix du SamplerStageStates impacte sur la qualité de l’affichage ou sur la vitesse du jeu.

Une autre solution au problème de distorsion est l’utilisation de mipmaps. Cette approche utilise une série de texture classée par taille/qualité. Si vous utilisez la méthode FromFile de la classe TextureLoader, DirectX génère automatiquement une chaine de mipmap. DirectX va faire cela sans avoir aucune connaissance de la sémantique d’une image. La réduction qu’il va opérer sur une image va peut être occulter un détail qu’il faudrait garder bien visible.

Le résultat que nous avons obtenu avec le fichier Down.jpg dans BatteTank2005 était passable, mais excessivement fastidieux. Un meilleur choix serait d’utiliser un programme externe pour créer une texture adapté à la spécificité géographique de notre terrain, mais que faire si nous voulons générer aléatoirement un terrain pour chaque niveau de jeu ? La réponse réside dans la technique du « procedural terrain generation ».

Haut de page Haut de page

Procedural Terrain Generation

La texture que nous avons utilise à la base de notre skybox et la coloration et l’éclairage que nous avons utilisé n’a pas vraiment de rapport avec le terrain que nous avons créé à partir de la heightmap. Une autre solution consiste à utiliser la même technique mais avec une texture spécifiquement créée pour cette heightmap (ou plus précisément la texture et la heightmap sont créées de concert) à l’aide d’un programme comme Terragen. Cette technique vous évite d’avoir à vous soucier de la texture appliqué sur votre territoire et vous simplifie grandement la tâche, surtout si vous avez à créer un grand nombre de niveau de difficultés.

Une autre approche consiste à générer la texture à l’aide d’une heightmap et un ensemble de textures par l’intermédiaire d’une technique nommée "Procedural Texture Generation.". Avec cette approche vous pouvez utiliser des textures pour représenter le terrain à différentes altitudes. Par exemple, une texture représentant du sable pourrait être utilisée pour représenter les altitudes faibles. Une texture d’herbe pourra être utilisée pour les textures justes au dessus. Au final les texture de neige pourraient être utilisé pour les altitudes les plus élevées.

Il nous faut donc séparer le terrain en régions déterminées par leur altitude. Nous avons vu dans le dernier article que nous avions 256 altitudes possibles à l’aide d’une heightmap basé sur une image en noir et blanc. En divisant cette valeur par 4, alors chaque région possède une différence de niveau de 64 unités.

Chacune de ces régions est associée avec un fichier texture permettant au terrain de prendre la texture voulue suivant l’altitude.
Nous devons ajouter une petite amélioration à ce principe. Plutôt que d’utiliser la même texture à chaque élévation d’une région, nous devrions effectuer un mélange (blend) de texture afin de passer d’une texture à une autre (passage entre deux régions) de manière progressive et non brutale. Nous effectuons cela en calculant le % d’élévation.

Pour créer ce système de texture intelligente, j’ai ajouté une solution nommée TerrainGenerator. Cette application console prend quatre textures et une heightmap au format RAW et créé la texture du terrain. Si vous utilisiez une méthode automatique de génération de heightmap (j’en parlais dans le précédent article) vous pourriez combiner le tout pour générer le terrain de A à Z de manière automatique. Je vous laisse le soin de faire cela.

Tout le code se trouve dans la méthode principale qui s’occupe de gérer les parameters en entrée et effectues divers tests que je n’aborderai pas ici. Le point central de notre application est la méthode CreateTerrainTexture. Après avoir initialisé quelques tableaux et variables pour stockers les différentes données nécessaires, nous entrons dans une double boucle qui va réaliser le travail de génération :

Visual C#

for ( int z = 0; z < _numberOfVerticesZ; z=""++ )
{
for="" ( int="" x = ""0""; x=""< _numberOfVerticesX; x=""++ )
{ //Implementationducodeplusbas
}
}

Visual Basic

Dim z As Integer = 0
While z < _numberOfVerticesZ
Dim="" x="" As="" Integer = ""0"" While="" x=""
< _numberOfVerticesX
'Implementation code below
x = x + 1
End While
z = z + 1
End While

La première étape consiste à obtenir l’élévation pour chaque coordonnées X, Z sur le terrain.

Visual C#

elevation =(float) heightmap[ ( z * _numberOfVerticesZ ) + x ];

Visual Basic

elevation = CType(heightmap(((z * _numberOfVerticesZ) + x)), Single)

Nous pouvons calculer le % de texture visible à l’élévation ainsi obtenue. Etant donné que nous avons 4 textures, nous faisons cela quatre fois.

Visual C#

 textureFactor[0] = ComputeTexturePercent ( 256, elevation );
textureFactor[1] = ComputeTexturePercent ( 192, elevation );
textureFactor[2] = ComputeTexturePercent ( 128, elevation );
textureFactor[3] = ComputeTexturePercent ( 64, elevation );

Visual Basic

 textureFactor(0) = ComputeTexturePercent(256, elevation)
textureFactor(1) = ComputeTexturePercent(192, elevation)
textureFactor(2) = ComputeTexturePercent(128, elevation)
textureFactor(3) = ComputeTexturePercent(64, elevation)

La méthode ComputeTexturePercent renvoie une valeur comprise entre 0.0 and 1.0 correspondant au % de texture visible à la hauteur donnée.

Visual C#

 private static float ComputeTexturePercent ( float h1, float elevation)
{ float regionSize = 256/4;
float texturePercent = ( regionSize - Math.Abs ( elevation-h1 ) )
/ regionSize; if ( texturePercent < 0.0f ) texturePercent = ""0.0f"";
else="" if="" ( texturePercent="" >
1.0f ) texturePercent = 1.0f; return texturePercent; }

Visual Basic

 Private Function ComputeTexturePercent(ByVal h1 As Single, ByVal elevation As Single)
As Single Dim regionSize As Single = (256 / 4) Dim texturePercent As Single =
((regionSize - Math.Abs(( elevation - h1))) / regionSize) If (texturePercent < 0.0!)
Then="" texturePercent = ""0.0""! ElseIf="" (texturePercent="" >
1.0!) Then texturePercent = 1.0! End If Return texturePercent End Function

L’étape suivant obtient les valeurs RGB pour les coordonnées X et Z de chaque textures

Visual C#

 for ( int i = 0; i < 4; i=""++ )
{ Color="" color = ""textures""[i=""].GetPixel="" ( x="", z="" );
oldR=""[i=""] = color.R=""; oldG=""[i=""] = color.G=""; oldB=""[i=""] = color.B=""; }

Visual Basic

 Dim i As Integer = 0 Do While (i < 4) Dim="" color="" As=""
Color = ""textures(i).GetPixel(x"", z="")
oldR(i) = ""color.R"" oldG(i) = ""color.G""
oldB(i) = ""color.B"" i = (""i"" + 1="") Loop=""

La valeur "old" est factorise avec texture pour calculer la nouvelle valeur RGB.

Visual C#

 Dim i As Integer = 0 Do While (i < 4) Dim="" color="" As=""
Color = ""textures(i).GetPixel(x"", z="") oldR(i) = ""color.R""
oldG(i) = ""color.G"" oldB(i) = ""color.B"" i = (""i"" + 1="")
Loop=""

Visual Basic

 newR = ((textureFactor(0) * oldR(0)) + ((textureFactor(1) *
oldR(1)) + ((textureFactor(2) * oldR(2)) + (textureFactor(3) *
oldR(3))))) newG = ((textureFactor(0) * oldG(0)) + ((textureFactor(1) *
oldG(1)) + ((textureFactor(2) * oldG(2)) + (textureFactor(3) *
oldG(3))))) newB = ((textureFactor(0) * oldB(0)) + ((textureFactor(1) *
oldB(1)) + ((textureFactor(2) * oldB(2)) + (textureFactor(3) * oldB(3)))))

La dernière étape va créer la nouvelle couleur et affecter le pixel de la texture du terrain avec la nouvelle couleur.

Visual C#

 Color newColor = Color.FromArgb ( (int)newR, (int)newG,
(int)newB ); finalTexture.SetPixel ( x, z, newColor );

Visual Basic

 Dim newColor As Color = Color.FromArgb(CType(newR, Integer),
CType(newG, Integer), CType(newB, Integer)) finalTexture.SetPixel(x, z, newColor)

Lors que tout est fait, nous obtenons pour résultat :

Cette approche est sans doute la plus simple possible. Comme mentionné dans le precedent article, il existe des développeurs qui ne travaillent leur vie active Durant que ces techniques. Nous pourrions imagnier quelques améliorations :

  • Prendre en compte la pente. Par exemple, la neige ne pourrait pas être présente sur les pentes raides.
  • Prendre en compte l’angle du soleil et afficher des zones d’ombre. Le neige pourrait être présente sur des zones basses à l’ombre mais inexistante en hauteur en plein soleil
  • Prendre le compte le climat et la densité de végétation
  • Utiliser plusieurs types de textures pour la même élévation pour augmenter le réalisme.

La résolution de la texture du terrain joue un rôle important dans la beauté et le réalisme du terrain généré. Vous devez peser le pour et le contre entre charger une grosse texture qui va ralentir le jeu mais le rendre plus beau et charger une texture petite avec moins de détails plus offrant un affichage plus fluide

Avant d’intégrer notre terrain généré nous devons mettre à jour la manière dont nous déplaçons la camera dans notre monde.

Haut de page Haut de page

Nouvelle Caméra

Le travail réalisé par la camera jusqu’ici n’est pas mauvais. Je vais juste la remplacer par une caméra plus proche de celles que nous trouvons dans les jeux de tir avec vue à la première personne.

Jetez un œil à cet article pour voir la théorie avant de passer à la pratique : http://www.toymaker.info/Games/html/camera.html.

Lorsque nous nous déplaçons sur le terrain, nous maintenons les valeurs liées au pitch donné par la souris. Pour rendre la caméra plus réaliste, nous devons prendre contrôle des valeurs Pitch, Yaw et Roll et être en mesure de les adapter à spécificité du terrain que nous sommes en train de parcourir. Si nous escaladons le coté d’une pente, le pitch et le roll devrons suivre l’inclinaison de la pente. Le Yaw ne change que par l’intermédiaire des actions de l’utilisateur.

Les valeurs Yaw, Pitch et Roll décrivent l’orientation sur les trios axes d’un object. Yaw exprime la rotation autour de l’axe Y, Pitch autour de l’axe X et Roll autour de l’axe Z.

Pour implementer ces modifications, nous allons modifier notre classe Camera et lui ajouter des méthodes.

Tout d’abord, ajoutons une méthode pour contrôle le Yaw, le Pitch et le Roll.

Visual C#

 public void AdjustYaw ( float radians ) {
Matrix rotation = Matrix.RotationAxis (_up, radians );
_right = Vector3.TransformNormal ( _right, rotation );
_look = Vector3.TransformNormal ( _look, rotation ); }
public void AdjustPitch ( float radians ) { _pitch -= radians; if
( _pitch > _maxPitch ) { radians += _pitch - _maxPitch; } else if
( _pitch < -_maxPitch ) { radians += ""_pitch"" + _maxPitch=""; }
Matrix="" rotation = ""Matrix.RotationAxis"" ( _right="", radians="" );
_up = ""Vector3.TransformNormal"" ( _up="", rotation="" );
_look = ""Vector3.TransformNormal"" ( _look="", rotation="" );}
public="" void="" AdjustRoll="" ( float="" radians="" )
{ Matrix="" rotation = ""Matrix.RotationAxis""( _look="", radians="" );
_right = ""Vector3.TransformNormal""( _right="", rotation="" );
_up = ""Vector3.TransformNormal""( _up="", rotation="" ); }

Visual Basic

 Public Sub AdjustYaw(ByVal radians As Single) If (radians = 0.0!)
Then Return End If Dim rotation As Matrix = Matrix.RotationAxis(_up, radians)
_right = Vector3.TransformNormal(_right, rotation)
_look = Vector3.TransformNormal(_look, rotation)
End Sub Public Sub AdjustPitch(ByVal radians As Single) If (radians = 0.0!)
Then Return End If _pitch = (_pitch - radians) If (_pitch > _maxPitch)
Then radians = (radians _ + (_pitch - _maxPitch))
ElseIf (_pitch _ < (_maxPitch * -1=""))
Then="" radians = (""radians"" _="" + (_pitch="" + _maxPitch=""))
End="" If="" Dim="" rotation="" As="" Matrix =
"Matrix.RotationAxis(_right", radians) _up = Vector3.TransformNormal
(_up, rotation)
_look = Vector3.TransformNormal(_look, rotation) End Sub Public
Sub AdjustRoll(ByVal radians As Single) If (radians = 0.0!)
Then Return End If Dim rotation As Matrix = Matrix.RotationAxis(_look, radians)
_right = Vector3.TransformNormal(_right, rotation) _up =
Vector3.TransformNormal(_up, rotation)
End Sub

Nous modifions ensuite la méthode Update pour mettre à jour la matrice View.

Visual C#

 public void Update ( ) { if ( Vector3.Length ( _velocity ) > _maxVelocity )
_velocity = Vector3.Normalize ( _velocity ) * _maxVelocity; // Update
_postion += _velocity; // Stop _velocity = new Vector3 ( ); _lookAt =
_postion + _look; Vector3 up = new Vector3 ( 0.0f, 1.0f, 0.0f ); _viewMatrix =
Matrix.LookAtLH ( _postion, _lookAt, up ); _right.X = _viewMatrix.M11; _right.Y =
_viewMatrix.M21; _right.Z = _viewMatrix.M31; _up.X = _viewMatrix.M12; _up.Y =
_viewMatrix.M22; _up.Z = _viewMatrix.M32; _look.X = _viewMatrix.M13; _look.Y =
_viewMatrix.M23; _look.Z = _viewMatrix.M33; float lookLengthOnXZ =
(float)Math.Sqrt ( _look.Z * _look.Z + _look.X * _look.X ); _pitch =
(float)Math.Atan2 ( _look.Y, lookLengthOnXZ ); _yaw =
(float)Math.Atan2 ( _look.X, _look.Z ); ComputeViewFrustum ( ); }

Visual Basic

 Public Sub Update() If (Vector3.Length(_velocity) > _maxVelocity)
Then _velocity = (Vector3.Normalize(_velocity) * _maxVelocity)
End If ' Update _postion = (_postion + _velocity) ' Stop _velocity = New Vector3
_lookAt = (_postion + _look) Dim up As Vector3 = New Vector3(0.0!, 1.0!, 0.0!)
_viewMatrix = Matrix.LookAtLH(_postion, _lookAt, up) _right.X = _viewMatrix.M11
_right.Y = _viewMatrix.M21 _right.Z = _viewMatrix.M31 _up.X = _viewMatrix.M12
_up.Y = _viewMatrix.M22 _up.Z = _viewMatrix.M32 _look.X = _viewMatrix.M13
_look.Y = _viewMatrix.M23 _look.Z = _viewMatrix.M33 Dim lookLengthOnXZ
As Single = CType(Math.Sqrt(((_look.Z * _look.Z) _ + (_look.X * _look.X))), Single)
_pitch = CType(Math.Atan2(_look.Y, lookLengthOnXZ), Single) _yaw =
CType(Math.Atan2(_look.X, _look.Z), Single) ComputeViewFrustum()
End Sub

Nous avons ajoutons une valeur maximum au pitch et une valeur de contrôle pour la vitesse de la camera. La vitesse de la caméra s’adapte suivant la valeur de la variable deltaTime afin de donner un déplacement uniforme.

Visual C#

 m_maxPitch = Geometry.DegreeToRadian( 89.0f ) m_maxVelocity = 1.0f;

Visual Basic

 Dim _maxPitch As Single = Geometry.DegreeToRadian(89.0F) Dim _maxVelocity As Single = 1.0F

Nous devons ensuite instancier notre camera d’une manière légèrement différente. Dans la méthode Initialize de la classe GameEngine, ajoutez le code suivant :

Visual C#

 _camera = new Camera ( ); _camera.MaxVelocity = 100.0f; _camera.Position =
new Vector3 ( 0.0f, 0.0f, 0.0f ); _camera.LookAt = new Vector3
( 0.0f, 0.0f, 0.0f ); _camera.Update ( );

Visual Basic

 m_camera = New Camera m_camera.MaxVelocity = 100.0F m_camera.Position =
New Vector3(0.0F, 0.0F, 0.0F) m_camera.LookAt =
New Vector3(0.0F, 0.0F, 0.0F) m_camera.Update()

Avec ces changements dans notre classe Caméra, l’ajoute d’un terrain dans notre jeu sera plus simple.

Haut de page Haut de page

Terrain Elevation

Dans le dernier article, vous avez sans doute remarqué que nous utilisions une matrice de Scaling (homothétie pour l’agrandissement ou la réduction) afin de changer la taille du terrain avant son affichage. Nous devons mettre les valeurs de scaling en variable afin de pouvoir les utiliser lorsque nous tenterons d’obtenir la hauteur du terrain en un point donné. Souvenez vous de bien ajouter l’instruction _device.RenderState.NormalizeNormals = true pour recalculer les normales comme indiqué au début de cet article..

A la fin de la classe terrain, ajoutez le code suivant :

Visual C#

private float _scaleX = 1.0f; private float _scaleZ = 1.0f; private float _scaleY = 0.3f;

Visual Basic

Dim _scaleX As Single = 4.0! Dim _scaleZ As Single = 4.0! Dim _scaleY As Single = 0.5!

Dans la méthode Render du terrain, modifiez la ligne de code qui calcule le scaling en :

Visual C#

 _device.Transform.World = Matrix.Scaling ( _scaleX, _scaleY, _scaleZ );

Visual Basic

 _device.Transform.World = Matrix.Scaling(_scaleX, _scaleY, _scaleZ)

Nous pouvons déterminer la hauteur (Y) du terrain à n’importe quel emplacement (X, Z) en prenant en compte l’agrandissement/réduction appliqué.

Visual C#

 public float TerrainHeight ( int x, int z )
{ x = x / (int)_scaleX; z = z / (int)_scaleZ; if
( _isHeightMapRAW ) return (float)( _elevationsRAW[ ( z * _numberOfVerticesZ ) + x] )
* _scaleY; else return (float)( _elevations[(int)x,(int)z] ) / _scaleY; }

Visual Basic

 Public Function TerrainHeight(ByVal x As Integer, ByVal z As Integer)
As Single x = (x / CType(_scaleX, Integer)) z = (z / CType(_scaleZ, Integer))
If m_isHeightMapRAW Then
Return (CType(_elevationsRAW(((z * _numberOfVerticesZ) + x)), Single) *
_scaleY) Else Return (CType(_elevations(CType(x, Integer), CType(z, Integer)), Single)
/ _scaleY) End If End Function

Tout ce que nous faisons ici est d’obtenir la valeur Y dans la tableau de hauteur à partir des coordonnées X et Z. Assurez vous de passer de la multiplication à la division lorsque les valeurs d’homothetie (scalling) sont supérieure à 1.

Nous devons ajuster la coordonnée Y de la camera avec la valeur Y du terrain. Dans la méthode Render de la classe GameEngine, ajoutez le code suivant juste après l’update de la caméra.

Visual C#

camera.SetHeight ( _terrain.TerrainHeight ( (int)_camera.Position.X, (int)_camera.Position.Z ) );

Visual Basic

m_camera.SetHeight(m_terrain.TerrainHeight(m_camera.Position.X, m_camera.Position.Z))

Ici, nous pourrions empêcher le joueur de modifier la valeur Y à l’aide des touches Q et Z. La dernière étape consiste a ajouter une nouvelle méthode à la classe Camera qui va nous permettre de spécifier directement la hauteur de la camera. La variable heightOfTurret nous assure que nous comme bien au dessus du terrain afin d’éviter de passer en dessous du sol pendant la conduite. Mettez la valeur 0 pour expérimenter cela.

Visual C#

 public void SetHeight ( float y ) { float heightOfTurret = 1.5f;
_postion.Y = y + heightOfTurret; }

Visual Basic

 Public Sub SetHeight(ByVal y As Single) Dim heightOfTurret As Single = 1.5!
_postion.Y = (y + heightOfTurret) End Sub

Le principal effet de ces modifications est de pouvoir conduire sur le terrain. Vous devez vous assurer d’annuler le déplacement vertical du véhicule (à l’aide des touches Q et Z). Il existe encore de nombreuses modifications à faire pour marier intelligemment les mouvements de la caméra et la géographique du terrain pour donner au jeu encore plus dé réalisme.

Haut de page Haut de page

Conclusion

Il existe de nombreux concepts de génération de terrain dont je n’ai pas parlé ici. Nous tenterons de les aborder plus tard. Si vous faites une recherche sur "Terrain DirectX" vous trouverez de nombreuses informations utiles, mais souvenez vous toujours qu’il s’agit là d’une technique avancée dont certains développeurs font leur unique spécialisation.

D’ici là, joyeux développements !

Haut de page Haut de page Précédent 7 sur 7