Portage Android d'un projet Ren'Py importable
(English version here)
Un projet Ren'Py commence rarement avec une feuille de route précise et fixe sur les fonctionnalités du jeu... ou même les plateformes de distribution.
Plus souvent, c'est après plusieurs mois voire années que l'on se rend compte que ce serait pas mal d'ajouter tel ou tel élément... après avoir gaiement (ou pas) écrit des lignes et des lignes de dialogue et de code.
C'est exactement ce qui nous est arrivé dans le développement de notre jeu Love Tryouts - entièrement orienté ordinateur avec une accessibilité douteuse - quand nous avons décidé de lui ajouter :
- un portage Android ;
- des options étendues d'accessibilité prise en compte de la dyslexie, du dyschromatopisme, de la dyscalculie et de la malvoyance.
Problème : le jeu compte plus de 400,000 mots et de 110 écrans.

Si vous vous reconnaissez dans cette situation, ce post a pour but de partager avec vous les (humbles) solutions que j'ai pu trouver au cours des dernières semaines pour transformer le bazar ci-dessus... et vous éviter mes tâtonnements.
Dans ce premier post, je vais d'abord me concentrer sur le volet portage Android.
1. Appareiller Ren'Py
La plupart des éléments sont expliqués dans la doc Ren'Py. Vous devrez installer les éléments permettant d'une part de tester et d'autre part de compiler votre jeu pour Android.
Attention cependant avec l'émulateur, s'il est bien pour tester en temps réel votre code il reste très important de tester sur un vrai appareil Android :
- à la fois pour tester la maniabilité et la lisibilité sur un petit écran, qui peut être très différente d'une combinaison petite fenêtre/souris ;
- deuxièmement car certains bugs ne se produisent que sur Android (par exemple, l'émulateur parvient à suivre un chemin d'image en '../', mais pas Android).
Dans l'émulateur, vous pouvez utiliser la console et l'autoreload pour tester efficacement votre code.
Une fois mis en place, vous pouvez vous attaquer sereinement à la transformation de votre jeu.
2. Prise en charge native par Ren'Py
Si votre jeu n'utilise que les fonctions les plus usuelles de Ren'Py (dialogue, personnages, choix) alors vous n'aurez rien à faire ou presque ! Ren'Py gère déjà très bien le passage à de petits écrans notamment par le biais de ses variantes et de son système de GUI.
Pour résumer, en fonction de l'appareil sur lequel vous jouez, Ren'Py définit à son lancement des variantes ("touch" si vous utilisez un appareil tactile, "small" si vous utilisez un appareil avec un petit écran, etc.). Vous pouvez vérifier simplement le type de variante utilisée par le joueur en testant renpy.variant(nom).
Les variantes se cumulent. Par exemple si le joueur joue sur un petit écran tactile alors renpy.variant("small") et renpy.variant("touch") renvoient tous les deux True.
De plus, Ren'Py modifie automatiquement certaines propriétés de style en fonction de la variante à travers son système de GUI.
Par exemple, dans gui.rpy, on trouve ces deux blocs de code :
define gui.text_size = 22
init python:
# (...)
@gui.variant
def small():
gui.text_size = 30
Si la variante "small" est présente au lancement, alors la taille de police du texte est redéfinie à 30, et un peu plus haut si "touch" est présent alors les boutons du bas de l'écran sont plus larges.
Ren'Py gère déjà très bien tout seul de nombreux paramètres, mais il peut être intéressant de commencer à en modifier certains s'ils ne sont pas adaptés à votre jeu.
Notamment, dans la plupart des systèmes le clavier virtuel des appareils Android vient couvrir le zone d'input :

Pour éviter ça, nous avons choisi d'insérer le code suivant dans le screen input() de screens.rpy, qui envoit le dialogue d'input vers le haut de l'écran :
screen input(prompt):
style_prefix "input"
window:
if renpy.variant("android"):
yalign 0.0

