Phase 7 : personnage animé par sprites (poses/images successives)
Build and deploy / test-python (push) Successful in 1m26s
Build and deploy / test-js (push) Successful in 6s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

Ajoute une nouvelle action de flow "jouer_animation_sprite" qui joue une
séquence de PNG sur un élément image (cycle en boucle type marche, ou
une fois type saut) — réutilise target_element_id (déjà whitelisté) et
data_value en JSON {frames, fps, loop}, exactement le patron déjà établi
par "jouer_son" (Phase 6) et "alea" (Phase 2) : aucune nouvelle colonne,
aucune migration de schéma.

Ajoute une propriété d'élément "orientation" (Modifier un élément) pour
retourner un personnage en miroir (gauche/droite/bascule) sans nécessiter
une 2e feuille de sprites "vue de dos" — même patron que "surbrillance"/
"désactivé".

Étend aussi la Timeline d'animation existante (kind="sprite", aux côtés
de "animate_css"/"custom") pour qu'un personnage puisse animer tout seul
dès l'affichage de l'écran (ex. idle en boucle perpétuelle), pas
seulement en réaction à un événement — réutilise la colonne générique
custom_keyframes (JSON) et le réglage iteration_count déjà là pour la
boucle, aucune migration non plus. Les deux entrées (action de flow et
clip de timeline) partagent le même moteur côté client
(runSpriteAnimation()/activeSpriteAnimations dans static/js/play/
actions.js, indexé par nœud DOM plutôt que par id d'élément pour
supporter plusieurs instances d'un même écran-modèle animées
indépendamment).

Une bibliothèque de sprites Forge (2 personnages Kenney CC0, sous-
ensemble curé idle/marche/saut copié dans static/characters/) est
proposée dans l'éditeur, mais un créateur peut tout aussi bien utiliser
ses propres sprites uploadés (même route d'upload générique que le son
de la Phase 6).

