Liens vers les applications et infos officielles :
Le site officiel de DOWiNO pour Smokitten Smokitten iOS Android Smokitten Park iOS Android
Smokitten et SmokittenPark sont les deux faces d’une même pièce. Une volonté, pour les créateurs de ce serious game, DOWiNO, d’aider les fumeurs à arrêter et les jeunes non-fumeurs à ne jamais commencer. Les premiers seront informés sur leur habitude, suivront leur progression, recevront des notifications, tandis que les seconds seront sensibilisés à cette addiction et aideront le personnage du chat à arrêter de fumer.
En collaboration avec des professionnels de santé spécialisés dans l’addiction, DOWiNO a conçu une série de mini-jeux amusants et addictifs, façon clicker, pour que les joueurs de Smokitten (fumeurs) puissent traverser leurs périodes d’envie, et que les joueurs de SmokittenPark (non-fumeurs) s’amusent en aidant le chat fumeur à se débarrasser de son addiction.
Les mini-jeux sont représentés par des activités physiques réparties tout autour de l’île flottante 3D du chat. Au fil du temps, l’île se transforme aussi progressivement, passant d’une île sèche et sale à une île tropicale luxuriante et verdoyante. Collecter des pièces en jouant aux activités permet également d’acheter des éléments décoratifs supplémentaires pour son île. Les mini-jeux, ou « activités », se débloquent progressivement.
La progression du jeu repose sur une analyse statistique du sevrage nicotinique. L’île possède un compteur de « jours » physique qui évolue au fil du temps. Le compteur démarre à 222 jours et décompte.
Smokitten est un jeu dans lequel on entre volontairement, et la règle implicite est de signaler dans le menu du jeu chaque fois qu’on ressent l’envie de fumer, afin que les joueurs de SmokittenPark - notamment vos amis - puissent vous envoyer un petit message d’encouragement, et qu’un multiplicateur de pièces bonus puisse s’appliquer pendant votre période d’envie, pour que vous puissiez essentiellement farmer plus de pièces durant cette période plutôt que de sortir vos cigarettes.
Ainsi, Smokitten comme Smokitten Park intègrent, en plus du jeu, un petit aspect social : un joueur de Smokitten peut ajouter des amis de Smokitten Park pour recevoir des messages d’encouragement via des notifications push pendant une période d’envie.
Le chat de Smokitten Park, un fumeur, traverse régulièrement des périodes d’envie au cours de la partie, et prévient le joueur via des notifications locales lorsqu’il a envie de fumer. Vous n’avez alors que quelques minutes pour ouvrir le jeu et « sauver » le chat, faire avancer le compteur de temps du jeu, se rapprocher un peu plus de la fin et rendre l’île un peu plus belle.
Les mini-jeux ont des niveaux de difficulté qui peuvent être basés sur le temps et/ou achetés avec des pièces du jeu (pas d’achats intégrés).
Cette présentation rapide ne montre pas complètement toutes les similitudes et différences entre les deux jeux, mais pour aborder les aspects techniques sur lesquels nous avons travaillé, il nous fallait simplement poser le bon contexte et donner une idée du jeu.
Vous pouvez installer SmokittenPark (le jeu est gratuit, consultez les liens en haut de l’article) pour vous faire une idée du gameplay et des mini-jeux, qui sont essentiellement similaires dans les deux jeux.

Un système de build sur mesure :
Puisque Smokitten et Smokitten Park partagent des mini-jeux à des fins différentes, le code de base est le même. Nous avons décidé, à l’époque, d’utiliser un seul et même projet Unity avec un système de build sur mesure capable de permuter automatiquement les identifiants d’application, les variables du jeu (Serialized Object), ainsi que le manifeste Android personnalisé ou le post-traitement du projet Xcode.
Le jeu tient entièrement dans une seule scène (il existe bien une scène de démarrage, mais développeurs et game designers travaillent sur la scène principale), donc le processus de build sur mesure est accessible via le Script d’un GameObject avec une vue d’inspecteur personnalisée.
Voici la page du manuel Unity qui explique comment construire un projet Unity par script. Avant de lancer le processus de build, nous analysons l’un des principaux manifestes Android (si la cible de build est Android) pour modifier certaines valeurs par regex, car les plugins de partage Facebook et Twitter avaient besoin de clés API ou de valeurs personnalisées, changées selon le jeu (Smokitten / SmokittenPark). Après le processus de build, sur iOS, certaines modifications sont apportées au projet Xcode via une fonction statique portant l’attribut PostProcessBuild.
En résumé, les projets Xcode avaient besoin de valeurs plist supplémentaires, et le namespace UnityEditor.iOS.Xcode contient des classes comme PlistDocument qui aident à éditer le fichier plist.
Voici une fonction statique pour ajouter automatiquement le mode d’exécution en arrière-plan « remote-notification » :
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
using UnityEditor;
using System.IO;
[PostProcessBuild]
public static void ChangeXcodePlist(BuildTarget buildTarget, string pathToBuiltProject)
{
if (buildTarget == BuildTarget.iOS)
{
string plistPath = pathToBuiltProject + "/Info.plist";
PlistDocument plist = new PlistDocument();
plist.ReadFromString(File.ReadAllText(plistPath));
PlistElementDict rootDict = plist.root;
var buildKey = "UIBackgroundModes";
rootDict.CreateArray(buildKey).AddString("remote-notification");
File.WriteAllText(plistPath, plist.WriteToString());
}
}
Le système de build sur mesure charge automatiquement (Resources.Load) un SerializedObject contenant les variables de jeu spécifiques à Smokitten ou SmokittenPark, et les applique au GameObject principal qui gère l’ensemble du jeu, juste avant le build.
Notez que nous aurions pu opter pour des flags de compilation personnalisés afin de bien séparer les panneaux de menu ou les mécaniques de gameplay spécifiques à Smokitten ou à SmokittenPark. Mais dans la plupart des cas, ce n’était pas la solution la plus pratique pendant la phase de prototypage, et nous avons gardé les choses telles quelles.
Évidemment, si vous deviez créer deux applications avec une base de code similaire, il faut réfléchir à la part de code partagée : n’y a-t-il que quelques classes qui diffèrent, comme dans notre cas, ou bien le cœur du projet devrait-il être partagé via un sous-module git, par exemple.