Sur mon appareil, je n'ai pas de bouton Home physique. Il faut faire un swipe depuis le bas pour accéder aux boutons home et return. Le bouton Retour du quick_menu permet le rollback et j'ai rajouté un bouton Quitter au menu de navigation en ajoutant une condition dans le menu navigation() de screens.rpy :
if renpy.variant("pc") or renpy.variant("android"):
## The quit button is banned on iOS and unnecessary on Android and
## Web.
textbutton _("Quitter") action Quit(confirm=not main_menu)
Notez bien que Ren'Py redimensionnera aussi automatique vos displayables, transitions, etc. pour que leur position soit respectée.
En dehors de ça et de quelques ajustements des valeurs de GUI (pensez à vérifier la lisibilité et la clicabilité de votre menu de navigation !), vous devriez vous en sortir si votre jeu n'utilise pas de screens et ne dévie pas du flow habituel de Ren'Py.
Malheureusement, ce n'est pas le cas de la plupart des jeux et assurément pas celui du nôtre.
3. Problèmes courants des screens à cibler
La description de LT mentionne 27 mini-jeux pour les deux premiers actes. J'ai dû les compter à un moment, je vais me faire confiance.
Il faut compter en plus plusieurs screens pour certains mini-jeux, des screens qui ne sont pas des mini-jeux (différentes phases de point and click), une galerie, un agenda, des phases de bureautique, un mini-site internet sur un téléphone (qui va bientôt devenir un téléphone dans un téléphone !) etc... Bref, une collection très hétérogène de 113 screens avec souvent un gameplay différent et même des niveaux de compétence très variables au fil des années de programmation :

