'Cubic Survival', développement et lancement
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}

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.


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Ă©.

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

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

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

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

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

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

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
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.


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


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

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


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


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


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.


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éé.


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.


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.