top of page

After School

Jeu solo et coopératif - Unreal Engine 5.1 C++

After School a été réalisé dans le cadre du concours universitaire Ubisoft 2023. Il a été réalisé en 10 semaines avec le thème Arcade. Le jeu devait aussi présenter un élément de gravité, de construction et de destruction. Dans after school, c'est moi qui me suis occupé de la gravité en réalisant une fusée qui affecte la gravité des personnages qui se trouve dans la zone autour de l'endroit ou à elle a été construite.

Trailer

Gameplay vidéo

image équipe ubi.jpg

Sommaire

La fusée

Initialisation de la fusée et comportement général

Quand le joueur possède assez de boîtes de carton, il peut alors faire apparaitre une fusée. Cette fusée crée une zone de gravité sous la forme d'une sphère qui change la gravité autant pour le joueur que pour les ennemis. Je devais donc faire varier la gravité quand un personnage se trouve à l'intérieur de la sphère et le remettre à sa gravité normale quand il en sort ou quand la fusée est détruite. 

La fusée

Faire flotter les ennemis 

Pour faire flotter les ennemis je change leurs gravités et je leur donne une vitesse réduite pour les faire flotter dans les airs tranquillement pour donner l'effet de flottement comme s'ils étaient dans l'espace. Je me suis également assuré qu'ils ne sortent pas de la zone en mettant une limite de hauteur maximale.

Défis rencontrés

Le principal défi rencontré quant à cette mécanique était de trouver comment faire flotter les ennemis sans les faires voler trop haut ou trop vite. Je devais également m'assurer de faire flotter les ennemis à l'intérieur la sphère quand celle-ci est créée et des faires retomber au sol quand les ennemis sont encore à l'intérieur de la zone et que la fusée est détruite. Pour régler cela, j'ai dû utilisé la méthode "get overlapping actors" et gérer ces comportements avec les ennemis détectées.

rocket high res screenshot for itch.png

La fusée

La navigation AI des ennemis pendant le flottement

Dans la zone de gravité, les ennemis doivent également suivre le déplacement du joueur pendant qu'ils sont en train de flotter. Autrement, les ennemis ne feraient que flotter droit en avant d'eux jusqu'à ce qu'ils sortent de la zone et cela n'est pas très fun. 

Défis rencontrés

Cette mécanique était probablement l'un des aspects les plus complexes de la fusée. En effet, le problème était que comme ceux-ci sont dans les airs, ils ne sont pas sur le nav mesh. Donc, tout comportement de path finding classic de AI est impossible. C'est pourquoi je devais donc trouver une façon de déplacer tous les AI qui flotte dans la zone de façon optimale et sans avoir recours au nav mesh. Pour régler ce problème, j'ai décidé de créer une blackboard key qui détermine quand une ennemi entre dans la zone de gravité et ainsi activer une tâche AI dans le behavior tree des ennemis pour ensuite les faire bouger dans les airs. Après plusieurs tentatives avec des add force et add impulse, j'ai finalement obtenu le résultat escompté en ajoutant des inputs "AddMovementInput" à chaque ennemi se trouvant dans la zone. Et l'input en question est un vecteur de direction normalisé dans la direction du joueur. De cette façon, je parviens à faire déplacer les AI dans la zone de gravité en leur ajoutant plusieurs inputs vers la position du joueur sans avoir recours au nav mesh!

La fusée

Le flottement du joueur

Comme vous pouvez le voir dans la vidéo ci-dessus j'ai également fait en sorte que le joueur puisse bien contrôler ses mouvements dans la zone de gravité tout en donnant une impression de flottement. En effet, le joueur peut monter dans la zone de gravité en maintenant le bouton A et descendre en maintenant le bouton B. Je me suis également assuré que même si le joueur maintient le bouton B et qu'il est déjà au niveau du sol, le personnage ne remet pas les pieds au sol et il continue à flotter dans les airs tant que la fusée est active. Ainsi, cela ne brise pas l'immersion de la gravité. J'ai également donné une légère vélocité résultante quand le joueur cesse de monter ou de descendre pour donner une impression de flottement et démontrer l'absence de friction et conservant la vélocité dans la même direction même si le joueur est actuellement immobile. Toutefois, j'ai tout de même conservé une certaine friction des mouvements latéraux quand le joueur arrête de bouger pour garder le mouvement agréable et facilement contrôlable. Cela évite donc toute frustration venant du joueur et rend la zone de gravité plus agréable.