Il n'y a pas de solution miracle : j'ai dû reprendre un par un les screens et les tester sur émulateur puis sur Android (par vagues, une fois le teste concluant sur émulateur) avec un script de test qui les appelait un par un à la queue-leu-leu.
Les principaux points à vérifier sont :
- le redimensionnement de l'écran (augmentation de la taille du texte et des boutons)à la fois pour des problèmes de lisibilité et d'imprécision du touch : cela implique des boutons plus grand mais également que le drag and drop ne soit pas toujours la solution ;
- tout screen contrôlé ne serait-ce qu'en partie au clavier est à reprendre pour proposer une alternative en variante "touch" ;
- les screens avec une interface d'input sont globalement à revoir (on avait plusieurs screens bureautiques avec plusieurs inputs entre lesquels on basculait avec TAB...) : d'une part parce que le clavier occulte les deux-tiers de votre écran, d'autre part parce que souvent les inputs ne peuvent être validés ;
- les mécanismes basés sur le hover, notamment des imagebuttons : sur Android le hover n'apparaît que si vous restez enfoncé sur une zone. Hover sans valider implique d'enfoncer son doigt sur une zone puis de glisser ailleurs sans finaliser le touch... autant dire qu'aucun utilisateur ne le fera. Oubliez toute notion de donner des indices à vos joueuses si elles survolent une zone de l'écran sans modifs.
Voici quelques exemples de solutions (probablement imparfaites) mises en place sur LT pour des screens concernés par ces problèmes.
4. Redimensionnement
Cela va de soi mais votre texte doit toujours être surdimensionné en variante small puisqu'il sera automatiquement redimensionné par Ren'Py en proportion de votre écran. De même, les boutons doivent être plus grands en raison de l'imprécision du touch. Un minijeu dont les boutons sont difficiles à atteindre peut très vite devenir pénible voire injouable.
Vous pouvez utiliser autant que possible les styles standards définis dans le GUI plutôt que des styles définis dans l'écran, pour permettre le redimensionnement auto. Personnellement, je n'ai pas eu l'occasion d'utiliser ce premier niveau cette première solution puisque nos screens utilisaient rarement les styles par défaut de Ren'Py sauf dans des cas de mimiques des écrans say et choice.
Pour les cas où vous utilisez déjà des styles personnalisés dans vos screens, vous pouvez facilement appliquer un nouveau style en utilisant la propriété variant, par exemple :
style contrat_text:
color "#000000"
size 15
text_align 0.5
xalign 0.5
style contrat_text:
variant "small"
size 22
![]() | ![]() |
...m'a servi à redimensionner très simplement le texte d'un screen simple, dans lequel les éléments étaient positionnés par une vbox et avec suffisamment de place pour supporter le redimensionnement.
J'ai assez peu utilisé cette solution à cause de mon implémentation d'un persistent.forcesmall qui ne peut être géré par le système de variantes de styles (voir infra). Cependant, j'ai utilisé un équivalent plus long (et qui ne reprend pas les propriétés du premier style) :
style contrat_text:
color "#000000"
font "tahoma.ttf"
size 15
style contratsmall_text:
color "#000000"
font "tahoma.ttf"
size 28
Bien sûr, même dans un exemple aussi simple que ci-dessus, j'ai eu à apporter quelques modifications au screen en plus d'augmenter la taille du texte.
Au cas présent, il a s'agit de redimensionner la window qui contenait l'ensemble de mon screen ET d'augmenter la taille de la vbox qu'elle contient :
screen scr_pizz_app():
default i = 0
default resultats_pizzas = [0,0,0,0,""]
window:
xalign 0.5
if renpy.variant("small") or persistent.forcesmall:
yalign 0.5
xsize 900
ysize 900
background Frame("images/UI/acte2b/tel_jl.png")
else:
yalign 0.5
xsize 640
ysize 640
background "images/UI/acte2b/tel_jl.png"
vbox:
xalign 0.5
if renpy.variant("small") or persistent.forcesmall:
ypos 270
xsize 380
else:
ypos 200
xsize 275
Cependant, il existe une autre méthode, très utile pour resizer un élément supérieur et tous les éléments qu'il contient.
Ce screen contient de larges espaces peu utiles autour d'un espace central de gameplay :
![]() |
|
(N'hésitez pas à ouvrir les images dans un nouvel onglet pour mieux voir)
Le code contient une window supérieure, qui contient elle-même une grid, qui contient des fixed qui contiennent eux-même des boutons avec des images.
En appliquant un transform avec un zoom à la window, je peux redimensionner l'ensemble de ses enfants en même temps qu'elle, améliorant à la fois la lisibilité et la clicabilité des boutons à moindre coût :
|
|
Évidemment, tous les screens ne se prêtent pas à ce tour de passe-passe. Attention aux éléments qui ne sont pas des enfants de l'objet transformé, qui ne subiront pas le transform.
Il est possible de combiner des éléments dans le transform et hors transform (comme des boutons placés à des coins de l'écran).
Cet écran contient initialement trois étagères à ranger (à droite) et un grand espace entre les cartons et les étagères.
![]() | |
Afin de combler l'espace vide en rendant les cartons (drags) plus maniables, j'ai d'abord enivsagé un resize horizontal, qui était insuffisant.
J'ai donc supprimé une étagère dans le gameplay du minijeu, ce qui m'a permis d'également beaucoup augmenter le yzoom. Cependant, cela a également pour effet de placer les cartons hors de l'image, le transform étendant ma window hors de l'affichage.
Il a donc fallu modifier la position du drag group en fonction. Cette modification a entraîné une perturbation des effets de mes fonctions de drop qui m'a obligé à redéfinir certaines coordonnées à partir de points variant-dépendants :
| Fonction de drop avant, extrait:
|
Fonction de drop après, extrait :
| Résultat : |
Notez que si vous voulez - contrairement à moi - éviter de saucissonner vos screens de conditions , vous pouvez également choisir d'écrire une variante entière sans bloc if en ajoutant la propriété variant à votre screen :

Autre exemple de raccourci, avec ce screen qui reçoit comme paramètre une liste de strings (correspondant aux pages d'un livre) :

(NB : les _() sont des balises pour la traduction)
Comme le redimensionnement entraînait un problème de mise en page, j'ai ajouté des tags {#pg_br} dans le texte et utilisé la fonction suivante pour le splitter (divise les pages en deux à chaque tag, max. deux tags par string) :

Cependant, en dehors de ces raccourcis pratiques, vous devrez souvent redimensionner manuellement tous les éléments de votre screen pour gérer le redimensionnement du texte au cas par cas, en conditionnant toujours vos modifications de style avec renpy.variant.
Si pour une raison où pour une autre vous avez besoin de cropper un texte redimensionné pour éviter qu'il ne dépasse d'une zone ou ne wrappe sur la ligne d'après, la meilleure solution reste de le placer dans un viewport non draggable, par ex :

5. Remplacer le clavier
Les solutions à mettre en œuvre dépendent beaucoup du gameplay de vos minijeux. Après avoir recherché toutes les occurrences de key dans mes screens, voici quelques exemples de ce que j'ai retenu en fonction des minijeux de LT :
- Pour ce minijeu qui singe l'interface de FFVI en utilisant le menu choice standard de Ren'Py, j'ai choisi de remplacer les touches haut/bas du clavier par de faux boutons de SNES, disposés aux deux bords opposés de l'écran pour ne pas gêner la lisibilité
![]() | ![]() |

J'en ai également profité pour donner un coup de brosse à mon ancien code antérieur à la mise en place des data actions (conservé en bas sur les keys pour illustration) pour le remplacer par quelque chose de plus efficace et qui loop sur les options.
Les hotspots couvrent en réalité chacun la moitié de la croix directionnelle et la dépassent de 50 bons px pour compenser l'imprécision de l'écran tactile.
- Pour un minijeu qui faisait masher la barre d'espace pour faire grimper une variable, j'ai simplement mis en place un dismiss. Attention à bien le placer DERRIERE (et donc avant dans votre screen) tout bouton ou autre qui doit prendre le pas sur le dismiss :

Par ailleurs, ce même minijeu oblige à appuyer sur une flèche à des moments timés, ce qui est remplacé par des boutons situés en bas, gauche, haut et droite de l'écran. Attention à nettement augmenter le temps de réaction sur ce type d'interactions ! Il est bien plus long de viser un bouton même si on sait où il va apparaître que d'appuyer sur une touche avec les doigts sur les bonnes touches. Ici, après test et retest pour m'assurer que ça impliquait la même réactivité de la part de la joueuse, le timer passe de 0.4 à 0.9 secondes.

Cela bien sûr des conséquences à répercuter sur d'autres timers du screen :
- Plus simplement; certains déplacements peuvent être gérés par des boutons mobiles qui suivent l'objet à déplacer :
![]() | ![]() |
Évidemment, si la réactivité est un enjeu les boutons fixes doivent être privilégiés plutôt que les boutons mobiles. Nearrect peut être un outil intéressant pour placer des boutons mobiles près de l'objet à déplacer.
- Pensez également à ajouter des options alternatives à vos viewports contrôlés exclusivement par le clavier :

Notez que la plupart de ces options sont proposées avec la variante touch. Si small et touch sont souvent liées, elles ne sont pas appropriée pour un appareil qui aurait un clavier et un petit écran. Gardez cependant à l'esprit vos différentes modifications pour ne pas vous retrouver avec un écran dysfonctionnel à moitié affecté par votre réécriture !
6. Gestion des inputs
C'est un des principaux problèmes du passage sur Android.
- Par exemple, cet écran emblématique du jeu, consistant à mettre à jour des catégories de stocks en passant d'un champ d'input à un autre comme ça*...
![]() | ![]() |
* Disclaimer : il s'agissait d'un de mes tous premiers screens Ren'Py, le code est vraiment bordélique et empirique...
... ne fonctionnait plus du tout sur Android.
Non seulement la touche tab n'existait plus, en plus le clavier virtuel occupait les deux tiers de l'écran, mais en plus il était impossible d'utiliser l'autre mode de navigation (cliquer sur un autre bouton) à cause du clavier.
A noter que valider le champ d'input ne fonctionnait pas non plus, probablement à cause d'une mauvaise gestion du focus par le screen.
Bref, je me suis retrouvé complètement bloqué sans pouvoir ni avancer ni revenir en arrière. et obligé de force quit le jeu.
Mon screen était certes très mal fichu, mais cela n'arrive pas qu'aux autres VOS JOUEUSES PEUVENT ÊTRE IMPACTÉES !
L'avantage de ce screen, qui utilise des valeurs numériques, c'est que le problème a pu être très facilement contourné (au prix d'un peu moins d'ergonomie, quand même) :

- Pour un autre minijeu dans lequel la joueuse doit deviner des mots, j'ai dû opter pour des choice buttons :
![]() | ![]() |
Idem, impossible de valider une réponse via l'input compte tenu de l'architecture du screen.

À chaque fois que la joueuse tente une réponse, la liste des mots proposés est actualisée par une fonction (2e action du textebutton) :

La fonction :
- prend une liste de mots standards
- ajoute les mauvaises réponses proposées par les adversaires
- ajoute la bonne réponse
- mélange aléatoirement les réponses
- aléatoirement, avec une chance croissance au fur et à mesure du minijeu, réajoute la bonne réponse en première position
- prend les neuf première réponses
- remélange ces neufs réponses, pour éviter que la bonne réponse soit toujours en 1er
- return la liste des réponses possibles, qui devient la nouvelle valeur de la screen variable.
C'est un peu fastidieux et seulement à moitié satisfaisant mais adapté aux enjeux du minijeu.
- Dans cet autre minijeu, la joueuse doit remplir une série de cinq formulaires à partir d'informations disséminées n'importe comment sur des post-its - ou comme le dit Gilou :

Si le minijeu est différent, l'interface est un peu sur le même principe que pour les stocks : on navigue entre des inputs avec le clavier ou la souris ; un dragggroup sans aucune fonction de drop est présent à droite pour réorganiser les post-its et trier les informations.
![]() | ![]() |
Pour une double raison de lisibilité et de clavier trop envahissant, il a fallu remplacer ce gameplay par un système plus complexe :

- les formulaires sont remplacés par des zones de drop des post-its
- les post-its ne sont plus cosmétiques, désormais les dropper sur une zone les fait disparaître et complète les informations qu'ils contiennent :
![]() | ![]() |
- pour garder l'esprit du minijeu original, le code vérifie que le même type d'information n'est pas ajouté deux fois (auquel cas il snappe le drag), mais ne contrôle pas la cohérence des informations entre elles (le fait qu'elles se rapportent bien à la même livraison).
- ajout de deux boutons pour permettre à la joueuse de bâcler le minijeu ou de recommencer, ce qui est possible dans le mj original en signant les formulaires vides les uns après les autres. Les drags des post-its placés sont ajoutés dans une liste, ce qui permet de tous les snapper vers des positions aléatoires si la joueuse clique sur recommencer (bien plus sympa que de juste relancer le screen), à toutes fins utiles :
# Dans la fonction drop, on ajoute le drag à une list qui est une screen variable appelée drags_places :
def mj_postits_collectinfo(drop, drags):
if drop is not None:
# (...)
l_dr_pl = renpy.get_screen_variable("drags_places")
# (...)
l_dr_pl.append(drags[0])
# Ma solution pour masquer les post-its : les envoyer derrière les zones de drop et les rendre indraggables
drags[0].bottom()
drags[0].draggable=False
# Ne pas oublier pour la MAJ graphique
renpy.restart_interaction()
return
# Fait réapparaître et disperse les post-its et vide les infos en variante small (bouton)
# Les autres variables sont des screens variables transmises pour réinitialiser les informations etc.
# Ce qui nous intéresse ici, c'est surtout le dernière paramètre, je me contenterai de montrer ce qu'on fait à cette liste de drags
def mj_postits_reshuffle(l_s_pi,l_h_pi,l_dr_pl):
# (...)
for p in l_dr_pl:
p.draggable = True
p.top()
p.snap(renpy.random.randint(620,1080),renpy.random.randint(120,600),1.0)
l_dr_pl.clear()
return
- À noter qu'il n'est pas impossible de conserver certains inputs, dès lors que vous disposez de suffisamment d'espace libre pour afficher le clavier virtuel :
![]() | ![]() |
(le téléphone se décale un peu vers le haut quand on passe au mot de passe, par prudence si le clavier n'est pas exactement de la taille indiquée)
(certains éléments superflus ont été supprimés pour permettre la mise en page appropriée)
(notez bien qu'il est facile d'"échapper" à l'input dans ce screen en cliquant sur les boutons de la barre de navigation marronnasse)
Ou encore :
![]() | ![]() |
(pas besoin d'accéder aux objets en bas ou au bouton abandonner pendant cette phase du minijeu)
(de façon très intéressante, la dernière ligne FONCTIONNE quand on valide le champ d'input)
7. Remplacer le hover
Dans certaines situations le hover peut être utilisé comme indice pour la joueuse, si l'on souhaite lui indiquer subtilement que certaines zones sont clicables lorsqu'elle les survole en passant avec sa souris.
Pour remplacer cette information en variant touch, notamment dans une phase d'enquête, j'ai ajouté un effet de scintillement qui simule le hover à mon écran.
Notez bien que le scintillement - dans cette version - ne se produit qu'à l'affichage de l'écran, pas de façon répétée. Ajouter un repeat à la fin du bloc d'ATL permet de le répéter.
screen scr_indices_val(lbl, over):
# Ne pas tenir compte de cette condition
if lbl.split(".")[1] not in pastref:
imagebutton:
auto "images/decor/acte3b/indices/"+over+"_%s.png"
focus_mask True
# Ce screen appelle une label de dialogue quand on clique sur l'élément à inspecter
# A la fin du label il revient donc où il était
# Il est prévu pour un show screen et non un call screen
# Cela ne peut ne pas être l'idéal en fonction de ce que vous voulez faire
action Call(lbl)
if renpy.variant("touch"):
# Notez bien que cette image se superpose à l'image idle de l'imagebutton,elle ne la remplace pas
add "images/decor/acte3b/indices/"+over+"_hover.png":
at transform:
alpha 0.0
linear 0.2 alpha 1.0
linear 0.2 alpha 0.0
repeat 3
Voilà ! C'est la fin de ce petit tour d'horizon des méthodes que j'ai mises en place pour notre jeu, en espérant que vous pourrez y trouver des idées ou du code utile pour vos propres jeux.
N'hésitez pas à commenter si vous avez de meilleures suggestions, des retours d'expérience ou des questions.
Bon courage !
Get Love Tryouts
Love Tryouts
An adventure in corporate hell
| Status | In development |
| Authors | LEAR, Bosh'tet |
| Genre | Visual Novel, Adventure, Interactive Fiction |
| Tags | absurd, Comedy, Creepy, office, Romance, satire, Story Rich, Working Simulator |
| Languages | English, French |
| Accessibility | Color-blind friendly, Subtitles, High-contrast |
More posts
- Version 2.2 - accessibility, android and bugfixes27 days ago
- Version 2.2 - accessibilité, android et bugs29 days ago
- Porting an unportable Ren'Py game to Android48 days ago
- Amazing results in translation this quarter!Jun 28, 2025
- Version 2.1.8.4 (en)May 29, 2025
- Version 2.1.8.4May 29, 2025
- Version 2.1 (minor patch)Feb 23, 2025






etc. 

















Leave a comment
Log in with itch.io to leave a comment.