Dans: Jeux et Applications, Nos projets, Projet "Libra", Unity

Si vous créez un roman visuel (ou n’importe quel jeu qui a beaucoup de texte), alors sûrement la première chose que vous voulez construire est une boîte de dialogue. Ça peut sembler assez simple, cependant, pas toutes les boîtes de dialogue sont créés égales.

Le rôle d'une boîte de dialogue va au delà de simplement afficher le texte de notre histoire. Il devrait élever notre scénario à un tout nouveau niveau. Il est beaucoup plus facile de transmettre des émotions comme la peur, le suspense et la joie quand on peut changer la vitesse du texte à la volée (ou jouer une animation spécifique quand un certain mot apparaît à l'écran).

Et pour résultat, nos joueurs se sentiront plus impliqués dans l'histoire.

Pour ce faire, voici une liste de fonctionnalités que je voulais personnellement pour notre boîte de dialogue dans le projet Libra:

  • Le texte apparaît un caractère à la fois. L'effet machine à écrire.
  • Vitesses de texte variables pour différentes lignes de dialogue.
  • Accélérez l'animation de texte lorsque vous maintenez un bouton spécifique.
  • Changer la couleur du texte, peut-être le texte entier ou certains mots.
  • Des commandes/événements spéciaux peuvent se produire lorsque le texte s'anime.
    • Jouer un son à un moment clé.
    • Changez la vitesse du texte à tout moment.
    • Modifier l'animation d'un personnage pendant qu'il parle ou qu'il écoute.
    • Secouez la caméra principale pour les effets théâtraux.
  • Faire trembler le texte.

Je vais essayer de décortiquer la façon dont nous avons réussi à construire notre boîte de dialogue avec toutes ces fonctionnalités. C'est la première chose que j'ai fait sur ce projet, et beaucoup de choses ont changé depuis que j'ai commencé.

Je vais aller à travers les itérations ci-dessous. Je vous suggère de tout lire avant de coder votre propre boîte de dialogue.

Les bases

Bon, commençons par les bases. La boîte de dialogue la plus simple serait celle où nous affichons le texte un caractère à la fois.

Dans Unity 5, il est assez facile de créer une boîte de dialogue animée. Tout ce dont nous avons besoin est un script qui passe par votre chaîne de texte et sort un caractère à la fois dans un élément de texte. Nous pouvons le faire avec une Coroutine:

Dans une Coroutine, nous ajoutons un caractère à notre élément de texte toutes les quelques millisecondes.

Simple et efficace.

