Image de fond de scène + caméra qui suit le personnage, fps d'animation cohérent
Trois retours utilisateur : - fps d'idle/interagir/touches supplémentaires alignés sur celui de la marche (8 i/s partout, était 4 pour idle — perçu comme "les autres animations sont lentes à côté de la marche"). - Nouveau menu "🏞️ Images de fond" dans "Objets de cette scène" : pose un objet kind="fond" à la taille RÉELLE de l'image choisie (pack CraftPix intégré en galerie, admin seulement — licence, voir core/sprite_gate.py), derrière tout le reste, insensible au clic. - Si ce fond dépasse la scène, elle devient le "monde" : .playScreen. playScene est désormais le viewport (overflow:hidden, taille fixe), .sceneWorld le monde à l'intérieur — la caméra centre le premier personnage trouvé, bornée pour ne jamais montrer au-delà des bords (voir personnage-controller.js::forgeUpdateSceneCamera). Le personnage peut désormais se déplacer sur tout le monde, pas seulement le petit cadre visible (clampSceneObjectPosition, actions.js). Un jeu sans fond XXL garde un comportement strictement identique à avant (monde == scène, transform vide). Bibliothèque générée une fois par scripts/generate_background_manifest.py (assets/background/, non versionné, licence CraftPix) vers static/backgrounds/ (committé), même patron que generate_animal_sprite_manifest.py. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
5891c82c62
commit
9199897784
@@ -113,6 +113,25 @@ test('clampSceneObjectPosition — borne au bord gauche/haut (jamais négatif)',
|
||||
});
|
||||
});
|
||||
|
||||
// world_width/world_height (voir personnage-controller.js::
|
||||
// forgeUpdateSceneWorldSize) : demandé par l'utilisateur — un fond plus
|
||||
// grand que la scène doit laisser le personnage se déplacer sur TOUT le
|
||||
// fond, pas seulement le petit cadre visible de la scène d'origine.
|
||||
test('clampSceneObjectPosition — borne au MONDE (world_width/height) quand un fond XXL l\'a agrandi', () => {
|
||||
withScreensData({ id: 1, scene_width: 200, scene_height: 100, world_width: 2000, world_height: 1000 }, () => {
|
||||
const el = { style: { width: '40px', height: '40px' } };
|
||||
assert.equal(clampSceneObjectPosition(el, 'left', 500), 500); // pas encore au bord du MONDE
|
||||
assert.equal(clampSceneObjectPosition(el, 'left', 5000), 1960); // 2000 - 40
|
||||
});
|
||||
});
|
||||
|
||||
test('clampSceneObjectPosition — sans world_width/height (aucun fond), retombe sur scene_width/height', () => {
|
||||
withScreensData({ id: 1, scene_width: 200, scene_height: 100 }, () => {
|
||||
const el = { style: { width: '40px', height: '40px' } };
|
||||
assert.equal(clampSceneObjectPosition(el, 'left', 500), 160); // identique au tout premier test ci-dessus
|
||||
});
|
||||
});
|
||||
|
||||
test('applyObjectProperty — pos_x_relatif reste dans les limites de la scène après plusieurs pas', () => {
|
||||
withScreensData({ id: 1, scene_width: 100, scene_height: 100 }, () => {
|
||||
const el = { style: { left: '90px', top: '0px', width: '20px', height: '20px' } };
|
||||
|
||||
@@ -16,7 +16,7 @@ Object.assign(global, actionsMod);
|
||||
|
||||
const {
|
||||
forgePersonnageAnimExists, forgePersonnageMovementAnim, forgePersonnageIdleAnim, forgePersonnageActionAnim,
|
||||
forgeRunPersonnageControllerTick,
|
||||
forgeRunPersonnageControllerTick, forgeUpdateSceneWorldSize, forgeUpdateSceneCamera,
|
||||
} = require('../personnage-controller.js');
|
||||
|
||||
function fakeSceneEl() {
|
||||
@@ -33,7 +33,11 @@ function fakeSceneEl() {
|
||||
|
||||
function withPersonnageObject(obj, elByObjectId, fn) {
|
||||
const previousDoc = global.document;
|
||||
global.document = { querySelector: sel => elByObjectId[sel.match(/"(\d+)"/)[1]] };
|
||||
// forgeRunPersonnageControllerTick appelle aussi forgeUpdateSceneCamera
|
||||
// (cherche ".sceneWorld", jamais présente dans elByObjectId ici) — un
|
||||
// sélecteur qui ne matche aucun id numérique renvoie undefined plutôt
|
||||
// que de lever, comme un vrai document.querySelector sans résultat.
|
||||
global.document = { querySelector: sel => { const m = sel.match(/"(\d+)"/); return m ? elByObjectId[m[1]] : undefined; } };
|
||||
global.screensData = [{ id: 1, scene_width: 500, scene_height: 500 }];
|
||||
global.window.currentScreenId = 1;
|
||||
global.gameData = { personnage_animations: { [obj.id]: { walk: ['/w0.png'], idle: ['/i0.png'] } } };
|
||||
@@ -137,3 +141,84 @@ test('forgePersonnageActionAnim — "interagir" prioritaire, sinon la touche sup
|
||||
test('forgePersonnageActionAnim — commandes sans "extra" (personnage posé avant l\'ajout du réglage)', () => {
|
||||
assert.equal(forgePersonnageActionAnim({ interagir: 'e' }, 'j'), null);
|
||||
});
|
||||
|
||||
// Caméra qui suit le personnage derrière un fond plus grand que la scène
|
||||
// (voir screens/scenes/add_scene_object.py, kind="fond") — demandé par
|
||||
// l'utilisateur : "si l'image de fond est plus grande que la scène, la
|
||||
// caméra devra défiler en suivant le joueur".
|
||||
function fakeWorldEl() {
|
||||
return { style: {} };
|
||||
}
|
||||
|
||||
test('forgeUpdateSceneWorldSize — le monde reste la scène tant qu\'aucun fond ne dépasse (comportement inchangé)', () => {
|
||||
const screenData = { id: 1, scene_width: 500, scene_height: 500, elements: [{ kind: 'decor', x: 10, y: 10, width: 50, height: 50 }] };
|
||||
const world = forgeUpdateSceneWorldSize(screenData);
|
||||
assert.deepEqual(world, { width: 500, height: 500 });
|
||||
assert.equal(screenData.world_width, 500);
|
||||
assert.equal(screenData.world_height, 500);
|
||||
});
|
||||
|
||||
test('forgeUpdateSceneWorldSize — un fond plus grand agrandit le monde jusqu\'à ses propres bords', () => {
|
||||
const screenData = { id: 1, scene_width: 500, scene_height: 500, elements: [{ kind: 'fond', x: 0, y: 0, width: 3840, height: 2160 }] };
|
||||
const world = forgeUpdateSceneWorldSize(screenData);
|
||||
assert.deepEqual(world, { width: 3840, height: 2160 });
|
||||
});
|
||||
|
||||
test('forgeUpdateSceneCamera — sans fond XXL, transform vidé (aucun effet, comportement d\'avant)', () => {
|
||||
const worldEl = fakeWorldEl();
|
||||
const previousDoc = global.document;
|
||||
global.document = { querySelector: () => worldEl };
|
||||
const screenData = { id: 1, scene_width: 500, scene_height: 500, elements: [] };
|
||||
try {
|
||||
forgeUpdateSceneCamera(screenData);
|
||||
assert.equal(worldEl.style.width, '500px');
|
||||
assert.equal(worldEl.style.height, '500px');
|
||||
assert.equal(worldEl.style.transform, '');
|
||||
} finally { global.document = previousDoc; }
|
||||
});
|
||||
|
||||
test('forgeUpdateSceneCamera — centre le monde sur le personnage, borné aux bords du monde', () => {
|
||||
const worldEl = fakeWorldEl();
|
||||
const playerEl = { style: { left: '1900px', top: '1000px', width: '40px', height: '60px' } };
|
||||
const previousDoc = global.document;
|
||||
global.document = {
|
||||
querySelector: sel => (sel.indexOf('sceneWorld') !== -1 ? worldEl : playerEl),
|
||||
};
|
||||
const screenData = {
|
||||
id: 1, scene_width: 500, scene_height: 500,
|
||||
elements: [
|
||||
{ kind: 'fond', x: 0, y: 0, width: 3840, height: 2160 },
|
||||
{ id: 42, kind: 'personnage' },
|
||||
],
|
||||
};
|
||||
try {
|
||||
forgeUpdateSceneCamera(screenData);
|
||||
assert.equal(worldEl.style.width, '3840px');
|
||||
assert.equal(worldEl.style.height, '2160px');
|
||||
// Centre du joueur : 1900+20=1920, 1000+30=1030 -> décalage caméra
|
||||
// 1920-250=1670, 1030-250=780 (aucune des deux bornes atteinte ici).
|
||||
assert.equal(worldEl.style.transform, 'translate(-1670px, -780px)');
|
||||
} finally { global.document = previousDoc; }
|
||||
});
|
||||
|
||||
test('forgeUpdateSceneCamera — ne montre jamais au-delà des bords du monde (bornage)', () => {
|
||||
const worldEl = fakeWorldEl();
|
||||
const playerEl = { style: { left: '3820px', top: '0px', width: '20px', height: '20px' } }; // tout au bord droit
|
||||
const previousDoc = global.document;
|
||||
global.document = {
|
||||
querySelector: sel => (sel.indexOf('sceneWorld') !== -1 ? worldEl : playerEl),
|
||||
};
|
||||
const screenData = {
|
||||
id: 1, scene_width: 500, scene_height: 500,
|
||||
elements: [
|
||||
{ kind: 'fond', x: 0, y: 0, width: 3840, height: 2160 },
|
||||
{ id: 42, kind: 'personnage' },
|
||||
],
|
||||
};
|
||||
try {
|
||||
forgeUpdateSceneCamera(screenData);
|
||||
// max offsetX = 3840 - 500 = 3340 ; jamais plus, même si le joueur est
|
||||
// encore plus près du bord (pas de bande vide visible au-delà du monde).
|
||||
assert.equal(worldEl.style.transform, 'translate(-3340px, 0px)');
|
||||
} finally { global.document = previousDoc; }
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user