Pourquoi j’ai repris le dĂ©veloppement

Cet article fait suite au précédent.

gantt
    title Période de développement du jeu de tir mobile
    Développement initial: a1, 2023-02-07, 60d
    Développement avancé: crit, 2023-06-19, 150d
    Publication test sur le store: milestone, 2023-03-28, 0d
    Republication sur le store: crit, 2023-10-29, 18d

En mars, le nouveau semestre a commencĂ©. Pendant environ le premier mois, j’ai continuĂ© Ă  crĂ©er les systĂšmes de base du jeu comme l’inventaire du joueur, mais Ă  l’approche des examens, le dĂ©veloppement s’est interrompu sous la pression. Cependant, aprĂšs les examens finaux de juin, j’ai retrouvĂ© du temps libre et j’ai dĂ©cidĂ© de reprendre le jeu que j’avais commencĂ©.

Pendant les deux mois de vacances d’étĂ©, je me suis fortement intĂ©ressĂ© aux aspects visuels comme les assets image et les effets de particules. L’impression que le jeu s’amĂ©liorait efficacement, couplĂ©e Ă  l’idĂ©e de crĂ©er une ambiance unique qu’on ne trouve nulle part ailleurs, me semblait trĂšs singuliĂšre. Cette expĂ©rience n’étant pas facile Ă  vivre, j’ai tentĂ© un pari un peu risquĂ© au second semestre : suivre les cours et les examens correctement, mais investir autant de temps que possible dans le dĂ©veloppement du jeu.

Ce post résume donc ce qui a été fait et comment sur toutes les autres parties durant cette période.

Création des armes