(j'ai également ajouté une variable de vitesse de base qui détermine à quelle vitesse le texte doit être imprimé dans cette boîte. Nous pouvons augmenter la vitesse lorsque le joueur tient une certaine touche, plus tard.)

Regardez le code par ici.

(vous avez probablement réalisé comment le texte saute d'une ligne à l'autre. Je parle de sauts de ligne et comment résoudre ce problème plus tard dans ce post.)

Commandes spéciales

Essayez de lire les lignes de dialogue suivantes:

“J'aime {sound:1}les {shake}gâteauuuuuu!”
“C'est {color:#ff0000}délicieux.{color:end}”

As you can imagine, the text should display like so in the game’s text element output:

“J'aime les gâteauuuuuu!”
“C'est délicieux.

Lors de l'impression du texte, le script doit séparer les commandes spéciales de la chaîne de sorte qu'il ne montre pas dans la sortie finale du jeu. Puis, lorsque le texte s'imprime, il appelle une fonction basée sur la commande trouvée.

Passons sur ce que nous avons: la commande de {Shake} fait secouer la caméra tandis qu'un caractère affiche. La commande {Sound: 1} joue un son spécifique ici avec l'ID de 1.

Enfin, la commande {Color} est séparée en deux commandes: le début et la fin. Être capable de colorer des mots spécifiques est une fonctionnalité intéressante à avoir si vous voulez mettre l'accent sur certains mots dans une ligne de dialogue.

Prenons un regard plus approfondi sur la façon dont nous pouvons coder ces fonctionnalités dans notre jeu.

Commandes simples

J'ai pensé deux façons d'attraper les commandes de la zone de texte animée:

  • Lors de l'impression, si le caractère actuel est "{", ne l'imprimez pas et bouclez les caractères suivants jusqu'à ce que nous obtenions la parenthèse fermante "}". Ensuite, envoyez la commande à une fonction qui fera les bons appels (jouer un son, écran qui boufe, etc); Ou
  • Obtenez toutes les commandes avant l'impression, les organiser tous dans une liste et les exécuter lorsque le texte d'impression atteint le point où il était avant qu'on les enlève. Nous avons besoin de stocker les positions de début pour chaque commande.

J'ai essayé les deux pour mon jeu. J'ai fini par utiliser le second.

Ensuite, il faut créé une nouvelle classe SpecialCommand afin de construire une liste contenant toutes nos commandes et les effets de ceux-ci.

BuildSpecialCommandList(text)

Cette fonction renvoie une liste contenant toutes les commandes qu'elle a supprimées du texte de dialogue d'origine. Il utilise GetSpecialCommand() pour convertir la chaîne {commande:valeur} en une commande réelle.

GetSpecialCommand(text)

Cela retourne une SpecialCommand. Nous pouvons ensuite accéder au nom et aux valeurs de la commande plus tard lors de l'impression de texte à l'écran.

StripAllCommands(text)

Les expressions régulières* sont vraiment utiles lorsque nous voulons valider des données. Elles vérifient si votre texte correspond à un modèle donné.

*note de l'éditeure: les expressions régulières sont la vie.

Les expressions régulières sont couramment utilisées pour valider les formulaires sur le web. Par exemple, nous pouvons utiliser une Regex pour s'assurer que l'utilisateur a entré un numéro de téléphone valide dans le champ téléphone.

Dans mon cas, j'utilise une expression régulière pour intercepter et supprimer toutes les commandes de la ligne de dialogue, afin que je puisse facilement les enlever avant d'imprimer le texte à l'écran.

Jusqu'à présent, nous avons accompli:

  • Création d'une liste de toutes les commandes;
  • Création d'une liste des index (position) dans la chaîne de dialogue source;
  • Retirer toutes les commandes de cette chaîne, prêtes à être imprimées à l'écran.

Avec cela de fait, nous savons maintenant quand et quelle commande à exécuter. Nous avons juste besoin d'ajouter un contrôle final dans notre Coroutine afin d'exécuter les commandes à leur index approprié.

CheckForCommands(int index)

Cette fonction va vérifier pour nous s'il ya une commande dans l'index actuel de notre chaîne, puis l'exécuter.

(plus tard dans le projet, j'ai remarqué que j'avais aussi besoin de parcourir ma liste à chaque index, juste au cas où il y avait plusieurs commandes avec le même index. Personnellement, je pense que cette partie pourrait être optimisée, mais en ce moment ça fonctionne très bien.)

ExecuteCommand(SpecialCommand command)

Ajouter plus de commandes est facile. Tout ce que vous devez faire est d'implémenter plus de contrôles dans cette fonction. Ensuite, vous créez votre propre code pour n'importe quel effet que vous recherchez.

Rappel: les espaces entre vos commandes et votre dialogue sont importants! Assurez-vous d'avoir seulement un espace entre les mots réels, et pas plus entre les commandes, ou alors il y aura 2 + espaces entre chaque mot. Bien sûr, cela est facilement perceptible lorsque vous testez, mais si vous avez de grandes quantités de texte dans votre jeu, cela peut être long. Une façon de le contourner est de vérifier pour un espace, juste après un espace a été imprimé, dans votre Coroutine. Si le dernier caractère était un espace et que le courant est aussi un espace, passez au caractère suivant.

Cliquez ici pour le code des commandes spéciales.

Commandes avancées (paires)

Dans Unity 5, vous pouvez colorier n'importe quelle partie d'un texte en faisant simplement ceci:

<color=#0000ff>Neige</color>

Juste le faire fera du mot Neige appaître en bleu dans votre texte.

Rappelons-nous de la commande plus tôt:

“C'est {color:#ff0000}délicieux.{color:end}”

Lorsque le script atteint une commande {Color}, il doit engendrer les deux éléments de balisage ci-dessus. Toutefois, si nous faisons que remplacer les commandes pour le balisage, nous courons dans un problème:

Si vous imprimez les éléments de balisage un caractère à la fois, vous verrez les balises de couleur afficher, jusqu'à ce qu'ils soient fermés, ensuite seulement le texte est de couleur. C'est un problème.

Ce que nous voulons réellement, c'est le texte à être déjà coloré avant qu'il ne s'affiche sur l'écran.

Pour ce faire, vous pouvez imprimer chaque caractère à l'intérieur de ces deux éléments de balisage, individuellement, jusqu'à ce que le jeu obtienne la commande {Color:end}.

Bien sûr, c'est juste une des façons de colorier votre texte. C'est pourquoi je ne vous donne pas le code pour cet exemple. Il ya une meilleure façon, et je vais en parler plus tard dans ce post.

Le problème du saut de lignes

Dans la première itération de notre boîte de dialogue, nous avons eu un problème avec le saut de texte d'une ligne à l'autre, comme il le fait ici:

Eh bien, c'est chiant. Il n'y a aucun moyen pour la Coroutine de savoir quand un mot doit commencer sur la ligne suivante avant de l'imprimer.

J'en suis venu avec trois solutions différentes à ce problème:

  • Faire apparaître tout le texte à l'avance mais entièrement transparent, puis créer l'effet dactylographie en changeant l'opacité de chaque lettre individuellement;
  • Calculez la largeur de chaque caractère dans la chaîne actuelle, puis insérez les sauts de ligne en fonction de la largeur maximale de notre élément TextBox.
  • Ajouter manuellement des sauts de ligne à notre scénario entier. Ne faites pas ça.

Je ne vais pas aller sur la dernière solution, car il devrait être votre dernier recours. Votre temps est mieux utilisé pour travailler sur votre histoire (rappelez-vous pourquoi nous faisons cette boîte de dialogue.) Les sauts de ligne manuels ne doivent être utilisés que s'ils servent un but stylistique à votre dialogue.

Texte invisible

Pour créer l'effet, nous pouvons utiliser les éléments de balisage d'avant et de faire les valeurs alpha 0.

<color=#ffffff00>Cette chaîne entière est invisible.</color>

Ensuite, nous choisirions simplement un caractère à la fois dans le balisage, puis le mettre au début de la chaîne:

Ce texte est visible <color=#ffffff00>, mais ce n'est pas encore.</color=#ffffff00>

Étant donné que le texte sera existant dans le TextBox dès le début, les sauts de ligne seront conservés tels quels et aucun mot ne "sautera".

Notez que si vous utilisez également la stratégie de coloration pour des mots-clés spécifiques dans une ligne de dialogue, vous devez être prudent sur l'endroit où mettre les deux éléments de balisage. Cette solution est toujours valide, vous aurez juste besoin de vous assurer de ne pas confondre le script entre les balises d'opacité et les balises de couleur.

Je n'ai pas pris le temps de le faire moi-même. C'est juste une façon de le faire.

Calculer la largeur de la boîte et du texte

La deuxième solution sonne bien sur le papier. En fait, je voulais vraiment que ça marche.

En théorie, il devrait être assez facile d'ajouter des sauts de ligne automatiquement à notre chaîne. Nous avons simplement besoin de calculer la largeur de chaque caractère dans notre chaîne et puis voir si le mot actuel déborde dans notre zone de texte avant de l'imprimer.

Il est possible dans Unity 5 de calculer la largeur d'un caractère spécifique dans une police donnée. C'est très bien pour nous, car nous n'avons pas à changer quoi que ce soit si jamais nous décidons de changer la police de notre boîte de dialogue. Il devrait également travailler sur des résolutions différentes. Parfait!

maxWidthBeforeBreak

J'ai déterminé une largeur maximale avant d'ajouter un saut de ligne. J'ai parcouru ma chaîne filtrée (sans commandes) et gardé les sommes de la largeur de tous les caractères, que j'ai stockées dans lineWidth (ce nom aurait pu être mieux).

J'ai aussi gardé la position du dernier index où j'avais un espace vide dans ma chaîne, que j'ai mis dans lastSpace.

Si lineWidth > maxWidthBeforeBreak, nous mettons un saut de ligne après le dernier espace (lastSpace). Et boum! Ça devrait faire l'affaire.

Code mis à jour pour cette partie.

AVERTISSEMENT: ce code a fini par ne pas être bon pour moi à long terme. Voir ci-dessous.

Incohérences avec la largeur du caractère

Pour une raison étrange, Unity n'a pas toujours retourné la bonne largeur pour un caractère spécifique, qui a foiré les calculs de saut de ligne.

Après beaucoup de recherches, il semble que le Canvas interférait avec le code ci-dessus. Si le mode échelle de l'interface utilisateur (UI Scale Mode) est défini sur échelle avec la taille de l'écran (Scale With Screen Size), le code qui vérifie la largeur des caractères cesse de retourner les bonnes valeurs.

J'avais besoin du Canvas à être à ces paramètres, de sorte que l'interface utilisateur peut s'ajuster à l'échelle basée sur l'écran du joueur. (Notez que, si vous ciblez juste une résolution et/ou rapport d'aspect, le code ci-dessus devrait fonctionner, bien que je vous conseille de lire plus ci-dessous pour quelque chose beaucoup mieux quand même.)

