Créer une caméra de JRPG

final-fantasy-x-2

Dans les JRPG, on retrouve régulièrement une caméra fixe dans un coin de la scène qui « suit des yeux » le joueur alors qu’il se déplace. Dès que le joueur s’éloigne de la caméra et entre dans une nouvelle zone, une nouvelle caméra est utilisée, et ainsi de suite.

Il peut également arriver que cette caméra soit sur un rail et se déplace en fonction de la position du joueur dans la zone. Dans ces deux cas, cela pose un problème au niveau de la gestion des inputs du joueur. Voyons ensemble comment gérer ça correctement.

L'Atelier de Drakulo déménage vers Esprit Unity

Disclaimer

Cet article a été originalement écrit le 19/09/2014 sur mon précédent blog. Certaines informations présentées ici peuvent donc ne plus fonctionner telles quelles.

Introduction

Comme la plupart des jeux type JRPG sortent sur console (même s’il y a des remakes produits pour PC), je vais baser le tutoriel sur une entrée joueur de type manette. De ce fait, je vais utiliser InControl présenté dans le tutoriel précédent Utiliser une manette avec Unity3D. Je vais également utiliser une partie des assets d’exemple d’Unity (quelque peu remaniés pour l’occasion).

Les assets d’exemple ne sont pas directement compatibles avec InControl. Il faut effectuer quelques modifications pour que cela fonctionne. Pour gagner du temps, je vous ai préparé un package de base qui comprend ces adaptations. Il contient les scripts modifiés ainsi que les assets que j’utiliserai par la suite.

Installation et mise en place

Téléchargez et installez le package de départ dans votre projet Unity3D. Comme expliqué dans le tutoriel sur InControl, il faut le mettre à jour manuellement. On va donc faire d’une pierre deux coup avec le script InputWrapper :

  • C’est un MonoBehaviour qui va s’occuper de mettre à jour les entrées joueur de InControl
  • C’est également un wrapper statique rendant accessible de partout les entrées joueur.

L’intérêt de passer par un wrapper et de ne pas directement utiliser InControl est la possibilité de paramétrage et de « retouche » des informations. Je n’irai pas plus loin, ce n’est pas l’objet de ce tutoriel.

Ajoutez donc à la scène un GameObject vide et attachez lui le script InputWrapper.

Passons à la mise en place de la scène. Tout d’abord le décors. La mise en place n’est pas très importante, posez quelques un des éléments histoire d’avoir une zone ouverte au centre et quelques obstacles sur les côtés. Quelque chose comme ça :
scene

N’oubliez pas de mettre un peu de lumière avec une Directional Light !

Nous avons maintenant notre cadre. Le joueur va se déplacer dans cette zone et une caméra va suivre ses déplacements. Ajoutons le personnage contrôlé par le joueur : Ethan du package d’assets. Certains composants sont retirés suite à la mise en place de la compatibilité InControl :

Ethan

Placez la caméra par ici, à côté du pilier :

camera

La caméra ne bougera pas, en revanche, elle suivra les mouvements du joueur. Il est donc important de la placer à un endroit où il n’y aura pas de collision visuelle avec le décors : le joueur doit toujours être visible depuis le point de vue de la caméra (tout du moins pour les zones principales).

Affectez à la caméra le script LookatTarget, qui fait exactement ce que le nom indique : il effectue des rotations pour « regarder » vers la cible. Ce script est tiré du pack d’assets. Il faut le paramétrer un peu, sinon il ne se passera rien ! Quelque chose comme ça est pas mal pour notre tutoriel :

cameraParam

Lorsque vous ajoutez la caméra, veillez à bien enlever le tag MainCamera s’il est mis par défaut, nous allons voir plus bas pourquoi.

Si vous avez bien suivi jusqu’à maintenant… lancez le jeu et testez. Vous pouvez déplacer Ethan et la caméra vous suit dans ses limites de rotation !

Affiner les contrôles

En testant un petit moment, il y a un détail assez désagréable qui doit vous tirer quelques grimaces : la gestion de l’input de la manette. Tant que la caméra « regarde » selon son axe de départ (Z), tout va bien, mais dès que l’ont déplace le joueur et qu’on s’arrête… en poussant le stick vers le haut, on ne va pas « en face » de la caméra…

C’est une question de référentiel. InControl donne des informations brutes correspondant à l’état des sticks et boutons : pousser un stick vers le haut lui donnera une valeur Y entre 0 et 1. Pousser le stick vers la droite lui donnera une valeur en X entre 0 et 1. Ces informations sont directement interprétées par le contrôleur d’Ethan qui transforme ces valeurs en mouvement en X et Z. Cette interprétation se fait indépendamment du rendu de la caméra. Expliquons ça par des schémas.