Défis rencontrés

Le principal défi de cette mécanique était de rendre le déplacement du joueur à la fois fun, contrôlable et relativement réaliste. Cela a donc demandé beaucoup de paramétrage d'ajustement, mais comme j'étais déjà familiarisé avec les enhanced input de unreal engine, cela s'est tout de même bien passé et je suis très satisfait du résultat.  

La fusée

La gestion du overlapping de plusieurs fusées

Contre toute attente, la mécanique de la fusée qui m'a causé le plus de problèmes est la gestion du overlapping de plusieurs fusées. En effet, dans le jeu, il est possible de créer autant de fusée que voulut dans un même endroit, tant que le joueur possède la quantité de carton suffisante. Je devais donc gérer l'overlap de plusieurs fusées sans que cela ne brise le déplacement gravitationnel des personnages. 

Défis rencontrés

En effet, j'active la gravité quand un personnage entre dans une zone ou quand la zone est créée et que le personnage se trouve à l'intérieur de celle-ci. Ensuite, je la désactive (remets la gravité par défaut) quand un personnage quitte la zone ou que la fusée est détruite (après le timer de 25 secondes que je lui ai assigné). Toutefois, si les personnages se trouvent dans plusieurs fusées l'une par-dessus les autres,  les personnages ne doivent pas retomber au sol quand ils vont sortir de la première fusée ou encore continuer de flotter quand ils vont être sortis de toutes les zones de gravitées. Pour régler ce problème, j'ai dû faire preuve d'ingéniosité et j'ai trouvé que la meilleure façon était de me créer un compteur d'overlaps pour détecter le nombre d'overlap actuel pour chaque personnage et à quel moment le personnage est sortie de la zone. 

 

Chaque personnage (ennemi ou joueur) possède donc un compteur de zone de gravité qui lui est propre. Ainsi, quand un personnage entre dans une nouvelle  zone de gravité et qu'il était déjà en train de flotter, j'ajoute 1 au compteur. Et à chaque fois que le personnage quitte une zone de gravité je retire 1 au compteur. Si ce compteur redevient à 0, cela veut dire que le personnage est sortie de toutes les zones, donc je peux lui remettre sa gravité normale. J'ai aussi ajouté 1 au compteur quand le personnage est initialement dans la fusée lorsqu'elle est construite, sinon l'overlap ne serait pas appelé correctement. De la même façon, le retire 1 au compteur si une fusée est détruite alors que le personnage flotte déjà dans une zone de gravité au même endroit. Autrement, le personnage ne serait pas à jour avec le compteur ou encore, le personnage retomberait sur le sol. Dans la vidéo ci-dessous je démontre le résultat final obtenu grâce à cette méthode.

La fusée

Shader de la zone de la fusée et mécanique abandonnée pour une raison de game design

C'est également moi qui à créer le shader de la fusée (la bulle multicolore qui délimite la zone de gravité) et qui en a fait un material instance. J'ai également réalisé une mécanique supplémentaire avec la fusée, mais celle-ci n'a pas été retenu dans la version finale du jeu pour une raison de game design du game designer. Cette mécanique est l'ajout d'un saut augmenté, un peu comme un astronaute qui saute sur la lune. En effet, tant qu'il y a une fusée d'active dans le jeu, le joueur peut sauter plus haut et plus loin, comme s'il était sur la lune. Il reprend également son saut normal quand il n'y a plus de fusée active dans le jeu. Dans la vidéo ci-dessous vous pouvez voir une fois de plus mon shader et cette mécanique de saut, que j'ai appelé : moon jump.

Défis rencontrés

Le premier défi rencontré était de rendre le shader de la bulle visible autant de l'intérieur que de l'extérieur. Comme c'est une bulle et que c'est un shader de type translucide pour voir au travers, le rendre "two-sided" ne donnait pas un résultat intéressant. J'ai donc réglé ce problème en donnant une petite opacité au shader, pour que la propriété "two-sided" du shader puisse rendre l'intérieur de la bulle plus visible. Pour le moon jump, le principal défi était de détecter s'il y avait une fusée active dans la map. Mais j'ai facilement résolu ce défi en utilisant un compteur sur le joueur un peu comme le compteur que j'ai  utilisé pour l'overlap de plusieurs fusées. 