Serious games, itération et travail avec une équipe médicale :
La plupart des informations éducatives, comme les popups informatifs ou les succès du jeu qui délivrent aussi de l’information tout en réagissant à l’atteinte de certains objectifs par le joueur, ont été conçues en gardant à l’esprit une réalité médicale. Ainsi, toutes les données des succès, des informations basées sur le temps (par exemple, au bout de sept jours, un popup pouvait vous annoncer qu’après une semaine vous commenciez à retrouver le goût) ou même des récompenses basées sur la date ou la période (après un certain nombre de semaines, ou à une période de l’année où vous pourriez être plus à risque, le jeu pouvait vous accorder un multiplicateur de pièces bonus pour encourager à jouer) étaient fournies via un simple document Excel que l’équipe médicale pouvait remplir.
Au départ, le fichier Excel était importé, analysé, et un ScriptableObject était créé. Vu la fréquence des mises à jour, ce n’était pas pratique, et nous avons failli opter pour des systèmes synchronisant Google Sheets avec le jeu. Finalement, pour plusieurs raisons, les données sont toujours conservées sous forme de ScriptableObject, mais elles sont mises à jour depuis un serveur qui centralise toutes les données et gère également la communication entre joueurs.
Quoi qu’il en soit, l’important ici, c’est que nous, en tant que développeurs, ne connaissant pas à l’avance toutes les conditions possibles de réussite ou d’apparition d’un popup informatif, devions concevoir un système permettant aux game designers et à l’équipe médicale d’écrire eux-mêmes leurs conditions de date, de temps ou de variables de jeu pour qu’un tel événement apparaisse dans le jeu. Il s’agissait essentiellement de définir un ensemble de types de conditions possibles, et de laisser un interpréteur d’expressions décider si ces conditions sont effectivement remplies.
Sans dévoiler tout le système, assez complexe sous le capot, on pourrait écrire une expression fictive comme celle-ci :
00:00:00:00:00 > 00:10:00:00:00 && ( d01 || d02 ) && player_age >= 36
La première partie de cette condition est un format que nous avons choisi pour décrire des périodes de dates APRÈS le début de la partie. Cette première partie signifie donc « entre 0 et 10 jours après le début de la partie », la seconde partie après l’opérateur && signifie « si on est aujourd’hui lundi ou mardi », et la dernière condition combinée est simplement « l’âge du joueur est supérieur ou égal à 36 ».
C’est un ensemble de conditions assez complexe que nous ne connaissons pas à l’avance. Mais c’est tout de même « assez » lisible pour un humain.
Après une formule regex complexe, la condition se réduit à :
di0 && ( d01 || d02 ) && player_age >= 36
Où di0 remplace « l’intervalle de date » et est stocké sous forme de valeurs. di0 est désormais une variable qui peut être remplacée, tout comme d01, d02 et player_age, de sorte que l’ensemble forme une expression mathématique valide (en utilisant bien sûr des opérateurs bit à bit).
Ainsi, avec un analyseur d’expressions comme mxParser, le résultat final est évalué, chaque argument étant remplacé par une valeur numérique, et on obtient soit 0 (pour faux), soit >=1 quand c’est vrai.
Nous avons toujours une logique fixe dans le jeu pour définir la valeur de di0 : il faut tester si la date actuelle se trouve dans l’intervalle défini. player_age est connu si le joueur a renseigné une date de naissance… et d01 ou d02, ici pour déterminer si on est lundi ou mardi, sont des arguments prédéfinis que l’on peut écrire, et qui sont réglés sur vrai/faux selon le jour de la semaine.
Donc même si on ne pouvait pas tout faire, puisque les valeurs des arguments devaient être remplacées à l’exécution pour correspondre au joueur, à la date et aux autres paramètres du jeu, la possibilité de définir et de combiner des conditions a permis à l’équipe de créer des succès, des popups informatifs et des récompenses pour un grand nombre de profils différents, selon de nombreux paramètres, et ce tout au long du jeu, sans que le développeur n’ait à écrire de conditions supplémentaires, tout en gardant le fichier Excel initial aussi lisible que possible.
Aujourd’hui, le fichier Excel est lu, analysé, et le système de regex n’est plus exécuté dans l’application : elle reçoit directement les expressions traduites (la seconde version), ce qui lui laisse moins de traitement à faire.
Visuels :
Cela fait plus d’un an que nous réfléchissons et travaillons sur ce jeu. Il y a eu de nombreux obstacles à surmonter. Le premier : comment faire évoluer visuellement une île flottante sur plus de 222 jours, avec un très grand nombre d’éléments de décoration pouvant être achetés ou apparaître d’eux-mêmes.
Les premiers tests ont été réalisés en 2D. Mais le nombre d’assets à produire pour une version 2D, plus le défi technique de disposer de nombreuses textures pour faire pousser l’herbe progressivement et faire apparaître les plantes… c’était trop. Nous avons décidé de pousser DOWiNO à passer en 3D sur ce projet.
Ce n’est pas notre premier jeu ensemble, et « A Blind Legend » était en 3D mais sans rien à montrer visuellement, donc on peut dire que c’est notre premier « vrai » jeu 3D ensemble, et c’était pour résoudre les problèmes liés à l’évolution visuelle de l’île. Plutôt que de rafistoler avec des textures d’herbe, la révélation de la belle île en pleine santé se fait en interpolant deux textures de l’île entre elles, non pas linéairement mais à partir d’un masque qui fait apparaître progressivement des zones d’herbe, de neige, de terre, à mesure que l’interpolation approche de sa fin. Et la cascade d’eau verte et dégoûtante devient progressivement d’un bleu éclatant.
Ce sont en fait des effets faciles à réaliser, et d’un jour à l’autre, on ne voit pas forcément la différence, un peu comme on ne voit pas ses cheveux pousser en temps réel (vous voyez l’idée), mais c’était parfait pour faire évoluer l’île en douceur. Bien sûr, chaque jour qui passe, le compteur géant sur l’île vous rappelle que vous avancez, que vous progressez. Mais vous serez d’autant plus surpris de réaliser à quel point l’île était affreuse au début, comparée à sa beauté par la suite. Une belle surprise que nous n’aurions probablement pas pu obtenir, en tout cas pas aussi facilement, en 2D.
En plus, en 3D, on s’est tous beaucoup plus amusés avec des shaders et des techniques sur mesure pour atteindre les objectifs visuels de DOWiNO !