Animation de tir {#weapon-animation}

flare

if (shotTimer > fireThreshold)
{
    WeaponAnimator.SetTrigger("Fire");
}

shotTimer += Time.deltaTime;

J’ai créé une animation de tir Ă  l’aide du composant Animation d’Unity. Le composant Animation d’Unity permet non seulement l’animation cut traditionnelle par remplacement d’images de sprite, mais aussi l’animation en ajustant directement la position des objets enfants. J’ai utilisĂ© les deux types de maniĂšre appropriĂ©e pour que l’animation de recul correspondante soit jouĂ©e lors du tir.

L’effet de flamme devant le canon a Ă©tĂ© floutĂ© sur l’image elle-mĂȘme, puis j’ai exagĂ©rĂ© la taille du sprite et ajoutĂ© l’effet lumineux d’URP et l’effet Bloom du post-processing pour rĂ©duire l’aspect terne et crĂ©er un effet accrocheur et flamboyant.

flare-clipstudioflare-images

Les images composant l’effet d’animation de flamme ont Ă©tĂ© créées avec la fonction d’animation de Clip Studio. CrĂ©er des sprites d’animation soi-mĂȘme Ă©tant un travail numĂ©rique fastidieux, j’ai envisagĂ© d’utiliser les assets officiels d’Unity, mais aucun n’avait le rendu que je voulais, donc je les ai dessinĂ©s moi-mĂȘme. En les crĂ©ant, j’ai lentement Ă©tudiĂ© d’autres animations de tir image par image pour obtenir le rendu souhaitĂ©.

gained-animation

Pour attĂ©nuer le cĂŽtĂ© artificiel du changement d’objet d’arme, j’ai Ă©galement créé une animation de vĂ©rification de chambre spĂ©cifique Ă  chaque arme, jouĂ©e uniquement lors du changement ou de l’obtention d’une nouvelle arme. Un lĂ©ger dĂ©lai est imposĂ© aux contrĂŽles du joueur pendant la manipulation de l’arme, ce qui rend l’expĂ©rience de jeu beaucoup plus organique et satisfaisante.

Effet de hit sur les ennemis

hit-particle particle-graph

public void Hit()
{
    ParticleSystem hitEnemyParticle = hit.collider.GetComponent<ParticleSystem>();
    hitEnemyParticle.Emit(particleNumber);
}

L’effet de hit a Ă©tĂ© créé avec le systĂšme de particules. Au dĂ©but, je l’avais simplement implĂ©mentĂ© avec des particules se dĂ©plaçant dans des directions alĂ©atoires en ralentissant progressivement, mais le rĂ©sultat Ă©tait Ă©trange, ce qui m’a posĂ© problĂšme.

La solution est venue un peu par hasard : en rĂ©glant la vitesse linĂ©aire et la vitesse orbitale sur “Random between two curves” dans le module Velocity over Lifetime, puis en tordant deux fois la courbe, j’ai obtenu un effet de poussiĂšre qui s’élĂšve, que j’ai adoptĂ©. Le rendu est correct et l’impact semble plutĂŽt bon.

SystĂšme de munitions

pistol-reload pistol-animation

public virtual void Update()
{
    if (roundsCurrent > 0)
        Fire();
    else if (!WeaponAnimationInfo.IsTag("Weapon_Reload"))
        WeaponAnimator.SetTrigger("RoundIsEmpty");
    else
        roundsCurrent = roundsMax;
}

public virtual void Fire()
{
    if      (currentRounds == 1) WeaponAnimator.SetTrigger("FiredLastRound");
    else if (currentRounds > 0)  WeaponAnimator.SetTrigger("Fired");

    roundsCurrent -= 1;
}

J’ai créé un systĂšme d’affichage des balles restantes. Quand les balles atteignent zĂ©ro, l’animation de rechargement est jouĂ©e, et Ă  la fin de celle-ci, les munitions reviennent Ă  la valeur maximale dĂ©finie sur l’objet de l’arme. Comme pour les PV du joueur, l’interface des munitions est affichĂ©e simplement sous forme d’objet de jeu au-dessus de la tĂȘte du joueur.

J’ai aussi ajoutĂ© un petit dĂ©tail : si l’arme est changĂ©e avant la fin de l’animation de rechargement, une animation GainedEmpty distincte de l’animation Gained est jouĂ©e lorsqu’on reprend cette arme plus tard. La diffĂ©rence est que GainedEmpty commence le rechargement avec la culasse bloquĂ©e en position arriĂšre, laissant la chambre visible. J’ai repris cela aprĂšs l’avoir vu implĂ©mentĂ© dans de nombreux FPS.

Effet de dégùts

damage-effect damage-effect-animation

L’effet de dĂ©gĂąts lui-mĂȘme avait Ă©tĂ© implĂ©mentĂ© lors du dĂ©veloppement initial, mais comme son comportement Ă©tait codĂ© plutĂŽt qu’avec un composant Animation et que son rendu visuel laissait Ă  dĂ©sirer, je l’ai refait. Au lieu de simplement disparaĂźtre en devenant transparent, j’ai rendu la taille et la vitesse de dĂ©placement de l’effet dynamiques.

En le crĂ©ant, j’ai aussi implĂ©mentĂ© un systĂšme de critique : quand les dĂ©gĂąts doublent de maniĂšre probabiliste, une animation spĂ©cifique est jouĂ©e. L’animation se distingue de l’animation de dĂ©gĂąts normaux par sa taille et sa couleur, pour signaler facilement qu’un coup critique a Ă©tĂ© infligĂ©.

Diversification des armes

classDiagram
    class Weapon {
		- rpm: Int
        - maxRounds: Int
        - currentRounds: Int
    }

    class Bullet {
        - damage: Int
        - speed: Int
    }

    Weapon <|-- Pistol
    Weapon <|-- Shotgun
    Weapon <|-- Sniper
    Bullet <|-- PistolBullet
    Bullet <|-- ShotgunBullet
    Bullet <|-- SniperBullet
public abstract class Weapon : MonoBehaviour
{
    protected int   RPM;
    protected int   maxRounds, currentRounds;

    public virtual void Awake()
    {
        /* ... */
    }
}
public class Pistol : Weapon
{
    public override void Awake()
    {
        base.Awake();
        
        maxRounds     = 10;
        rotationSpeed = 40;
    }
}

Au dĂ©part, je n’avais pas prĂ©vu de crĂ©er principalement des fusils, mais en rĂ©utilisant ce que j’avais fait au dĂ©but, j’ai fini par en faire plusieurs, surtout des armes Ă  feu. En les crĂ©ant, j’ai Ă©tĂ© attentif au polymorphisme de la programmation orientĂ©e objet : j’ai Ă©crit les Ă©lĂ©ments de base comme RPM, maxRounds, currentRounds dans la classe parente Weapon.cs, et des classes d’armes spĂ©cifiques comme Minigun.cs, Shotgun.cs, SMG.cs l’hĂ©ritent.

C’était ma premiĂšre utilisation de l’hĂ©ritage, et le travail Ă©tait nettement plus efficace qu’avant. L’idĂ©e de centraliser le code rĂ©pĂ©tĂ© Ă  un niveau bas pour l’appeler dans le code terminal Ă©tait trĂšs diffĂ©rente de l’utilisation d’une bibliothĂšque, ce qui m’a paru Ă  la fois Ă©trange et fascinant.

CrĂ©ation d’animations

Déplacement du joueur

player-moving player-animation

Utiliser la forme rectangulaire de base fournie par Unity comme joueur me semblait trop nĂ©gligĂ©, j’ai donc ajoutĂ© un corps et des jambes en mouvement. Selon que le joueur tire le joystick Ă  fond ou non, l’animation de marche ou de course est jouĂ©e de maniĂšre appropriĂ©e.

Pour rĂ©duire l’aspect artificiel de l’animation, la vitesse de lecture de l’animation de marche varie dynamiquement selon la force de traction du joystick, et j’ai ajoutĂ© la possibilitĂ© pour le joueur de marcher en arriĂšre selon la direction de visĂ©e. Par exemple, si le joueur marche vers la gauche mais que l’ennemi est Ă  droite, le joueur recule lentement en visant l’ennemi. RĂ©sultat : le mouvement semble assez naturel, pas artificiel.

Systùme d’EXP

exp-bar exp-bar-animation

Pour attĂ©nuer un peu l’ennui pendant le jeu, j’ai créé un systĂšme d’EXP. En Ă©liminant des ennemis, le joueur gagne de l’EXP. Quand l’EXP atteint un certain seuil, le niveau augmente, le joueur est renforcĂ©, et le niveau accumulĂ© s’affiche sous forme de score dans l’écran de fin de partie.

Au dĂ©but, le joueur devait collecter directement les particules d’EXP, mais plus la partie avançait, plus l’écran devenait encombrĂ© par le nombre croissant d’ennemis. J’ai donc changĂ© pour que l’EXP soit acquise immĂ©diatement Ă  l’élimination d’un ennemi. AprĂšs application, cette mĂ©thode est bien plus propre, au point de sembler standard.

Transition vers l’écran de jeu

game-enter

Personnellement, je prĂ©fĂšre les transitions fluides entre les scĂšnes plutĂŽt que des coupures brusques, car cela donne l’impression d’ĂȘtre pris en charge par le programme. Je voulais appliquer cela Ă  mon jeu aussi.

Ainsi, lorsque l’on appuie sur le bouton de jeu, la scĂšne ne se contente pas de changer : le joueur apparaĂźt d’abord Ă  partir d’un objet de la mĂȘme forme que le bouton, de taille identique. Lorsque le bouton est pressĂ©, l’UI de la scĂšne principale disparaĂźt en douceur, et l’UI de la scĂšne de jeu apparaĂźt depuis les bords de l’écran. Bien qu’on y voie des dĂ©fauts typiques d’un amateur, je suis un peu fier d’avoir créé une expĂ©rience unique qu’on ne trouve pas dans d’autres jeux.

Développement avancé : autres travaux

Assets image

object-design
Dessiné sur Galaxy Tab

Comme expliquĂ© prĂ©cĂ©demment, tous les assets image ont Ă©tĂ© créés maison sans utiliser l’Unity Asset Store. AprĂšs avoir d’abord dessinĂ© les armes en pixel art, le rendu n’étant pas artificiel, j’ai Ă©galement créé d’autres images comme les ennemis, les effets de hit, le joystick, la barre d’EXP en pixel art. Le pixel art Ă©tant peu contraignant Ă  dessiner, j’étais assez libre dans mon travail, pouvant crĂ©er plusieurs maquettes ou renouveler les images utilisĂ©es.

Je les ai principalement exportĂ©es depuis Clip Studio au format PNG avec fond transparent, dĂ©coupĂ©es Ă  la taille de chaque image, puis importĂ©es en tant que Sprite (2D and UI) avec le Filter Mode sur Point (no filter) et la Max Size adaptĂ©e Ă  la rĂ©solution de l’image.

Caméra

GrĂące Ă  mon hobby photographique, j’ai dĂ©couvert qu’on pouvait exprimer beaucoup de choses avec l’angle de vue, et j’ai voulu l’appliquer Ă  mon jeu. L’environnement 2D d’Unity affiche la scĂšne en projection orthographique, donc le concept diffĂšre, mais du point de vue abstrait de ce qui est inclus dans le cadre, j’ai estimĂ© qu’il y avait aussi des considĂ©rations Ă  prendre en compte en 2D.

pistol-reload

game-enter-cropped

J’ai donc fait en sorte que la valeur Camera.orthographicSize, qui dĂ©termine le champ de vision de la camĂ©ra, puisse changer selon mes souhaits Ă  diffĂ©rents moments. Par exemple, lors d’un rechargement ou au dĂ©but d’une nouvelle partie, j’ai rĂ©duit l’angle de vue pour exprimer vulnĂ©rabilitĂ© et tension. En buildant une application de test et en jouant directement, j’ai constatĂ© que l’intention Ă©tait bien rendue et rendait le gameplay unique, ce qui m’a satisfait.

Audio

audacityaudacity
Son de coup critique

GĂ©rer moi-mĂȘme les Ă©lĂ©ments sonores comme la musique de fond et les effets sonores a Ă©tĂ© un peu dĂ©routant. Contrairement au dessin ou au codage, je ne connaissais rien au domaine audio. Je ne savais pas oĂč ni comment obtenir des fichiers audio, ni comment les Ă©diter.

Finalement, aprĂšs avoir cherchĂ© un peu partout, j’ai obtenu des fichiers audio gratuits depuis Pixabay et GDC Game Audio, puis je les ai Ă©ditĂ©s avec Audacity en rĂ©duisant le bruit ou en augmentant les graves selon les besoins.

Le rĂ©sultat n’était pas mauvais, mais l’aspect sonore reste assez dĂ©concertant. Si je devais refaire un jeu, je pense qu’il faudrait d’abord trouver les effets sonores et la musique de fond avant de commencer.

Publicité in-app

iaa

void PlayerDied()
{
    ShowInterstitialAd();
}

C’est une fonctionnalitĂ© que j’ai implĂ©mentĂ©e assez tĂŽt dans le dĂ©veloppement avancĂ©. AprĂšs avoir créé un petit robot de trading automatique d’actions, je m’intĂ©ressais Ă  l’utilisation de modules externes comme les API ou SDK, et par curiositĂ© j’ai créé la fonction d’appel publicitaire. Lorsque le joueur meurt et passe Ă  l’écran de fin, une interstitielle s’affiche entre-temps.

En suivant la documentation officielle Google AdMob, le guide officiel Ă©tant trĂšs accessible, la crĂ©ation a Ă©tĂ© bien plus facile que prĂ©vu. Le rĂ©sultat fonctionnait proprement, ce qui m’a Ă©tonnĂ©.

Achat in-app

iapiap

void Purchase()
{
    if (playerDonateKimbab)
    {
        DonateKimbab();
        playerDonateKimbab = false;
    }
}

Je voulais aussi implĂ©menter l’achat in-app dans le mĂȘme esprit. Cependant, comme il n’y a ni monnaie ni objet in-game, j’en ai fait une forme de don. J’ai conçu trois aliments (kimbab, poulet Ă©picĂ©, steak), enregistrĂ© les produits in-app dans la Google Console, et configurĂ© le paiement sans rĂ©compense dans le jeu.

Pendant l’implĂ©mentation, on m’a dit qu’il fallait faire attention Ă  la sĂ©curitĂ© pour les achats in-app. Ce projet Ă©tant un toy project sans but lucratif, ce n’était pas trĂšs grave, mais si je devais implĂ©menter des achats in-app Ă  l’avenir, je ferai plus attention.

Publication sur le store

Préparation

logologo
Logo de l’application

Pour l’uniformitĂ©, le logo de l’application reprend la mĂȘme image que le bouton de jeu. La publication sur le store avait une signification plutĂŽt symbolique pour ce projet — je ne cherchais pas Ă  attirer l’attention avec ce jeu — donc j’ai acceptĂ© le manque d’intuitivitĂ©. Le nom du package, com.payang.palette, vient du compte dĂ©veloppeur et du nom informel du projet.

Publication sur le store

google-play-consolegoogle-play-console
Formulaire d’informations de publication dans la Google Console

La publication s’est limitĂ©e au Play Store, via la Google Console. En fait, j’avais dĂ©jĂ  publiĂ© une fois en phase de dĂ©veloppement initial, par curiositĂ© pour voir si mon application serait vraiment mise en ligne. AprĂšs avoir confirmĂ© la publication, j’avais immĂ©diatement dĂ©sactivĂ© l’application.

Six mois plus tard, l’investissement de temps dans ce projet commençait Ă  peser, et la qualitĂ© du jeu Ă©tait devenue assez prĂ©sentable. J’ai donc dĂ©cidĂ© de mettre Ă  jour l’application et de la rĂ©activer. Pour la publication, j’ai réécrit le nom et la description de l’application, et mis Ă  jour l’icĂŽne, les images graphiques et les captures d’écran.

play-storeplay-store
Le jeu affiché sur Google Play Store

Finalement, l’application est rĂ©activĂ©e et disponible en tĂ©lĂ©chargement. Une semaine environ aprĂšs la rĂ©activation, elle apparaĂźt sans problĂšme lorsqu’on recherche son titre.

Promotion et retours

HonnĂȘtement, j’aurais honte d’appeler ça une activitĂ© promotionnelle digne de ce nom. Je n’avais jamais pensĂ© Ă  faire de la publicitĂ© auparavant, donc c’était difficile, mais comme il s’agissait d’un “jeu”, j’espĂ©rais que des gens y joueraient. J’ai donc commencĂ© Ă  chercher oĂč et comment promouvoir.

Cependant, ce jeu Ă©tant plus un toy project qui a pris de l’ampleur par intĂ©rĂȘt qu’un jeu conçu pour ĂȘtre jouĂ© dĂšs le dĂ©part, je me demandais si le promouvoir Ă©tait appropriĂ©. Le dĂ©veloppement Ă©tait amusant, mais la promotion Ă©tait un autre problĂšme — j’étais gĂȘnĂ© Ă  l’idĂ©e de faire connaĂźtre ce que j’avais créé.

reddit-lightreddit-dark

J’ai quand mĂȘme pris mon courage et postĂ© un court message sur le subreddit Unity2D. Je l’ai postĂ© en espĂ©rant qu’au moins une centaine de personnes le verraient, mais en une semaine les vues ont dĂ©passĂ© 20 000, et aprĂšs un mois, prĂšs de 100 000 personnes s’y Ă©taient intĂ©ressĂ©es — j’étais vraiment surpris.

reddit-feedback-lightreddit-feedback-dark

Certaines personnes, vraiment reconnaissantes, ont mĂȘme jouĂ© et laissĂ© des retours dĂ©taillĂ©s. Les retours portaient notamment sur : “la position du joystick est fixe et non personnalisable, ce qui est inconfortable”, “le Bloom est excessif”, “le jeu ressemble Ă  d’autres jeux”.

Je comprends certains de ces retours, mais je ne souhaite pas poursuivre le dĂ©veloppement pour l’instant. Je les prendrai en compte plus tard, quand j’aurai du temps, ou dans un prochain projet.

Conclusion

Vous pouvez télécharger et jouer au jeu sur le Play Store.

Ainsi s’achĂšve ce projet auquel j’ai consacrĂ© beaucoup de temps et d’attention. AprĂšs environ six mois Ă  y travailler, voir l’application listĂ©e sur le store m’inspire diverses rĂ©flexions, mais trois choses m’ont particuliĂšrement marquĂ©.

  • CrĂ©er des animations est amusant et gratifiant, mais cela demande Ă©normĂ©ment de temps car tout doit ĂȘtre fait Ă  la main. À moins d’ĂȘtre un animateur professionnel, crĂ©er l’animation souhaitĂ©e nĂ©cessite une bonne prĂ©paration mentale, et crĂ©er des animations dĂ©diĂ©es pour chaque objet est inefficace. Il serait plus efficace de faire partager la mĂȘme animation Ă  plusieurs objets.

  • CrĂ©er un projet de maniĂšre improvisĂ©e, bottom-up, sans planification, peut ĂȘtre amusant dans un petit contexte, mais a clairement ses limites. Le flux de dĂ©veloppement Ă©tait souvent interrompu, le flux de crĂ©ation d’animations aussi, et quand je n’étais pas satisfait, je devais annuler ou supprimer le travail accompli pour recommencer.
    J’ai donc constamment regrettĂ© qu’une planification minutieuse initiale aurait pu Ă©viter ces inefficacitĂ©s. La prochaine fois, je m’efforcerai de bien planifier au dĂ©part.

  • Enfin, la gestion du temps. Ce projet Ă©tait initialement prĂ©vu pour les vacances d’hiver, pour durer au maximum un mois. Mais il est devenu tellement amusant qu’il est devenu un projet de vacances d’étĂ©, et a failli devenir un projet des vacances d’hiver suivantes.
    À faire du dĂ©veloppement en parallĂšle des cours, le jeu Ă©tait si amusant que les Ă©tudes sont passĂ©es au second plan psychologiquement. Cela a naturellement affectĂ© mes notes, et je regrette de ne pas avoir bien gĂ©rĂ© mon temps.

MalgrĂ© tout, l’expĂ©rience de crĂ©ation a Ă©tĂ© trop amusante et gratifiante pour que je ne crĂ©e pas un prochain jalon avec Unity, en corrigeant les dĂ©fauts. J’ai envie d’appliquer de nouveaux conseils et patterns appris en cours de route, et si le premier pas a Ă©tĂ© difficile, le second ne devrait pas l’ĂȘtre. Mais si je m’y remets, je veux faire Ă©voluer le jeu d’un cran avec un plan et une prĂ©paration plus systĂ©matiques.