Retire la collision sur les images de fond + les blocs de logique/timeline de l'éditeur 2D
Build and deploy / test-python (push) Successful in 6m55s
Build and deploy / test-js (push) Successful in 55s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

Deux retours utilisateur sur l'éditeur de scène 2D :

- Le panneau "🧱 Collision" (réglages + aperçu visuel sur le canevas)
  n'apparaît plus pour un objet kind="fond" : jamais un obstacle ni une
  cible de collision possible (déjà exclu de forgeSolidObstacles côté
  jeu), ce panneau n'aurait donc aucun effet.

- Retire les onglets "🧩 Blocs de logique" et "🎬 Timeline d'animation"
  de CET éditeur (l'éditeur document, templates/screen_edit.html, les
  garde tel quel) — en prévision d'un éditeur dédié aux scènes 2D
  (collision/périmètre -> action), pas encore construit. switchBuilderTab()/
  toggleDashCreate() (génériques, nécessaires à l'onglet "Événements"
  restant) extraites dans un nouveau builder-tabs.js partagé par les deux
  éditeurs, pour ne plus avoir à charger flow-editor.js/tabs-and-blocks.js/
  animation-timeline.js (propres aux blocs/timeline) dans l'éditeur 2D.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-09-02 16:41:56 +02:00
co-authored by Claude Sonnet 5
parent a231bcf2fa
commit dfaf85fac1
4 changed files with 48 additions and 475 deletions
+26
View File
@@ -0,0 +1,26 @@
// ---------- Onglets du centre (Écran/Logique/Timeline/Événements pour
// l'éditeur document ; Scène/Événements pour l'éditeur de scène 2D) — un
// seul visible à la fois, chacun occupant tout l'espace disponible.
// Extrait de tabs-and-blocks.js (voir ce fichier) : ces deux fonctions
// sont GÉNÉRIQUES (aucune dépendance aux blocs de logique), donc chargées
// par LES DEUX éditeurs — screen_edit.html (blocs/timeline conservés) ET
// scene_edit.html (blocs/timeline retirés, voir le plan "Éditeur de
// collision" — ce dernier n'a donc plus besoin de charger
// tabs-and-blocks.js/flow-editor.js/animation-timeline.js du tout). ----------
function switchBuilderTab(tab) {
document.querySelectorAll('.builderTabBtn').forEach(function (btn) {
btn.classList.toggle('active', btn.dataset.tab === tab);
});
document.querySelectorAll('.builderTabPanel').forEach(function (panel) {
panel.classList.toggle('active', panel.dataset.tabPanel === tab);
});
}
function toggleDashCreate(tab) {
var bar = document.getElementById('dashCreate-' + tab);
bar.style.display = bar.style.display === 'none' ? 'flex' : 'none';
if (bar.style.display === 'flex') {
var firstInput = bar.querySelector('input, select');
if (firstInput) firstInput.focus();
}
}
+4 -22
View File
@@ -1,25 +1,7 @@
// ---------- Onglets du centre : Écran / Logique de la scène / Timeline
// d'animation — un seul visible à la fois, chacun occupant tout l'espace
// disponible (remplace l'ancien panneau du bas rétractable/redimensionnable
// à la souris : un onglet "prend toute la place" par nature, et en changer
// équivaut à "fermer" celui qu'on quitte). ----------
function switchBuilderTab(tab) {
document.querySelectorAll('.builderTabBtn').forEach(function (btn) {
btn.classList.toggle('active', btn.dataset.tab === tab);
});
document.querySelectorAll('.builderTabPanel').forEach(function (panel) {
panel.classList.toggle('active', panel.dataset.tabPanel === tab);
});
}
function toggleDashCreate(tab) {
var bar = document.getElementById('dashCreate-' + tab);
bar.style.display = bar.style.display === 'none' ? 'flex' : 'none';
if (bar.style.display === 'flex') {
var firstInput = bar.querySelector('input, select');
if (firstInput) firstInput.focus();
}
}
// switchBuilderTab()/toggleDashCreate() ont déménagé dans builder-tabs.js
// (générique, chargé par les DEUX éditeurs — voir ce fichier) : tout ce
// qui reste ici est propre aux blocs de logique/panneaux flottants,
// jamais chargé par l'éditeur de scène 2D (scene_edit.html).
function toggleBlockEditRow(blockId) {
document.getElementById('blockEditRow' + blockId).classList.toggle('hidden');
+17 -453
View File
@@ -19,11 +19,11 @@
{% endblock %}
{% block content %}
<!-- Fondations multi-éditeurs — éditeur de scène 2D (jeux "jeu_2d", voir
db/games/get_game_type.py). Réutilise TEL QUEL le moteur de logique de
flow partagé (mêmes JS : flow-editor.js/tabs-and-blocks.js/
animation-timeline.js) — seule la palette de nœuds et le panneau
gauche/canevas diffèrent de templates/screen_edit.html. Voir le plan
"Fondations d'une plateforme multi-éditeurs". -->
db/games/get_game_type.py). Le graphe de logique générique (blocs de
logique/Timeline d'animation, voir templates/screen_edit.html, gardé
tel quel pour l'éditeur document) a été retiré de CET éditeur à la
demande de l'utilisateur, en prévision d'un éditeur dédié aux scènes
2D (collision/périmètre -> action), pas encore construit. -->
<p class="hint" style="background:var(--panel); border:1px solid var(--border); border-radius:8px; padding:10px 14px; margin:10px 0;">
🎮 Jeu 2D — scène à taille fixe ({{ screen.scene_width }}×{{ screen.scene_height }} px).
</p>
@@ -93,8 +93,6 @@
<div class="builderCanvasArea">
<div class="builderTabs" id="builderTabs">
<button type="button" class="builderTabBtn active" data-tab="screen" onclick="switchBuilderTab('screen')">🖥️ Scène</button>
<button type="button" class="builderTabBtn" data-tab="blocks" onclick="switchBuilderTab('blocks')">🧩 Blocs de logique</button>
<button type="button" class="builderTabBtn" data-tab="anim" onclick="switchBuilderTab('anim')">🎬 Timeline d'animation</button>
<button type="button" class="builderTabBtn" data-tab="events" onclick="switchBuilderTab('events')">📣 Événements</button>
</div>
@@ -107,11 +105,12 @@
onmousedown="onSceneObjectMouseDown(event, {{ o.id }})">
<div class="canvasElementInner">{{ o.rendered_html|safe }}</div>
<div class="resizeHandle" onmousedown="onSceneObjectResizeMouseDown(event, {{ o.id }})"></div>
{% if o.id == selected_id %}
{% if o.id == selected_id and o.kind != 'fond' %}
<!-- Boîte de collision (voir screens/rendering/collision_settings.py) :
aperçu visuel + réglable directement au glisser — seulement pour
l'objet SÉLECTIONNÉ (celui dont le panneau "🧱 Collision" est ouvert),
voir initCollisionBoxOverlay() dans scene-editor.js. -->
voir initCollisionBoxOverlay() dans scene-editor.js. JAMAIS pour un
"fond" (voir la même garde sur le panneau "🧱 Collision" plus bas). -->
<div class="collisionBoxOverlay {{ 'is-circle' if collision_settings.shape == 'cercle' else '' }} {{ 'is-disabled' if not collision_settings.enabled else '' }}"
id="collisionBoxOverlay"
style="left:{{ (o.width - collision_settings.width) / 2 + collision_settings.offset_x }}px; top:{{ (o.height - collision_settings.height) / 2 + collision_settings.offset_y }}px; width:{{ collision_settings.width }}px; height:{{ collision_settings.height }}px;"
@@ -126,445 +125,6 @@
<p class="hint" style="text-align:center;">Glisse pour déplacer, tire le coin pour redimensionner, clique pour sélectionner. Positions en pixels (scène à taille fixe).</p>
</div>
<div class="builderTabPanel" id="blocksTabPanel" data-tab-panel="blocks">
<div class="dashPanelHeader">
<h2>Blocs de logique</h2>
<button type="button" class="button is-small primary" onclick="toggleDashCreate('blocks')">+ Créer un bloc</button>
</div>
<form method="post" action="{{ url_for('flow_block_new', slug=game.slug, screen_id=screen.id) }}" class="dashCreateBar" id="dashCreate-blocks" style="display:none;">
<input type="text" name="name" placeholder="Nom du bloc (ex. Ouvrir la porte)" required autocomplete="off">
<input type="text" name="description" placeholder="Description (optionnel)">
<button type="submit" class="primary">Créer</button>
</form>
{% if blocks_view %}
{% for b in blocks_view %}
<form method="post" action="{{ url_for('flow_block_edit', slug=game.slug, screen_id=screen.id, block_id=b.id) }}" id="blockEditForm{{ b.id }}"></form>
{% endfor %}
<div class="fieldsTableWrap">
<table class="table is-fullwidth is-hoverable fieldsTable">
<thead><tr><th>Nom</th><th>Description</th><th>Objets concernés</th><th>Nœuds</th><th></th></tr></thead>
<tbody>
{% for b in blocks_view %}
<tr>
<td><strong>{{ b.name }}</strong></td>
<td>{{ b.description|truncatesentences(3) }}</td>
<td>{% if b.element_labels %}{{ b.element_labels|join(', ') }}{% else %}<span class="hint">—</span>{% endif %}</td>
<td>{{ b.node_count }}</td>
<td class="fieldsTableActions">
<button type="button" class="button is-small is-primary" onclick="openLogicBlockPanel({{ b.id }})" title="Ouvrir le graphe de ce bloc">🧩 Ouvrir</button>
<button type="button" class="button is-small" onclick="toggleBlockEditRow({{ b.id }})" title="Modifier le nom/la description">✏️</button>
<form method="post" action="{{ url_for('flow_block_delete', slug=game.slug, screen_id=screen.id, block_id=b.id) }}"
onsubmit="return confirm('Supprimer le bloc « {{ b.name }} » ? Cela supprime aussi ses {{ b.node_count }} nœud(s) et leurs connexions.');" style="display:inline;">
<button type="submit" class="button is-small is-danger" title="Supprimer">🗑️</button>
</form>
</td>
</tr>
<tr class="hidden" id="blockEditRow{{ b.id }}">
<td colspan="5">
<div class="dashCreateBar" style="display:flex;">
<input type="text" form="blockEditForm{{ b.id }}" name="name" value="{{ b.name }}" required>
<input type="text" form="blockEditForm{{ b.id }}" name="description" value="{{ b.description or '' }}" placeholder="Description">
<button type="submit" form="blockEditForm{{ b.id }}" class="primary">Enregistrer</button>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="hint">Aucun bloc pour l'instant — crée le premier ci-dessus, puis ouvre-le pour poser des déclencheurs/conditions/actions.</p>
{% endif %}
</div>
<div class="floatPanel floatPanel--center hidden" id="logicBlockPanel">
<div class="floatPanelHeader" id="logicBlockPanelHeader">
<span class="floatPanelTitle">⋮⋮ <span id="logicBlockPanelTitle">Bloc</span></span>
<button type="button" class="button is-small floatPanelFullscreenBtn" title="Plein écran">⛶</button>
<button type="button" class="button is-small" onclick="closeLogicBlockPanel()">✕</button>
</div>
<div class="floatPanelBody logicBlockPanelBody">
<div class="flowToolbar">
<button type="button" class="smallBtn button is-small" onclick="openNodeForm('trigger')">➕ Déclencheur</button>
<button type="button" class="smallBtn button is-small" onclick="openNodeForm('condition')">➕ Condition</button>
<button type="button" class="smallBtn button is-small" onclick="openNodeForm('action')">➕ Action</button>
<span class="hint">Clique un point de sortie d'un nœud, puis le point d'entrée d'un autre nœud, pour les relier. Reclique une connexion pour la supprimer.</span>
</div>
<div id="nodeForm" class="nodeForm" style="display:none;">
<div class="fieldRow" style="align-items:center; justify-content:space-between;">
<strong id="nodeFormTitle">Nouveau nœud</strong>
<button type="button" class="smallBtn button is-small" onclick="closeNodeForm()">✕</button>
</div>
<div id="nodeFormTrigger" style="display:none;">
<label>Événement<select id="nf-trigger-event" onchange="toggleFlowTriggerFields()">
{% for key, label in trigger_events %}
<option value="{{ key }}">{{ label }}</option>
{% endfor %}
</select></label>
<label id="nf-trigger-element-row" style="display:none;">Objet<select id="nf-trigger-element">
{% for o in objects %}
<option value="{{ o.id }}">{{ o.name or ('Personnage' if o.kind == 'personnage' else 'Décor') }} (obj-{{ o.id }})</option>
{% endfor %}
</select></label>
<p class="hint" id="nf-trigger-repeater-hint" style="display:none;"></p>
<p class="hint" id="nf-trigger-affichage-hint" style="display:none;"></p>
<div id="nf-trigger-event-row" style="display:none;">
<label>Événement personnalisé<select id="nf-trigger-custom-event">
{% for ev in custom_events %}
<option value="{{ ev.id }}">{{ ev.name }}</option>
{% endfor %}
</select></label>
{% if not custom_events %}
<p class="hint">Aucun événement pour l'instant — <a href="javascript:void(0)" onclick="switchBuilderTab('events'); return false;">crées-en un dans l'onglet Événements</a>.</p>
{% endif %}
</div>
<div id="nf-trigger-key-row" style="display:none;">
<label>Touche à surveiller
<input type="text" id="nf-trigger-key-display" readonly placeholder="Clique ici puis appuie sur une touche" onkeydown="captureFlowTriggerKey(event)">
</label>
<input type="hidden" id="nf-trigger-key">
</div>
<div id="nf-trigger-interval-row" style="display:none;">
<label>Toutes les... millisecondes
<input type="number" id="nf-trigger-interval" min="50" step="50" value="1000" placeholder="ex. 1000">
</label>
</div>
</div>
<div id="nodeFormCondition" style="display:none;">
<label>Source<select id="nf-cond-source" onchange="onFlowConditionSourceChange()">
<option value="objet">Champ d'un objet de données</option>
<option value="variable">Variable globale</option>
<option value="collision">Collision entre deux objets de scène</option>
</select></label>
<div id="nf-cond-objet-fields" {% if not definitions %}style="display:none;"{% endif %}>
<label>Objet de données<select id="nf-cond-definition" onchange="onFlowDefinitionChange('cond')">
{% for d in definitions %}
<option value="{{ d.id }}">{{ d.name }}</option>
{% endfor %}
</select></label>
<label>Ligne concernée<select id="nf-cond-row"></select></label>
<label>Champ<select id="nf-cond-field" onchange="onFlowFieldChange()"></select></label>
</div>
{% if not definitions %}
<p class="hint" id="nf-cond-no-objet-hint">Aucun objet de données défini dans ce jeu pour l'instant — <a href="{{ url_for('game_dashboard', slug=game.slug, tab='objects') }}">crées-en un</a>.</p>
{% endif %}
<div id="nf-cond-variable-fields" style="display:none;">
<label>Variable<select id="nf-cond-variable">
<option value="">(sélectionner une variable)</option>
{% for v in global_variables %}
<option value="{{ v.name }}">{{ v.name }}</option>
{% endfor %}
</select></label>
<label>Chemin (optionnel)<input type="text" id="nf-cond-variable-chemin" placeholder="ex. .nom ou [0].valeur"></label>
{% if not global_variables %}
<p class="hint">Aucune variable globale pour l'instant — <a href="{{ url_for('game_dashboard', slug=game.slug, tab='variables') }}" target="_blank">crées-en une ↗</a></p>
{% endif %}
</div>
<div id="nf-cond-collision-fields" style="display:none;">
<label>Premier objet<select id="nf-cond-element-a">
{% for o in objects %}
<option value="{{ o.id }}">{{ o.name or ('Personnage' if o.kind == 'personnage' else 'Décor') }} (obj-{{ o.id }})</option>
{% endfor %}
</select></label>
<label>Deuxième objet<select id="nf-cond-element-b">
{% for o in objects %}
<option value="{{ o.id }}">{{ o.name or ('Personnage' if o.kind == 'personnage' else 'Décor') }} (obj-{{ o.id }})</option>
{% endfor %}
</select></label>
<p class="hint">Vrai si les deux objets se touchent (leurs rectangles se chevauchent à l'écran) — relie le port "Faux" du nœud pour "ne se touchent pas".</p>
</div>
<div id="nf-cond-operator-value-row">
<label>Condition<select id="nf-cond-operator">
{% for key, label in condition_operators %}
<option value="{{ key }}">{{ label }}</option>
{% endfor %}
</select></label>
<label>Valeur de comparaison<input type="text" id="nf-cond-value" placeholder="ex. 1, vrai, 10..."></label>
</div>
<div id="nf-cond-extra-wrap">
<label id="nf-cond-combinator-row" style="display:none;">Combiner les conditions avec
<select id="nf-cond-combinator">
<option value="et">ET (toutes les conditions doivent être vraies)</option>
<option value="ou">OU (au moins une condition doit être vraie)</option>
</select>
</label>
<div id="nf-cond-extra-clauses"></div>
<button type="button" class="smallBtn button is-small" onclick="addFlowConditionClause()">+ Ajouter une condition</button>
</div>
</div>
<div id="nodeFormAction" style="display:none;">
<label>Type d'action<select id="nf-action-type" onchange="toggleFlowActionFields()">
{% for key, label in action_type_labels.items() %}
<option value="{{ key }}">{{ label }}</option>
{% endfor %}
</select></label>
<select id="nf-action-target-screen" style="display:none;">
{% for s in all_screens %}
<option value="{{ s.id }}">{{ s.name }}</option>
{% endfor %}
</select>
<!-- Équivalent scène de "Modifier un élément" — palette réduite
(visibilité/position en pixels/orientation), voir
OBJECT_ACTION_PROPERTIES (screens/scenes/flow_palette.py). -->
<div id="nf-action-object-fields" style="display:none;">
<select id="nf-action-target-object">
{% for o in objects %}
<option value="{{ o.id }}">{{ o.name or ('Personnage' if o.kind == 'personnage' else 'Décor') }} (obj-{{ o.id }})</option>
{% endfor %}
</select>
<select id="nf-action-object-property" onchange="toggleFlowObjectPropertyValue()" style="margin-top:6px;">
{% for key, label in object_action_properties %}
<option value="{{ key }}">{{ label }}</option>
{% endfor %}
</select>
<select id="nf-action-object-value-visibility" style="margin-top:6px;">
{% for key, label in element_visibility_values %}
<option value="{{ key }}">{{ label }}</option>
{% endfor %}
</select>
<select id="nf-action-object-value-orientation" style="display:none; margin-top:6px;">
{% for key, label in element_orientation_values %}
<option value="{{ key }}">{{ label }}</option>
{% endfor %}
</select>
<input type="number" id="nf-action-object-value-number" style="display:none; margin-top:6px;" placeholder="Nouvelle valeur en pixels">
</div>
<div id="nf-action-wait-fields" style="display:none;">
<label>Délai en secondes avant de continuer
<input type="number" id="nf-action-wait-seconds" min="0" step="0.5" value="3" placeholder="ex. 3">
</label>
</div>
<div id="nf-action-data-fields" style="display:none;">
{% if not definitions %}
<p class="hint">Aucun objet de données défini dans ce jeu pour l'instant — <a href="{{ url_for('game_dashboard', slug=game.slug, tab='objects') }}">crées-en un</a>.</p>
{% else %}
<select id="nf-action-data-definition" onchange="onFlowDefinitionChange('action')">
{% for d in definitions %}
<option value="{{ d.id }}">{{ d.name }}</option>
{% endfor %}
</select>
<select id="nf-action-data-row" style="margin-top:6px;"></select>
<select id="nf-action-data-field" style="margin-top:6px;"></select>
<select id="nf-action-data-operation" style="margin-top:6px;" onchange="updateDataValuePlaceholder('nf-action-data-operation', 'nf-action-data-value')">
{% for key, label in data_operations %}
<option value="{{ key }}">{{ label }}</option>
{% endfor %}
</select>
<input type="text" id="nf-action-data-value" style="margin-top:6px;" placeholder="Valeur / montant">
{% endif %}
</div>
<div id="nf-action-sound-fields" style="display:none;">
<label>URL du son
<input type="text" id="nf-action-sound-url" placeholder="URL du fichier audio">
</label>
<div style="margin-top:6px;">
<label for="nf-action-sound-upload" style="font-size:12px;">Envoyer un fichier :</label>
<input type="file" class="nodeFormUploadInput" id="nf-action-sound-upload" data-target="nf-action-sound-url" accept="audio/*" style="font-size:12px;">
<span class="uploadStatus" style="font-size:12px; color:var(--text-dim);"></span>
</div>
</div>
<div id="nf-action-sprite-fields" style="display:none;">
<label>Personnage à animer
<select id="nf-action-sprite-target-element" onchange="onSpriteTargetElementChange()">
<option value="">— Choisir —</option>
{% for o in objects if o.kind == 'personnage' %}
<option value="{{ o.id }}">{{ o.name or 'Personnage' }} (obj-{{ o.id }})</option>
{% endfor %}
</select>
</label>
{% if not objects|selectattr('kind', 'equalto', 'personnage')|list %}
<p class="hint">Aucun personnage sur cette scène — ajoute-en un depuis la galerie "🎭 Personnages" du panneau de gauche.</p>
{% endif %}
<div id="nf-action-sprite-animation-grid" class="iconGallery" style="margin-top:6px;"></div>
<input type="hidden" id="nf-action-sprite-animation-name" value="">
<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 le tableau de bord.</p>
{% else %}
<select id="nf-action-variable-name">
{% for v in global_variables %}
<option value="{{ v.name }}">{{ v.name }}</option>
{% endfor %}
</select>
<select id="nf-action-variable-operation" style="margin-top:6px;" onchange="updateDataValuePlaceholder('nf-action-variable-operation', 'nf-action-variable-value')">
{% for key, label in data_operations %}
<option value="{{ key }}">{{ label }}</option>
{% endfor %}
</select>
<input type="text" id="nf-action-variable-value" style="margin-top:6px;" placeholder="Valeur / montant">
{% endif %}
</div>
<!-- Score/progression (voir db/scoring/) : concept de premier
ordre distinct des variables globales ci-dessus — un seul
score par joueur, pas de nom à choisir. Réutilise le même
vocabulaire d'opérations (DATA_OPERATIONS). -->
<div id="nf-action-score-fields" style="display:none;">
<select id="nf-action-score-operation" onchange="updateDataValuePlaceholder('nf-action-score-operation', 'nf-action-score-value')">
{% for key, label in data_operations %}
<option value="{{ key }}">{{ label }}</option>
{% endfor %}
</select>
<input type="text" id="nf-action-score-value" style="margin-top:6px;" placeholder="Valeur / montant">
</div>
<div id="nf-action-status-fields" style="display:none;">
<select id="nf-action-status-value">
{% for key, label in score_status_choices %}
<option value="{{ key }}">{{ label }}</option>
{% endfor %}
</select>
</div>
<div id="nf-action-event-fields" style="display:none;">
{% if not custom_events %}
<p class="hint">Aucun événement pour l'instant — <a href="javascript:void(0)" onclick="switchBuilderTab('events'); return false;">crées-en un dans l'onglet Événements</a>.</p>
{% else %}
<select id="nf-action-custom-event">
{% for ev in custom_events %}
<option value="{{ ev.id }}">{{ ev.name }}</option>
{% endfor %}
</select>
{% endif %}
</div>
</div>
<div class="actionsRow"><button type="button" class="smallBtn button is-small is-primary" onclick="submitNodeForm()">+ Ajouter ce nœud</button></div>
</div>
<div class="flowCanvasWrap">
<svg id="flowEdges" class="flowEdgesSvg"></svg>
<div id="flowCanvas" class="flowCanvas"></div>
</div>
</div>
<div class="floatPanelResizeHandle" id="logicBlockPanelResizeHandle"></div>
</div>
<div class="builderTabPanel" id="animTabPanel" data-tab-panel="anim">
<div class="flowToolbar">
<label style="display:flex; align-items:center; gap:6px;">
Piste
<select id="anim-new-track-element">
{% for o in objects %}
<option value="{{ o.id }}">{{ o.name or ('Personnage' if o.kind == 'personnage' else 'Décor') }}</option>
{% endfor %}
</select>
</label>
<button type="button" class="smallBtn button is-small" onclick="addAnimTrack()">➕ Ajouter un clip</button>
</div>
<div id="animClipForm" class="nodeForm" style="display:none;">
<div class="fieldRow" style="align-items:center; justify-content:space-between;">
<strong id="animClipFormTitle">Clip d'animation</strong>
<button type="button" class="smallBtn button is-small" onclick="closeAnimClipForm()">✕</button>
</div>
<label>Type
<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>
<div id="anim-clip-animate-css-fields">
<label>Animation
<select id="anim-clip-animate-name">
{% for category, items in animate_css_catalog %}
<optgroup label="{{ category }}">
{% for key, label in items %}
<option value="{{ key }}">{{ label }}</option>
{% endfor %}
</optgroup>
{% endfor %}
</select>
</label>
</div>
<div id="anim-clip-custom-fields" style="display:none;">
<div id="anim-clip-keyframes"></div>
<button type="button" class="smallBtn button is-small" onclick="addAnimKeyframeRow()">+ Ajouter une image-clé</button>
</div>
<div id="anim-clip-sprite-fields" style="display:none;">
<p class="hint" id="anim-clip-sprite-no-animations" style="display:none;">Cet objet n'est pas un personnage, ou n'a encore aucune animation.</p>
<div id="anim-clip-sprite-animation-grid" class="iconGallery"></div>
<input type="hidden" id="anim-clip-sprite-animation-name" value="">
<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">
</label>
<label style="flex:1;">Durée en secondes
<input type="number" id="anim-clip-duration" min="0.1" step="0.1" value="1">
</label>
</div>
<div class="fieldRow">
<label style="flex:1;">Délai en secondes
<input type="number" id="anim-clip-delay" min="0" step="0.1" value="0">
</label>
<label style="flex:1;">Vitesse
<select id="anim-clip-easing">
<option value="ease">Progressive</option>
<option value="linear">Constante</option>
<option value="ease-in">Accélère</option>
<option value="ease-out">Ralentit</option>
<option value="ease-in-out">Accélère puis ralentit</option>
</select>
</label>
</div>
<label>Répétitions
<select id="anim-clip-iteration">
<option value="1">Une fois</option>
<option value="2">2 fois</option>
<option value="3">3 fois</option>
<option value="infinite">En boucle</option>
</select>
</label>
<div class="actionsRow">
<button type="button" class="smallBtn button is-small is-primary" onclick="saveAnimClipForm()">Enregistrer</button>
<button type="button" class="smallBtn button is-small" onclick="previewAnimClip()">▶ Aperçu</button>
<button type="button" class="smallBtn button is-small is-danger" onclick="deleteAnimClipForm()">🗑️ Supprimer</button>
</div>
</div>
<div class="animTimelineWrap">
<div id="animRuler" class="animRuler"></div>
<div id="animTracks" class="animTracks"></div>
</div>
</div>
<div class="builderTabPanel" id="eventsTabPanel" data-tab-panel="events">
<div class="dashPanelHeader">
<h2>Événements personnalisés</h2>
@@ -615,7 +175,7 @@
</table>
</div>
{% else %}
<p class="hint">Aucun événement pour l'instant — crée le premier ci-dessus, puis pose un nœud "Sur un événement personnalisé" (Déclencheur) ou "Déclencher un événement" (Action) dans la Logique de la scène.</p>
<p class="hint">Aucun événement pour l'instant — crée le premier ci-dessus.</p>
{% endif %}
</div>
</div>
@@ -666,12 +226,17 @@
</div>
</details>
{% if obj.kind != 'fond' %}
<!-- Boîte de collision (voir screens/rendering/collision_settings.py,
static/js/play/conditions.js::elementsOverlap) : posée AUTOMATIQUEMENT
sur tout objet (= sa boîte visuelle par défaut, tant que rien
n'est changé ici) — désactivable, taille/forme/décalage réglables
pour un sprite très paddé dont la silhouette réelle est plus
petite que son canevas. -->
petite que son canevas. JAMAIS pour un "fond" (image de décor,
voir add_scene_object.py) : ni obstacle ni cible de collision
possible (forgeSolidObstacles l'exclut déjà côté jeu, voir
static/js/play/personnage-controller.js), ce panneau n'aurait
donc aucun effet. -->
<details class="propGroup">
<summary>🧱 Collision</summary>
<div class="propGroupBody" id="collisionPanelBody" data-element-id="{{ obj.id }}">
@@ -708,6 +273,7 @@
<p class="help">Taille/décalage réglés par rapport au centre de l'objet — laisse la taille par défaut (celle de l'objet) si son image n'a pas de marge inutile.</p>
</div>
</details>
{% endif %}
{% if obj.kind == 'personnage' %}
<details class="propGroup" open>
@@ -841,9 +407,7 @@
<script src="{{ url_for('static', filename='js/screen_edit/tree-panels.js') }}"></script>
<script src="{{ url_for('static', filename='js/screen_edit/panel-init.js') }}"></script>
<script src="{{ url_for('static', filename='js/screen_edit/personnage-panel.js') }}"></script>
<script src="{{ url_for('static', filename='js/screen_edit/flow-editor.js') }}"></script>
<script src="{{ url_for('static', filename='js/screen_edit/tabs-and-blocks.js') }}"></script>
<script src="{{ url_for('static', filename='js/screen_edit/animation-timeline.js') }}"></script>
<script src="{{ url_for('static', filename='js/screen_edit/builder-tabs.js') }}"></script>
<script src="{{ url_for('static', filename='js/screen_edit/personnage-preview.js') }}"></script>
<script src="{{ url_for('static', filename='js/scenes/scene-editor.js') }}"></script>
{% endblock %}
+1
View File
@@ -1305,6 +1305,7 @@
<script src="{{ url_for('static', filename='js/screen_edit/tree-panels.js') }}"></script>
<script src="{{ url_for('static', filename='js/screen_edit/panel-init.js') }}"></script>
<script src="{{ url_for('static', filename='js/screen_edit/personnage-panel.js') }}"></script>
<script src="{{ url_for('static', filename='js/screen_edit/builder-tabs.js') }}"></script>
<script src="{{ url_for('static', filename='js/screen_edit/flow-editor.js') }}"></script>
<script src="{{ url_for('static', filename='js/screen_edit/tabs-and-blocks.js') }}"></script>
<script src="{{ url_for('static', filename='js/screen_edit/animation-timeline.js') }}"></script>