Non seulement les valeurs étaient erronées, parfois j'ai vu un caractère ne retournant rien. Je n'arrivais pas à comprendre ce qui l'a fait agir ainsi. Parfois, la valeur était complètement différente de ce que je m'attendais. Ça changeait à chaque fois que je cliquais sur jouer.

Je n'ai trouvé aucune solution en-ligne pour le problème.

(j'ai vu une seule mention dans une section de commentaires suggérant que le Canvas peut être le coupable. Je voulais l'ajouter ici, mais j'ai perdu le lien.)

Le pouvoir derrière TextMeshPro

Parce que je ne pouvais pas comprendre quoi que ce soit sur la façon de corriger le code ci-dessus, j'ai continué à travailler sur d'autres fonctionnalités pour mon jeu.

Nous pouvons maintenant animer des personnages et changer leur animation quand nous le voulons.

Notre dialogue est maintenant stocké à l'intérieur des fichiers JSON, facile à modifier. Le code le lit comme un scénario réel.

Jusque là, j'ai toujours eu les problèmes avec les sauts de ligne dans ma boîte de dialogue.

Jusqu'à TextMeshPro.

TextMeshPro fournit une grande liste de fonctionnalités: plus de contrôle sur le format de texte, le rendu de texte avancé, etc. Utilisez le dans tous vos projets. Sérieusement.

Avec TextMeshPro, il est en fait assez facile de créer l'animation de machine à écrire. Il y a même une démo le mettant en vedette:

Si nous revenons à notre code de base avec notre Coroutine, tout ce que nous devons faire est de remplacer notre objet texte par le nouvel objet texte TextMeshPro.

Nous pouvons maintenant utiliser maxLetterDisplayed afin d'animer notre texte.

Au lieu d'ajouter une lettre à notre objet texte à un intervalle régulier, nous pouvons lancer maxLetterDisplayed = 0 et l'incrémenter de 1 après quelques millisecondes.

Le texte est déjà formaté. TextMeshPro montre simplement ou masque le nombre de caractères défini. Cela signifie que le texte est déjà placé sur notre écran et se placera correctement.

Enfin, le texte ne saute pas!

Vous pouvez consulter le code mis à jour ici.

Avantages supplémentaires

Je ne l'ai pas mentionné avant, mais les alignements de texte ont également été un problème avec notre version précédente.

Nous ne pouvions pas centrer une ligne de texte, car cela signifierait que la chaîne commencerait au milieu et se développerait à partir de là, au lieu de montrer à la position la plus à droite, tout en restant centré. TextMeshPro arrange ça aussi.

Effet de tremblement pour le texte

TextMeshPro est livré avec plusieurs démos, y compris un effet de tremblement pour les lettres:

Bien que cette démo semble terrible, nous allons l'utiliser comme notre base dans notre code.

Cependant, il semble y avoir un problème avec mon script actuel. Nous ne pouvons pas faire trembler le texte alors qu'il n'est pas actuellement affiché sur l'écran du joueur.

Il semble que maxLetterDisplayed a une sorte de limitation. Je veux quand même que textetremble tout en apparaissant à l'écran.

Afin de réussir, j'ai eu besoin de jeter un œil de plus attentif au démo. Le démo utilise les quatre sommets de chaque lettres afin de les faire trembler.

Par conséquent, si un caractère n'est pas affiché, le script casse et le jeu plante.

Avant cette découverte, j'étais certain que, avec maxLetterDisplayed, le texte était encore dans la scène et certaines parties ne sont que cachées, mais il semble que le script ne peut pas obtenir les sommets quand ils ne sont pas affichés.

Les caractères ne sont probablement pas affichés dans l'élément de la zone de texte, et ne sont donc pas accessibles.

Cela signifie que nous ne pouvons pas pré-préventivement faire trembler le texte avant de l'afficher sans faire quelques changements d'abord.

La solution que je suis venu avec est un rappel à la première solution que j'ai trouvé plus tôt pour notre problème de saut de ligne. Que faire si, au lieu d'utiliser maxLetterDisplayed, nous animons l'opacité de chaque caractère dans notre zone de texte?

Techniquement, le texte sera rendu à l'écran; son alpha est juste réglé sur 0. Cela signifie que le script peut effectivement obtenir les quatre sommets dont il a besoin pour l'effet de tremblement que je veux.

Il ya même une démo dans le dossier TextMeshPro où ils vous montrer comment modifier la couleur d'une lettre spécifique à l'aide de leurs sommets!

Nous avons mis l'opacité à 0 pour tous le texte. Ensuite, on le fait trembler. Enfin, nous faisons l'effet de machine à écrire en changeant l'opacité de nos caractères, un à la fois.

Remarque: avec quelques modifications, la commande {Color} peut également utiliser ce script. Il est beaucoup plus facile et nous n'avons plus besoin d'utiliser des éléments de balisage dans notre zone de texte. Il suffit de changer la couleur de chaque caractère à celui que nous trouvons dans notre commande couleur. Lorsque nous obtenons la commande {Color: end}, nous retournons à la couleur de base.

Consultez ce code mis à jour ici.

Quelle est la suite?

Ça devrait être tout pour ma boîte de dialogue. La prochaine chose que nous pourrions faire est de faire pauser Coroutine plus longtemps lorsque le caractère actuel est la ponctuation. Cela rendrait la conversation plus naturelle.

Aussi, envisager d'avoir aucune pause lorsque le caractère actuel est un espace. Cela devrait supprimer la pause bizarre entre chaque mot.

(au moment de l'écriture de cet article, j'ai oublié de faire ces choses dans mon propre jeu. J'ai juste pensé à ça maintenant, en cherchant d'autres choses à faire pour finir cet article.)

Nous pourrions également ajouter des vitesses de texte diverses pour notre jeu. De cette façon, nos acteurs ne parlent pas toujours au même rythme. Les émotions peuvent être plus faciles à comprendre pour vos joueurs lorsque vous prenez le temps de mettre en œuvre ces détails subtils. Et la vitesse du texte est juste une façon de le faire.

Dans mon jeu, chaque personne a un clip audio vocal, qui émet un bip en plus du son d'une machine à écrire. Le script boîte de dialogue sait quelle personne est en train de parler et joue le clip audio en conséquence. Le clip vocal joue à peu près au même intervalle que le texte qui s'anime à l'écran.

Votre scénario/dialogue pourrait également être stocké en dehors de votre jeu, surtout si vous cherchez à avoir votre jeu localisé. Il sera plus facile à éditer.

Voici tous les exemples trouvés dans cet article de blog.

Vous pouvez avoir un coup d’œil sur le code derrière chaque démo unique que nous avons fait pour ce billet de blog ici.

Enfin, la fin

Ici se trouve la fin de mon tout premier article de blog public. J'espère que cette expérience peut en quelque sorte aider certains d'entre vous.

Voici quelques ressources supplémentaires et ce que j'ai utilisé, pour référence. Si vous avez des questions, n'hésitez pas à poster un commentaire ci-dessous! Nous allons les lire tous et nous serions heureux de vous aider.

Ressources, recherche et référence