Aperçu de la boîte de collision dans l'éditeur + blocage physique au jeu
Deux retours utilisateur sur la boîte de collision (ajoutée précédemment) : - Visible et réglable directement dans l'éditeur de scène : pour l'objet sélectionné, un contour pointillé (rectangle/cercle) se superpose sur le canevas — glisser son corps ajuste le décalage, glisser sa poignée (coin bas-droit) ajuste la taille, les deux se répercutent dans le panneau "🧱 Collision" et inversement (édition des champs -> aperçu à jour). static/js/scenes/scene-editor.js::onCollisionBoxMouseDown/ onCollisionBoxResizeMouseDown, mirror des poignées de position/taille déjà existantes pour l'objet lui-même. - Bloque désormais RÉELLEMENT le déplacement au clavier : avant chaque pas, personnage-controller.js teste si la position candidate chevaucherait la boîte de collision d'un autre objet solide (jamais un "fond", jamais un objet à collision désactivée) et annule ce pas — par AXE séparément, pour permettre de glisser le long d'un mur en diagonale plutôt qu'un blocage total au moindre contact. Réutilise forgeShapesOverlap (conditions.js, factorisé depuis elementsOverlap pour ne jamais dupliquer la règle "qu'est-ce qui se touche"). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
bc3feb5080
commit
a231bcf2fa
@@ -16,6 +16,7 @@ const {
|
||||
forgeCollisionRectFromBox,
|
||||
forgeRectanglesOverlap,
|
||||
forgeCirclesOverlap,
|
||||
forgeShapesOverlap,
|
||||
} = require('../conditions.js');
|
||||
|
||||
function withGameData(data, fn) {
|
||||
@@ -127,19 +128,19 @@ test('evaluateConditionNode — clauses combinées OU (une seule vraie suffit)',
|
||||
// forme, activée/désactivée).
|
||||
test('forgeCollisionRectFromBox — sans réglage, le rectangle visuel EXACT (comportement inchangé)', () => {
|
||||
const box = { left: 10, top: 20, width: 40, height: 60 };
|
||||
assert.deepEqual(forgeCollisionRectFromBox(box, undefined), { left: 10, top: 20, right: 50, bottom: 80, width: 40, height: 60 });
|
||||
assert.deepEqual(forgeCollisionRectFromBox(box, undefined), { left: 10, top: 20, right: 50, bottom: 80, width: 40, height: 60, shape: 'rectangle' });
|
||||
});
|
||||
|
||||
test('forgeCollisionRectFromBox — une boîte plus petite est centrée par défaut (offset 0,0)', () => {
|
||||
const box = { left: 0, top: 0, width: 100, height: 100 };
|
||||
const rect = forgeCollisionRectFromBox(box, { width: 20, height: 20, offset_x: 0, offset_y: 0 });
|
||||
assert.deepEqual(rect, { left: 40, top: 40, right: 60, bottom: 60, width: 20, height: 20 });
|
||||
assert.deepEqual(rect, { left: 40, top: 40, right: 60, bottom: 60, width: 20, height: 20, shape: 'rectangle' });
|
||||
});
|
||||
|
||||
test('forgeCollisionRectFromBox — un décalage déplace la boîte de collision sans toucher au rectangle visuel', () => {
|
||||
const box = { left: 0, top: 0, width: 100, height: 100 };
|
||||
const rect = forgeCollisionRectFromBox(box, { width: 20, height: 20, offset_x: 15, offset_y: -5 });
|
||||
assert.deepEqual(rect, { left: 55, top: 35, right: 75, bottom: 55, width: 20, height: 20 });
|
||||
assert.deepEqual(rect, { left: 55, top: 35, right: 75, bottom: 55, width: 20, height: 20, shape: 'rectangle' });
|
||||
});
|
||||
|
||||
test('forgeRectanglesOverlap — chevauchement/non-chevauchement', () => {
|
||||
@@ -154,3 +155,17 @@ test('forgeCirclesOverlap — deux cercles qui se touchent/s\'évitent', () => {
|
||||
assert.equal(forgeCirclesOverlap(a, touching), true);
|
||||
assert.equal(forgeCirclesOverlap(a, apart), false);
|
||||
});
|
||||
|
||||
test('forgeShapesOverlap — deux cercles utilisent le test cercle-cercle', () => {
|
||||
const a = { left: 0, top: 0, right: 20, bottom: 20, width: 20, height: 20, shape: 'cercle' };
|
||||
const b = { left: 15, top: 0, right: 35, bottom: 20, width: 20, height: 20, shape: 'cercle' };
|
||||
const apart = { left: 100, top: 100, right: 120, bottom: 120, width: 20, height: 20, shape: 'cercle' };
|
||||
assert.equal(forgeShapesOverlap(a, b), true);
|
||||
assert.equal(forgeShapesOverlap(a, apart), false);
|
||||
});
|
||||
|
||||
test('forgeShapesOverlap — une paire rectangle/cercle retombe sur le test rectangle-rectangle', () => {
|
||||
const rect = { left: 0, top: 0, right: 20, bottom: 20, shape: 'rectangle' };
|
||||
const circle = { left: 15, top: 15, right: 35, bottom: 35, shape: 'cercle' };
|
||||
assert.equal(forgeShapesOverlap(rect, circle), true); // les rectangles englobants se chevauchent
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ Object.assign(global, actionsMod);
|
||||
|
||||
const {
|
||||
forgePersonnageAnimExists, forgePersonnageMovementAnim, forgePersonnageIdleAnim, forgePersonnageActionAnim,
|
||||
forgeRunPersonnageControllerTick, forgeUpdateSceneWorldSize, forgeUpdateSceneCamera,
|
||||
forgeRunPersonnageControllerTick, forgeUpdateSceneWorldSize, forgeUpdateSceneCamera, forgeWouldCollide,
|
||||
} = require('../personnage-controller.js');
|
||||
|
||||
function fakeSceneEl() {
|
||||
@@ -236,3 +236,69 @@ test('forgeUpdateSceneCamera — ne montre jamais au-delà des bords du monde (b
|
||||
assert.equal(worldEl.style.transform, 'translate(-3340px, 0px)');
|
||||
} finally { global.document = previousDoc; }
|
||||
});
|
||||
|
||||
// Blocage physique du déplacement en cas de collision — demandé par
|
||||
// l'utilisateur : "elle doit empêcher d'avancer en cas de collision".
|
||||
// forgeWouldCollide lit la position des AUTRES objets directement sur le
|
||||
// DOM (jamais screenData.elements, qui ne bouge jamais après le premier
|
||||
// chargement) — un test document mock dédié, avec plusieurs éléments.
|
||||
function withScene(objectsById, screenData, fn) {
|
||||
const previousDoc = global.document;
|
||||
global.document = { querySelector: sel => { const m = sel.match(/"(\d+)"/); return m ? objectsById[m[1]] : undefined; } };
|
||||
global.screensData = [{ id: 1, scene_width: 500, scene_height: 500 }];
|
||||
global.window.currentScreenId = 1;
|
||||
try { fn(); } finally { global.document = previousDoc; }
|
||||
}
|
||||
|
||||
test('forgeWouldCollide — un décor solide bloque le personnage qui l\'approche', () => {
|
||||
const player = { id: 20, kind: 'personnage' };
|
||||
const wallEl = { style: { left: '140px', top: '100px', width: '20px', height: '20px' } };
|
||||
const screenData = { elements: [player, { id: 21, kind: 'decor' }] };
|
||||
withScene({ 21: wallEl }, screenData, () => {
|
||||
// Le joueur (20x20) tente d'aller en (130,100) -> chevauche le mur (140..160, 100..120).
|
||||
assert.equal(forgeWouldCollide(screenData, player, 130, 100, 20, 20), true);
|
||||
// Assez loin : aucun chevauchement.
|
||||
assert.equal(forgeWouldCollide(screenData, player, 0, 0, 20, 20), false);
|
||||
});
|
||||
});
|
||||
|
||||
test('forgeWouldCollide — ignore un "fond" (jamais un obstacle) et un décor à collision désactivée', () => {
|
||||
const player = { id: 22, kind: 'personnage' };
|
||||
const bgEl = { style: { left: '0px', top: '0px', width: '3840px', height: '2160px' } };
|
||||
const disabledEl = { style: { left: '130px', top: '100px', width: '20px', height: '20px' } };
|
||||
const screenData = {
|
||||
elements: [
|
||||
player,
|
||||
{ id: 23, kind: 'fond' },
|
||||
{ id: 24, kind: 'decor', collision: { enabled: false } },
|
||||
],
|
||||
};
|
||||
withScene({ 23: bgEl, 24: disabledEl }, screenData, () => {
|
||||
assert.equal(forgeWouldCollide(screenData, player, 130, 100, 20, 20), false);
|
||||
});
|
||||
});
|
||||
|
||||
test('tick — un décor solide empêche d\'avancer (bloqué à son bord, jamais dans le mur)', () => {
|
||||
const player = { id: 25, kind: 'personnage', personnage_role: 'joueur', personnage_commandes: { haut: 'z', bas: 's', gauche: 'q', droite: 'd', interagir: 'e', axe: 'libre', vitesse: 4 } };
|
||||
const wall = { id: 26, kind: 'decor' };
|
||||
const playerEl = fakeSceneEl(); // left/top 100px, 20x20
|
||||
const wallEl = { style: { left: '122px', top: '100px', width: '20px', height: '20px' } };
|
||||
withPersonnageObject(player, { 25: playerEl, 26: wallEl }, () => {
|
||||
global.heldKeys = new Set(['d']); // vers la droite, vers le mur juste à côté
|
||||
forgeRunPersonnageControllerTick({ elements: [player, wall] });
|
||||
assert.equal(playerEl.style.left, '100px'); // bloqué, jamais déplacé dans le mur
|
||||
});
|
||||
});
|
||||
|
||||
test('tick — glisse le long d\'un mur en diagonale (un seul axe bloqué)', () => {
|
||||
const player = { id: 27, kind: 'personnage', personnage_role: 'joueur', personnage_commandes: { haut: 'z', bas: 's', gauche: 'q', droite: 'd', interagir: 'e', axe: 'libre', vitesse: 4 } };
|
||||
const wall = { id: 28, kind: 'decor' };
|
||||
const playerEl = fakeSceneEl(); // left/top 100px, 20x20
|
||||
const wallEl = { style: { left: '122px', top: '100px', width: '20px', height: '20px' } }; // bloque seulement à droite
|
||||
withPersonnageObject(player, { 27: playerEl, 28: wallEl }, () => {
|
||||
global.heldKeys = new Set(['d', 's']); // droite (bloquée) + bas (libre)
|
||||
forgeRunPersonnageControllerTick({ elements: [player, wall] });
|
||||
assert.equal(playerEl.style.left, '100px'); // bloqué
|
||||
assert.equal(playerEl.style.top, '104px'); // continue de descendre
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user