Autres aspects techniques :
Le projet a été assez long, et nous avons eu l’occasion de travailler avec plusieurs artistes avec lesquels nous échangions sur l’atlasing des textures ou le changement des skins des plantes. De nombreux outils ont été créés pour les aider dans ce sens… certains simples, d’autres complexes, beaucoup n’étaient que des outils temporaires. Comme toujours, nous adorons créer des fenêtres d’éditeur et des inspecteurs personnalisés pour faciliter le processus de création ou accélérer certaines tâches, comme la réapplication de textures.
Je ne me souviens plus de tous les petits problèmes résolus plus vite avec un script qu’en y passant des heures à la main - même si le faire à la main est parfois bien meilleur.
La localisation a été mise en place avec le plugin Unity i2Loc, qui synchronise une feuille Google Sheets avec l’application et nous permet éventuellement d’ajouter d’autres langues au fil du temps sans forcément mettre à jour l’application, bien qu’il soit préférable de le faire pour qu’un cache hors-ligne soit disponible pour tous ceux qui ne se connecteront jamais à internet ou auront une mauvaise connexion. i2Loc a été très intéressant à mettre en place, même si toutes les données de jeu (événements) peuvent changer à tout moment (si on décide d’ajouter un événement ou une information spécifique), la synchronisation des clés et valeurs de texte a été un peu pénible à gérer, mais c’est vraiment spécifique à ce projet et je n’ai pas de conseil particulier à partager là-dessus. Le plugin en lui-même est excellent, et on peut y faire transiter d’autres données que de simples chaînes localisées, puisqu’une clé peut renvoyer une valeur de donnée plutôt qu’une simple traduction pour un bouton play.
Pour plus d’informations, rendez-vous sur notre site : https://davikingcode.com/fr/projets/smokitten