DayNightActor entre les vagues

Déplacement du soleil par programmation de façon paramétrable entre les vagues d'ennemis

Entre les vagues d'ennemis, j'ai fait un acteur qui fait déplacer le soleil à l'endroit voulu de façon progressive. Il est également possible de décider du temps que prend le soleil pour se déplacer de la position A à la position B. Plus le temps est long, plus le déplacement du soleil sera lent. Ce temps de déplacement est particulièrement intéressant, car il permet de bien voir le déplacement du soleil et des ombres des divers objets dans la map par la même occasion. J'ai réalisé cela en créant un tableau d'enum qui représente les vagues 1 à 5. Chaque élément de l'enum est associé à un certain angle du soleil, ces angles sont ensuite bouclés de façon linéaire entre chaque vague d'ennemis. Ainsi, de la vague 0 à la vague 4, le soleil continu de descendre vers l'horizon pour montrer au joueur que le temps avance et faire varier la lumière dans la map. Cela permet aussi d'avertir que la vague cauchemar approche. Une fois la vague cauchemar lancé à la vague 5, le soleil reprend sa position original et le cycle continue ainsi pour un nombre infini de vagues d'ennemis.

Défis rencontrés

Le principal défi de cette mécanique était de rendre le tout le plus optimal possible, sans couter trop cher en performances. En effet, le changement de position de soleil et de l'ombre de beaucoup d'objets en même temps peut être très demandant en temps de calcul. Mais en enlevant certaines ombres et en mettant un temps de déplacement du soleil qui n'était pas trop élevé j'ai pu avoir un résultat plutôt satisfaisant. J'ai aussi pris soin de ne pas mettre un déplacement graduel à la vague cauchemar, car en raison de l'épais brouillard rouge, le joueur ne voit pas le soleil se déplacer ou même les ombres au sol de toute façon.

Comportement de déplacement des collectables

Déplacement des collectables lors du spawn et lors de la détection avec le joueur

Lorsqu'un ennemi meurt, il fait apparaitre divers collectables qui se déplacent dans un rayon aléatoire autour de sa position de spawn initiale selon un certain radius. Je me suis donc occupé de ce déplacement ainsi que  de la détection de l'environnement pour permettre aux collectables de toujours spawn et se déplacer à un endroit visible et accessible au joueur. J'ai également fait en sorte que les collectables, soit attirées comme un aimant vers le joueur quand celui-ci se trouve a proximité. Cela rend la collecte des objets plus facile et satisfaisante. Par soucis d'optimisation, j'utilise des "timelines" pour les divers déplacements des collectables, mais la détection et vérification des destinations finales des collectables sont toutes trouvées par programmation. 

Défis rencontrés

Les principaux défis de cette mécanique tournaient autour de la gestion et de la détection de la position finale aléatoire des divers collectables qui était spawn. En effet, je devais m'assurer que les collectables spawn toujours au sol, peu importe le niveau du sol (soit à une hauteur normale, soit plus bas dans le skatepark). En effet, les ennemis qui meurent pendant qu'ils sont en train de flotter en hauteur dans une fusée doivent ainsi faire spawn leurs collectables au niveau du sol, pour que le joueur puisse les atteindre. Je devais aussi éviter que les collectables spawn dans la fontaine, en dehors de la map ou dans un objet de la map. Pour régler ces problèmes, j'ai utilisé plusieurs "sphere trace" et "line trace" à l'endroit dès la destination finale aléatoire des collectables qui est précalculée, pour m'assurer qu'elle est valide. Ainsi, je détecte si la end destination est dans une fontaine, si oui je change le end destination pour une position aléatoire autour de la fontaine en question. Pour la détection du sol, je fais descendre une petite sphère de la potion initiale vers le bas jusqu'au contact avec le sol et je récupère la position de l'impact pour ensuite déterminer la position en Z de la end destination. Pour éviter que le collectable ne spawn en dehors de la map (de la zone jouable) je vérifie si la end destination est sur le nav mesh et je réduis la distance de la random end destination en conséquence. Et je procède de la même façon pour éviter de faire spawn les collectables à l'intérieur d'un static mesh dans la map. Vous pouvez voir le comportement de mes collectables dans la vidéo ci-dessous.  

