Enrichit les declencheurs/actions de scene (clic/survol/affichage, surbrillance/video/son/visibilite/indication/attendre) et fiabilise la pose d'un fond/decor importe
- Ajoute clic/survol/affichage-ecran comme declencheurs, et surbrillance, video, son, visibilite, indication, attendre comme actions, utilisables aussi bien par l'editeur manuel (menu lateral Objets/Ecran) que par Ruby (IA), avec blocs deplacables/supprimables dans une chaine. - Corrige plusieurs variantes du bug "impossible de poser un objet hors du champ de la camera" (troncature du chainage d'actions a 4 maillons, fond importe pose a 128x128 au lieu de sa taille reelle, decalage du fond au vrai glisser-depose, redimensionnement manuel jamais propage au monde). - Ajoute un vrai glisser-depose depuis la galerie vers la scene, la gestion complete de "Mes assets" (sous-sections Fonds/Decors/Sons/ Videos, suppression, reclassement fond<->decor sans re-upload). - Ajoute l'upload de son (limite 3 min) et de video (MP4 uniquement, limite 5 min), avec validation de la duree reelle du fichier, et une replique audio optionnelle dans une bulle de dialogue. - Fixe la taille de pose d'un objet/decor importe a 200x200 avec une boite de collision de 150x150. - Filtre le selecteur de fichier des actions son/video par type reel. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
b07b231a61
commit
559331f9cf
+143
-20
@@ -75,20 +75,117 @@
|
||||
<details class="propGroup" open>
|
||||
<summary>Mes assets</summary>
|
||||
<div class="propGroupBody">
|
||||
<div class="iconGallery personnageGallery">
|
||||
{% for asset in user_assets %}
|
||||
<button type="button" class="iconGalleryTile" title="{{ asset.original_name or 'Asset' }}"
|
||||
onclick="addSceneObject('decor', null, null, {{ asset.id }})">
|
||||
<img src="{{ url_for('serve_user_asset', user_id=asset.user_id, filename=asset.filename) }}"
|
||||
alt="{{ asset.original_name or 'Asset' }}" class="personnageThumb">
|
||||
</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<label class="button is-small is-fullwidth mt-2">
|
||||
+ Importer une image
|
||||
<input type="file" accept="image/*" style="display:none" onchange="uploadUserAsset(this)">
|
||||
</label>
|
||||
<p class="help">Réutilisable dans tous tes projets — jamais visible par les autres comptes.</p>
|
||||
<p class="help">Réutilisable dans tous tes projets — jamais visible par les autres comptes. Un son ne doit pas dépasser 3 minutes.</p>
|
||||
|
||||
<!-- Deux boutons d'import D'IMAGE distincts (demande explicite :
|
||||
"un bouton d'import séparé pour les images de fond et les
|
||||
objets") — le fond et le décor se posent différemment (voir
|
||||
screens/scenes/add_scene_object.py : un fond démarre au coin
|
||||
haut-gauche, s'empile en dessous de tout, peut devenir plus
|
||||
grand que la caméra), il faut donc savoir lequel des deux le
|
||||
créateur veut AVANT de poser l'objet, pas seulement au clic
|
||||
sur la tuile. Le choix est mémorisé (scene_kind, voir
|
||||
auth/create_user_asset.py) pour que TOUTE tuile de "Mes
|
||||
assets" pose ensuite automatiquement le bon type. -->
|
||||
<details class="propGroup" open>
|
||||
<summary>Fonds</summary>
|
||||
<div class="propGroupBody">
|
||||
<label class="button is-small is-fullwidth mb-2">
|
||||
+ Importer une image de fond
|
||||
<input type="file" accept="image/*" style="display:none" onchange="uploadUserAsset(this, 'fond')">
|
||||
</label>
|
||||
<div class="iconGallery personnageGallery">
|
||||
{% for asset in user_assets_fond %}
|
||||
<div class="userAssetTileWrap">
|
||||
<button type="button" class="iconGalleryTile" title="{{ asset.original_name or 'Asset' }}"
|
||||
draggable="true" ondragstart="onGalleryTileDragStart(event, 'fond', null, null, {{ asset.id }})"
|
||||
onclick="addSceneObject('fond', null, null, {{ asset.id }})">
|
||||
<img src="{{ asset.url }}" alt="{{ asset.original_name or 'Asset' }}" class="personnageThumb">
|
||||
</button>
|
||||
<div class="userAssetRowFooter">
|
||||
<button type="button" class="userAssetReclassifyBtn" title="Utiliser comme décor/objet"
|
||||
onclick="reclassifyUserAsset({{ asset.id }}, 'decor')">→ Décor</button>
|
||||
<button type="button" class="userAssetDeleteBtn" title="Supprimer"
|
||||
onclick="deleteUserAsset({{ asset.id }}, '{{ (asset.original_name or asset.filename)|e }}')">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if not user_assets_fond %}<p class="help">Aucune image de fond importée pour l'instant.</p>{% endif %}
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="propGroup" open>
|
||||
<summary>Décors/objets</summary>
|
||||
<div class="propGroupBody">
|
||||
<label class="button is-small is-fullwidth mb-2">
|
||||
+ Importer un décor/objet
|
||||
<input type="file" accept="image/*" style="display:none" onchange="uploadUserAsset(this, 'decor')">
|
||||
</label>
|
||||
<div class="iconGallery personnageGallery">
|
||||
{% for asset in user_assets_decor %}
|
||||
<div class="userAssetTileWrap">
|
||||
<button type="button" class="iconGalleryTile" title="{{ asset.original_name or 'Asset' }}"
|
||||
draggable="true" ondragstart="onGalleryTileDragStart(event, 'decor', null, null, {{ asset.id }})"
|
||||
onclick="addSceneObject('decor', null, null, {{ asset.id }})">
|
||||
<img src="{{ asset.url }}" alt="{{ asset.original_name or 'Asset' }}" class="personnageThumb">
|
||||
</button>
|
||||
<div class="userAssetRowFooter">
|
||||
<button type="button" class="userAssetReclassifyBtn" title="Utiliser comme fond"
|
||||
onclick="reclassifyUserAsset({{ asset.id }}, 'fond')">→ Fond</button>
|
||||
<button type="button" class="userAssetDeleteBtn" title="Supprimer"
|
||||
onclick="deleteUserAsset({{ asset.id }}, '{{ (asset.original_name or asset.filename)|e }}')">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if not user_assets_decor %}<p class="help">Aucun décor/objet importé pour l'instant.</p>{% endif %}
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="propGroup">
|
||||
<summary>Sons</summary>
|
||||
<div class="propGroupBody">
|
||||
<label class="button is-small is-fullwidth mb-2">
|
||||
+ Importer un son
|
||||
<input type="file" accept="audio/*" style="display:none" onchange="uploadUserAsset(this)">
|
||||
</label>
|
||||
{% for asset in user_assets_audio %}
|
||||
<div class="userAssetRow">
|
||||
<audio controls preload="none" src="{{ asset.url }}"></audio>
|
||||
<div class="userAssetRowFooter">
|
||||
<span class="userAssetName" title="{{ asset.original_name or asset.filename }}">{{ asset.original_name or asset.filename }}</span>
|
||||
<button type="button" class="userAssetDeleteBtn" title="Supprimer"
|
||||
onclick="deleteUserAsset({{ asset.id }}, '{{ (asset.original_name or asset.filename)|e }}')">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if not user_assets_audio %}<p class="help">Aucun son importé pour l'instant.</p>{% endif %}
|
||||
<p class="help">Utilisable dans les actions "Jouer un son" des déclencheurs.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="propGroup">
|
||||
<summary>Vidéos</summary>
|
||||
<div class="propGroupBody">
|
||||
<label class="button is-small is-fullwidth mb-2">
|
||||
+ Importer une vidéo (MP4, 5 min max)
|
||||
<input type="file" accept="video/mp4,.mp4" style="display:none" onchange="uploadUserAsset(this)">
|
||||
</label>
|
||||
{% for asset in user_assets_video %}
|
||||
<div class="userAssetRow">
|
||||
<video controls preload="none" src="{{ asset.url }}" style="max-height:120px;"></video>
|
||||
<div class="userAssetRowFooter">
|
||||
<span class="userAssetName" title="{{ asset.original_name or asset.filename }}">{{ asset.original_name or asset.filename }}</span>
|
||||
<button type="button" class="userAssetDeleteBtn" title="Supprimer"
|
||||
onclick="deleteUserAsset({{ asset.id }}, '{{ (asset.original_name or asset.filename)|e }}')">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if not user_assets_video %}<p class="help">Aucune vidéo importée pour l'instant.</p>{% endif %}
|
||||
<p class="help">Utilisable dans les actions "Jouer une vidéo" des déclencheurs.</p>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</details>
|
||||
<!-- Galerie de personnages Forge (VRAIES miniatures) — cliquer une
|
||||
@@ -100,7 +197,9 @@
|
||||
<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 }}')">
|
||||
<button type="button" class="iconGalleryTile" title="{{ character.label }}"
|
||||
draggable="true" ondragstart="onGalleryTileDragStart(event, 'personnage', '{{ slug_c }}', null, null)"
|
||||
onclick="addSceneObject('personnage', '{{ slug_c }}')">
|
||||
<img src="{{ character.animations.idle[0] }}" alt="{{ character.label }}" class="personnageThumb">
|
||||
</button>
|
||||
{% endfor %}
|
||||
@@ -119,7 +218,9 @@
|
||||
<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 }}')">
|
||||
<button type="button" class="iconGalleryTile" title="{{ background.label }}"
|
||||
draggable="true" ondragstart="onGalleryTileDragStart(event, 'fond', null, '{{ slug_b }}', null)"
|
||||
onclick="addSceneObject('fond', null, '{{ slug_b }}')">
|
||||
<img src="{{ background.url }}" alt="{{ background.label }}" class="personnageThumb">
|
||||
</button>
|
||||
{% endfor %}
|
||||
@@ -138,9 +239,15 @@
|
||||
<details class="propGroup">
|
||||
<summary>Interface</summary>
|
||||
<div class="propGroupBody">
|
||||
<button type="button" class="button is-small is-fullwidth mb-2" onclick="addSceneObject('dialogue_box')">Boîte de dialogue</button>
|
||||
<button type="button" class="button is-small is-fullwidth mb-2" onclick="addSceneObject('quiz_box')">Boîte à quiz</button>
|
||||
<button type="button" class="button is-small is-fullwidth" onclick="addSceneObject('score_widget')">Score</button>
|
||||
<button type="button" class="button is-small is-fullwidth mb-2"
|
||||
draggable="true" ondragstart="onGalleryTileDragStart(event, 'dialogue_box', null, null, null)"
|
||||
onclick="addSceneObject('dialogue_box')">Boîte de dialogue</button>
|
||||
<button type="button" class="button is-small is-fullwidth mb-2"
|
||||
draggable="true" ondragstart="onGalleryTileDragStart(event, 'quiz_box', null, null, null)"
|
||||
onclick="addSceneObject('quiz_box')">Boîte à quiz</button>
|
||||
<button type="button" class="button is-small is-fullwidth"
|
||||
draggable="true" ondragstart="onGalleryTileDragStart(event, 'score_widget', null, null, null)"
|
||||
onclick="addSceneObject('score_widget')">Score</button>
|
||||
<p class="help">Boîte de dialogue/quiz : gérées automatiquement par le moteur (voir l'onglet "Déclencheurs") — position et style réglables une fois posées. Score : affiche en continu les points gagnés.</p>
|
||||
</div>
|
||||
</details>
|
||||
@@ -167,7 +274,8 @@
|
||||
|
||||
<div class="builderTabPanel active" data-tab-panel="screen">
|
||||
<div class="canvasFrame">
|
||||
<div class="canvas" id="canvas" style="width:{{ world_width }}px; height:{{ world_height }}px; aspect-ratio:auto; margin:0 auto; position:relative; overflow:hidden; background:#1a1d24;">
|
||||
<div class="canvas" id="canvas" style="width:{{ world_width }}px; height:{{ world_height }}px; aspect-ratio:auto; margin:0 auto; position:relative; overflow:hidden; background:#1a1d24;"
|
||||
ondragover="onCanvasDragOver(event)" ondrop="onCanvasDrop(event)">
|
||||
{% if world_width > screen.scene_width or world_height > screen.scene_height %}
|
||||
<!-- "🎥 Champ de la caméra" : le monde (ce canevas, voir
|
||||
world_width/world_height ci-dessus, routes/scenes/
|
||||
@@ -647,6 +755,21 @@
|
||||
// noms des variables globales du jeu (voir l'onglet "Variables"
|
||||
// ci-dessus), pour peupler son sélecteur de variable.
|
||||
var GLOBAL_VARIABLE_NAMES = {{ global_variables | map(attribute='name') | list | tojson }};
|
||||
// Nouvelles actions "surbrillance"/"vidéo" (voir static/js/scenes/
|
||||
// collision-rules-editor.js) — même patron que les autres LABELS_MAP
|
||||
// ci-dessus.
|
||||
var SURBRILLANCE_LABELS_MAP = {{ surbrillance_labels_json|safe }};
|
||||
var VIDEO_MODE_LABELS_MAP = {{ video_mode_labels_json|safe }};
|
||||
// "Mes assets" (voir auth/user_assets_dir.py) — url déjà calculée côté
|
||||
// serveur (url_for), pour peupler le sélecteur "asset_url" de "son"/
|
||||
// "vidéo" sans redemander une URL au serveur à chaque ouverture de
|
||||
// l'assistant.
|
||||
var USER_ASSETS_OPTIONS = {{ user_assets_options_json|safe }};
|
||||
// Section "Déclencheurs de l'écran" (voir static/js/triggers/
|
||||
// trigger-editor.js::initScreenTriggersSection) — id de l'écran ouvert,
|
||||
// nécessaire pour poster sur /game/<slug>/screens/<id>/triggers.
|
||||
var CURRENT_SCREEN_ID = {{ screen.id }};
|
||||
var SCREEN_TRIGGERS_INITIAL = {{ screen_triggers|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>
|
||||
|
||||
Reference in New Issue
Block a user