Panneau scène simplifié + boîte de collision + rôle joueur/ennemi/pnj
Build and deploy / test-python (push) Successful in 11m52s
Build and deploy / test-js (push) Successful in 56s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

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>
This commit is contained in:
william
2026-09-02 15:30:34 +02:00
co-authored by Claude Sonnet 5
parent 9199897784
commit bc3feb5080
19 changed files with 602 additions and 49 deletions
+107
View File
@@ -0,0 +1,107 @@
"""Boîte de collision d'un objet de scène (voir
screens/rendering/collision_settings.py, static/js/play/conditions.js::
elementsOverlap pour son usage au jeu, testé côté client dans
static/js/play/__tests__/conditions.test.js) : posée AUTOMATIQUEMENT sur
CHAQUE objet (= sa propre boîte, tant que rien n'est réglé), activable/
désactivable, taille/forme/décalage configurables."""
import re
import screens
def _create_jeu2d_game(client, tmp_game_slug_cleanup, name="pytest_collision"):
resp = client.post("/games/new", data={"name": name}, follow_redirects=False)
slug = tmp_game_slug_cleanup(resp.headers["Location"].rstrip("/").split("/")[-1])
screen_id = screens.create_screen(slug, "Scène 1", kind="jeu_2d")
return slug, screen_id
def _add_decor(client, slug, screen_id):
resp = client.post(f"/game/{slug}/screens/{screen_id}/scene-objects/add", data={"kind": "decor"}, follow_redirects=False)
assert resp.status_code == 302, resp.data
return int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1))
def test_freshly_placed_object_defaults_to_its_own_box_as_collision(client, tmp_game_slug_cleanup):
"""Sans réglage explicite, la boîte de collision EST la boîte de
l'objet (comportement inchangé pour tout objet déjà posé — la
collision se comportait déjà comme ça, implicitement, avant l'ajout
de ce réglage)."""
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
obj_id = _add_decor(client, slug, screen_id)
obj = screens.get_scene_object(slug, obj_id)
collision = screens.resolve_collision_settings(obj)
assert collision["enabled"] is True
assert collision["shape"] == "rectangle"
assert collision["width"] == obj["width"]
assert collision["height"] == obj["height"]
assert collision["offset_x"] == 0 and collision["offset_y"] == 0
def test_collision_route_persists_a_custom_box(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
obj_id = _add_decor(client, slug, screen_id)
resp = client.post(
f"/game/{slug}/scene-objects/{obj_id}/collision",
data='{"enabled": true, "shape": "cercle", "width": 30, "height": 30, "offset_x": 5, "offset_y": -5}',
content_type="application/json",
)
assert resp.status_code == 200
assert resp.get_json()["collision"] == {
"enabled": True, "shape": "cercle", "width": 30.0, "height": 30.0, "offset_x": 5.0, "offset_y": -5.0,
}
obj = screens.get_scene_object(slug, obj_id)
assert screens.resolve_collision_settings(obj)["shape"] == "cercle"
def test_collision_route_can_disable_the_box_entirely(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
obj_id = _add_decor(client, slug, screen_id)
resp = client.post(
f"/game/{slug}/scene-objects/{obj_id}/collision",
data='{"enabled": false}', content_type="application/json",
)
assert resp.get_json()["collision"]["enabled"] is False
def test_collision_route_rejects_an_unknown_shape_and_falls_back_to_rectangle(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
obj_id = _add_decor(client, slug, screen_id)
resp = client.post(
f"/game/{slug}/scene-objects/{obj_id}/collision",
data='{"shape": "triangle"}', content_type="application/json",
)
assert resp.get_json()["collision"]["shape"] == "rectangle"
def test_collision_route_ignores_invalid_size_values(client, tmp_game_slug_cleanup):
"""Une largeur/hauteur invalide (texte, vide) ne plante pas — retombe
sur la taille de l'objet, comme si rien n'avait été réglé."""
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
obj_id = _add_decor(client, slug, screen_id)
resp = client.post(
f"/game/{slug}/scene-objects/{obj_id}/collision",
data='{"width": "pas-un-nombre", "height": ""}', content_type="application/json",
)
obj = screens.get_scene_object(slug, obj_id)
collision = resp.get_json()["collision"]
assert collision["width"] == obj["width"]
assert collision["height"] == obj["height"]
def test_collision_route_404s_for_missing_object(client, game):
resp = client.post(f"/game/{game}/scene-objects/999999/collision", data="{}", content_type="application/json")
assert resp.status_code == 404
def test_full_game_payload_exposes_collision_settings_for_every_scene_object_kind(client, tmp_game_slug_cleanup):
"""Pas seulement "personnage" — un décor/fond peut aussi servir
d'obstacle à une condition de collision."""
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
obj_id = _add_decor(client, slug, screen_id)
payload = screens.full_game_payload(slug)
scene_screen = next(s for s in payload["screens"] if s["id"] == screen_id)
obj_payload = next(o for o in scene_screen["elements"] if o["id"] == obj_id)
assert obj_payload["collision"]["enabled"] is True