Trois retours utilisateur sur l'éditeur de scène 2D : - Retire l'arborescence "Objets de cette scène" (un objet reste sélectionnable en cliquant dessus sur le canevas) et le bouton "+ Ajouter un décor" (le type "decor" reste supporté côté serveur, juste plus accessible depuis ce menu). - Chaque objet de scène a désormais une boîte de collision AUTOMATIQUE (= sa propre boîte, comportement inchangé pour une condition de collision déjà posée) réglable dans un nouveau panneau "🧱 Collision" : activée/désactivée, forme (rectangle/cercle), taille et décalage — utile pour un sprite très paddé (CraftPix) dont la silhouette réelle est bien plus petite que son canevas. elementsOverlap() (conditions.js) applique ces réglages en restant identique par défaut. - Nouveau champ "🏷️ Rôle" (Joueur/Ennemi/PNJ) sur un personnage : SEUL un personnage "Joueur" est désormais déplacé/animé au clavier et suivi par la caméra (personnage-controller.js) — "Ennemi"/"PNJ" restent immobiles tant qu'aucune logique de flow ne les pilote (déplacement ennemi automatique/apparition, dialogue de PNJ... hors scope de ce réglage, qui ne fait qu'identifier "quel objet est le joueur"). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
838 lines
48 KiB
HTML
838 lines
48 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}{{ screen.name }} — {{ game.name }}{% endblock %}
|
||
{% block content_class %}content-builder{% endblock %}
|
||
{% block body_class %}builderBody{% endblock %}
|
||
{% block extra_head %}
|
||
<link rel="stylesheet" href="{{ url_for('static', filename='vendor/fonts/fonts.css') }}">
|
||
<link rel="stylesheet" href="{{ url_for('static', filename='vendor/animate.min.css') }}">
|
||
<style>
|
||
/* render_scene_object.py positionne son <img> en absolu (left/top/width/
|
||
height en px) pour s'afficher correctement en enfant DIRECT de
|
||
.playScreen.playScene (voir templates/play.html) — dans le canevas de
|
||
CET éditeur, cette même balise est nichée dans .canvasElementInner,
|
||
lui-même déjà positionné/dimensionné par .canvasElement (voir plus
|
||
bas) : sans ce correctif, l'image se positionnait EN PLUS depuis
|
||
#canvas (son ancêtre positionné le plus proche), en double du
|
||
décalage déjà appliqué par .canvasElement. */
|
||
.canvasElementInner > img{ position:static !important; left:auto !important; top:auto !important; width:100% !important; height:100% !important; z-index:auto !important; }
|
||
</style>
|
||
{% 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". -->
|
||
<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>
|
||
|
||
<div class="builder3" id="builder3">
|
||
{% set obj = objects|selectattr("id", "equalto", selected_id)|first if selected_id else none %}
|
||
|
||
<button type="button" id="panelLeftReopenBtn" class="button is-small floatPanelReopenBtn" onclick="openLeftPanel()" title="Rouvrir le panneau des objets">🧩 Objets</button>
|
||
<div class="floatPanel floatPanel--left" id="panelLeftFloat">
|
||
<div class="floatPanelHeader" data-drag-panel="panelLeftFloat" data-drag-key="left">
|
||
<span class="floatPanelTitle">⋮⋮ Objets de cette scène</span>
|
||
<button type="button" class="button is-small" onclick="closeLeftPanel()" title="Fermer">✕</button>
|
||
</div>
|
||
<div class="floatPanelBody">
|
||
<form method="post" action="{{ url_for('screen_rename', slug=game.slug, screen_id=screen.id) }}" class="field has-addons" style="align-items:center; margin-bottom:8px;">
|
||
<div class="control is-expanded"><input class="input is-small" type="text" name="name" value="{{ screen.name }}" required></div>
|
||
<div class="control"><button type="submit" class="button is-small">Renommer</button></div>
|
||
</form>
|
||
<a class="button is-small is-primary is-fullwidth mb-2" href="{{ url_for('game_play', slug=game.slug) }}" target="_blank">▶️ Jouer depuis le début</a>
|
||
<hr style="border-color:var(--border); margin:10px 0;">
|
||
|
||
<!-- "Objets de cette scène" (arborescence des objets déjà posés)
|
||
retirée à la demande de l'utilisateur — un objet posé reste
|
||
sélectionnable en cliquant directement dessus sur le canevas
|
||
(voir onSceneObjectMouseDown, static/js/scenes/scene-editor.js). -->
|
||
|
||
<!-- Galerie de personnages Forge (VRAIES miniatures) — cliquer une
|
||
tuile pose un personnage déjà configuré, animé immédiatement (voir
|
||
personnage-preview.js, réutilisé tel quel : ne dépend que de
|
||
data-personnage, pas de la table d'origine). -->
|
||
<details class="propGroup" open>
|
||
<summary>🎭 Personnages</summary>
|
||
<div class="propGroupBody">
|
||
<div class="iconGallery personnageGallery">
|
||
{% for slug_c, character in sprite_library_families %}
|
||
<button type="button" class="iconGalleryTile" title="{{ character.label }}" onclick="addSceneObject('personnage', '{{ slug_c }}')">
|
||
<img src="{{ character.animations.idle[0] }}" alt="{{ character.label }}" class="personnageThumb">
|
||
</button>
|
||
{% endfor %}
|
||
</div>
|
||
<p class="help">Personnage Kenney (licence libre CC0) ou animal Forge, avec toutes ses animations — les variantes de couleur d'un animal se choisissent depuis le panneau de propriétés.</p>
|
||
</div>
|
||
</details>
|
||
{% if background_library %}
|
||
<!-- Images de fond CraftPix (screens.BACKGROUND_LIBRARY, admin
|
||
seulement — voir core/sprite_gate.py). Posée à la taille RÉELLE
|
||
de l'image (voir screens/scenes/add_scene_object.py) : plus
|
||
grande que la scène, la caméra la suit en défilant derrière le
|
||
personnage (voir static/js/play/personnage-controller.js). -->
|
||
<details class="propGroup">
|
||
<summary>🏞️ Images de fond</summary>
|
||
<div class="propGroupBody">
|
||
<div class="iconGallery personnageGallery">
|
||
{% for slug_b, background in background_library.items() %}
|
||
<button type="button" class="iconGalleryTile" title="{{ background.label }}" onclick="addSceneObject('fond', null, '{{ slug_b }}')">
|
||
<img src="{{ background.url }}" alt="{{ background.label }}" class="personnageThumb">
|
||
</button>
|
||
{% endfor %}
|
||
</div>
|
||
<p class="help">Posée à sa taille réelle (souvent plus grande que la scène) — un seul fond par scène recommandé, la caméra le suit en défilant.</p>
|
||
</div>
|
||
</details>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<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>
|
||
|
||
<div class="builderTabPanel active" data-tab-panel="screen">
|
||
<div class="canvasFrame">
|
||
<div class="canvas" id="canvas" style="width:{{ screen.scene_width }}px; height:{{ screen.scene_height }}px; aspect-ratio:auto; margin:0 auto; position:relative; overflow:hidden; background:#1a1d24;">
|
||
{% for o in objects %}
|
||
<div class="canvasElement {{ 'selected' if o.id == selected_id else '' }}" id="el-{{ o.id }}"
|
||
style="position:absolute; left:{{ o.x }}px; top:{{ o.y }}px; width:{{ o.width }}px; height:{{ o.height }}px;"
|
||
onmousedown="onSceneObjectMouseDown(event, {{ o.id }})">
|
||
<div class="canvasElementInner">{{ o.rendered_html|safe }}</div>
|
||
<div class="resizeHandle" onmousedown="onSceneObjectResizeMouseDown(event, {{ o.id }})"></div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
<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>
|
||
<button type="button" class="button is-small primary" onclick="toggleDashCreate('events')">+ Créer un événement</button>
|
||
</div>
|
||
<form method="post" action="{{ url_for('custom_event_new', slug=game.slug) }}" class="dashCreateBar" id="dashCreate-events" style="display:none;">
|
||
<input type="hidden" name="screen_id" value="{{ screen.id }}">
|
||
<input type="text" name="name" placeholder="NOM (ex. NEED_ACTION)" required autocomplete="off">
|
||
<input type="text" name="description" placeholder="Description (optionnel)">
|
||
<button type="submit" class="primary">Créer</button>
|
||
</form>
|
||
|
||
{% if custom_events %}
|
||
{% for ev in custom_events %}
|
||
<form method="post" action="{{ url_for('custom_event_edit', slug=game.slug, event_id=ev.id) }}" id="eventEditForm{{ ev.id }}">
|
||
<input type="hidden" name="screen_id" value="{{ screen.id }}">
|
||
</form>
|
||
{% endfor %}
|
||
<div class="fieldsTableWrap">
|
||
<table class="table is-fullwidth is-hoverable fieldsTable">
|
||
<thead><tr><th>Nom</th><th>Description</th><th>Utilisé par</th><th></th></tr></thead>
|
||
<tbody>
|
||
{% for ev in custom_events %}
|
||
<tr>
|
||
<td><code>{{ ev.name }}</code></td>
|
||
<td><input type="text" form="eventEditForm{{ ev.id }}" name="description" value="{{ ev.description or '' }}" placeholder="Description" aria-label="Description de « {{ ev.name }} »"></td>
|
||
<td>
|
||
{% set usages = custom_event_usages.get(ev.id, []) %}
|
||
{% if usages %}
|
||
{% for u in usages %}
|
||
<a href="{{ url_for('screen_edit', slug=game.slug, screen_id=u.screen_id) }}" class="linkLikeButton" title="{{ 'Écouté ici' if u.role == 'ecoute' else 'Déclenché ici' }}">{{ '🔔' if u.role == 'ecoute' else '📣' }} {{ u.screen_name }}{{ ' (modèle)' if u.is_template else '' }}</a>{{ ', ' if not loop.last else '' }}
|
||
{% endfor %}
|
||
{% else %}
|
||
<span class="hint">Inutilisé</span>
|
||
{% endif %}
|
||
</td>
|
||
<td class="fieldsTableActions">
|
||
<button type="submit" form="eventEditForm{{ ev.id }}" class="button is-small" title="Enregistrer">💾</button>
|
||
<form method="post" action="{{ url_for('custom_event_delete', slug=game.slug, event_id=ev.id) }}"
|
||
onsubmit="return confirm('Supprimer l\'événement « {{ ev.name }} » ? Cela supprime aussi tous ses écouteurs et déclencheurs, sur toutes les scènes et tous les modèles.');">
|
||
<input type="hidden" name="screen_id" value="{{ screen.id }}">
|
||
<button type="submit" class="button is-small is-danger" title="Supprimer">🗑️</button>
|
||
</form>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</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>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Panneau droit : propriétés de l'objet sélectionné. -->
|
||
<button type="button" id="propsPanelReopenBtn" class="button is-small floatPanelReopenBtn" onclick="openPropsPanel()" title="Rouvrir le panneau de propriétés">⚙️ Propriétés</button>
|
||
<div class="floatPanel floatPanel--right" id="propsPanelFloat">
|
||
<div class="floatPanelHeader" data-drag-panel="propsPanelFloat" data-drag-key="props">
|
||
<span class="floatPanelTitle">⋮⋮ Propriétés</span>
|
||
<button type="button" class="button is-small" onclick="closePropsPanel()" title="Fermer">✕</button>
|
||
</div>
|
||
<div class="floatPanelBody">
|
||
{% if not selected_id or not obj %}
|
||
<div class="hint" style="padding:20px 0;">Sélectionne un objet (sur la scène ou dans la liste) pour voir et modifier ses propriétés ici.</div>
|
||
{% else %}
|
||
<div class="fieldRow" style="align-items:center; justify-content:space-between;">
|
||
<h2 class="subtitle is-6" style="margin:0;">{{ '🏃' if obj.kind == 'personnage' else '🖼️' }} {{ obj.name or ('Personnage' if obj.kind == 'personnage' else 'Décor') }}</h2>
|
||
<form method="post" action="{{ url_for('scene_object_delete', slug=game.slug, object_id=obj.id) }}" onsubmit="return confirm('Supprimer cet objet ?');">
|
||
<button type="submit" class="button is-small is-danger">🗑️</button>
|
||
</form>
|
||
</div>
|
||
|
||
<details class="propGroup" open>
|
||
<summary>Position & taille (pixels)</summary>
|
||
<div class="propGroupBody">
|
||
<div class="columns is-multiline posGrid">
|
||
<div class="column is-half">
|
||
<div class="field"><label class="label is-small">X (px)</label>
|
||
<div class="control"><input class="input is-small" type="number" id="scene-pos-x" min="0" value="{{ obj.x }}"></div>
|
||
</div>
|
||
</div>
|
||
<div class="column is-half">
|
||
<div class="field"><label class="label is-small">Y (px)</label>
|
||
<div class="control"><input class="input is-small" type="number" id="scene-pos-y" min="0" value="{{ obj.y }}"></div>
|
||
</div>
|
||
</div>
|
||
<div class="column is-half">
|
||
<div class="field"><label class="label is-small">Largeur (px)</label>
|
||
<div class="control"><input class="input is-small" type="number" id="scene-pos-w" min="1" value="{{ obj.width }}"></div>
|
||
</div>
|
||
</div>
|
||
<div class="column is-half">
|
||
<div class="field"><label class="label is-small">Hauteur (px)</label>
|
||
<div class="control"><input class="input is-small" type="number" id="scene-pos-h" min="1" value="{{ obj.height }}"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
|
||
<!-- 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. -->
|
||
<details class="propGroup">
|
||
<summary>🧱 Collision</summary>
|
||
<div class="propGroupBody" id="collisionPanelBody" data-element-id="{{ obj.id }}">
|
||
<div class="field">
|
||
<div class="control">
|
||
<label class="checkbox">
|
||
<input type="checkbox" id="collision-enabled" {{ 'checked' if collision_settings.enabled else '' }} onchange="saveSceneObjectCollision()">
|
||
Boîte de collision activée
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div class="field"><label class="label is-small">Forme</label>
|
||
<div class="control">
|
||
<div class="select is-small">
|
||
<select id="collision-shape" onchange="saveSceneObjectCollision()">
|
||
<option value="rectangle" {{ 'selected' if collision_settings.shape == 'rectangle' else '' }}>Rectangle</option>
|
||
<option value="cercle" {{ 'selected' if collision_settings.shape == 'cercle' else '' }}>Cercle</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="field is-grouped">
|
||
<div class="control"><label class="label is-small">Largeur (px)</label>
|
||
<input class="input is-small" type="number" min="1" id="collision-width" value="{{ collision_settings.width }}" onchange="saveSceneObjectCollision()"></div>
|
||
<div class="control"><label class="label is-small">Hauteur (px)</label>
|
||
<input class="input is-small" type="number" min="1" id="collision-height" value="{{ collision_settings.height }}" onchange="saveSceneObjectCollision()"></div>
|
||
</div>
|
||
<div class="field is-grouped">
|
||
<div class="control"><label class="label is-small">Décalage X (px)</label>
|
||
<input class="input is-small" type="number" id="collision-offset-x" value="{{ collision_settings.offset_x }}" onchange="saveSceneObjectCollision()"></div>
|
||
<div class="control"><label class="label is-small">Décalage Y (px)</label>
|
||
<input class="input is-small" type="number" id="collision-offset-y" value="{{ collision_settings.offset_y }}" onchange="saveSceneObjectCollision()"></div>
|
||
</div>
|
||
<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>
|
||
|
||
{% if obj.kind == 'personnage' %}
|
||
<details class="propGroup" open>
|
||
<summary>🎭 Personnage</summary>
|
||
<div class="propGroupBody" id="personnagePanelBody" data-element-id="{{ obj.id }}">
|
||
<!-- Rôle (voir screens/rendering/personnage_role.py) : seul un
|
||
personnage "Joueur" est déplacé/animé automatiquement au
|
||
clavier et suivi par la caméra — "Ennemi"/"PNJ" restent
|
||
immobiles tant qu'aucune logique de flow ne les pilote. -->
|
||
<div class="field"><label class="label is-small">🏷️ Rôle</label>
|
||
<div class="control">
|
||
<div class="select is-small">
|
||
<select id="personnage-role" onchange="saveSceneObjectRole(this.value)">
|
||
{% for value in personnage_role_choices %}
|
||
<option value="{{ value }}" {{ 'selected' if value == personnage_role else '' }}>{{ personnage_role_labels[value] }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="help">Personnage Forge — clique un autre pour en changer.</p>
|
||
<div class="iconGallery personnageGallery" id="personnageGallerySwap">
|
||
{% for slug_c, character in sprite_library_families %}
|
||
<button type="button" class="iconGalleryTile {{ 'selected' if slug_c == personnage_data.forge_character else '' }}"
|
||
title="{{ character.label }}" onclick="swapSceneObjectCharacter('{{ slug_c }}')">
|
||
<img src="{{ character.animations.idle[0] }}" alt="{{ character.label }}" class="personnageThumb">
|
||
</button>
|
||
{% endfor %}
|
||
</div>
|
||
{% set current_character = sprite_library.get(personnage_data.forge_character) %}
|
||
{% if current_character and current_character.get('family') %}
|
||
<p class="help">Variantes de {{ current_character.family_label }} :</p>
|
||
<div class="iconGallery personnageGallery" id="personnageGalleryVariants">
|
||
{% for slug_c, character in sprite_library.items() if character.get('family') == current_character.family %}
|
||
<button type="button" class="iconGalleryTile {{ 'selected' if slug_c == personnage_data.forge_character else '' }}"
|
||
title="{{ character.label }}" onclick="swapSceneObjectCharacter('{{ slug_c }}')">
|
||
<img src="{{ character.animations.idle[0] }}" alt="{{ character.label }}" class="personnageThumb">
|
||
</button>
|
||
{% endfor %}
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</details>
|
||
<!-- Déplacement/animation automatiques (voir static/js/play/
|
||
personnage-controller.js) : ce personnage se déplace et s'anime
|
||
TOUT SEUL avec ces touches dès qu'il est posé sur la scène, sans
|
||
le moindre nœud de flow à poser — ce panneau ne fait que
|
||
permettre de les remapper/bloquer un axe. -->
|
||
<details class="propGroup">
|
||
<summary>🕹️ Commandes</summary>
|
||
<div class="propGroupBody" id="personnageCommandsBody" data-element-id="{{ obj.id }}">
|
||
<p class="help">Ce personnage se déplace et s'anime automatiquement avec ces touches, sans rien à poser dans la logique. Clique un champ puis appuie sur la touche voulue.</p>
|
||
<div class="field is-grouped">
|
||
<div class="control"><label class="label is-small">Haut</label>
|
||
<input class="input is-small personnageKeyCapture" id="cmd-haut" readonly value="{{ personnage_commands.haut }}"></div>
|
||
<div class="control"><label class="label is-small">Bas</label>
|
||
<input class="input is-small personnageKeyCapture" id="cmd-bas" readonly value="{{ personnage_commands.bas }}"></div>
|
||
</div>
|
||
<div class="field is-grouped">
|
||
<div class="control"><label class="label is-small">Gauche</label>
|
||
<input class="input is-small personnageKeyCapture" id="cmd-gauche" readonly value="{{ personnage_commands.gauche }}"></div>
|
||
<div class="control"><label class="label is-small">Droite</label>
|
||
<input class="input is-small personnageKeyCapture" id="cmd-droite" readonly value="{{ personnage_commands.droite }}"></div>
|
||
</div>
|
||
<div class="field"><label class="label is-small">Interagir</label>
|
||
<div class="control"><input class="input is-small personnageKeyCapture" id="cmd-interagir" readonly value="{{ personnage_commands.interagir }}"></div>
|
||
</div>
|
||
<div class="field"><label class="label is-small">Axe de déplacement</label>
|
||
<div class="control">
|
||
<div class="select is-small">
|
||
<select id="cmd-axe">
|
||
<option value="libre" {{ 'selected' if personnage_commands.axe == 'libre' else '' }}>Libre</option>
|
||
<option value="horizontal" {{ 'selected' if personnage_commands.axe == 'horizontal' else '' }}>Horizontal seulement (gauche/droite)</option>
|
||
<option value="vertical" {{ 'selected' if personnage_commands.axe == 'vertical' else '' }}>Vertical seulement (haut/bas)</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="field"><label class="label is-small">Vitesse (px/tick)</label>
|
||
<div class="control"><input class="input is-small" type="number" id="cmd-vitesse" min="1" max="20" value="{{ personnage_commands.vitesse }}"></div>
|
||
</div>
|
||
<!-- Touches supplémentaires (sauter, attaquer...) : au-delà des 4
|
||
touches de déplacement et de "Interagir" ci-dessus, chacune
|
||
liée à UNE pose au choix du personnage (voir
|
||
ELEMENT_ANIMATIONS_MAP/PERSONNAGE_COMMANDS_EXTRA, déjà
|
||
connues de scene-editor.js) — construites en JS, jamais en
|
||
Jinja : le nombre de lignes change dynamiquement (+ Ajouter
|
||
/ ×). -->
|
||
<div class="field"><label class="label is-small">Animations supplémentaires (sauter, attaquer...)</label>
|
||
<div id="personnageCommandsExtra"></div>
|
||
<button type="button" class="button is-small" onclick="addPersonnageCommandExtraRow()">+ Ajouter une touche</button>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
{% endif %}
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
var GAME_SLUG = "{{ game.slug }}";
|
||
var SCREEN_ID = {{ screen.id }};
|
||
var SCREEN_EDIT_URL = "{{ url_for('screen_edit', slug=game.slug, screen_id=screen.id) }}";
|
||
var SCENE_WIDTH = {{ screen.scene_width }};
|
||
var SCENE_HEIGHT = {{ screen.scene_height }};
|
||
var CURRENT_SELECTED_ID = {{ selected_id or 'null' }};
|
||
var DEFINITIONS_DATA = {{ definitions_json|safe }};
|
||
var DEFINITIONS_NAMES = {{ definitions_names_json|safe }};
|
||
var FLOW_NODES_INITIAL = {{ flow_nodes_json|safe }};
|
||
var FLOW_EDGES_INITIAL = {{ flow_edges_json|safe }};
|
||
var FLOW_BLOCKS = {{ flow_blocks_json|safe }};
|
||
var FLOW_TARGETS_OBJECTS = true;
|
||
var ELEMENTS_LABELS = {{ elements_labels_json|safe }};
|
||
var SCREENS_MAP = {{ screens_map_json|safe }};
|
||
var ACTION_TYPE_LABELS_MAP = {{ action_type_labels_json|safe }};
|
||
var ELEMENT_PROPERTY_LABELS_MAP = {{ element_action_property_labels_json|safe }};
|
||
var ELEMENT_VISIBILITY_LABELS_MAP = {{ element_visibility_labels_json|safe }};
|
||
var ELEMENT_ORIENTATION_LABELS_MAP = {{ element_orientation_labels_json|safe }};
|
||
var DATA_OPERATION_LABELS_MAP = {{ data_operation_labels_json|safe }};
|
||
var SCORE_STATUS_LABELS_MAP = {{ score_status_labels_json|safe }};
|
||
var CONDITION_OPERATOR_LABELS_MAP = {{ condition_operator_labels_json|safe }};
|
||
var TRIGGER_LABELS_MAP = {};
|
||
var CUSTOM_EVENTS_MAP = {{ custom_events_map_json|safe }};
|
||
var ANIM_CLIPS = {{ animation_clips_json|safe }};
|
||
var ANIMATE_CSS_LABELS = {{ animate_css_labels_json|safe }};
|
||
var SPRITE_LIBRARY = {{ sprite_library_json|safe }};
|
||
var ELEMENT_ANIMATIONS_MAP = {{ element_animations_map_json|safe }};
|
||
var PERSONNAGE_COMMANDS_EXTRA = {{ (personnage_commands.extra if personnage_commands else [])|tojson }};
|
||
</script>
|
||
<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/personnage-preview.js') }}"></script>
|
||
<script src="{{ url_for('static', filename='js/scenes/scene-editor.js') }}"></script>
|
||
{% endblock %}
|