Fix éditeur de scène 2D : personnage déformé, animations muettes, galerie petite
Build and deploy / test-python (push) Failing after 1m52s
Build and deploy / test-js (push) Successful in 6s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

Trois correctifs remontés en test manuel sur l'éditeur de scène :

1. Personnage écrasé/déformé dans sa boîte de sélection : les frames
   CraftPix (screens/labels/animal_sprite_library.py) sont de grands
   canevas très paddés (~788×504 px), très différents des sprites Kenney
   déjà recadrés serré (~96×128 px) — sans object-fit, l'image remplissait
   quand même sa boîte (width/height:100%, voir la règle
   .canvasElementInner > img de scene_edit.html) mais DÉFORMÉE. Nouvelle
   classe .sceneObjectSprite (object-fit:contain) posée sur le <img> rendu
   par render_scene_object.py, éditeur ET jeu jouable.

2. Aucun clip de Timeline (sprite, Animate.css ou personnalisé) ne se
   jouait jamais sur un objet de scène : applyAnimationClip()
   (static/js/play/screens.js) et animation-timeline.js sélectionnent
   TOUJOURS leur cible via l'attribut data-anim-target (posé sur
   .canvasElement/.playElement pour le DOM), jamais data-element-id/
   data-object-id — cet attribut n'était simplement jamais posé sur les
   objets de scène. Ajouté sur le <img> rendu (render_scene_object.py).

3. Boîte par défaut d'un nouvel objet de scène trop petite (64×64 px sur
   une scène de 960×540) pour bien voir le personnage ou saisir la
   poignée de redimensionnement confortablement — portée à 128×128
   (ensure_scene_schema.py ; n'affecte que les NOUVEAUX objets, les
   objets déjà posés gardent leur taille actuelle).

