Project Silva : Dynamic Leaning System

Salut !

Aujourd’hui je vais vous parler de Project Silva et plus particulièrement d’un aspect précis du gameplay que j’ai développé : le leaning. Project Silva, c’est quoi ? Et bien c’est un FPS orienté infiltration dans un univers sci-fi et développé par une petite équipe d’amateurs dont je fait partie en tant que développeur. Nous utilisons bien sûr Unity comme moteur de jeu !

Quelques screens pour découvrir l’ambiance générale du jeu.

Je vous invite à aller voir des présentations plus complètes de Project Silva sur le site officiel / indiedb / unity3dfr / blenderclan / openclassrooms.

Revenons maintenant au système de leaning qui fait partie intégrante du gameplay (et qui est d’ailleurs un élément « classique » de ce type de jeu). Celui-ci permet, quand on est au bord d’un mur par exemple, de se pencher pour espionner ou encore viser. Ce concept est parfois intégré à un système de couverture plus complet. En pratique et en gif, ça donne ça (juste le leaning, aucune couverture ici) :

Un système de ce type est en fait relativement facile à développer, bien que ça peut varier pas mal selon l’interaction voulue : par exemple dans Project Silva le leaning est automatique quand on vise (c’est à dire que si on vise dans un mur et qu’on est assez proche de son bord, la caméra se penche et se décale sur le côté) et dynamique : le level designer n’a pas besoin de placer des zones de détection (triggers) pour définir là où le leaning est possible, ce qui est un gain de temps appréciable, vous en conviendrez !

Des triggers seraient par exemple plus particulièrement adaptés dans le cas d’un système de couverture ou autre, avec plus d’interaction (et on peut également les générer par code, mais faut prévoir quelques cachets d’aspirine -ou autre :P - pour le dev).

Dans cet article je vais donc vous expliquer l’implémentation générale du système que j’ai développé pour Project Silva (notez que le but n’est pas de vous donner un code prêt à l’emploi, ce que je ne ferais pas !).

Cette implémentation se base sur des raycasts, pour ce qui ne connaissent pas Unity un raycast est un « lancer » de rayon où le but est de récupérer des infos sur l’objet touché (ex : le point touché sera l’intersection entre le rayon et le premier objet qu’il rencontre).
Le but du script est de pencher la caméra à gauche, à droite et de la relever légèrement dans le cas où on est accroupi derrière un obstacle suffisamment bas, c’est-à-dire de changer l’état de la caméra (nous utilisons UFPS, un tool pour Unity dédié aux FPS et qui se base sur un système d’états et d’évènements non sans rappeler les FSM).

L’élément déclencheur de ce leaning est dans notre cas la visée : à ce moment on doit donc déterminer si le joueur peut lean ou non. Pour cela on commence par lancer un rayon droit devant, à la hauteur des yeux pour savoir ce qu’il y a devant le joueur. Ensuite on récupère la normale de la face touché (un vecteur perpendiculaire à cette face).

leaning1

En vert, le FPC (First Person Controller, le joueur), en rouge le raycast et en bleu la normale de l’obstacle touché.

C’est facultatif mais on peut projeter cette normale sur un plan horizontal pour éviter les bugs (style un obstacle en pente qui aurait une normale pointée vers le haut, ce qui peut perturber les calculs suivants) : il suffit de réduire la composante y du vecteur à 0 et de normaliser :

1
2
3
4
5
6
7
Raycast hit;
if (Physics.Raycast ([position des yeux], [avant du joueur], out hit, [distance max au mur]))
{
	Vector3 normal = hit.normal;
	normal.y = 0;
	normal.Normalize ();
}

Ensuite on calcule la position de 2 points, à droite et à gauche de cette normale.

leaning2

Les vecteurs rouge et vert sont horizontaux et perpendiculaires à la normale, on peut donc les calculer par cross product.

Le calcul en question ressemble à :

1
2
Vector3 leftVector = Vector3.Cross (-normal, Vector3.up);
Vector3 rightVector = Vector3.Cross (normal, Vector3.up);

Les points au bout de ces vecteurs sont donc :

1
2
3
4
// hit.point est le point touché par le premier rayon lancé,
// c'est-à-dire l'origine de la normale (le trait bleu)
Vector3 leftPoint = hit.point + leftVector;
Vector3 rightPoint = hit.point + rightVector;

Notez que dans ce dernier calcul on peut multiplier leftVector et rightVector par une valeur de tolérance, qui définira la distance maximum au coin du mur dans laquelle le leaning est possible (si on ne multiplie pas les vecteurs cette distance reste de 1 mètre).

Ensuite on décale légèrement ces points vers le mur pour ne pas rester juste au bord de celui-ci :

leaning3

Les petits traits bleus représentent le décalage (oui bon ok ! vous l’aurez compris je suis pas un graphiste :P ).

1
2
leftPoint -= normal * [quelques cm];
rightPoint -= normal * [quelques cm];

Finalement il suffit, à partir des nouveaux points, de renvoyer un rayon vers le mur pour trouver ses bords (le décalage précédent sert en fait à les envoyer bien dans le mur et pas juste au bord, où la détection peut être plus aléatoire) :

leaning4

A droite le raycast part de l’intérieur du mur, il est donc invalide et ne touche rien.

1
2
3
4
5
// hit.collider.Raycast permet de lancer le raycast uniquement
// sur le collider (l'obstacle) touché précédemment
if (hit.collider.Raycast (new Ray (leftPoint, -leftVector), out hit, tolerance))
	...
// idem pour rightPoint

Ici on se retrouve dans un cas assez simple, le joueur peut se pencher uniquement du côté gauche :

leaning5

Le leaning est possible uniquement à gauche (côté bleu).

Dans le cas où le leaning est possible des 2 côtés, il suffit de prendre celui qui est le plus proche de la normale :

leaning_r

Ici le bord droit est plus proche de la normale, on va lean vers la droite.

Et si on souhaite retrouver le coin exact du mur, on a juste à enlever le décalage :

1
Vector3 corner = hit.point + normal * [quelques cm];

Et voilà, vous avez le principal :) ! Il reste seulement à mettre à jour l’état de la caméra à partir de ce qu’on a calculé. Évidement les subtilités arrivent rapidement comme pour gérer certains cas quand le player est accroupi ou quand il doit relever la tête pour voir par dessus un petit obstacle, mais le raisonnement reste le même.

Bookmark the permalink.

One Comment

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>