Loot drop system des ennemis

Spawn de butin selon un pourcentage de chance lors de la mort d'un enemie

Je me suis également occupé de faire spawn les collectables à la mort de l'ennemi selon un pourcentage de chance de drop du collectable en question pour chaque ennemi. Avec les comportements de déplacement décrit dans la section précédente.

Loot drop system de la pinata

Spawn de butin quand la pinata est détruite

J'ai également fait le system de spawn des collectables lors de la destruction de la pinata (la pinata a été faits par l'une des membres de mon équipe). Toutefois, pour le spawn des collectables de la pinata, il n'y a pas de valeur aléatoire et on peut décider du nombre précis d'objets que l'on souhaite spawn à chaque fois. Le radius de déplacement aléatoire lors du spawn des collectables de la pinata est également plus grand et ne requiert aucune détection supplémentaire, car le spawn et la position initiale se fait toujours au même endroit, contrairement au loot drop system des ennemis.

Zone de spawn (spawner)

Zone de spawn de n'importe quel acteur à une position aléatoire dans les limites de la zone selon un temps paramétrable

J'ai également fait un spawner au tout début du projet qui permet de faire spawn n'importe quel acteur. Ce spawner a été utilisé plus tard par l'un de mes collègues pour faire spawn les oeufs d'araignées à des positions aléatoires dans la zone du spawner avec un intervalle de temps désirée.

Défis rencontrés

Le principal défi du spawner était de trouver comment faire en sorte de bien calculer les dimensions de la zone de spawn et de trouver la position aléatoire à l'intérieur de celle-ci. Ainsi que de permettre de pouvoir choisir parmi tous les acteurs que l'on désire faire spawn dans la zone. C'était mon premier spawner avec unreal en c++, cela a donc été très instructif.

Indicateur visuel lors d'une partie coop (blueprint)

Indicateur visuel qui indique la position de l'autre joueur lors du partie coopérative

L'indicateur visuel qui indique la position du second joueur, est l'UI le plus complexe que j'avais réalisé jusqu'à présent. En effet, il s'agit de la flèche qui informe le joueur de la position dans le monde du second joueur en splitscreen. Pour cela je devais faire une détection quand le second joueur est onscreen ou offscreen. Je dois également définir la position de la flèche pour qu'elle soit toujours au-dessus de la tête du second joueur et que la position que pointer la flèche ne cache pas des éléments importants du reste du HUD. Toutefois, cela n'est pas la partie qui m'a causé le plus de problèmes. En effet, l'élément qui m'a causé le plus de problèmes n'a pas été soumis dans le build final du jeu pour une raison de game design. 

Version dans le build final
Défis rencontrés

Le défi de cette mécanique est que selon les tailles différentes des écrans, l'indicateur ne suivait pas correctement les bordures de l'écran. Pour régler cela, j'ai dû jouer avec les pourcentages de la taille de l'écran sur lequel joue le joueur pour m'assurer que le comportement de rotation de la flèche soit toujours bien représenté. J'ai également dû faire plusieurs expérimentations avec les offsets pour toujours avoir la flèche qui ne dépasse pas de la zone en bordure de l'écran et conserve un mouvement fluide dans la section correspondante en splitscreen. 

Les défis de conceptions du projet et conclusion

Finalement, After School aura été un projet rempli de défis vraiment intéressants que je n'aurais jamais crû rencontrer un jour. Mais chaque embuche m'a permis de m'améliorer et de découvrir de nouvelles choses pour parvenir à régler mes problèmes (apprendre à utiliser des lines traces, delegates, manipuler des valeurs et positions aléatoires, manipuler la gravité, faire spawner des acteurs, etc). Chacune de ces mécaniques m'a permis de faire preuves d'ingéniosité et de prendre de l'expérience en tant que programmeur gameplay. Même si deux de ces mécaniques ne se sont pas rendu au build final, je suis tout de même très content du travail que j'ai fait et de pouvoir les partager tout de même dans mon portfolio. Même si cela n'a pas toujours été facile et que cela m'a demandé de nombreuses heures de travail acharné je suis fier du jeu que moi et mon équipe a créé et de l'incroyable expérience qu'était le concours Ubisoft 2023.

bottom of page