Au passage : galerie de personnages ~3x plus grande (2 colonnes au lieu
de 6 dans .iconGallery.personnageGallery) — les tuiles étaient trop
petites pour bien distinguer les personnages.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-08-31 21:52:46 +02:00
co-authored by Claude Sonnet 5
parent a27d08c6b0
commit c240181518
3 changed files with 33 additions and 5 deletions
+2 -2
View File
@@ -18,8 +18,8 @@ def ensure_scene_schema(slug):
name TEXT, name TEXT,
x REAL NOT NULL DEFAULT 100, x REAL NOT NULL DEFAULT 100,
y REAL NOT NULL DEFAULT 100, y REAL NOT NULL DEFAULT 100,
width REAL NOT NULL DEFAULT 64, width REAL NOT NULL DEFAULT 128,
height REAL NOT NULL DEFAULT 64, height REAL NOT NULL DEFAULT 128,
z_index INTEGER NOT NULL DEFAULT 1, z_index INTEGER NOT NULL DEFAULT 1,
attributes TEXT NOT NULL DEFAULT '{}', attributes TEXT NOT NULL DEFAULT '{}',
created_at TEXT DEFAULT CURRENT_TIMESTAMP created_at TEXT DEFAULT CURRENT_TIMESTAMP
+20 -2
View File
@@ -23,10 +23,28 @@ def render_scene_object(obj):
# voir static/js/play/actions.js/conditions.js. Sans risque de # voir static/js/play/actions.js/conditions.js. Sans risque de
# collision d'id : un jeu est ENTIÈREMENT "document" (_screen_elements) # collision d'id : un jeu est ENTIÈREMENT "document" (_screen_elements)
# OU "jeu_2d" (_scene_objects), jamais les deux à la fois. # OU "jeu_2d" (_scene_objects), jamais les deux à la fois.
# data-anim-target (Timeline d'animation, Bug corrigé) : applyAnimationClip()
# (static/js/play/screens.js) et son équivalent d'aperçu éditeur
# (animation-timeline.js) sélectionnent TOUJOURS leur cible via
# [data-anim-target=...] (jamais data-element-id/data-object-id, posé
# sur .canvasElement/.playElement pour le DOM, voir visible_attrs.py) —
# un objet de scène n'ayant pas de wrapper équivalent, cet attribut est
# posé directement sur son <img> : SANS lui, aucun clip de Timeline (ni
# même une action de flow "Jouer une animation") ne trouvait jamais sa
# cible pour un objet de scène, quel que soit son type d'animation.
# class="sceneObjectSprite" (object-fit:contain, voir static/style.css) :
# les frames CraftPix (screens/labels/animal_sprite_library.py) sont de
# GRANDS canevas très paddés (~788×504 px, très différent des sprites
# Kenney déjà bien recadrés, ~96×128 px) — sans object-fit, l'image
# remplirait quand même son cadre (width/height:100%, voir la règle
# `.canvasElementInner > img` dans scene_edit.html) mais DÉFORMÉE,
# rendant le personnage minuscule/écrasé dans sa boîte de sélection.
if obj["kind"] == "personnage": if obj["kind"] == "personnage":
src = resolve_personnage_idle_frame(obj) src = resolve_personnage_idle_frame(obj)
src_attr = f' src="{html_lib.escape(src)}"' if src else "" src_attr = f' src="{html_lib.escape(src)}"' if src else ""
return f'<img data-object-id="{obj["id"]}" data-element-id="{obj["id"]}" data-personnage="1" style="{style}"{src_attr}>' return (f'<img class="sceneObjectSprite" data-object-id="{obj["id"]}" data-element-id="{obj["id"]}" '
f'data-anim-target="{obj["id"]}" data-personnage="1" style="{style}"{src_attr}>')
src = (obj.get("attributes") or {}).get("src", "") src = (obj.get("attributes") or {}).get("src", "")
src_attr = f' src="{html_lib.escape(src)}"' if src else "" src_attr = f' src="{html_lib.escape(src)}"' if src else ""
return f'<img data-object-id="{obj["id"]}" data-element-id="{obj["id"]}" style="{style}"{src_attr}>' return (f'<img class="sceneObjectSprite" data-object-id="{obj["id"]}" data-element-id="{obj["id"]}" '
f'data-anim-target="{obj["id"]}" style="{style}"{src_attr}>')
+11 -1
View File
@@ -473,7 +473,11 @@ body.builderBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden; d
.iconGalleryTile.selected{ border-color:var(--accent); box-shadow:0 0 0 2px var(--accent) inset; } .iconGalleryTile.selected{ border-color:var(--accent); box-shadow:0 0 0 2px var(--accent) inset; }
/* Phase 8 — galerie de personnages : réutilise .iconGallery/.iconGalleryTile /* Phase 8 — galerie de personnages : réutilise .iconGallery/.iconGalleryTile
(grille + tuile), mais avec de VRAIES miniatures (<img>, pas un masque (grille + tuile), mais avec de VRAIES miniatures (<img>, pas un masque
SVG monochrome) — voir screen_edit.html et personnage-panel.js. */ SVG monochrome) — voir screen_edit.html et personnage-panel.js.
.personnageGallery : tuiles ~3x plus grandes que la grille d'icônes
générique (2 colonnes au lieu de 6) — un personnage doit rester
reconnaissable, contrairement à une icône. */
.iconGallery.personnageGallery{ grid-template-columns:repeat(2, 1fr); gap:10px; max-height:420px; }
.personnageThumb{ width:100%; height:100%; object-fit:contain; image-rendering:pixelated; } .personnageThumb{ width:100%; height:100%; object-fit:contain; image-rendering:pixelated; }
.widgetBtnForm{ margin:0; } .widgetBtnForm{ margin:0; }
.widgetBtn{ .widgetBtn{
@@ -520,6 +524,12 @@ body.builderBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden; d
.canvasElement:hover{ border-color:rgba(91,140,255,.5); } .canvasElement:hover{ border-color:rgba(91,140,255,.5); }
.canvasElement.selected{ border:2px solid var(--accent); } .canvasElement.selected{ border:2px solid var(--accent); }
.canvasElementInner{ width:100%; height:100%; pointer-events:none; } .canvasElementInner{ width:100%; height:100%; pointer-events:none; }
/* Objet de scène (screens/scenes/render_scene_object.py, éditeur ET jeu
jouable) : object-fit:contain — les frames CraftPix (screens/labels/
animal_sprite_library.py) sont de grands canevas très paddés, très
différents des sprites Kenney déjà recadrés serré ; sans ça l'image
remplirait quand même sa boîte (width/height:100%) mais DÉFORMÉE. */
.sceneObjectSprite{ object-fit:contain; }
/* Surbrillance de l'élément sélectionné dans l'arborescence, posée en JS sur /* Surbrillance de l'élément sélectionné dans l'arborescence, posée en JS sur
sa propre balise (voir applySelectionHighlight) — seul moyen de la voir sa propre balise (voir applySelectionHighlight) — seul moyen de la voir