Périmètre volontairement limité à ce qui a été demandé : pas d'avatar
modulable en couches (assemblage cheveux/haut/bas par le joueur),
écarté du plan initial à la demande de l'utilisateur.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-08-30 19:18:07 +02:00
co-authored by Claude Sonnet 5
parent 76331f9285
commit d2c2f20b65
35 changed files with 519 additions and 3 deletions
+74
View File
@@ -431,6 +431,11 @@
<option value="{{ key }}">{{ label }}</option>
{% endfor %}
</select>
<select id="nf-action-element-value-orientation" style="display:none; margin-top:6px;">
{% for key, label in element_orientation_values %}
<option value="{{ key }}">{{ label }}</option>
{% endfor %}
</select>
<input type="color" id="nf-action-element-value-color" style="display:none; margin-top:6px;" value="#5b8cff">
<input type="number" id="nf-action-element-value-number" style="display:none; margin-top:6px;" value="50" placeholder="Nouvelle valeur en %">
<input type="text" id="nf-action-element-value-text" style="display:none; margin-top:6px;" placeholder="Nouveau texte affiché">
@@ -500,6 +505,46 @@
</div>
</div>
<!-- Phase 7 — personnage animé par sprites : joue une séquence de
PNG (cycle en boucle type marche, ou une fois type saut) sur
l'élément visé, depuis la bibliothèque Forge ou des sprites
propres au créateur (même bouton d'upload que le son ci-dessus,
mais ajoute une ligne plutôt que de remplacer le champ). -->
<div id="nf-action-sprite-fields" style="display:none;">
<label>Élément à animer
<select id="nf-action-sprite-target-element">
{% for e2 in elements %}
<option value="{{ e2.id }}">{{ e2|elabel }} (elt-{{ e2.id }})</option>
{% endfor %}
</select>
</label>
<label style="margin-top:6px; display:block;">Personnage Forge (optionnel)
<select id="nf-action-sprite-library-character" onchange="onSpriteLibraryCharacterChange()">
<option value="">— Choisir —</option>
{% for slug, character in sprite_library.items() %}
<option value="{{ slug }}">{{ character.label }}</option>
{% endfor %}
</select>
</label>
<select id="nf-action-sprite-library-animation" style="margin-top:6px; display:none;" onchange="applySpriteLibraryAnimation()"></select>
<label style="margin-top:10px; display:block;">Images (une URL par ligne)
<textarea id="nf-action-sprite-frames" rows="4" style="width:100%;" placeholder="https://.../frame1.png"></textarea>
</label>
<div style="margin-top:6px;">
<label for="nf-action-sprite-upload" style="font-size:12px;">Ajouter un fichier :</label>
<input type="file" id="nf-action-sprite-upload" accept="image/*" style="font-size:12px;">
<span id="nf-action-sprite-upload-status" class="uploadStatus" style="font-size:12px; color:var(--text-dim);"></span>
</div>
<div class="fieldRow" style="margin-top:6px;">
<label style="flex:1;">Images/seconde
<input type="number" id="nf-action-sprite-fps" min="1" step="1" value="8">
</label>
<label style="flex:1; display:flex; align-items:center; gap:6px;">
<input type="checkbox" id="nf-action-sprite-loop" checked style="width:auto;"> En boucle
</label>
</div>
</div>
<div id="nf-action-variable-fields" style="display:none;">
{% if not global_variables %}
<p class="hint">Aucune variable globale pour l'instant — crées-en une depuis "Condition de visibilité" dans les propriétés d'un élément.</p>
@@ -570,6 +615,7 @@
<select id="anim-clip-kind" onchange="toggleAnimClipKind()">
<option value="animate_css">Animation Animate.css</option>
<option value="custom">Animation personnalisée — images-clés</option>
<option value="sprite">Animation de sprites</option>
</select>
</label>
@@ -592,6 +638,32 @@
<button type="button" class="smallBtn button is-small" onclick="addAnimKeyframeRow()">+ Ajouter une image-clé</button>
</div>
<!-- Phase 7 — même champs que le nœud de flow "Jouer une animation
de sprites" (voir plus haut #nf-action-sprite-fields), formulaire
indépendant : bibliothèque Forge ou sprites propres au créateur. -->
<div id="anim-clip-sprite-fields" style="display:none;">
<label>Personnage Forge (optionnel)
<select id="anim-clip-sprite-library-character" onchange="onAnimClipSpriteLibraryCharacterChange()">
<option value="">— Choisir —</option>
{% for slug, character in sprite_library.items() %}
<option value="{{ slug }}">{{ character.label }}</option>
{% endfor %}
</select>
</label>
<select id="anim-clip-sprite-library-animation" style="margin-top:6px; display:none;" onchange="applyAnimClipSpriteLibraryAnimation()"></select>
<label style="margin-top:10px; display:block;">Images (une URL par ligne)
<textarea id="anim-clip-sprite-frames" rows="4" style="width:100%;" placeholder="https://.../frame1.png"></textarea>
</label>
<div style="margin-top:6px;">
<label for="anim-clip-sprite-upload" style="font-size:12px;">Ajouter un fichier :</label>
<input type="file" id="anim-clip-sprite-upload" accept="image/*" style="font-size:12px;">
<span id="anim-clip-sprite-upload-status" class="uploadStatus" style="font-size:12px; color:var(--text-dim);"></span>
</div>
<label style="margin-top:6px; display:block;">Images/seconde
<input type="number" id="anim-clip-sprite-fps" min="1" step="1" value="8">
</label>
</div>
<div class="fieldRow">
<label style="flex:1;">Départ en secondes
<input type="number" id="anim-clip-start" min="0" step="0.1" value="0">
@@ -1144,6 +1216,8 @@
var ELEMENT_VISIBILITY_LABELS_MAP = {{ element_visibility_labels_json|safe }};
var ELEMENT_SURBRILLANCE_LABELS_MAP = {{ element_surbrillance_labels_json|safe }};
var ELEMENT_DESACTIVE_LABELS_MAP = {{ element_desactive_labels_json|safe }};
var ELEMENT_ORIENTATION_LABELS_MAP = {{ element_orientation_labels_json|safe }};
var SPRITE_LIBRARY = {{ sprite_library_json|safe }};
var DATA_OPERATION_LABELS_MAP = {{ data_operation_labels_json|safe }};
var CONDITION_OPERATOR_LABELS_MAP = {{ condition_operator_labels_json|safe }};
var TRIGGER_LABELS_MAP = {{ trigger_labels_json|safe }};