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>
108 lines
5.0 KiB
Python
108 lines
5.0 KiB
Python
"""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
|