Fix bulle "Appuie sur X" trop haute au-dessus du personnage
Build and deploy / test-python (push) Successful in 6m7s
Build and deploy / test-js (push) Successful in 52s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

La bulle s'ancrait sur le cadre BRUT du sprite (<img> style.left/top/
width/height) — beaucoup de sprites (animaux CraftPix notamment, voir
screens/labels/animal_sprite_library.py) ont un très grand canevas
padé autour d'une silhouette bien plus petite, faisant flotter la
bulle loin au-dessus du personnage visible.

Elle s'ancre maintenant sur la BOÎTE DE COLLISION (objRect, déjà
résolue via forgeElementBoxRect — largeur/hauteur/décalage réglables
dans "🧱 Collision"), que l'auteur ajuste déjà pour épouser la
silhouette réelle : ancre bien plus fidèle, sans configuration
supplémentaire à faire.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-09-03 13:16:24 +02:00
co-authored by Claude Sonnet 5
parent 00f9cf33e9
commit aa17614c7b
2 changed files with 45 additions and 7 deletions
@@ -136,6 +136,38 @@ test('tick — la collision entre le joueur et un ennemi déclenche "attaque" un
} finally { global.document = previousDoc; } } finally { global.document = previousDoc; }
}); });
test('tick — la bulle "Appuie sur X" s\'ancre sur la BOÎTE DE COLLISION, pas le cadre brut du sprite', () => {
// Bug corrigé ("c'est trop haut") : un sprite très padé (animaux
// CraftPix...) a un cadre bien plus grand que sa silhouette visible —
// ancrer la bulle sur ce cadre brut la faisait flotter loin au-dessus
// du personnage. objRect (issu de forgeElementBoxRect, qui applique
// déjà collision.width/height/offset_x/offset_y) doit être utilisé.
const player = { id: 1, kind: 'personnage', personnage_role: 'joueur' };
const pnj = {
id: 3, kind: 'personnage',
// Sprite de 100×200 px, mais silhouette réelle 20×20 centrée en bas
// (offset_y positif = décalée vers le bas, voir collision_settings.py).
collision: { enabled: true, shape: 'rectangle', width: 20, height: 20, offset_x: 0, offset_y: 80 },
collision_rules: [{ trigger: 'collision', action: { type: 'interagir', sub_action: { type: 'quete', quete_id: 1 } } }],
};
const parent = fakeParentEl();
const playerEl = fakeEl(parent);
playerEl.style = { left: '45px', top: '175px', width: '20px', height: '20px' }; // chevauche la silhouette réelle du pnj (40,170)-(60,190)
const pnjEl = fakeEl(parent);
pnjEl.style = { left: '0px', top: '0px', width: '100px', height: '200px' }; // grand cadre padé
const previousDoc = global.document;
global.gameData = { personnage_animations: {} };
try {
global.document = fakeDocument({ 1: playerEl, 3: pnjEl });
forgeCollisionRuleTick({ elements: [player, pnj] });
const bubble = parent.querySelector('.collisionInteractBubble[data-for-object-id="3"]');
// Boîte de collision : left = (100-20)/2+0 = 40, top = (200-20)/2+80 = 170.
assert.equal(bubble.style.top, '170px'); // PAS "0px" (le haut du cadre brut)
assert.equal(bubble.style.left, '50px'); // centrée sur la boîte de collision (40 + 20/2)
} finally { global.document = previousDoc; }
});
test('tick — "interagir" affiche la bulle "Appuie sur [touche]" pendant la collision, puis la cache en sortant', () => { test('tick — "interagir" affiche la bulle "Appuie sur [touche]" pendant la collision, puis la cache en sortant', () => {
const player = { id: 1, kind: 'personnage', personnage_role: 'joueur', personnage_commandes: { interagir: 'e' } }; const player = { id: 1, kind: 'personnage', personnage_role: 'joueur', personnage_commandes: { interagir: 'e' } };
const pnj = { id: 3, kind: 'personnage', collision_rules: [{ trigger: 'collision', action: { type: 'interagir', sub_action: { type: 'quete', quete_id: 'q-2' } } }] }; const pnj = { id: 3, kind: 'personnage', collision_rules: [{ trigger: 'collision', action: { type: 'interagir', sub_action: { type: 'quete', quete_id: 'q-2' } } }] };
+13 -7
View File
@@ -93,17 +93,23 @@ function forgeCollisionInteractBubbleEl(objectId, parentEl) {
return bubble; return bubble;
} }
function forgeShowCollisionInteractBubble(objectEl, objectId, key) { // Positionnée sur la BOÎTE DE COLLISION (objRect, déjà résolue via
// forgeElementBoxRect — largeur/hauteur/décalage réglables dans "🧱
// Collision") plutôt que sur le cadre brut du <img> : beaucoup de
// sprites (notamment les animaux CraftPix, voir screens/labels/
// animal_sprite_library.py) ont un TRÈS GRAND canevas très padé autour
// d'une silhouette bien plus petite — ancrée sur le cadre brut, la
// bulle flottait loin au-dessus du personnage visible (bug corrigé :
// "c'est trop haut"). La boîte de collision, que l'auteur ajuste déjà
// pour épouser la silhouette réelle, donne une ancre bien plus fidèle.
function forgeShowCollisionInteractBubble(objectEl, objectId, objRect, key) {
var parentEl = objectEl.parentElement; var parentEl = objectEl.parentElement;
if (!parentEl) return; if (!parentEl) return;
var bubble = forgeCollisionInteractBubbleEl(objectId, parentEl); var bubble = forgeCollisionInteractBubbleEl(objectId, parentEl);
bubble.textContent = 'Appuie sur ' + key; bubble.textContent = 'Appuie sur ' + key;
bubble.style.display = 'block'; bubble.style.display = 'block';
var left = parseFloat(objectEl.style.left) || 0; bubble.style.left = (objRect.left + objRect.width / 2) + 'px'; // centrée horizontalement (voir transform:translateX(-50%), static/style.css)
var top = parseFloat(objectEl.style.top) || 0; bubble.style.top = objRect.top + 'px'; // AU-DESSUS de la boîte de collision (voir transform:translateY(-100%), static/style.css)
var width = parseFloat(objectEl.style.width) || 0;
bubble.style.left = (left + width / 2) + 'px'; // centrée horizontalement (voir transform:translateX(-50%), static/style.css)
bubble.style.top = top + 'px'; // AU-DESSUS de l'objet (voir transform:translateY(-100%), static/style.css)
} }
function forgeHideCollisionInteractBubble(objectId, objectEl) { function forgeHideCollisionInteractBubble(objectId, objectEl) {
@@ -158,7 +164,7 @@ function forgeCollisionRuleTick(screenData) {
if (rule.action.type === 'interagir') { if (rule.action.type === 'interagir') {
if (isActive) { if (isActive) {
anyInteractActive = true; anyInteractActive = true;
forgeShowCollisionInteractBubble(targetEl, obj.id, (player.personnage_commandes && player.personnage_commandes.interagir) || 'E'); forgeShowCollisionInteractBubble(targetEl, obj.id, objRect, (player.personnage_commandes && player.personnage_commandes.interagir) || 'E');
} }
forgeCollisionRuleActiveState[stateKey] = isActive; forgeCollisionRuleActiveState[stateKey] = isActive;
return; return;