Dans l’état de départ, tout est aligné. Pousser le stick vers le haut va déplacer Ethan selon l’axe Z en positif (vers le haut donc) :

camera1

Lorsqu’on va déplacer Ethan, la caméra va se tourner et son référentiel va donc changer… Tant que l’on bouge, on ajuste instinctivement le déplacement du stick pour aller où on veut. Mais dès que l’on s’arrête et que l’on veut repartir, quelque chose ne vas pas. Pousser le stick vers le haut ne fait pas se déplacer Ethan au loin comme on s’y attendrait. C’est à cause des différents référentiels entre celui de la caméra et celui dans lequel sont interprétés les mouvements d’Ethan :

camera2

Dans ce cas là, pousser le stick vers le haut déplace toujours Ethan selon l’axe Z… mais par rapport au référentiel de la scène, non celui de la caméra ! Dans cette configuration, pousser le stick vers le haut correspond à un mouvement en direction de la gauche par rapport à la caméra, et ce à cause de sa rotation.

Il faut donc effectuer un ajustement des déplacements d’Ethan en fonction de l’orientation de la caméra, en appliquant sa rotation aux directions appliquées. En fat, le script d’exemple comprend cette fonctionnalité mais elle n’est pas parfaite. Pour la tester, remettez le tag MainCamera sur la caméra de la scène. Si vous poussez le stick vers la gauche, vous allez vous déplacer vers la gauche, toujours vers la gauche, par rapport à la caméra. S’il n’y avait pas de décors, vous tourneriez en rond autour de la caméra…

Ce n’est pas tout à fait satisfaisant. Pour garder l’esprit JRPG, il faut que les mouvement ne soient pas totalement dépendant de l’orientation de la caméra. Nous allons donc modifier le contrôleur d’Ethan pour prendre en compte la rotation de notre caméra… mais pas tout le temps ! L’idée est simple : dès que le joueur commence à bouger, on note l’orientation de la caméra à cet instant et ce sera la transformation appliquée aux mouvements. Tant que le joueur se déplacera, elle ne changera pas. Ainsi, le joueur pourra continuer son déplacement dans une direction par rapport à la scène, sans que les potentiels mouvements de caméra ne viennent interférer dans sa trajectoire et le forcent à ajuster son mouvement.

Pour celà, il nous faut une information : est-ce que le joueur a bougé ou non. Nous allons donc modifier le gestionnaire d’input pour nous donner cette information via une nouvelle propriété :

 

public static bool Moved
{
 get
 {
  return InputManager.ActiveDevice.LeftStickX != 0
   || InputManager.ActiveDevice.LeftStickY != 0;
 }
}

Il faut ensuite modifier le script d’Ethan pour prendre en compte ce changement de référentiel. Tout d’abord, deux variables supplémentaires :

private Vector3 camRight;
private bool isMoving;

Il faut ensuite modifier la méthode FixedUpdate comme suit :

void FixedUpdate ()
{

 // read inputs
 bool crouch = Input.GetKey(KeyCode.C);
 bool jump = InputWrapper.Action1;
 float h = InputWrapper.LeftStick.x;
 float v = InputWrapper.LeftStick.y;
 // calculate move direction to pass to character
 if (cam != null)
 {
  // calculate camera relative direction to move:
  var moved = InputWrapper.Moved;
  if(moved && !isMoving)
  {
   // Le mouvement commence, on stocke l'orientation de la caméra
   camForward = Vector3.Scale (cam.forward, new Vector3(1,0,1)).normalized;
   camRight = Vector3.Scale (cam.right, new Vector3(1,0,1)).normalized;
  }
  isMoving = moved;
  move = v * camForward + h * camRight; 
 } else {
  // we use world-relative directions in the case of no main camera
  move = v * Vector3.forward + h * Vector3.right;
 }
 
}

On ne stocke la rotation de la caméra que lorsque le joueur commence son mouvement. Et on l’applique ensuite à chaque boucle. De cette manière, on obtient un mouvement lisse, qui suit la trajectoire de départ… ce qui est typiques des JRPGs !

Dans ce tutoriel, nous avons vu la gestion d’une seule caméra. Mais dans le cadre d’un jeu, vous aurez bien évidemment plusieurs caméras correspondant aux différentes zones que le joueur pourra parcourir. A vous de gérer la détection des zones et le changement de caméra active !

Aller, à vous de jouer maintenant !

L'Atelier de Drakulo déménage vers Esprit Unity

 

Publicité

Votre commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l’aide de votre compte WordPress.com. Déconnexion /  Changer )

Image Twitter

Vous commentez à l’aide de votre compte Twitter. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l’aide de votre compte Facebook. Déconnexion /  Changer )

Connexion à %s