Compare commits

...
15 Commits
Author SHA1 Message Date
JailDesigner 6f6aa182f7 Merge feat/control-remapping — redefinició de tecles i botons del mando 2026-04-29 19:58:25 +02:00
JailDesigner b65eeb7aaa chore: bump v0.30.0 — redefinició de controls 2026-04-29 19:58:20 +02:00
JailDesigner 37fdcb4751 feat(controls): redefinició de tecles i botons del mando des del menú de servei 2026-04-29 19:54:47 +02:00
JailDesigner 4739354875 feat(maps): tileset propi per piràmide + reload de tilemap a loadRoom 2026-04-29 13:30:39 +02:00
JailDesigner 2879cbdb17 merge feat/player-death-respawn — mort + respawn (DYING + death_spinning + cinemàtica aèria) 2026-04-29 12:16:48 +02:00
JailDesigner 38070b33aa feat(player): so de hurt al morir + respawn cinemàtic amb limbo invisible 2026-04-29 12:15:52 +02:00
JailDesigner 9f0a566ffb feat(player): mort i respawn (DYING + death_spinning + lives counter) 2026-04-29 11:40:05 +02:00
JailDesigner 6397f9d4eb Merge branch 'feat/animation-flip' — flip H/V al motor i cardinals N/S/E/W 2026-04-29 10:17:46 +02:00
JailDesigner 72384c3113 feat(anim): cardinal WEST i flip totalment governat pel motor d'animació 2026-04-29 10:11:27 +02:00
JailDesigner 9dc7ff0da3 refactor(anim): direccions cardinals (north/south/east) en YAML i codi 2026-04-29 08:59:01 +02:00
JailDesigner 1d43a77800 feat(anim): suport de flip H/V per frame i per animació 2026-04-29 08:30:08 +02:00
JailDesigner 39eda56c9d docs(player): neteja comentaris dels YAMLs (sense documentar el motor) 2026-04-28 20:31:35 +02:00
JailDesigner a351f920ba Merge branch 'chore/audio-reorg' — cicle de música per estat (INTRO/PLAYING/OUTRO) i sons de làtigo/HURT 2026-04-28 19:56:32 +02:00
JailDesigner 72642dc10e feat(player): so del swing del làtigo i de l'entrada a HURT 2026-04-28 19:55:53 +02:00
JailDesigner 404db74889 feat(audio): cicle de música per estat (INTRO/PLAYING/OUTRO) amb callback de fi i spawn escalonat 2026-04-28 19:41:38 +02:00
80 changed files with 3085 additions and 1318 deletions

No files matched your search

+20
View File
@@ -74,6 +74,8 @@ set(APP_SOURCES
source/core/input/global_inputs.cpp source/core/input/global_inputs.cpp
source/core/input/input_state.cpp source/core/input/input_state.cpp
source/core/input/gamepad_manager.cpp source/core/input/gamepad_manager.cpp
source/core/input/gamepad_bindings.cpp
source/core/input/define_buttons.cpp
source/core/input/mouse.cpp source/core/input/mouse.cpp
source/core/rendering/screen.cpp source/core/rendering/screen.cpp
source/core/rendering/canvas.cpp source/core/rendering/canvas.cpp
@@ -88,6 +90,7 @@ set(APP_SOURCES
source/core/rendering/sprite/sprite.cpp source/core/rendering/sprite/sprite.cpp
source/core/rendering/sprite/moving_sprite.cpp source/core/rendering/sprite/moving_sprite.cpp
source/core/rendering/sprite/animated_sprite.cpp source/core/rendering/sprite/animated_sprite.cpp
source/core/rendering/sprite/animated_sprite_parser.cpp
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp
source/core/rendering/sdl3gpu/viewport.cpp source/core/rendering/sdl3gpu/viewport.cpp
source/core/rendering/sdl3gpu/pipelines/crtpi_pipeline.cpp source/core/rendering/sdl3gpu/pipelines/crtpi_pipeline.cpp
@@ -444,4 +447,21 @@ if(BUILD_TESTS)
add_test(NAME game_fsm add_test(NAME game_fsm
COMMAND test_game_fsm COMMAND test_game_fsm
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
# Smoke del parser d'animacions amb suport de flip H/V. Stand-alone:
# només enllaça `animated_sprite_parser.cpp`, que és la part del motor
# que NO depèn de Resource::Cache ni de Surface — el parser és pure
# YAML-in / dades-out, així que no cal arrossegar tot l'stack.
add_executable(test_animated_sprite_flip
tests/test_animated_sprite_flip.cpp
source/core/rendering/sprite/animated_sprite_parser.cpp
)
target_include_directories(test_animated_sprite_flip PRIVATE
"${CMAKE_SOURCE_DIR}/source"
)
target_link_libraries(test_animated_sprite_flip PRIVATE SDL3::SDL3)
target_compile_options(test_animated_sprite_flip PRIVATE -Wall -Wextra -Wpedantic)
add_test(NAME animated_sprite_flip
COMMAND test_animated_sprite_flip
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
endif() endif()
+16 -7
View File
@@ -23,21 +23,30 @@ flags:
collides: true collides: true
interactable: false interactable: false
# Strip de 12 frames en graella 4×3 (mateix esquema que el jugador): # Strip de 12 frames en graella 4×3 (mateix esquema que el jugador):
# 0..3 → walk_down (cara avall) # 0..3 → walk_south (cara avall)
# 4..7 → walk_up (cara amunt) # 4..7 → walk_north (cara amunt)
# 8..11 → walk (lateral; flip horitzontal segons el sentit) # 8..11 → walk_east (lateral cap a l'est)
# La variant `walk_west` reaprofita els frames de l'est amb `flip_h: true`:
# el motor d'animació aplica el flip al sprite, així el codi de la mòmia
# no decideix flip per direcció (només selecciona quina animació mostrar).
# La momia no té variants `stand_*` perquè mai no para. # La momia no té variants `stand_*` perquè mai no para.
animations: animations:
walk_down: { frames: [0, 1, 2, 3], fps: 8, loop: true } walk_south: { frames: [0, 1, 2, 3], fps: 8, loop: true }
walk_up: { frames: [4, 5, 6, 7], fps: 8, loop: true } walk_north: { frames: [4, 5, 6, 7], fps: 8, loop: true }
walk: { frames: [8, 9, 10, 11], fps: 8, loop: true } walk_east: { frames: [8, 9, 10, 11], fps: 8, loop: true }
walk_west: { frames: [8, 9, 10, 11], fps: 8, loop: true, flip_h: true }
ai: ai:
type: corridor_wanderer type: corridor_wanderer
direction_choice: random direction_choice: random
initial_direction: right initial_direction: east
stats: stats:
speed: 30 speed: 30
damage: 1 damage: 1
# Quan true, el contacte amb el jugador no aplica `hurt` ni knockback:
# mata d'un sol toc i dispara el cicle DYING → respawn. El path `hurt`
# del jugador segueix funcionant per a futurs hazards no-letals (avui
# no n'hi ha cap que el dispari).
kills_outright: true
# Estat HURT: post-fuetada, fugint en línia recta. Si la direcció # Estat HURT: post-fuetada, fugint en línia recta. Si la direcció
# preferida (la del whip) està bloquejada, prova perpendiculars; si totes # preferida (la del whip) està bloquejada, prova perpendiculars; si totes
+9
View File
@@ -71,8 +71,13 @@ menu:
move_down: "Avall" move_down: "Avall"
move_left: "Esquerra" move_left: "Esquerra"
move_right: "Dreta" move_right: "Dreta"
jump: "Saltar"
attack: "Atacar"
service: "Opcions"
gamepad_p1: "Comandament P1" gamepad_p1: "Comandament P1"
gamepad_p2: "Comandament P2" gamepad_p2: "Comandament P2"
define_buttons_p1: "Definir botons P1"
define_buttons_p2: "Definir botons P2"
zoom: "Zoom" zoom: "Zoom"
screen: "Pantalla completa" screen: "Pantalla completa"
vsync: "VSync" vsync: "VSync"
@@ -104,3 +109,7 @@ menu:
render_info_bottom: "Baix" render_info_bottom: "Baix"
gamepad_none: "Cap" gamepad_none: "Cap"
gamepad_disconnected: "Desconnectat" gamepad_disconnected: "Desconnectat"
press_key: "Polsa una tecla"
press_button: "Polsa un botó"
define_buttons_complete: "Configuració completa"
define_buttons_no_pad: "Cap mando assignat"
+14
View File
@@ -57,6 +57,7 @@ menu:
system: "System" system: "System"
labels: labels:
player_1: "Player 1" player_1: "Player 1"
player_2: "Player 2"
gamepad_unavailable: "Not available" gamepad_unavailable: "Not available"
items: items:
video_submenu: "Video" video_submenu: "Video"
@@ -69,6 +70,13 @@ menu:
move_down: "Down" move_down: "Down"
move_left: "Left" move_left: "Left"
move_right: "Right" move_right: "Right"
jump: "Jump"
attack: "Attack"
service: "Options"
gamepad_p1: "Gamepad P1"
gamepad_p2: "Gamepad P2"
define_buttons_p1: "Define buttons P1"
define_buttons_p2: "Define buttons P2"
zoom: "Zoom" zoom: "Zoom"
screen: "Fullscreen" screen: "Fullscreen"
vsync: "VSync" vsync: "VSync"
@@ -98,3 +106,9 @@ menu:
render_info_off: "Off" render_info_off: "Off"
render_info_top: "Top" render_info_top: "Top"
render_info_bottom: "Bottom" render_info_bottom: "Bottom"
gamepad_none: "None"
gamepad_disconnected: "Disconnected"
press_key: "Press a key"
press_button: "Press a button"
define_buttons_complete: "Configuration complete"
define_buttons_no_pad: "No gamepad assigned"
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_1.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 1 pyramid: 1
room: 1 room: 1
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_1.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_1.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 1 pyramid: 1
room: 2 room: 2
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_1.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_1.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 1 pyramid: 1
room: 3 room: 3
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_1.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_1.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 1 pyramid: 1
room: 4 room: 4
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_1.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_1.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 1 pyramid: 1
room: 5 room: 5
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_1.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_1.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 1 pyramid: 1
room: 6 room: 6
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_1.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_2.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 2 pyramid: 2
room: 1 room: 1
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_2.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_2.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 2 pyramid: 2
room: 2 room: 2
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_2.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_2.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 2 pyramid: 2
room: 3 room: 3
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_2.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_2.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 2 pyramid: 2
room: 4 room: 4
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_2.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_2.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 2 pyramid: 2
room: 5 room: 5
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_2.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_2.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 2 pyramid: 2
room: 6 room: 6
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_2.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_3.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 3 pyramid: 3
room: 1 room: 1
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_3.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_3.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 3 pyramid: 3
room: 2 room: 2
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_3.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_3.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 3 pyramid: 3
room: 3 room: 3
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_3.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_3.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 3 pyramid: 3
room: 4 room: 4
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_3.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_3.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 3 pyramid: 3
room: 5 room: 5
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_3.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_3.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 3 pyramid: 3
room: 6 room: 6
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_3.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_4.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 4 pyramid: 4
room: 1 room: 1
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_4.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_4.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 4 pyramid: 4
room: 2 room: 2
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_4.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_4.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 4 pyramid: 4
room: 3 room: 3
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_4.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_4.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 4 pyramid: 4
room: 4 room: 4
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_4.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_4.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 4 pyramid: 4
room: 5 room: 5
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_4.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_4.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 4 pyramid: 4
room: 6 room: 6
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_4.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_5.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 5 pyramid: 5
room: 1 room: 1
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_5.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_5.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 5 pyramid: 5
room: 2 room: 2
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_5.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_5.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 5 pyramid: 5
room: 3 room: 3
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_5.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_5.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 5 pyramid: 5
room: 4 room: 4
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_5.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_5.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 5 pyramid: 5
room: 5 room: 5
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_5.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+18 -18
View File
@@ -1,4 +1,4 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. # Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_5.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles # Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es # en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. # dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
@@ -11,7 +11,7 @@
pyramid: 5 pyramid: 5
room: 6 room: 6
tileset: data/tilesets/tomb.gif tileset: data/tilesets/pyramid_5.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
@@ -21,7 +21,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
@@ -52,21 +52,21 @@ geometry:
door_col: 10 door_col: 10
data: data:
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1] - [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1] - [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1] - [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1] - [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1] - [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1] - [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1] - [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1] - [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1] - [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
# Spawns d'entitats per a aquest nivell. Es llegeixen des de # Spawns d'entitats per a aquest nivell. Es llegeixen des de
# EntityManager::loadFromLevel al GameScene. Convencions: # EntityManager::loadFromLevel al GameScene. Convencions:
+1 -1
View File
@@ -19,7 +19,7 @@ origin: [-8, 0]
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp # estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
# per desactivar el pintat per a aquest jugador. # per desactivar el pintat per a aquest jugador.
player1_paint_tile: 0 player1_paint_tile: 0
player2_paint_tile: 4 player2_paint_tile: 1
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre # Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i # el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

+82 -123
View File
@@ -1,143 +1,97 @@
# player animation
sprite: sprite:
path: morcus.gif path: morcus.gif
size: [16, 32] size: [16, 32]
# Geometria de col·lisió del jugador (independent de l'animació): # Geometria de col·lisió. `size` és la caixa contra murs/sarcs;
# size → costat del quadrat de col·lisió (16×16) # `hit_size`, la caixa contra altres actors (més petita); `height`,
# base_size → costat del quadrat visual inferior del sprite (els "peus"); # l'alçada que un salt ha de superar per passar per damunt.
# s'usa per centrar la caixa de col·lisió sobre ell.
collision: collision:
size: 16 size: 16
base_size: 16 base_size: 16
# Caixa de contacte actor-actor (jugador↔jugador, jugador↔momia/trampa).
# Més petita que `size` perquè els contactes d'acció siguin precisos;
# `size` es reserva per a la col·lisió amb murs/sarcòfags i per enfilar
# els passadissos. Centrada en el mateix punt que la caixa base.
hit_size: 8 hit_size: 8
# Alçada del cos en píxels. Funció DOBLE segons amb què col·lisione:
# · Contra entitats (mummy, rolling_rock, sarc): si `jump_z > height`
# d'aquella entitat, no hi ha col·lisió. Igual que als YAMLs
# d'entitats.
# · Contra altres jugadors: la col·lisió és binària per estat aeri.
# Si un està en l'aire i l'altre no → cap col·lisió. Si tots dos
# estan en l'aire, llavors `body_height` decideix si els rangs
# verticals solapen i s'aplica el rebot mid-aire. `0` = sense cos
# (ni rebot quan tots dos volen).
height: 12 height: 12
# Animacions en mapa (clau = nom). Camps per animació:
# frames → llista d'índexs dins de l'spritesheet.
# frame_ms → durada de cada frame en ms. Escalar = uniforme; llista =
# per-frame (mateixa mida que `frames`).
# loop → true per defecte. `loop: false` = un sol tret (es queda al
# darrer frame i `animationIsCompleted()` passa a true).
# loop_from → rebota a aquest índex en passar del final (implica loop).
animations: animations:
stand_down: { frames: [0] } # Les variants `*_west` reaprofiten els frames de l'est i declaren
stand_up: { frames: [4] } # `flip_h: true` perquè el motor d'animació apliqui el flip al sprite —
stand: { frames: [8] } # el codi del joc no toca mai `flip_` directament.
jump_down: { frames: [12] } stand_south: { frames: [0] }
jump_up: { frames: [13] } stand_north: { frames: [4] }
jump: { frames: [14] } stand_east: { frames: [8] }
stun_down: { frames: [16] } stand_west: { frames: [8], flip_h: true }
stun_up: { frames: [17] }
stun: { frames: [18] }
walk_down: jump_south: { frames: [12] }
frames: [0, 1, 2, 3] jump_north: { frames: [13] }
frame_ms: 100 jump_east: { frames: [14] }
jump_west: { frames: [14], flip_h: true }
walk_up: stun_south: { frames: [16] }
frames: [4, 5, 6, 7] stun_north: { frames: [17] }
frame_ms: 100 stun_east: { frames: [18] }
stun_west: { frames: [18], flip_h: true }
walk: walk_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
frames: [8, 9, 10, 11] walk_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
frame_ms: 100 walk_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
walk_west: { frames: [8, 9, 10, 11], frame_ms: 100, flip_h: true }
hurt_down: hurt_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
frames: [0, 1, 2, 3] hurt_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
frame_ms: 100 hurt_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
hurt_west: { frames: [8, 9, 10, 11], frame_ms: 100, flip_h: true }
hurt_up: attack_south: { frames: [20, 21, 22, 23], frame_ms: 50 }
frames: [4, 5, 6, 7] attack_north: { frames: [24, 25, 26, 27], frame_ms: 50 }
frame_ms: 100 attack_east: { frames: [28, 29, 30, 31], frame_ms: 50 }
attack_west: { frames: [28, 29, 30, 31], frame_ms: 50, flip_h: true }
hurt: recover_south: { frames: [23] }
frames: [8, 9, 10, 11] recover_north: { frames: [27] }
frame_ms: 100 recover_east: { frames: [31] }
recover_west: { frames: [31], flip_h: true }
# Placeholder mentre no arribe l'art real (el GIF de morcus encara no té # Animació de mort: 3 voltes horàries començant pel sud (south →
# frames d'atac específics — reaprofitem `walk_*`). El bloc està declarat # west → north → east, repetit 3×). Reaprofita els frames stand_*; el
# perquè `Player::animationName` el busque sense fallback i la mecànica # west és l'east amb `flip_h: true` declarat per-frame (mapping form,
# de l'atac estiga lligada amb morcus tan bon punt s'afegisquen els # no hereta del nivell d'anim). `frame_ms` per-frame creix linealment
# frames; ara mateix visualment el sprite caminarà mentre llança el # per donar la sensació de desaccelerar fins a aturar-se. `loop: false`
# látigo (acceptat per al placeholder). # → es queda al darrer frame i marca completed.
attack_down: death_spinning:
frames: [0, 1, 2, 3] frames:
frame_ms: 50 - 0
- { index: 8, flip_h: true }
- 4
- 8
- 0
- { index: 8, flip_h: true }
- 4
- 8
- 0
- { index: 8, flip_h: true }
- 4
- 8
frame_ms: [60, 80, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280]
loop: false
attack_up: # Inèrcia de frenada en soltar la direcció. `threshold_ms` filtra
frames: [4, 5, 6, 7] # tocs curts (passets d'un tile sense rellisquera).
frame_ms: 50
attack:
frames: [8, 9, 10, 11]
frame_ms: 50
# Cooldown post-swing (recovery): igual que a sam.yaml, placeholder als
# frames de `stand_*`. Quan arriben els frames reals només cal canviar
# els índexs ací.
recover_down:
frames: [0]
recover_up:
frames: [4]
recover:
frames: [8]
# Inèrcia només de frenada. L'acceleració és sempre instantània (soltar
# el teclat + prémer una direcció = velocitat plena); en canvi, quan es
# solta la direcció el jugador decau linealment a 0 en `brake_time_ms`.
# El `threshold_ms` filtra els tocs curts: si el temps acumulat de
# moviment continu no ha arribat al llindar, la parada és instantània
# (permet "passets" d'un tile sense rellisquera). L'acumulador es
# reseteja només quan no hi ha ni input ni velocitat residual, així
# els canvis de direcció enmig del coast no rearmen el llindar.
# `brake_time_ms: 0` o absència del bloc = inèrcia desactivada.
inertia: inertia:
brake_time_ms: 150 brake_time_ms: 150
threshold_ms: 300 threshold_ms: 300
# Gir de 180°: mentre dura, el facing queda fixat a la direcció # Gir de 180°: durant `duration_ms` el facing es fixa a la direcció
# intermèdia (efecte purament visual; el moviment continua sense # intermèdia indicada (purament visual).
# alterar-se). Els girs de 90° no passen per cap intermedi.
turn: turn:
duration_ms: 80 duration_ms: 80
# Direcció intermèdia per a cada gir de 180° (sentit horari).
transitions: transitions:
west_to_east: down # passa per baix (stand_down) west_to_east: south
east_to_west: up # passa per dalt (stand_up) east_to_west: north
north_to_south: east # passa per la dreta (stand, sense flip) north_to_south: east
south_to_north: west # passa per l'esquerra (stand, amb flip) south_to_north: west
# Salt vertical (px verticals "visuals", no afecta la posició al món). # Salt vertical (px de z, no toca la posició al món).
# impulse → velocitat vertical inicial (px/s) en deixar el sòl. # `shadow_y_offset`: puja la sombra per compensar la franja
# gravity → acceleració descendent constant (px/s²). # transparent sota els peus del sprite.
# landing_stun_ms → temps en què el jugador queda paralitzat en aterrar
# (no es pot moure ni tornar a saltar).
# shadow_y_offset → desplaçament vertical de la sombra (px, positiu =
# puja). Compensa la franja transparent que els
# sprites del jugador tenen sota els peus reals
# perquè camine "pel mig del passadís" (3 px aquí).
# sound → fitxer .wav reproduït al despegue (relatiu a
# data/sounds/). Buit o absent = sense so.
# land_sound → fitxer .wav reproduït en aterrar.
# L'alçada del cos viu al bloc `collision:` (`height`), igual que la resta
# d'entitats: és una propietat de col·lisió, no de la física del salt.
# Amb impulse=240 i gravity=600: pic ≈ 48 px, temps total en l'aire ≈ 0.8 s.
jump: jump:
impulse: 200 impulse: 200
gravity: 600 gravity: 600
@@ -146,22 +100,27 @@ jump:
sound: jump.wav sound: jump.wav
land_sound: land.wav land_sound: land.wav
# Estat HURT: el jugador queda projectat sense control per un impacte # Projecció post-impacte: surt cap a la direcció amb més recorregut
# (algú que cau al damunt; en el futur també fuetada de l'altre jugador). # lliure i frena fins a 0 en `brake_time_ms`.
# `enterHurt` tria automàticament la direcció amb més recorregut lliure
# entre els 4 cardinals, posa la velocitat inicial a `speed` i la fa
# decaure linealment fins a 0 en `brake_time_ms`. Si pel camí xoca amb
# un mur, para en sec. En tornar a velocitat 0, recupera l'estat
# GROUNDED i el control. Mentre dura, no col·lisiona amb altres jugadors.
# Amb speed=120 i brake_time=500ms ⇒ recorregut ≈ 30 px (≈2 tiles).
hurt: hurt:
speed: 120 speed: 120
brake_time_ms: 500 brake_time_ms: 500
sound: hurt.wav
# Configuració del látigo. Mateixos valors que Sam de moment; # Atac amb làtigo. `range_tiles` és la longitud abans de retallar
# diferenciar quan equilibrem cada personatge. # contra murs/sarcs; `cooldown_ms` allarga ATTACKING amb una
# recuperació en què la hitbox ja no fa dany.
whip: whip:
range_tiles: 3 range_tiles: 3
swing_ms: 200 swing_ms: 200
cooldown_ms: 200 cooldown_ms: 200
damage: 1 damage: 1
sound: whip.wav
# Mort + respawn. `respawn_delay_ms` és la pausa entre la fi de
# `death_spinning` i la reaparició al spawn. `lives` és el comptador
# inicial d'intents (estil Mario): cada respawn el decrementa en 1, pot
# anar a negatius (no hi ha game over).
death:
respawn_delay_ms: 500
lives: 3
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

+82 -128
View File
@@ -1,143 +1,97 @@
# player animation
sprite: sprite:
path: sam.gif path: sam.gif
size: [16, 32] size: [16, 32]
# Geometria de col·lisió del jugador (independent de l'animació): # Geometria de col·lisió. `size` és la caixa contra murs/sarcs;
# size → costat del quadrat de col·lisió (16×16) # `hit_size`, la caixa contra altres actors (més petita); `height`,
# base_size → costat del quadrat visual inferior del sprite (els "peus"); # l'alçada que un salt ha de superar per passar per damunt.
# s'usa per centrar la caixa de col·lisió sobre ell.
collision: collision:
size: 16 size: 16
base_size: 16 base_size: 16
# Caixa de contacte actor-actor (jugador↔jugador, jugador↔momia/trampa).
# Més petita que `size` perquè els contactes d'acció siguin precisos;
# `size` es reserva per a la col·lisió amb murs/sarcòfags i per enfilar
# els passadissos. Centrada en el mateix punt que la caixa base.
hit_size: 8 hit_size: 8
# Alçada del cos en píxels. Funció DOBLE segons amb què col·lisione:
# · Contra entitats (mummy, rolling_rock, sarc): si `jump_z > height`
# d'aquella entitat, no hi ha col·lisió. Igual que als YAMLs
# d'entitats.
# · Contra altres jugadors: la col·lisió és binària per estat aeri.
# Si un està en l'aire i l'altre no → cap col·lisió. Si tots dos
# estan en l'aire, llavors `body_height` decideix si els rangs
# verticals solapen i s'aplica el rebot mid-aire. `0` = sense cos
# (ni rebot quan tots dos volen).
height: 12 height: 12
# Animacions en mapa (clau = nom). Camps per animació:
# frames → llista d'índexs dins de l'spritesheet.
# frame_ms → durada de cada frame en ms. Escalar = uniforme; llista =
# per-frame (mateixa mida que `frames`).
# loop → true per defecte. `loop: false` = un sol tret (es queda al
# darrer frame i `animationIsCompleted()` passa a true).
# loop_from → rebota a aquest índex en passar del final (implica loop).
animations: animations:
stand_down: { frames: [1] } # Les variants `*_west` reaprofiten els frames de l'est i declaren
stand_up: { frames: [5] } # `flip_h: true` perquè el motor d'animació apliqui el flip al sprite —
stand: { frames: [9] } # el codi del joc no toca mai `flip_` directament.
jump_down: { frames: [12] } stand_south: { frames: [0] }
jump_up: { frames: [13] } stand_north: { frames: [4] }
jump: { frames: [14] } stand_east: { frames: [8] }
stun_down: { frames: [16] } stand_west: { frames: [8], flip_h: true }
stun_up: { frames: [17] }
stun: { frames: [18] }
walk_down: jump_south: { frames: [12] }
frames: [0, 1, 2, 3] jump_north: { frames: [13] }
frame_ms: 100 jump_east: { frames: [14] }
jump_west: { frames: [14], flip_h: true }
walk_up: stun_south: { frames: [16] }
frames: [4, 5, 6, 7] stun_north: { frames: [17] }
frame_ms: 100 stun_east: { frames: [18] }
stun_west: { frames: [18], flip_h: true }
walk: walk_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
frames: [8, 9, 10, 11] walk_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
frame_ms: 100 walk_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
walk_west: { frames: [8, 9, 10, 11], frame_ms: 100, flip_h: true }
hurt_down: hurt_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
frames: [0, 1, 2, 3] hurt_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
frame_ms: 100 hurt_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
hurt_west: { frames: [8, 9, 10, 11], frame_ms: 100, flip_h: true }
hurt_up: attack_south: { frames: [20, 21, 22, 23], frame_ms: 50 }
frames: [4, 5, 6, 7] attack_north: { frames: [24, 25, 26, 27], frame_ms: 50 }
frame_ms: 100 attack_east: { frames: [28, 29, 30, 31], frame_ms: 50 }
attack_west: { frames: [28, 29, 30, 31], frame_ms: 50, flip_h: true }
hurt: recover_south: { frames: [23] }
frames: [8, 9, 10, 11] recover_north: { frames: [27] }
frame_ms: 100 recover_east: { frames: [31] }
recover_west: { frames: [31], flip_h: true }
# Animacions del braç llançant el látigo. Placeholder: reaprofiten els # Animació de mort: 3 voltes horàries començant pel sud (south →
# frames de `walk_*` perquè els sprites reals encara no existeixen # west → north → east, repetit 3×). Reaprofita els frames stand_*; el
# (TODO: dibuixar-ne d'específics quan arribe l'art). El sprite del # west és l'east amb `flip_h: true` declarat per-frame (mapping form,
# látigo viu al `WhipAttack` i es renderitza superposat per z-order. # no hereta del nivell d'anim). `frame_ms` per-frame creix linealment
attack_down: # per donar la sensació de desaccelerar fins a aturar-se. `loop: false`
frames: [28, 29, 30, 31] # → es queda al darrer frame i marca completed.
frame_ms: 50 death_spinning:
frames:
- 0
- { index: 8, flip_h: true }
- 4
- 8
- 0
- { index: 8, flip_h: true }
- 4
- 8
- 0
- { index: 8, flip_h: true }
- 4
- 8
frame_ms: [60, 80, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280]
loop: false
attack_up: # Inèrcia de frenada en soltar la direcció. `threshold_ms` filtra
frames: [24, 25, 26, 27] # tocs curts (passets d'un tile sense rellisquera).
frame_ms: 50
attack:
frames: [20, 21, 22, 23]
frame_ms: 50
# Animacions de cooldown post-swing (recovery): mentre dura
# `whip_cooldown_ms` el jugador continua bloquejat però l'animació ja
# no és la d'atac. Placeholder: reaprofiten els frames de `stand_*`
# (la silueta no canvia respecte d'ells); quan arriben els frames
# reals només cal canviar els índexs ací, sense tocar el codi.
recover_down:
frames: [1]
recover_up:
frames: [5]
recover:
frames: [9]
# Inèrcia només de frenada. L'acceleració és sempre instantània (soltar
# el teclat + prémer una direcció = velocitat plena); en canvi, quan es
# solta la direcció el jugador decau linealment a 0 en `brake_time_ms`.
# El `threshold_ms` filtra els tocs curts: si el temps acumulat de
# moviment continu no ha arribat al llindar, la parada és instantània
# (permet "passets" d'un tile sense rellisquera). L'acumulador es
# reseteja només quan no hi ha ni input ni velocitat residual, així
# els canvis de direcció enmig del coast no rearmen el llindar.
# `brake_time_ms: 0` o absència del bloc = inèrcia desactivada.
inertia: inertia:
brake_time_ms: 150 brake_time_ms: 150
threshold_ms: 300 threshold_ms: 300
# Gir de 180°: mentre dura, el facing queda fixat a la direcció # Gir de 180°: durant `duration_ms` el facing es fixa a la direcció
# intermèdia (efecte purament visual; el moviment continua sense # intermèdia indicada (purament visual).
# alterar-se). Els girs de 90° no passen per cap intermedi.
turn: turn:
duration_ms: 80 duration_ms: 80
# Direcció intermèdia per a cada gir de 180° (sentit horari).
transitions: transitions:
west_to_east: down # passa per baix (stand_down) west_to_east: south
east_to_west: up # passa per dalt (stand_up) east_to_west: north
north_to_south: east # passa per la dreta (stand, sense flip) north_to_south: east
south_to_north: west # passa per l'esquerra (stand, amb flip) south_to_north: west
# Salt vertical (px verticals "visuals", no afecta la posició al món). # Salt vertical (px de z, no toca la posició al món).
# impulse → velocitat vertical inicial (px/s) en deixar el sòl. # `shadow_y_offset`: puja la sombra per compensar la franja
# gravity → acceleració descendent constant (px/s²). # transparent sota els peus del sprite.
# landing_stun_ms → temps en què el jugador queda paralitzat en aterrar
# (no es pot moure ni tornar a saltar).
# shadow_y_offset → desplaçament vertical de la sombra (px, positiu =
# puja). Compensa la franja transparent que els
# sprites del jugador tenen sota els peus reals
# perquè camine "pel mig del passadís" (3 px aquí).
# sound → fitxer .wav reproduït al despegue (relatiu a
# data/sounds/). Buit o absent = sense so.
# land_sound → fitxer .wav reproduït en aterrar.
# L'alçada del cos viu al bloc `collision:` (`height`), igual que la resta
# d'entitats: és una propietat de col·lisió, no de la física del salt.
# Amb impulse=240 i gravity=600: pic ≈ 48 px, temps total en l'aire ≈ 0.8 s.
jump: jump:
impulse: 200 impulse: 200
gravity: 600 gravity: 600
@@ -146,27 +100,27 @@ jump:
sound: jump.wav sound: jump.wav
land_sound: land.wav land_sound: land.wav
# Estat HURT: el jugador queda projectat sense control per un impacte # Projecció post-impacte: surt cap a la direcció amb més recorregut
# (algú que cau al damunt; en el futur també fuetada de l'altre jugador). # lliure i frena fins a 0 en `brake_time_ms`.
# `enterHurt` tria automàticament la direcció amb més recorregut lliure
# entre els 4 cardinals, posa la velocitat inicial a `speed` i la fa
# decaure linealment fins a 0 en `brake_time_ms`. Si pel camí xoca amb
# un mur, para en sec. En tornar a velocitat 0, recupera l'estat
# GROUNDED i el control. Mentre dura, no col·lisiona amb altres jugadors.
# Amb speed=120 i brake_time=500ms ⇒ recorregut ≈ 30 px (≈2 tiles).
hurt: hurt:
speed: 120 speed: 120
brake_time_ms: 500 brake_time_ms: 500
sound: hurt.wav
# Configuració del látigo. `range_tiles` és la longitud de la hitbox # Atac amb làtigo. `range_tiles` és la longitud abans de retallar
# abans de retallar contra murs/sarcs (3 tiles ≈ 48 px). `swing_ms` és # contra murs/sarcs; `cooldown_ms` allarga ATTACKING amb una
# la durada de l'animació de l'atac, durant la qual la hitbox del whip # recuperació en què la hitbox ja no fa dany.
# està viva. `cooldown_ms` allarga ATTACKING amb una recuperació en què
# el jugador segueix bloquejat però la hitbox ja ha desaparegut: castiga
# l'ús abusiu del látigo. `damage` queda llegit per a futur (els
# objectius reben `enterHurt` directament; cap entitat té HP encara).
whip: whip:
range_tiles: 3 range_tiles: 3
swing_ms: 200 swing_ms: 200
cooldown_ms: 200 cooldown_ms: 200
damage: 1 damage: 1
sound: whip.wav
# Mort + respawn. `respawn_delay_ms` és la pausa entre la fi de
# `death_spinning` i la reaparició al spawn. `lives` és el comptador
# inicial d'intents (estil Mario): cada respawn el decrementa en 1, pot
# anar a negatius (no hi ha game over).
death:
respawn_delay_ms: 500
lives: 3
+6 -7
View File
@@ -11,18 +11,17 @@ room_intro:
text_hold_ms: 2000 # text quiet al centre text_hold_ms: 2000 # text quiet al centre
text_exit_ms: 400 # text centre → fora per l'esquerra text_exit_ms: 400 # text centre → fora per l'esquerra
line_gap_px: -4 # px extra entre les dues línies (piràmide / sala) line_gap_px: -4 # px extra entre les dues línies (piràmide / sala)
player_spawn_delay_ms: 2400 # ms des de l'inici d'INTRO al spawn aeri. # Delays d'spawn aeri **mesurats des de la fi de la música d'INTRO**
player2_spawn_delay_ms: 2700 # només mode BOTH. Trenca la simultaneïtat. # (callback `Audio::setOnMusicEnded`). 0 = cau al moment exacte que
# `room_intro.ogg` acaba; valors > 0 escalonen els jugadors. En mode
# single-player s'usa només `player_spawn_delay_ms`.
player_spawn_delay_ms: 0
player2_spawn_delay_ms: 300
# Preset de reverb (catàleg a `data/sounds.yaml`) aplicat al primer # Preset de reverb (catàleg a `data/sounds.yaml`) aplicat al primer
# LAND de cada jugador durant la cinemàtica d'entrada. La sala dona # LAND de cada jugador durant la cinemàtica d'entrada. La sala dona
# "més detall" sonor a l'INTRO; els salts del joc i els respawns # "més detall" sonor a l'INTRO; els salts del joc i els respawns
# post-mort cauen al so sec. Buit o absent = sense reverb. # post-mort cauen al so sec. Buit o absent = sense reverb.
land_reverb: stone_hall land_reverb: stone_hall
# So puntual disparat quan apareix el text d'INTRO (phase 2,
# text_enter). Es resol contra `data/sounds/<text_sound>`. Buit o
# absent = sense so. Es preloda al ctor de GameScene perquè el
# primer dispar no faça disc.
text_sound: intro_room.wav
room_outro: room_outro:
text_enter_ms: 400 text_enter_ms: 400
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

+238
View File
@@ -0,0 +1,238 @@
#include "core/input/define_buttons.hpp"
#include <algorithm>
#include <utility>
#include "core/locale/locale.hpp"
#include "core/rendering/screen.hpp"
#include "core/rendering/text.hpp"
#include "core/ui/ui_config.hpp"
#include "utils/defines.hpp"
namespace {
// Threshold per als triggers L2/R2 — equivalent a CC (16384 ≈ 50%).
constexpr Sint16 TRIGGER_THRESHOLD = 16384;
// Geometria de l'overlay (canvas lògic 320×240).
constexpr int BOX_WIDTH = 240;
constexpr int PAD_Y = 8;
constexpr int LINE_GAP = 4;
constexpr Uint8 BG_ALPHA = 230;
} // namespace
void DefineButtons::init(std::shared_ptr<Text> title_font,
std::shared_ptr<Text> items_font,
const Palette& ui_palette) {
title_font_ = std::move(title_font);
items_font_ = std::move(items_font);
ui_palette_ = ui_palette;
}
auto DefineButtons::enable(SDL_Gamepad* pad, std::string name, std::string path) -> bool {
if (pad == nullptr) { return false; }
pad_ = pad;
pad_name_ = std::move(name);
pad_path_ = std::move(path);
enabled_ = true;
finished_ = false;
closing_ = false;
message_shown_ = false;
message_timer_ms_ = 0;
l2_was_pressed_ = false;
r2_was_pressed_ = false;
resetSequence();
return true;
}
void DefineButtons::disable() {
enabled_ = false;
finished_ = false;
closing_ = false;
message_shown_ = false;
message_timer_ms_ = 0;
l2_was_pressed_ = false;
r2_was_pressed_ = false;
pad_ = nullptr;
pad_name_.clear();
pad_path_.clear();
sequence_.clear();
index_ = 0;
}
void DefineButtons::resetSequence() {
// Ordre: ATTACK, JUMP, SERVICE — replica l'estructura de
// `clearButtons()` de coffee_crisis_arcade_edition (seqüència ordenada
// d'accions amb la SERVICE al final) adaptada al joc actual. La label
// de SERVICE va a una clau pròpia (`menu.items.service`) per no
// reutilitzar la del submenú dels mandos.
sequence_.clear();
sequence_.push_back({"menu.items.attack", GamepadBindings::Action::ATTACK, static_cast<int>(SDL_GAMEPAD_BUTTON_INVALID)});
sequence_.push_back({"menu.items.jump", GamepadBindings::Action::JUMP, static_cast<int>(SDL_GAMEPAD_BUTTON_INVALID)});
sequence_.push_back({"menu.items.service", GamepadBindings::Action::SERVICE, static_cast<int>(SDL_GAMEPAD_BUTTON_INVALID)});
index_ = 0;
}
auto DefineButtons::isInUse(int button) const -> bool {
return std::ranges::any_of(sequence_, [button](const ButtonAssignment& a) {
return a.button == button;
});
}
auto DefineButtons::isReadyToClose() const -> bool {
// Llest per tancar quan: la seqüència ha acabat, ja s'ha mostrat el
// missatge, està en mode tancament, i el timer ha expirat. Replica el
// patró de `coffee_crisis_arcade_edition::DefineButtons::isReadyToClose`,
// simplificat perquè ací no tenim animació de WindowMessage.
return finished_ && message_shown_ && closing_ && message_timer_ms_ <= 0;
}
void DefineButtons::update(int delta_ms) {
if (!enabled_) { return; }
if (finished_ && message_shown_) {
message_timer_ms_ -= delta_ms;
if (message_timer_ms_ <= 0 && !closing_) {
closing_ = true;
message_timer_ms_ = 0; // anchor a 0 per a `isReadyToClose`.
}
}
}
void DefineButtons::handleEvent(const SDL_Event& ev) {
if (!enabled_) { return; }
switch (ev.type) {
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
handleButtonDown(ev.gbutton);
break;
case SDL_EVENT_GAMEPAD_BUTTON_UP:
onButtonUp();
break;
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
handleAxisMotion(ev.gaxis);
break;
default:
break;
}
}
void DefineButtons::assignAndAdvance(int button) {
if (finished_ || isInUse(button)) { return; }
sequence_.at(index_).button = button;
if (index_ + 1 < sequence_.size()) {
++index_;
} else {
finished_ = true;
}
}
void DefineButtons::processTrigger(int virtual_button, bool& was_pressed, bool now) {
if (now && !was_pressed) { assignAndAdvance(virtual_button); }
if (!now && was_pressed) { onButtonUp(); }
was_pressed = now;
}
void DefineButtons::handleButtonDown(const SDL_GamepadButtonEvent& ev) {
// Filtra: només acceptem events del mando que estem configurant.
// (Per l'instance_id no podem comparar directament; obtenim el handle
// i el comparem.)
const SDL_Gamepad* source = SDL_GetGamepadFromID(ev.which);
if (source != pad_) { return; }
assignAndAdvance(static_cast<int>(ev.button));
}
void DefineButtons::handleAxisMotion(const SDL_GamepadAxisEvent& ev) {
const SDL_Gamepad* source = SDL_GetGamepadFromID(ev.which);
if (source != pad_) { return; }
// Triggers L2/R2 com a "botons" amb edge-detect (transició inactiu →
// actiu). Replica `doControllerAxisMotion` de
// coffee_crisis_arcade_edition.
const bool NOW = ev.value > TRIGGER_THRESHOLD;
if (ev.axis == SDL_GAMEPAD_AXIS_LEFT_TRIGGER) {
processTrigger(GamepadBindings::TRIGGER_L2_AS_BUTTON, l2_was_pressed_, NOW);
} else if (ev.axis == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {
processTrigger(GamepadBindings::TRIGGER_R2_AS_BUTTON, r2_was_pressed_, NOW);
}
}
void DefineButtons::onButtonUp() {
if (!finished_ || message_shown_) { return; }
persistAndShowComplete();
}
void DefineButtons::persistAndShowComplete() {
std::unordered_map<GamepadBindings::Action, int> mapping;
for (const auto& a : sequence_) {
mapping[a.action] = a.button;
}
GamepadBindings::get().setBindings(pad_name_, pad_path_, mapping);
message_shown_ = true;
message_timer_ms_ = MESSAGE_DISPLAY_DURATION_MS;
}
void DefineButtons::render() const {
if (!enabled_ || !title_font_ || !items_font_) { return; }
const int TITLE_H = title_font_->getLineHeight();
const int ITEM_H = items_font_->getLineHeight();
const int TITLE_KERNING = title_font_->getDefaultKerning();
const int ITEM_KERNING = items_font_->getDefaultKerning();
// Calcular l'alçada total: títol + 2 línies (instrucció + label).
int box_h = (PAD_Y * 2) + TITLE_H + LINE_GAP + (ITEM_H * 2) + LINE_GAP;
int box_x = (GameCanvas::WIDTH - BOX_WIDTH) / 2;
int box_y = (GameCanvas::HEIGHT - box_h) / 2;
const auto BG_ARGB = ui_palette_[UiConfig::get().menuBg()];
const auto BORDER_ARGB = ui_palette_[UiConfig::get().menuBorder()];
Screen::get().blendRect(box_x, box_y, BOX_WIDTH, box_h, BG_ARGB, BG_ALPHA);
Screen::get().fillRect(box_x, box_y, BOX_WIDTH, 1, BORDER_ARGB);
Screen::get().fillRect(box_x, box_y + box_h - 1, BOX_WIDTH, 1, BORDER_ARGB);
Screen::get().fillRect(box_x, box_y, 1, box_h, BORDER_ARGB);
Screen::get().fillRect(box_x + BOX_WIDTH - 1, box_y, 1, box_h, BORDER_ARGB);
int cursor_y = box_y + PAD_Y;
const int CENTER_X = box_x + (BOX_WIDTH / 2);
// Títol: nom del mando.
const std::string TITLE = pad_name_;
title_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
CENTER_X,
cursor_y,
TITLE,
TITLE_KERNING,
UiConfig::get().menuTitle());
cursor_y += TITLE_H + LINE_GAP;
if (message_shown_) {
// Pantalla final: missatge de configuració completa.
items_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
CENTER_X,
cursor_y,
Locale::get().text("menu.values.define_buttons_complete"),
ITEM_KERNING,
UiConfig::get().menuValue());
return;
}
// Instrucció + acció pendent. Replica visualment el WindowMessage de
// coffee_crisis_arcade_edition.
items_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
CENTER_X,
cursor_y,
Locale::get().text("menu.values.press_button"),
ITEM_KERNING,
UiConfig::get().menuLabel());
cursor_y += ITEM_H + LINE_GAP;
if (index_ < sequence_.size()) {
const auto& current = sequence_[index_];
items_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
CENTER_X,
cursor_y,
Locale::get().text(current.label_key),
ITEM_KERNING,
UiConfig::get().menuCursor());
}
}
+126
View File
@@ -0,0 +1,126 @@
#pragma once
#include <SDL3/SDL.h>
#include <cstddef>
#include <memory>
#include <string>
#include <vector>
#include "core/input/gamepad_bindings.hpp"
#include "core/rendering/surface.hpp" // Palette
class Text;
// Sub-mòdul de captura seqüencial de botons per a un mando concret.
// Replica `DefineButtons` de coffee_crisis_arcade_edition: l'usuari obre el
// menú de servei, escull "Definir botons P1" / "P2", i el sub-mòdul li
// demana un per un els botons per a cada acció (JUMP → ATTACK → SERVICE).
//
// Cicle de vida:
// 1. `enable(pad, name, path)`: arrenca la seqüència. `index_` = 0.
// 2. Cada SDL_EVENT_GAMEPAD_BUTTON_DOWN (o transició de L2/R2 via
// AXIS_MOTION) que NO duplique cap botó ja assignat dins la sessió
// avança `index_`. Si `index_` arriba a la mida de la seqüència,
// `finished_` es posa a true i, al pròxim BUTTON_UP, escrivim el mapeig
// complet a `GamepadBindings`, mostrem "Configuració completa" durant
// `MESSAGE_DISPLAY_DURATION_MS`, i posem `closing_`. Quan tot l'overlay
// ja no és visible, `isReadyToClose()` torna `true` i el caller ens
// fa `disable()`.
//
// El layer runtime de l'aplicació viu a `GamepadBindings`: aquesta classe
// només decideix què s'escriu i quan.
class DefineButtons {
public:
DefineButtons() = default;
~DefineButtons() = default;
DefineButtons(const DefineButtons&) = delete;
DefineButtons(DefineButtons&&) = delete;
auto operator=(const DefineButtons&) -> DefineButtons& = delete;
auto operator=(DefineButtons&&) -> DefineButtons& = delete;
// Injecta les fonts i la paleta UI; cridat des de `ServiceMenu::init`
// perquè comparteixin tipografia i colors de tema.
void init(std::shared_ptr<Text> title_font,
std::shared_ptr<Text> items_font,
const Palette& ui_palette);
// Arranca la sessió per al mando indicat. `pad` ha de ser un handle
// viu obtingut de `GamepadManager`. Retorna false si `pad` és nullptr.
auto enable(SDL_Gamepad* pad, std::string name, std::string path) -> bool;
// Atura la sessió (sense persistir) i neteja l'estat.
void disable();
[[nodiscard]] auto isEnabled() const -> bool { return enabled_; }
[[nodiscard]] auto isReadyToClose() const -> bool;
// Avança el timer de tancament (ms) — replica el `update` de
// coffee_crisis_arcade_edition. No fa res si no està actiu.
void update(int delta_ms);
// Pinta l'overlay sobre el canvas actual (centrat). No fa res si no
// està actiu.
void render() const;
// Punt d'entrada d'events: BUTTON_DOWN / BUTTON_UP / AXIS_MOTION del
// mando objectiu. La resta s'ignoren.
void handleEvent(const SDL_Event& ev);
private:
static constexpr int MESSAGE_DISPLAY_DURATION_MS = 2000;
struct ButtonAssignment {
std::string label_key;
GamepadBindings::Action action;
int button; // SDL_GamepadButton o trigger virtual; INVALID = sense assignar.
};
// Reinicialitza la seqüència ordenada (JUMP → ATTACK → SERVICE) amb
// tots els botons sense assignar. Cridat a `enable`.
void resetSequence();
// Comprova si un botó ja ha estat assignat dins de la sessió actual.
// Replica `checkButtonNotInUse` / `checkTriggerNotInUse`.
[[nodiscard]] auto isInUse(int button) const -> bool;
void handleButtonDown(const SDL_GamepadButtonEvent& ev);
void handleAxisMotion(const SDL_GamepadAxisEvent& ev);
// Helper de la lògica comuna a button-down i als triggers L2/R2:
// assigna `button` a l'acció pendent i avança l'índex (o marca
// finished_ si era l'última). No fa res si `button` ja està en ús.
void assignAndAdvance(int button);
// Tracta una transició de trigger (L2 o R2) com a botó virtual amb
// edge-detect. `virtual_button` és `TRIGGER_L?_AS_BUTTON`,
// `was_pressed` és l'estat anterior (mutat per referència).
void processTrigger(int virtual_button, bool& was_pressed, bool now);
// Crida `onSequenceEnd()` quan l'usuari deixa anar el botó després
// d'haver completat la seqüència — replica la branca de
// `SDL_EVENT_GAMEPAD_BUTTON_UP` a `DefineButtons::handleEvents`.
void onButtonUp();
// Persistix les assignacions a `GamepadBindings` i posa `message_shown_`.
void persistAndShowComplete();
SDL_Gamepad* pad_{nullptr};
std::string pad_name_;
std::string pad_path_;
std::shared_ptr<Text> title_font_;
std::shared_ptr<Text> items_font_;
Palette ui_palette_{};
std::vector<ButtonAssignment> sequence_;
std::size_t index_{0};
int message_timer_ms_{0};
bool enabled_{false};
bool finished_{false};
bool closing_{false};
bool message_shown_{false};
bool l2_was_pressed_{false};
bool r2_was_pressed_{false};
};
+220
View File
@@ -0,0 +1,220 @@
#include "core/input/gamepad_bindings.hpp"
#include <algorithm>
#include <array>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string_view>
#include <utility>
#include "external/fkyaml_node.hpp"
namespace {
// Threshold per a considerar L2/R2 com a premuts (mateix valor que
// coffee_crisis_arcade_edition: 16384 ≈ 50% de ±32767).
constexpr Sint16 TRIGGER_THRESHOLD = 16384;
// Taules de mapejat string ↔ valor: replica BUTTON_TO_STRING /
// STRING_TO_BUTTON / ACTION_TO_STRING / STRING_TO_ACTION del projecte
// de referència. Els noms en lowercase per llegibilitat al YAML.
using ActionPair = std::pair<GamepadBindings::Action, std::string_view>;
constexpr std::array ACTION_NAMES = {
ActionPair{GamepadBindings::Action::JUMP, std::string_view{"jump"}},
ActionPair{GamepadBindings::Action::ATTACK, std::string_view{"attack"}},
ActionPair{GamepadBindings::Action::SERVICE, std::string_view{"service"}},
};
using ButtonPair = std::pair<int, std::string_view>;
constexpr std::array BUTTON_NAMES = {
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_SOUTH), std::string_view{"south"}},
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_EAST), std::string_view{"east"}},
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_WEST), std::string_view{"west"}},
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_NORTH), std::string_view{"north"}},
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_BACK), std::string_view{"back"}},
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_GUIDE), std::string_view{"guide"}},
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_START), std::string_view{"start"}},
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_LEFT_STICK), std::string_view{"left_stick"}},
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_RIGHT_STICK), std::string_view{"right_stick"}},
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), std::string_view{"l1"}},
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), std::string_view{"r1"}},
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_DPAD_UP), std::string_view{"dpad_up"}},
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_DPAD_DOWN), std::string_view{"dpad_down"}},
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_DPAD_LEFT), std::string_view{"dpad_left"}},
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), std::string_view{"dpad_right"}},
ButtonPair{GamepadBindings::TRIGGER_L2_AS_BUTTON, std::string_view{"l2"}},
ButtonPair{GamepadBindings::TRIGGER_R2_AS_BUTTON, std::string_view{"r2"}},
};
auto actionToString(GamepadBindings::Action a) -> std::string_view {
for (const auto& [act, name] : ACTION_NAMES) {
if (act == a) { return name; }
}
return ACTION_NAMES.front().second;
}
auto stringToAction(std::string_view s, GamepadBindings::Action& out) -> bool {
for (const auto& [act, name] : ACTION_NAMES) {
if (name == s) {
out = act;
return true;
}
}
return false;
}
auto buttonToString(int btn) -> std::string_view {
for (const auto& [val, name] : BUTTON_NAMES) {
if (val == btn) { return name; }
}
return std::string_view{""};
}
auto stringToButton(std::string_view s, int& out) -> bool {
for (const auto& [val, name] : BUTTON_NAMES) {
if (name == s) {
out = val;
return true;
}
}
return false;
}
} // namespace
auto GamepadBindings::get() -> GamepadBindings& {
static GamepadBindings instance_;
return instance_;
}
auto GamepadBindings::defaultButton(Action action) -> int {
// Defaults històrics de l'aee_2026 (vegeu Player::accumulateGamepadMovement
// i les consultes del Director per al menú).
switch (action) {
case Action::JUMP:
return static_cast<int>(SDL_GAMEPAD_BUTTON_SOUTH);
case Action::ATTACK:
return static_cast<int>(SDL_GAMEPAD_BUTTON_WEST);
case Action::SERVICE:
return static_cast<int>(SDL_GAMEPAD_BUTTON_BACK);
}
return static_cast<int>(SDL_GAMEPAD_BUTTON_INVALID);
}
auto GamepadBindings::findConfig(const std::string& name, const std::string& path) const -> int {
if (!path.empty()) {
for (std::size_t i = 0; i < configs_.size(); ++i) {
if (configs_[i].path == path) { return static_cast<int>(i); }
}
}
if (!name.empty()) {
for (std::size_t i = 0; i < configs_.size(); ++i) {
if (configs_[i].name == name) { return static_cast<int>(i); }
}
}
return -1;
}
auto GamepadBindings::getButton(const std::string& name, const std::string& path, Action action) const -> int {
const int IDX = findConfig(name, path);
if (IDX < 0) { return defaultButton(action); }
const auto& m = configs_[static_cast<std::size_t>(IDX)].bindings;
auto it = m.find(action);
return (it != m.end()) ? it->second : defaultButton(action);
}
void GamepadBindings::setBindings(const std::string& name,
const std::string& path,
const std::unordered_map<Action, int>& bindings) {
int idx = findConfig(name, path);
if (idx < 0) {
configs_.push_back(Config{.name = name, .path = path, .bindings = bindings});
} else {
auto& cfg = configs_[static_cast<std::size_t>(idx)];
cfg.name = name;
cfg.path = path;
cfg.bindings = bindings;
}
save();
}
auto GamepadBindings::isPressed(SDL_Gamepad* pad, int button) -> bool {
if (pad == nullptr) { return false; }
if (button == TRIGGER_L2_AS_BUTTON) {
return SDL_GetGamepadAxis(pad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER) > TRIGGER_THRESHOLD;
}
if (button == TRIGGER_R2_AS_BUTTON) {
return SDL_GetGamepadAxis(pad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) > TRIGGER_THRESHOLD;
}
return SDL_GetGamepadButton(pad, static_cast<SDL_GamepadButton>(button));
}
void GamepadBindings::load(const std::string& file_path) {
file_path_ = file_path;
configs_.clear();
std::ifstream file(file_path);
if (!file) { return; }
std::stringstream buf;
buf << file.rdbuf();
const std::string CONTENT = buf.str();
if (CONTENT.empty()) { return; }
try {
auto yaml = fkyaml::node::deserialize(CONTENT);
if (!yaml.contains("gamepads") || !yaml["gamepads"].is_sequence()) {
std::cerr << "GamepadBindings: secció 'gamepads' absent o invàlida a "
<< file_path << '\n';
return;
}
for (const auto& entry : yaml["gamepads"]) {
Config cfg;
if (entry.contains("name")) { cfg.name = entry["name"].get_value<std::string>(); }
if (entry.contains("path")) { cfg.path = entry["path"].get_value<std::string>(); }
if (entry.contains("bindings") && entry["bindings"].is_mapping()) {
for (const auto& kv : entry["bindings"].as_map()) {
const auto KEY = kv.first.get_value<std::string>();
const auto VAL = kv.second.get_value<std::string>();
Action action{};
int btn = 0;
if (stringToAction(KEY, action) && stringToButton(VAL, btn)) {
cfg.bindings[action] = btn;
}
}
}
configs_.push_back(std::move(cfg));
}
std::cout << "GamepadBindings: " << configs_.size() << " mandos carregats de "
<< file_path << '\n';
} catch (const std::exception& e) {
std::cerr << "GamepadBindings: error parsejant " << file_path << ": "
<< e.what() << '\n';
}
}
auto GamepadBindings::save() -> bool {
if (file_path_.empty()) { return false; }
std::ostringstream out;
out << "# AEE 2026 - Vinculacions de mando per dispositiu.\n";
out << "# Auto-generat des del menú de servei. Editable a mà.\n";
out << "gamepads:\n";
for (const auto& cfg : configs_) {
out << " - name: \"" << cfg.name << "\"\n";
out << " path: \"" << cfg.path << "\"\n";
out << " bindings:\n";
for (const auto& [action, button] : cfg.bindings) {
out << " " << actionToString(action) << ": \""
<< buttonToString(button) << "\"\n";
}
}
std::ofstream file(file_path_, std::ios::binary);
if (!file) {
std::cerr << "GamepadBindings: no es pot escriure " << file_path_ << '\n';
return false;
}
file << out.str();
return file.good();
}
+91
View File
@@ -0,0 +1,91 @@
#pragma once
#include <SDL3/SDL.h>
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>
// Vinculacions remapeables d'accions del joc a botons d'un mando concret,
// indexades per `(name, path)` del mando — replica el patró
// `GamepadConfig` + `GamepadConfigManager` de coffee_crisis_arcade_edition.
//
// Persisteix en un YAML aïllat al system folder (no a `config.yaml`) perquè
// és per dispositiu, no per usuari/perfil: dos mandos diferents poden
// tenir mappings diferents i cap d'ells va lligat a un slot de jugador.
//
// El layer runtime (consultat per Player/Director cada frame) és
// directament aquesta classe; no hi ha caché a `GamepadManager::Entry`. El
// nombre d'entries persistides és típicament <10, i les accions són 3
// (JUMP/ATTACK/SERVICE), així que les consultes són trivialment ràpides.
class GamepadBindings {
public:
enum class Action : std::uint8_t {
JUMP,
ATTACK,
SERVICE,
};
// Triggers L2/R2 representats com a "botons virtuals" — replica la
// convenció de coffee_crisis_arcade_edition (`TRIGGER_L2_AS_BUTTON`,
// `TRIGGER_R2_AS_BUTTON`). El threshold viu a `isPressed`.
static constexpr int TRIGGER_L2_AS_BUTTON = 100;
static constexpr int TRIGGER_R2_AS_BUTTON = 101;
static auto get() -> GamepadBindings&;
GamepadBindings(const GamepadBindings&) = delete;
GamepadBindings(GamepadBindings&&) = delete;
auto operator=(const GamepadBindings&) -> GamepadBindings& = delete;
auto operator=(GamepadBindings&&) -> GamepadBindings& = delete;
// Fixa la ruta del fitxer persistent + el carrega. Si no existix
// encara, l'estat queda buit (les consultes retornaran defaults) i
// `save()` el crearà al primer `setBindings`.
void load(const std::string& file_path);
// Persistix l'estat actual (totes les Config conegudes). Cridat
// automàticament dins de `setBindings` — no cal cridar-la a mà.
auto save() -> bool;
// Retorna el botó (o trigger virtual 100/101) lligat a `action` per al
// mando identificat per (name, path). Si no hi ha entrada per aquest
// mando, retorna el default global. La cerca és primer per `path`
// (re-vincular el mateix mando al mateix port és prioritari) i, si no
// troba, fallback per `name` (mando connectat a un altre port USB).
[[nodiscard]] auto getButton(const std::string& name, const std::string& path, Action action) const -> int;
// Reescriu totes les vinculacions del mando (name, path). Si no hi
// havia config per aquest dispositiu, en crea una nova. Persisteix a
// disc immediatament. Replicació de `Input::saveGamepadConfigFromGamepad`.
void setBindings(const std::string& name,
const std::string& path,
const std::unordered_map<Action, int>& bindings);
// Wrapper de polling: torna `true` si el "botó" (o trigger virtual)
// està premut ara mateix al mando `pad`. Encapsula el threshold per
// a L2/R2 (~50% del rang).
[[nodiscard]] static auto isPressed(SDL_Gamepad* pad, int button) -> bool;
// Default de fàbrica per a una acció — usat tant pel fallback de
// `getButton` com per inicialitzar la seqüència del DefineButtons.
[[nodiscard]] static auto defaultButton(Action action) -> int;
private:
struct Config {
std::string name;
std::string path;
std::unordered_map<Action, int> bindings;
};
GamepadBindings() = default;
~GamepadBindings() = default;
// Localitza la Config que coincideix amb (name, path); retorna -1 si
// no la troba. Match prioritari per path, fallback per nom.
[[nodiscard]] auto findConfig(const std::string& name, const std::string& path) const -> int;
std::vector<Config> configs_;
std::string file_path_;
};
@@ -7,36 +7,19 @@
#include "core/rendering/surface.hpp" #include "core/rendering/surface.hpp"
#include "core/resources/resource_cache.hpp" #include "core/resources/resource_cache.hpp"
#include "external/fkyaml_node.hpp"
// El parser pure de YAML d'animacions (parseAnimationsFromYaml + helpers)
// viu a `animated_sprite_parser.cpp` perquè és testable sense Surface ni
// Resource::Cache. Aquesta unitat es queda amb el runtime de la classe i
// amb el que sí toca el cache de surfaces (parseSpriteBlock).
namespace { namespace {
auto convertYamlFramesToRects(const fkyaml::node& frames_node, float frame_width, float frame_height, int frames_per_row, int max_tiles) -> std::vector<SDL_FRect> {
std::vector<SDL_FRect> frames;
SDL_FRect rect = {.x = 0.0F, .y = 0.0F, .w = frame_width, .h = frame_height};
for (const auto& frame_index_node : frames_node) {
const int NUM_TILE = frame_index_node.get_value<int>();
if (NUM_TILE <= max_tiles) {
rect.x = static_cast<float>(NUM_TILE % frames_per_row) * frame_width;
rect.y = static_cast<float>(NUM_TILE / frames_per_row) * frame_height;
frames.emplace_back(rect);
}
}
return frames;
}
struct SheetParams {
float frame_width{0.0F};
float frame_height{0.0F};
int frames_per_row{1};
int max_tiles{1};
};
// Llegeix el bloc `sprite: { path, size: [w, h] }` — mateix format que // Llegeix el bloc `sprite: { path, size: [w, h] }` — mateix format que
// les plantilles d'entitat. La surface es resol via Resource::Cache amb // les plantilles d'entitat. La surface es resol via Resource::Cache amb
// el basename del fitxer. // el basename del fitxer.
auto parseSpriteBlock(const fkyaml::node& yaml, std::shared_ptr<Surface>& surface) -> SheetParams { auto parseSpriteBlock(const fkyaml::node& yaml, std::shared_ptr<Surface>& surface) -> AnimatedSprite::SheetParams {
SheetParams params; AnimatedSprite::SheetParams params;
if (!yaml.contains("sprite")) { return params; } if (!yaml.contains("sprite")) { return params; }
const auto& s = yaml["sprite"]; const auto& s = yaml["sprite"];
if (s.contains("path")) { if (s.contains("path")) {
@@ -57,62 +40,18 @@ namespace {
return params; return params;
} }
// `frame_ms` pot ser un escalar (temps uniforme) o una llista (per-frame). // Tradueix els flags lògics flip_h/flip_v al `SDL_FlipMode` que admet
// Convertim sempre a segons perquè `animate()` treballa amb delta_time // `Surface::render`. Important: a SDL3, `SDL_FlipMode` és un enum
// en segons. Si falta o val 0, `speeds` queda buit i l'animació es // exclusiu (NONE/HORIZONTAL/VERTICAL), no un bitmask combinable. Per
// considera estàtica (no avança). // tant, V+H simultani al mateix frame no es pot expressar amb una
void parseFrameTimes(const fkyaml::node& anim_node, AnimatedSprite::AnimationData& animation) { // única crida — prioritzem H (suficient per als casos pràctics
if (!anim_node.contains("frame_ms")) { return; } // d'animació, com `walk_west` o `death_spin`). Si en algun moment fa
const auto& fm = anim_node["frame_ms"]; // falta combinar, caldrà ampliar `Surface::render` per a doble-flipar
if (fm.is_sequence()) { // a baix nivell.
for (const auto& v : fm) { auto toFlipMode(bool flip_h, bool flip_v) -> SDL_FlipMode {
animation.speeds.push_back(static_cast<float>(v.get_value<int>()) / 1000.0F); if (flip_h) { return SDL_FLIP_HORIZONTAL; }
} if (flip_v) { return SDL_FLIP_VERTICAL; }
} else { return SDL_FLIP_NONE;
const float SEC = static_cast<float>(fm.get_value<int>()) / 1000.0F;
if (SEC > 0.0F) { animation.speeds.assign(animation.frames.size(), SEC); }
}
}
// Ordre de prioritat per a la política de loop:
// 1) `loop_from: N` → rebota al frame N en passar del final (implica loop).
// 2) `loop: false` → animació d'un sol tret (es queda al darrer frame).
// 3) Res → loop complet des del frame 0 (default).
void parseLoopPolicy(const fkyaml::node& anim_node, AnimatedSprite::AnimationData& animation) {
if (anim_node.contains("loop_from")) {
animation.loop_from = anim_node["loop_from"].get_value<int>();
} else if (anim_node.contains("loop") && !anim_node["loop"].get_value<bool>()) {
animation.loop_from = -1;
} else {
animation.loop_from = 0;
}
}
auto parseAnimations(const fkyaml::node& yaml, const SheetParams& params) -> std::unordered_map<std::string, AnimatedSprite::AnimationData> {
std::unordered_map<std::string, AnimatedSprite::AnimationData> animations;
if (!yaml.contains("animations") || !yaml["animations"].is_mapping()) {
return animations;
}
const auto& anims_node = yaml["animations"];
for (auto it = anims_node.begin(); it != anims_node.end(); ++it) {
const auto NAME = it.key().get_value<std::string>();
const auto& anim_node = *it;
AnimatedSprite::AnimationData animation;
if (anim_node.contains("frames") && anim_node["frames"].is_sequence()) {
animation.frames = convertYamlFramesToRects(
anim_node["frames"],
params.frame_width,
params.frame_height,
params.frames_per_row,
params.max_tiles);
}
parseFrameTimes(anim_node, animation);
parseLoopPolicy(anim_node, animation);
animations.emplace(NAME, std::move(animation));
}
return animations;
} }
} // namespace } // namespace
@@ -122,7 +61,7 @@ AnimatedSprite::AnimatedSprite(const AnimationResource& cached_data) {
try { try {
auto yaml = fkyaml::node::deserialize(yaml_content); auto yaml = fkyaml::node::deserialize(yaml_content);
auto params = parseSpriteBlock(yaml, surface_); auto params = parseSpriteBlock(yaml, surface_);
animations_ = parseAnimations(yaml, params); animations_ = parseAnimationsFromYaml(yaml, params);
setWidth(params.frame_width); setWidth(params.frame_width);
setHeight(params.frame_height); setHeight(params.frame_height);
@@ -132,7 +71,7 @@ AnimatedSprite::AnimatedSprite(const AnimationResource& cached_data) {
// abans del primer `update()`, així que el valor real el posa ell. // abans del primer `update()`, així que el valor real el posa ell.
if (!animations_.empty()) { if (!animations_.empty()) {
const auto& first = animations_.begin()->second; const auto& first = animations_.begin()->second;
if (!first.frames.empty()) { setClip(first.frames[0]); } if (!first.frames.empty()) { setClip(first.frames[0].rect); }
} }
} catch (const fkyaml::exception& e) { } catch (const fkyaml::exception& e) {
std::cerr << "YAML parsing error in animation " << cached_data.name << ": " << e.what() << '\n'; std::cerr << "YAML parsing error in animation " << cached_data.name << ": " << e.what() << '\n';
@@ -154,11 +93,20 @@ void AnimatedSprite::animate(float delta_time) {
const float TOTAL = std::accumulate(anim.speeds.begin(), anim.speeds.end(), 0.0F); const float TOTAL = std::accumulate(anim.speeds.begin(), anim.speeds.end(), 0.0F);
// Helper local per a propagar el frame actual (clip + flip) al sprite.
// El motor sempre sobreescriu el flip — si el YAML no declara `flip_h`/
// `flip_v`, el frame té els flags a false i el resultat és `SDL_FLIP_NONE`.
auto apply_current_frame = [&](int frame_index) {
const auto& frame = anim.frames[frame_index];
setClip(frame.rect);
setFlip(toFlipMode(frame.flip_h, frame.flip_v));
};
if (anim.accumulated_time >= TOTAL) { if (anim.accumulated_time >= TOTAL) {
if (anim.loop_from < 0) { if (anim.loop_from < 0) {
anim.current_frame = static_cast<int>(anim.frames.size()) - 1; anim.current_frame = static_cast<int>(anim.frames.size()) - 1;
anim.completed = true; anim.completed = true;
setClip(anim.frames[anim.current_frame]); apply_current_frame(anim.current_frame);
return; return;
} }
float loop_start = 0.0F; float loop_start = 0.0F;
@@ -172,13 +120,13 @@ void AnimatedSprite::animate(float delta_time) {
cursor += anim.speeds[i]; cursor += anim.speeds[i];
if (anim.accumulated_time < cursor) { if (anim.accumulated_time < cursor) {
anim.current_frame = i; anim.current_frame = i;
setClip(anim.frames[i]); apply_current_frame(i);
return; return;
} }
} }
anim.current_frame = static_cast<int>(anim.frames.size()) - 1; anim.current_frame = static_cast<int>(anim.frames.size()) - 1;
setClip(anim.frames[anim.current_frame]); apply_current_frame(anim.current_frame);
} }
auto AnimatedSprite::animationIsCompleted() -> bool { auto AnimatedSprite::animationIsCompleted() -> bool {
@@ -200,7 +148,10 @@ void AnimatedSprite::setCurrentAnimation(const std::string& name) {
anim.current_frame = 0; anim.current_frame = 0;
anim.accumulated_time = 0.0F; anim.accumulated_time = 0.0F;
anim.completed = false; anim.completed = false;
if (!anim.frames.empty()) { setClip(anim.frames[0]); } if (!anim.frames.empty()) {
setClip(anim.frames[0].rect);
setFlip(toFlipMode(anim.frames[0].flip_h, anim.frames[0].flip_v));
}
} }
void AnimatedSprite::update(float delta_time) { void AnimatedSprite::update(float delta_time) {
@@ -8,14 +8,41 @@
#include "core/rendering/sprite/moving_sprite.hpp" #include "core/rendering/sprite/moving_sprite.hpp"
#include "core/resources/resource_types.hpp" #include "core/resources/resource_types.hpp"
#include "external/fkyaml_node.hpp"
class AnimatedSprite : public MovingSprite { class AnimatedSprite : public MovingSprite {
public: public:
// Un frame d'animació = rectangle dins el spritesheet + flags de flip
// associats. El flip s'aplica al sprite a `animate()` quan l'animació
// declara que ella controla el flip (vegeu `AnimationData::controls_flip`).
struct Frame {
SDL_FRect rect{.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F};
bool flip_h{false};
bool flip_v{false};
};
// Paràmetres geomètrics del spritesheet, derivats del bloc `sprite:`
// del YAML. Es passen al parser d'animacions per a calcular els
// rectangles de cada frame a partir dels seus índexs.
struct SheetParams {
float frame_width{0.0F};
float frame_height{0.0F};
int frames_per_row{1};
int max_tiles{1};
};
// Dades d'una animació concreta dins d'un spritesheet. Les claus del // Dades d'una animació concreta dins d'un spritesheet. Les claus del
// mapa (`animations_`) són els noms; aquí només es guarden els fotogrames // mapa (`animations_`) són els noms; aquí només es guarden els fotogrames
// i l'estat de reproducció. // i l'estat de reproducció.
//
// El flip és sempre propietat del motor: a cada `animate()` s'escriu el
// flip del frame actual al sprite (default `SDL_FLIP_NONE` quan el YAML
// no declara res). El codi del joc no pot — ni ha de — tocar el flip
// manualment; ho expressa al YAML declarant `flip_h: true` a l'animació
// simètrica (p. ex. `walk_west`) o, per a casos puntuals, per frame
// amb la forma mapping `{ index, flip_h?, flip_v? }`.
struct AnimationData { struct AnimationData {
std::vector<SDL_FRect> frames; std::vector<Frame> frames;
// Durada de cada frame en segons (convertit des de `frame_ms` al YAML). // Durada de cada frame en segons (convertit des de `frame_ms` al YAML).
// Si té un sol valor, val per a tots els frames; altrament ha de tenir // Si té un sol valor, val per a tots els frames; altrament ha de tenir
// la mateixa mida que `frames`. // la mateixa mida que `frames`.
@@ -40,6 +67,12 @@ class AnimatedSprite : public MovingSprite {
void resetAnimation(); void resetAnimation();
void animate(float delta_time); void animate(float delta_time);
// Helper de parsing exposat per a testabilitat. NO toca el cache de
// recursos — només el bloc `animations:` del YAML i els paràmetres de
// full ja calculats. El ctor d'`AnimatedSprite` el crida internament.
static auto parseAnimationsFromYaml(const fkyaml::node& yaml, const SheetParams& params)
-> std::unordered_map<std::string, AnimationData>;
private: private:
std::unordered_map<std::string, AnimationData> animations_; std::unordered_map<std::string, AnimationData> animations_;
// Nom de l'animació activa. Buit fins que algú crida `setCurrentAnimation`. // Nom de l'animació activa. Buit fins que algú crida `setCurrentAnimation`.
@@ -0,0 +1,114 @@
#include "core/rendering/sprite/animated_sprite.hpp"
// Implementació del parser pure de YAML d'animacions. Viu en una unitat
// separada perquè NO depèn de Resource::Cache ni de Surface — només de
// fkyaml — i això permet als tests stand-alone enllaçar només aquest .cpp
// per a verificar el parsing sense haver d'arrossegar tot l'stack de
// recursos i renderitzat.
namespace {
// Construeix el rectangle d'un frame a partir del seu índex dins del
// spritesheet. Centralitzat per a no duplicar la fórmula als dos
// formats (escalar / mapping) que admet `frames:` al YAML.
auto rectFromIndex(int index, const AnimatedSprite::SheetParams& params) -> SDL_FRect {
return SDL_FRect{
.x = static_cast<float>(index % params.frames_per_row) * params.frame_width,
.y = static_cast<float>(index / params.frames_per_row) * params.frame_height,
.w = params.frame_width,
.h = params.frame_height,
};
}
// Llig un node `frames` (seqüència) i el converteix a la nova
// representació `vector<Frame>`. Cada element pot ser:
// - escalar (int) → hereta `default_flip_h`/`default_flip_v` de l'animació.
// - mapping `{ index: N, flip_h?: bool, flip_v?: bool }` → fully
// explícit. Els flags absents defaulten a false (NO hereta de l'anim).
auto convertYamlFrames(const fkyaml::node& frames_node, const AnimatedSprite::SheetParams& params, bool default_flip_h, bool default_flip_v) -> std::vector<AnimatedSprite::Frame> {
std::vector<AnimatedSprite::Frame> frames;
for (const auto& frame_node : frames_node) {
if (frame_node.is_mapping()) {
if (!frame_node.contains("index")) { continue; }
const int NUM_TILE = frame_node["index"].get_value<int>();
if (NUM_TILE > params.max_tiles) { continue; }
AnimatedSprite::Frame frame;
frame.rect = rectFromIndex(NUM_TILE, params);
if (frame_node.contains("flip_h")) { frame.flip_h = frame_node["flip_h"].get_value<bool>(); }
if (frame_node.contains("flip_v")) { frame.flip_v = frame_node["flip_v"].get_value<bool>(); }
frames.emplace_back(frame);
} else {
const int NUM_TILE = frame_node.get_value<int>();
if (NUM_TILE > params.max_tiles) { continue; }
frames.emplace_back(AnimatedSprite::Frame{
.rect = rectFromIndex(NUM_TILE, params),
.flip_h = default_flip_h,
.flip_v = default_flip_v,
});
}
}
return frames;
}
// `frame_ms` pot ser un escalar (temps uniforme) o una llista (per-frame).
// Convertim sempre a segons perquè `animate()` treballa amb delta_time
// en segons. Si falta o val 0, `speeds` queda buit i l'animació es
// considera estàtica (no avança).
void parseFrameTimes(const fkyaml::node& anim_node, AnimatedSprite::AnimationData& animation) {
if (!anim_node.contains("frame_ms")) { return; }
const auto& fm = anim_node["frame_ms"];
if (fm.is_sequence()) {
for (const auto& v : fm) {
animation.speeds.push_back(static_cast<float>(v.get_value<int>()) / 1000.0F);
}
} else {
const float SEC = static_cast<float>(fm.get_value<int>()) / 1000.0F;
if (SEC > 0.0F) { animation.speeds.assign(animation.frames.size(), SEC); }
}
}
// Ordre de prioritat per a la política de loop:
// 1) `loop_from: N` → rebota al frame N en passar del final (implica loop).
// 2) `loop: false` → animació d'un sol tret (es queda al darrer frame).
// 3) Res → loop complet des del frame 0 (default).
void parseLoopPolicy(const fkyaml::node& anim_node, AnimatedSprite::AnimationData& animation) {
if (anim_node.contains("loop_from")) {
animation.loop_from = anim_node["loop_from"].get_value<int>();
} else if (anim_node.contains("loop") && !anim_node["loop"].get_value<bool>()) {
animation.loop_from = -1;
} else {
animation.loop_from = 0;
}
}
} // namespace
auto AnimatedSprite::parseAnimationsFromYaml(const fkyaml::node& yaml, const SheetParams& params)
-> std::unordered_map<std::string, AnimationData> {
std::unordered_map<std::string, AnimationData> animations;
if (!yaml.contains("animations") || !yaml["animations"].is_mapping()) {
return animations;
}
const auto& anims_node = yaml["animations"];
for (auto it = anims_node.begin(); it != anims_node.end(); ++it) {
const auto NAME = it.key().get_value<std::string>();
const auto& anim_node = *it;
// Flags de flip a nivell d'animació (defaults a false). Els frames
// declarats com a int els hereten; els declarats com a mapping no.
bool anim_flip_h = false;
bool anim_flip_v = false;
if (anim_node.contains("flip_h")) { anim_flip_h = anim_node["flip_h"].get_value<bool>(); }
if (anim_node.contains("flip_v")) { anim_flip_v = anim_node["flip_v"].get_value<bool>(); }
AnimationData animation;
if (anim_node.contains("frames") && anim_node["frames"].is_sequence()) {
animation.frames = convertYamlFrames(anim_node["frames"], params, anim_flip_h, anim_flip_v);
}
parseFrameTimes(anim_node, animation);
parseLoopPolicy(anim_node, animation);
animations.emplace(NAME, std::move(animation));
}
return animations;
}
+37 -9
View File
@@ -13,6 +13,7 @@
#include "core/audio/audio.hpp" #include "core/audio/audio.hpp"
#include "core/audio/sound_effects_config.hpp" #include "core/audio/sound_effects_config.hpp"
#include "core/input/gamepad_bindings.hpp"
#include "core/input/gamepad_manager.hpp" #include "core/input/gamepad_manager.hpp"
#include "core/input/key_config.hpp" #include "core/input/key_config.hpp"
#include "core/input/mouse.hpp" #include "core/input/mouse.hpp"
@@ -82,6 +83,11 @@ Director::Director() {
std::cerr << "Director: no s'ha pogut carregar ni desar config.yaml a " << system_folder_ << '\n'; std::cerr << "Director: no s'ha pogut carregar ni desar config.yaml a " << system_folder_ << '\n';
} }
// Vinculacions de mando per dispositiu — fitxer aïllat de `config.yaml`
// perquè és per (name, path) del mando, no per perfil/usuari. Replica
// el patró de coffee_crisis_arcade_edition (`gamepad_configs.json`).
GamepadBindings::get().load(system_folder_ + "gamepad_configs.yaml");
// Carrega els presets de PostFX des de shaders/postfx.yaml; si no existeix // Carrega els presets de PostFX des de shaders/postfx.yaml; si no existeix
// es crea amb presets per defecte (off, crt, ntsc). // es crea amb presets per defecte (off, crt, ntsc).
Options::setPostFXFile(system_folder_ + Defaults::Video::POSTFX_FILE); Options::setPostFXFile(system_folder_ + Defaults::Video::POSTFX_FILE);
@@ -132,9 +138,12 @@ Director::Director() {
Resource::Cache::get().loadSurface(ROOM_TEXT_FONT_GIF_KEY, UiConfig::get().roomTextFont().gif); Resource::Cache::get().loadSurface(ROOM_TEXT_FONT_GIF_KEY, UiConfig::get().roomTextFont().gif);
Resource::Cache::get().getSurface(ROOM_TEXT_FONT_GIF_KEY)->setPalette(Resource::Cache::get().getPalette("ui")); Resource::Cache::get().getSurface(ROOM_TEXT_FONT_GIF_KEY)->setPalette(Resource::Cache::get().getPalette("ui"));
// Música de la piràmide 1 (primer i únic track de moment). Carreguem ací // Pistes de música del cicle de sala (intro/playing/outro). Es preloden
// perquè forma part del preload d'assets, com la font i la paleta. // ací perquè formen part del preload d'assets, com la font i la paleta;
Resource::Cache::get().loadMusic("pyramid_1.ogg", "data/music/pyramid_1.ogg"); // la lògica de quina sona en cada estat viu a `GameScene`.
Resource::Cache::get().loadMusic("room_intro.ogg", "data/music/room_intro.ogg");
Resource::Cache::get().loadMusic("playing.ogg", "data/music/playing.ogg");
Resource::Cache::get().loadMusic("room_outro.ogg", "data/music/room_outro.ogg");
// Catàleg de presets d'efectes per als sons. Es carrega abans d'Audio // Catàleg de presets d'efectes per als sons. Es carrega abans d'Audio
// perquè els primers playSoundWith* (al començament del joc) ja // perquè els primers playSoundWith* (al començament del joc) ja
@@ -293,12 +302,29 @@ void Director::processEvents() {
if (QUIT_REQUESTED) { current_scene_.reset(); } if (QUIT_REQUESTED) { current_scene_.reset(); }
} }
namespace {
// Resol el botó SERVICE configurat per al mando origen d'aquest event.
// Retorna -1 si no en troba cap. La cerca passa per `GamepadManager`
// per obtenir el (name, path) i d'ahí a `GamepadBindings`.
auto serviceButtonForGamepadEvent(SDL_JoystickID jid) -> int {
for (const auto& info : GamepadManager::get().getAll()) {
if (info.pad == nullptr) { continue; }
if (SDL_GetJoystickID(SDL_GetGamepadJoystick(info.pad)) != jid) { continue; }
return GamepadBindings::get().getButton(info.name, info.path, GamepadBindings::Action::SERVICE);
}
return -1;
}
} // namespace
void Director::processGamepadEvents() { void Director::processGamepadEvents() {
// Dues responsabilitats: (1) passar ADDED/REMOVED al GamepadManager // Dues responsabilitats: (1) passar ADDED/REMOVED al GamepadManager
// perquè obri/tanqui mandos i emeti les notificacions hot-plug; (2) // perquè obri/tanqui mandos i emeti les notificacions hot-plug; (2)
// tractar el botó BACK com a toggle del menú quan està tancat (el // tractar el botó SERVICE (per defecte BACK; remapejable per
// paral·lel de F9 per al teclat). Quan el menú està obert, el BACK // `GamepadBindings`) com a toggle del menú quan està tancat (el
// passa pel pipeline del `ServiceMenu::handleEvent()` via // paral·lel de F9 per al teclat). Quan el menú està obert, el botó
// SERVICE passa pel pipeline del `ServiceMenu::handleEvent()` via
// `processMenuInput()` i allà es converteix en `close()`. // `processMenuInput()` i allà es converteix en `close()`.
for (auto it = EventBuffer::events.begin(); it != EventBuffer::events.end();) { for (auto it = EventBuffer::events.begin(); it != EventBuffer::events.end();) {
bool consumed = false; bool consumed = false;
@@ -306,10 +332,12 @@ void Director::processGamepadEvents() {
GamepadManager::get().handleEvent(*it); GamepadManager::get().handleEvent(*it);
consumed = true; consumed = true;
} else if (it->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN && } else if (it->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN &&
it->gbutton.button == SDL_GAMEPAD_BUTTON_BACK &&
!ServiceMenu::get().isOpen()) { !ServiceMenu::get().isOpen()) {
ServiceMenu::get().toggle(); const int SERVICE_BTN = serviceButtonForGamepadEvent(it->gbutton.which);
consumed = true; if (SERVICE_BTN >= 0 && static_cast<int>(it->gbutton.button) == SERVICE_BTN) {
ServiceMenu::get().toggle();
consumed = true;
}
} }
if (consumed) { if (consumed) {
it = EventBuffer::events.erase(it); it = EventBuffer::events.erase(it);
+12 -1
View File
@@ -4,9 +4,14 @@
#include <utility> #include <utility>
AnimationComponent::AnimationComponent(std::vector<int> frames, float fps, bool loop) AnimationComponent::AnimationComponent(std::vector<int> frames, float fps, bool loop)
: AnimationComponent(std::move(frames), fps, loop, false, false) {}
AnimationComponent::AnimationComponent(std::vector<int> frames, float fps, bool loop, bool flip_h, bool flip_v)
: frames_(std::move(frames)), : frames_(std::move(frames)),
fps_(fps > 0.0F ? fps : 1.0F), fps_(fps > 0.0F ? fps : 1.0F),
loop_(loop) { loop_(loop),
flip_h_(flip_h),
flip_v_(flip_v) {
// Una animació d'1 sol frame amb loop=false és una animació que ja s'ha // Una animació d'1 sol frame amb loop=false és una animació que ja s'ha
// acabat de reproduir: no hi ha cap transició per mostrar. Marcant-la // acabat de reproduir: no hi ha cap transició per mostrar. Marcant-la
// com a completada des de l'inici permet als consumidors (p.ex. Door) // com a completada des de l'inici permet als consumidors (p.ex. Door)
@@ -46,3 +51,9 @@ auto AnimationComponent::currentFrame() const -> int {
if (frames_.empty()) { return 0; } if (frames_.empty()) { return 0; }
return frames_[static_cast<std::size_t>(current_)]; return frames_[static_cast<std::size_t>(current_)];
} }
auto AnimationComponent::flipMode() const -> SDL_FlipMode {
if (flip_h_) { return SDL_FLIP_HORIZONTAL; }
if (flip_v_) { return SDL_FLIP_VERTICAL; }
return SDL_FLIP_NONE;
}
@@ -1,5 +1,7 @@
#pragma once #pragma once
#include <SDL3/SDL.h>
#include <cstddef> #include <cstddef>
#include <vector> #include <vector>
@@ -13,10 +15,18 @@
// algun dia aterri l'art definitiu, el renderer consultarà `currentFrame()` // algun dia aterri l'art definitiu, el renderer consultarà `currentFrame()`
// i traduirà l'índex al rectangle del sprite corresponent. De moment els // i traduirà l'índex al rectangle del sprite corresponent. De moment els
// placeholders tenen un únic frame i `currentFrame()` retorna sempre 0. // placeholders tenen un únic frame i `currentFrame()` retorna sempre 0.
//
// El flip (H/V) és propietat de l'animació, no del codi del joc: el YAML
// declara `flip_h`/`flip_v` al bloc d'animació i el component els exposa
// via `flipMode()`. Les entitats apliquen aquest valor al seu `flip_` quan
// canvien d'animació, sense decidir-lo per direcció. Així una mòmia que va
// cap a l'oest reprodueix `walk_west` (frames de l'est + `flip_h: true`)
// sense que el codi de la mòmia toqui flip directament.
class AnimationComponent { class AnimationComponent {
public: public:
AnimationComponent() = default; AnimationComponent() = default;
AnimationComponent(std::vector<int> frames, float fps, bool loop); AnimationComponent(std::vector<int> frames, float fps, bool loop);
AnimationComponent(std::vector<int> frames, float fps, bool loop, bool flip_h, bool flip_v);
// Avanç del temps. Si `frames_` està buit o té un sol element, no fa // Avanç del temps. Si `frames_` està buit o té un sol element, no fa
// res. En mode loop, `current_frame_` rebota a 0 en passar del final; // res. En mode loop, `current_frame_` rebota a 0 en passar del final;
@@ -37,10 +47,19 @@ class AnimationComponent {
[[nodiscard]] auto completed() const -> bool { return completed_; } [[nodiscard]] auto completed() const -> bool { return completed_; }
[[nodiscard]] auto loops() const -> bool { return loop_; } [[nodiscard]] auto loops() const -> bool { return loop_; }
[[nodiscard]] auto flipH() const -> bool { return flip_h_; }
[[nodiscard]] auto flipV() const -> bool { return flip_v_; }
// Combinació dels dos flags al `SDL_FlipMode` que entén el render.
// SDL3 no admet bitmask: si fos VH simultani, prioritzem H (cap cas
// pràctic actual el necessita combinat).
[[nodiscard]] auto flipMode() const -> SDL_FlipMode;
private: private:
std::vector<int> frames_; std::vector<int> frames_;
float fps_{1.0F}; float fps_{1.0F};
bool loop_{true}; bool loop_{true};
bool flip_h_{false};
bool flip_v_{false};
int current_{0}; int current_{0};
float accumulator_{0.0F}; float accumulator_{0.0F};
bool completed_{false}; bool completed_{false};
+89 -59
View File
@@ -26,11 +26,15 @@ namespace {
// Llegeix un bloc d'animació del YAML amb fallbacks tolerants (mateix // Llegeix un bloc d'animació del YAML amb fallbacks tolerants (mateix
// patró que Door/Sarcophagus). Si la clau no apareix, retorna una // patró que Door/Sarcophagus). Si la clau no apareix, retorna una
// animació d'1 frame loopant, neutra — la momia es podrà moure però // animació d'1 frame loopant, neutra — la momia es podrà moure però
// visualment es quedarà estàtica. // visualment es quedarà estàtica. Llegeix `flip_h`/`flip_v` (default
// false) i els passa al component perquè el motor sàpiga aplicar-los
// al sprite quan aquesta animació estigui activa.
auto buildAnimation(const fkyaml::node& yaml, const std::string& key) -> AnimationComponent { auto buildAnimation(const fkyaml::node& yaml, const std::string& key) -> AnimationComponent {
std::vector<int> frames = {0}; std::vector<int> frames = {0};
float fps = 8.0F; float fps = 8.0F;
bool loop = true; bool loop = true;
bool flip_h = false;
bool flip_v = false;
if (yaml.contains("animations") && yaml["animations"].contains(key)) { if (yaml.contains("animations") && yaml["animations"].contains(key)) {
const auto& anim = yaml["animations"][key]; const auto& anim = yaml["animations"][key];
if (anim.contains("frames")) { if (anim.contains("frames")) {
@@ -43,8 +47,10 @@ namespace {
} }
if (anim.contains("fps")) { fps = anim["fps"].get_value<float>(); } if (anim.contains("fps")) { fps = anim["fps"].get_value<float>(); }
if (anim.contains("loop")) { loop = anim["loop"].get_value<bool>(); } if (anim.contains("loop")) { loop = anim["loop"].get_value<bool>(); }
if (anim.contains("flip_h")) { flip_h = anim["flip_h"].get_value<bool>(); }
if (anim.contains("flip_v")) { flip_v = anim["flip_v"].get_value<bool>(); }
} }
return {frames, fps, loop}; return {frames, fps, loop, flip_h, flip_v};
} }
// Busca si el segment [prev, prev+delta] travessa una lane del patró // Busca si el segment [prev, prev+delta] travessa una lane del patró
@@ -73,35 +79,41 @@ namespace {
} // namespace } // namespace
Mummy::Mummy(const fkyaml::node& yaml) Mummy::Mummy(const fkyaml::node& yaml)
: anim_walk_down_(buildAnimation(yaml, "walk_down")), : anim_walk_south_(buildAnimation(yaml, "walk_south")),
anim_walk_up_(buildAnimation(yaml, "walk_up")), anim_walk_north_(buildAnimation(yaml, "walk_north")),
anim_walk_lateral_(buildAnimation(yaml, "walk")) { anim_walk_east_(buildAnimation(yaml, "walk_east")),
anim_walk_west_(buildAnimation(yaml, "walk_west")) {
name_ = yaml.contains("name") ? yaml["name"].get_value<std::string>() : std::string("mummy"); name_ = yaml.contains("name") ? yaml["name"].get_value<std::string>() : std::string("mummy");
collides_ = true; collides_ = true;
interactable_ = false; interactable_ = false;
// Velocitat i dany des del bloc `stats` (si existeix). // Velocitat i dany des del bloc `stats` (si existeix). El flag
// `kills_outright` també hi viu: per defecte true (la mòmia mata
// d'un sol toc); posat a false al YAML, el contacte només publica
// `player_hit` sense disparar el cicle DYING.
if (yaml.contains("stats")) { if (yaml.contains("stats")) {
const auto& s = yaml["stats"]; const auto& s = yaml["stats"];
if (s.contains("speed")) { setSpeed(s["speed"].get_value<float>()); } if (s.contains("speed")) { setSpeed(s["speed"].get_value<float>()); }
if (s.contains("damage")) { setDamage(s["damage"].get_value<int>()); } if (s.contains("damage")) { setDamage(s["damage"].get_value<int>()); }
if (s.contains("kills_outright")) { kills_outright_ = s["kills_outright"].get_value<bool>(); }
} }
// Direcció inicial llegida del bloc `ai:`. Per defecte dreta, per // Direcció inicial llegida del bloc `ai:`. Per defecte est, per
// coherència amb el que fa RollingRock. Valors acceptats: right, left, // coherència amb el que fa RollingRock. Valors acceptats:
// up, down. Qualsevol altre s'interpreta com a "sense direcció" (la // east, west, north, south. Qualsevol altre s'interpreta com a
// momia no es mou fins que step() no triï una direcció). // "sense direcció" (la momia no es mou fins que step() no triï una
std::string initial = "right"; // direcció).
std::string initial = "east";
if (yaml.contains("ai") && yaml["ai"].contains("initial_direction")) { if (yaml.contains("ai") && yaml["ai"].contains("initial_direction")) {
initial = yaml["ai"]["initial_direction"].get_value<std::string>(); initial = yaml["ai"]["initial_direction"].get_value<std::string>();
} }
if (initial == "right") { if (initial == "east") {
changeDirection(1.0F, 0.0F); changeDirection(1.0F, 0.0F);
} else if (initial == "left") { } else if (initial == "west") {
changeDirection(-1.0F, 0.0F); changeDirection(-1.0F, 0.0F);
} else if (initial == "up") { } else if (initial == "north") {
changeDirection(0.0F, -1.0F); changeDirection(0.0F, -1.0F);
} else if (initial == "down") { } else if (initial == "south") {
changeDirection(0.0F, 1.0F); changeDirection(0.0F, 1.0F);
} }
@@ -124,22 +136,26 @@ Mummy::Mummy(const fkyaml::node& yaml)
void Mummy::changeDirection(float dx, float dy) { void Mummy::changeDirection(float dx, float dy) {
setDirection(dx, dy); setDirection(dx, dy);
// Prevalença lateral: si hi ha component horitzontal s'usa l'anim // Una animació per cada cardinal — el flip viu al YAML
// lateral + flip segons el signe. Només direccions purament verticals // (`walk_west: flip_h: true`) i el codi només selecciona quina
// disparen walk_up/walk_down. Quan la direcció queda (0,0) (cul-de-sac // animació mostrar. Prevalença lateral en diagonals; quan la direcció
// amb NONE) no toquem l'animació: la momia es quedarà al frame actual // queda (0,0) (cul-de-sac amb NONE) no toquem l'animació: la mòmia es
// fins que step() li torni a assignar direcció. // quedarà al frame actual fins que step() li torni a assignar direcció.
if (dx != 0.0F) { if (dx > 0.0F) {
anim_ = anim_walk_lateral_; anim_ = anim_walk_east_;
flip_ = (dx < 0.0F) ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE; } else if (dx < 0.0F) {
anim_ = anim_walk_west_;
} else if (dy < 0.0F) { } else if (dy < 0.0F) {
anim_ = anim_walk_up_; anim_ = anim_walk_north_;
flip_ = SDL_FLIP_NONE;
} else if (dy > 0.0F) { } else if (dy > 0.0F) {
anim_ = anim_walk_down_; anim_ = anim_walk_south_;
flip_ = SDL_FLIP_NONE; }
if (anim_.has_value()) {
anim_->reset();
// Propaguem el flip que l'animació declara al YAML al camp
// `flip_` que el render llegeix (entity_manager_loadlevel.cpp).
flip_ = anim_->flipMode();
} }
if (anim_.has_value()) { anim_->reset(); }
} }
auto Mummy::chooseDirection(const std::vector<Direction>& allowed) -> Direction { auto Mummy::chooseDirection(const std::vector<Direction>& allowed) -> Direction {
@@ -234,10 +250,10 @@ auto Mummy::detectLaneCrossing(float dx, float dy) const -> std::optional<float>
} }
auto Mummy::currentDirection() const -> Direction { auto Mummy::currentDirection() const -> Direction {
if (dir_x_ > 0.0F) { return Direction::RIGHT; } if (dir_x_ > 0.0F) { return Direction::EAST; }
if (dir_x_ < 0.0F) { return Direction::LEFT; } if (dir_x_ < 0.0F) { return Direction::WEST; }
if (dir_y_ > 0.0F) { return Direction::DOWN; } if (dir_y_ > 0.0F) { return Direction::SOUTH; }
if (dir_y_ < 0.0F) { return Direction::UP; } if (dir_y_ < 0.0F) { return Direction::NORTH; }
return Direction::NONE; return Direction::NONE;
} }
@@ -268,10 +284,10 @@ auto Mummy::collectDirectionPools(float step) const -> DirectionPools {
Direction dir; Direction dir;
}; };
const std::array<Candidate, 4> ALL{{ const std::array<Candidate, 4> ALL{{
{.dx = step, .dy = 0.0F, .dir = Direction::RIGHT}, {.dx = step, .dy = 0.0F, .dir = Direction::EAST},
{.dx = -step, .dy = 0.0F, .dir = Direction::LEFT}, {.dx = -step, .dy = 0.0F, .dir = Direction::WEST},
{.dx = 0.0F, .dy = -step, .dir = Direction::UP}, {.dx = 0.0F, .dy = -step, .dir = Direction::NORTH},
{.dx = 0.0F, .dy = step, .dir = Direction::DOWN}, {.dx = 0.0F, .dy = step, .dir = Direction::SOUTH},
}}; }};
DirectionPools pools; DirectionPools pools;
for (const auto& c : ALL) { for (const auto& c : ALL) {
@@ -293,16 +309,16 @@ auto Mummy::collectDirectionPools(float step) const -> DirectionPools {
void Mummy::applyChoice(Direction dir) { void Mummy::applyChoice(Direction dir) {
switch (dir) { switch (dir) {
case Direction::UP: case Direction::NORTH:
changeDirection(0.0F, -1.0F); changeDirection(0.0F, -1.0F);
break; break;
case Direction::DOWN: case Direction::SOUTH:
changeDirection(0.0F, 1.0F); changeDirection(0.0F, 1.0F);
break; break;
case Direction::LEFT: case Direction::WEST:
changeDirection(-1.0F, 0.0F); changeDirection(-1.0F, 0.0F);
break; break;
case Direction::RIGHT: case Direction::EAST:
changeDirection(1.0F, 0.0F); changeDirection(1.0F, 0.0F);
break; break;
case Direction::NONE: case Direction::NONE:
@@ -312,11 +328,25 @@ void Mummy::applyChoice(Direction dir) {
} }
void Mummy::onPlayerContact(Player* player) { void Mummy::onPlayerContact(Player* player) {
if (bus_ != nullptr) { if (bus_ == nullptr) { return; }
const int PID = (player != nullptr) ? player->getId() : -1;
bus_->publish(Event{
.name = "player_hit",
.payload = getDamage(),
.player_id = PID,
});
// Path letal: si la mòmia està marcada com a `kills_outright`
// (default a Oh Mummy!), notifiquem al GameScene que el jugador
// ha de morir. La transició concreta (`Player::enterDying`) la fa
// el subscriptor — així mummy.cpp no toca cap símbol del Player més
// enllà de la forward-decl, i els tests d'entitats no han d'arrossegar
// l'stack pesat del jugador per linkar-se. El subscriptor (GameScene)
// és qui resol el `player_id` a la instància concreta.
if (kills_outright_) {
bus_->publish(Event{ bus_->publish(Event{
.name = "player_hit", .name = "player_killed",
.payload = getDamage(), .payload = getDamage(),
.player_id = (player != nullptr) ? player->getId() : -1, .player_id = PID,
}); });
} }
} }
@@ -347,16 +377,16 @@ auto Mummy::chooseHurtDirection(Direction preferred) const -> Direction {
float dx = 0.0F; float dx = 0.0F;
float dy = 0.0F; float dy = 0.0F;
switch (d) { switch (d) {
case Direction::UP: case Direction::NORTH:
dy = -STEP; dy = -STEP;
break; break;
case Direction::DOWN: case Direction::SOUTH:
dy = STEP; dy = STEP;
break; break;
case Direction::LEFT: case Direction::WEST:
dx = -STEP; dx = -STEP;
break; break;
case Direction::RIGHT: case Direction::EAST:
dx = STEP; dx = STEP;
break; break;
case Direction::NONE: case Direction::NONE:
@@ -373,21 +403,21 @@ auto Mummy::chooseHurtDirection(Direction preferred) const -> Direction {
Direction left = Direction::NONE; Direction left = Direction::NONE;
Direction right = Direction::NONE; Direction right = Direction::NONE;
switch (preferred) { switch (preferred) {
case Direction::UP: case Direction::NORTH:
left = Direction::LEFT; left = Direction::WEST;
right = Direction::RIGHT; right = Direction::EAST;
break; break;
case Direction::DOWN: case Direction::SOUTH:
left = Direction::RIGHT; left = Direction::EAST;
right = Direction::LEFT; right = Direction::WEST;
break; break;
case Direction::LEFT: case Direction::WEST:
left = Direction::DOWN; left = Direction::SOUTH;
right = Direction::UP; right = Direction::NORTH;
break; break;
case Direction::RIGHT: case Direction::EAST:
left = Direction::UP; left = Direction::NORTH;
right = Direction::DOWN; right = Direction::SOUTH;
break; break;
case Direction::NONE: case Direction::NONE:
return Direction::NONE; return Direction::NONE;
+24 -13
View File
@@ -16,10 +16,10 @@
class Mummy : public DynamicHazard { class Mummy : public DynamicHazard {
public: public:
enum class Direction : std::uint8_t { NONE, enum class Direction : std::uint8_t { NONE,
UP, NORTH,
DOWN, SOUTH,
LEFT, WEST,
RIGHT }; EAST };
// Estat d'alt nivell. ROAMING = IA normal (corridor_wanderer); HURT = // Estat d'alt nivell. ROAMING = IA normal (corridor_wanderer); HURT =
// post-fuetada, fugint en línia recta durant `hurt_config_.duration_s`. // post-fuetada, fugint en línia recta durant `hurt_config_.duration_s`.
@@ -69,6 +69,7 @@ class Mummy : public DynamicHazard {
[[nodiscard]] auto getState() const -> State { return state_; } [[nodiscard]] auto getState() const -> State { return state_; }
[[nodiscard]] auto isWhippable() const -> bool { return whippable_; } [[nodiscard]] auto isWhippable() const -> bool { return whippable_; }
[[nodiscard]] auto getHurtRemainingS() const -> float { return hurt_remaining_s_; } [[nodiscard]] auto getHurtRemainingS() const -> float { return hurt_remaining_s_; }
[[nodiscard]] auto killsOutright() const -> bool { return kills_outright_; }
private: private:
// Caixa de col·lisió candidata si l'entitat es desplaçara (dx, dy). // Caixa de col·lisió candidata si l'entitat es desplaçara (dx, dy).
@@ -91,10 +92,11 @@ class Mummy : public DynamicHazard {
}; };
[[nodiscard]] auto collectDirectionPools(float step) const -> DirectionPools; [[nodiscard]] auto collectDirectionPools(float step) const -> DirectionPools;
// Canvi de direcció + aplicació de l'animació i flip corresponents. // Canvi de direcció + selecció de l'animació corresponent (un cardinal
// Tots els camins que trien nova direcció (ctor amb `initial_direction`, // per branch). Tots els camins que trien nova direcció (ctor amb
// gir després d'un bloqueig) passen per ací per mantenir anim_ i // `initial_direction`, gir després d'un bloqueig) passen per ací; el
// flip_ en sincronia amb (dir_x_, dir_y_). // flip viu al YAML (`walk_west: flip_h: true`) i es propaga des de
// l'animació al `flip_` heretat.
void changeDirection(float dx, float dy); void changeDirection(float dx, float dy);
// Aplica la direcció triada (wrapper del switch en una sola crida). // Aplica la direcció triada (wrapper del switch en una sola crida).
@@ -128,11 +130,14 @@ class Mummy : public DynamicHazard {
// totes ho estan, retorna `NONE` (la mòmia queda quieta en HURT). // totes ho estan, retorna `NONE` (la mòmia queda quieta en HURT).
[[nodiscard]] auto chooseHurtDirection(Direction preferred) const -> Direction; [[nodiscard]] auto chooseHurtDirection(Direction preferred) const -> Direction;
// Animacions carregades del YAML, una per cada facing. Per a horitzontal // Animacions carregades del YAML, una per cada cardinal. La variant
// només en cal una (`walk`) i el flip ja diferencia esquerra/dreta. // `walk_west` reaprofita els frames de l'est però amb `flip_h: true`
AnimationComponent anim_walk_down_; // declarat al YAML — el motor d'animació aplica el flip al sprite
AnimationComponent anim_walk_up_; // automàticament; aquesta classe no toca mai `flip_` directament.
AnimationComponent anim_walk_lateral_; AnimationComponent anim_walk_south_;
AnimationComponent anim_walk_north_;
AnimationComponent anim_walk_east_;
AnimationComponent anim_walk_west_;
// Estat HURT i la seua configuració. `hurt_speed_` és l'efectiva // Estat HURT i la seua configuració. `hurt_speed_` és l'efectiva
// (resultat d'aplicar el `speed` del bloc `hurt:` o, si val 0, la // (resultat d'aplicar el `speed` del bloc `hurt:` o, si val 0, la
@@ -142,4 +147,10 @@ class Mummy : public DynamicHazard {
bool whippable_{true}; bool whippable_{true};
float hurt_remaining_s_{0.0F}; float hurt_remaining_s_{0.0F};
float hurt_speed_{0.0F}; float hurt_speed_{0.0F};
// Si true, el contacte amb el jugador dispara `Player::enterDying`
// (mort d'un sol toc, bypass del flux `hurt`/knockback). Llegit del
// bloc `stats.kills_outright` del YAML; default true per coherència
// amb el comportament d'Oh Mummy! (les mòmies maten).
bool kills_outright_{true};
}; };
@@ -255,13 +255,13 @@ namespace {
auto whipDirToMummyDir(WhipAttack::Direction d) -> Mummy::Direction { auto whipDirToMummyDir(WhipAttack::Direction d) -> Mummy::Direction {
switch (d) { switch (d) {
case WhipAttack::Direction::NORTH: case WhipAttack::Direction::NORTH:
return Mummy::Direction::UP; return Mummy::Direction::NORTH;
case WhipAttack::Direction::SOUTH: case WhipAttack::Direction::SOUTH:
return Mummy::Direction::DOWN; return Mummy::Direction::SOUTH;
case WhipAttack::Direction::EAST: case WhipAttack::Direction::EAST:
return Mummy::Direction::RIGHT; return Mummy::Direction::EAST;
case WhipAttack::Direction::WEST: case WhipAttack::Direction::WEST:
return Mummy::Direction::LEFT; return Mummy::Direction::WEST;
} }
return Mummy::Direction::NONE; return Mummy::Direction::NONE;
} }
+5 -4
View File
@@ -155,10 +155,11 @@ class Entity {
// el sprite) amb el tile. Nullopt si l'entitat no en té. // el sprite) amb el tile. Nullopt si l'entitat no en té.
[[nodiscard]] auto getCollisionBox() const -> const std::optional<CollisionBox>& { return collision_box_; } [[nodiscard]] auto getCollisionBox() const -> const std::optional<CollisionBox>& { return collision_box_; }
// Flip del sprite. Les entitats estàtiques mai no el toquen (queda a // Flip del sprite, propagat des del motor d'animació
// SDL_FLIP_NONE); les dinàmiques que miren cap als costats (p. ex. // (`AnimationComponent::flipMode()`): cap entitat el decideix per
// Mummy en direcció horitzontal) el canvien quan gira. El manager // direcció — el YAML declara `flip_h: true` a l'animació simètrica
// el passa a Surface::render. // (p. ex. `walk_west`) i el setter d'animació l'aplica al `flip_`.
// El manager el passa a `Surface::render`.
[[nodiscard]] auto getFlip() const -> SDL_FlipMode { return flip_; } [[nodiscard]] auto getFlip() const -> SDL_FlipMode { return flip_; }
void setFlip(SDL_FlipMode flip) { flip_ = flip; } void setFlip(SDL_FlipMode flip) { flip_ = flip; }
@@ -65,7 +65,10 @@ void EntityManager::checkPlayerCollisions(const std::vector<Player*>& players) {
const auto BOX = entity->getCollisionRect(); const auto BOX = entity->getCollisionRect();
const int E_HEIGHT = entity->getHeight(); const int E_HEIGHT = entity->getHeight();
for (auto* p : players) { for (auto* p : players) {
if (p == nullptr || p->isOutOfPlay()) { continue; } // Filtrem també DYING: el jugador en cicle de mort no rep
// contacte amb hazards (un segon toc de mòmia mentre la roda
// ja és no-op; cap altra entitat l'ha de fer transicionar).
if (p == nullptr || p->isOutOfPlay() || p->isDying()) { continue; }
// Filtre per altura: si el jugador està en l'aire i està per // Filtre per altura: si el jugador està en l'aire i està per
// damunt de l'alçada del cos de l'entitat, no hi ha contacte. // damunt de l'alçada del cos de l'entitat, no hi ha contacte.
// És el mecanisme del salt per esquivar momies, roques, etc. // És el mecanisme del salt per esquivar momies, roques, etc.
+268 -126
View File
@@ -9,6 +9,7 @@
#include <utility> #include <utility>
#include "core/audio/audio.hpp" #include "core/audio/audio.hpp"
#include "core/input/gamepad_bindings.hpp"
#include "core/input/gamepad_manager.hpp" #include "core/input/gamepad_manager.hpp"
#include "core/input/input_state.hpp" #include "core/input/input_state.hpp"
#include "core/rendering/screen.hpp" #include "core/rendering/screen.hpp"
@@ -16,6 +17,7 @@
#include "core/resources/resource_helper.hpp" #include "core/resources/resource_helper.hpp"
#include "external/fkyaml_node.hpp" #include "external/fkyaml_node.hpp"
#include "game/entities/event_bus.hpp" #include "game/entities/event_bus.hpp"
#include "game/options.hpp"
#include "utils/defaults.hpp" #include "utils/defaults.hpp"
namespace { namespace {
@@ -69,6 +71,7 @@ Player::InertiaConfig Player::inertia_config;
Player::JumpConfig Player::jump_config; Player::JumpConfig Player::jump_config;
Player::HurtConfig Player::hurt_config; Player::HurtConfig Player::hurt_config;
Player::WhipConfig Player::whip_config; Player::WhipConfig Player::whip_config;
Player::DeathConfig Player::death_config;
void Player::loadFrom(const std::string& yaml_path) { void Player::loadFrom(const std::string& yaml_path) {
auto bytes = Resource::Helper::loadFile(yaml_path); auto bytes = Resource::Helper::loadFile(yaml_path);
@@ -120,6 +123,11 @@ void Player::loadFrom(const std::string& yaml_path) {
// jugador no atacarà efectivament (ATTACKING dura 0 ms i el flanc // jugador no atacarà efectivament (ATTACKING dura 0 ms i el flanc
// d'input es perd). // d'input es perd).
readWhipBlock(yaml); readWhipBlock(yaml);
// Bloc opcional `death:` — pausa de respawn i comptador inicial de
// vides. Si manca, el respawn és immediat i les vides queden a 3
// per defecte (no és un valor crític: avui no hi ha game over).
readDeathBlock(yaml);
} }
void Player::readCollisionBlock(const fkyaml::node& yaml, const std::string& yaml_path) { void Player::readCollisionBlock(const fkyaml::node& yaml, const std::string& yaml_path) {
@@ -179,6 +187,19 @@ void Player::readHurtBlock(const fkyaml::node& yaml) {
const auto& h = yaml["hurt"]; const auto& h = yaml["hurt"];
if (h.contains("speed")) { hurt_config.initial_speed_px_per_s = h["speed"].get_value<float>(); } if (h.contains("speed")) { hurt_config.initial_speed_px_per_s = h["speed"].get_value<float>(); }
if (h.contains("brake_time_ms")) { hurt_config.brake_time_ms = h["brake_time_ms"].get_value<int>(); } if (h.contains("brake_time_ms")) { hurt_config.brake_time_ms = h["brake_time_ms"].get_value<int>(); }
// So d'entrada a HURT — mateix patró que `jump.sound` i `whip.sound`.
if (h.contains("sound")) {
hurt_config.sound_key = h["sound"].get_value<std::string>();
Resource::Cache::get().loadSound(hurt_config.sound_key, "data/sounds/" + hurt_config.sound_key);
}
}
void Player::readDeathBlock(const fkyaml::node& yaml) {
death_config = DeathConfig{};
if (!yaml.contains("death")) { return; }
const auto& d = yaml["death"];
if (d.contains("respawn_delay_ms")) { death_config.respawn_delay_ms = d["respawn_delay_ms"].get_value<int>(); }
if (d.contains("lives")) { death_config.initial_lives = d["lives"].get_value<int>(); }
} }
void Player::readWhipBlock(const fkyaml::node& yaml) { void Player::readWhipBlock(const fkyaml::node& yaml) {
@@ -189,25 +210,30 @@ void Player::readWhipBlock(const fkyaml::node& yaml) {
if (w.contains("swing_ms")) { whip_config.swing_ms = w["swing_ms"].get_value<int>(); } if (w.contains("swing_ms")) { whip_config.swing_ms = w["swing_ms"].get_value<int>(); }
if (w.contains("cooldown_ms")) { whip_config.cooldown_ms = w["cooldown_ms"].get_value<int>(); } if (w.contains("cooldown_ms")) { whip_config.cooldown_ms = w["cooldown_ms"].get_value<int>(); }
if (w.contains("damage")) { whip_config.damage = w["damage"].get_value<int>(); } if (w.contains("damage")) { whip_config.damage = w["damage"].get_value<int>(); }
// So del swing: mateix patró que `jump.sound` — basename val de
// clau al cache i de path. Si manca, queda buit i no es dispara.
if (w.contains("sound")) {
whip_config.sound_key = w["sound"].get_value<std::string>();
Resource::Cache::get().loadSound(whip_config.sound_key, "data/sounds/" + whip_config.sound_key);
}
} }
void Player::applyCardinal(Cardinal c) { void Player::applyCardinal(Cardinal c) {
// El flip del sprite el governa l'animació al motor: només cal posar
// el `facing_` que tocaria; `animationName()` ja resol el sufix
// (`_west` per Cardinal::WEST) i el YAML hi declara `flip_h: true`.
switch (c) { switch (c) {
case Cardinal::NORTH: case Cardinal::NORTH:
facing_ = Facing::UP; facing_ = Facing::NORTH;
setFlip(SDL_FLIP_NONE);
break; break;
case Cardinal::SOUTH: case Cardinal::SOUTH:
facing_ = Facing::DOWN; facing_ = Facing::SOUTH;
setFlip(SDL_FLIP_NONE);
break; break;
case Cardinal::EAST: case Cardinal::EAST:
facing_ = Facing::SIDE; facing_ = Facing::EAST;
setFlip(SDL_FLIP_NONE);
break; break;
case Cardinal::WEST: case Cardinal::WEST:
facing_ = Facing::SIDE; facing_ = Facing::WEST;
setFlip(SDL_FLIP_HORIZONTAL);
break; break;
case Cardinal::NONE: case Cardinal::NONE:
break; break;
@@ -225,10 +251,10 @@ auto Player::cardinalFromInput(float vx, float vy) -> Cardinal {
} }
auto Player::cardinalFromString(const std::string& s) -> Cardinal { auto Player::cardinalFromString(const std::string& s) -> Cardinal {
if (s == "north" || s == "up") { return Cardinal::NORTH; } if (s == "north") { return Cardinal::NORTH; }
if (s == "south" || s == "down") { return Cardinal::SOUTH; } if (s == "south") { return Cardinal::SOUTH; }
if (s == "east" || s == "right") { return Cardinal::EAST; } if (s == "east") { return Cardinal::EAST; }
if (s == "west" || s == "left") { return Cardinal::WEST; } if (s == "west") { return Cardinal::WEST; }
throw std::runtime_error("Player: cardinal desconegut '" + s + "' al bloc turn.transitions"); throw std::runtime_error("Player: cardinal desconegut '" + s + "' al bloc turn.transitions");
} }
@@ -302,17 +328,14 @@ void Player::updateFacingAndTurn(float delta_time, float vx, float vy) {
turning_remaining_ms_ = static_cast<float>(turn_config.duration_ms); turning_remaining_ms_ = static_cast<float>(turn_config.duration_ms);
applyCardinal(intermediate_cardinal_); applyCardinal(intermediate_cardinal_);
} else if (vx != 0.0F) { } else if (vx != 0.0F) {
// Tria de facing original: prevalença lateral, flip segons el // Tria de facing: prevalença lateral, EAST/WEST segons el signe
// signe de vx; verticals purs van a UP/DOWN amb flip NONE; en // de vx; verticals purs van a NORTH/SOUTH. En aturar-se
// aturar-se conservem `facing_` per a l'animació stand. // conservem `facing_` per a l'animació stand.
facing_ = Facing::SIDE; facing_ = (vx < 0.0F) ? Facing::WEST : Facing::EAST;
setFlip(vx < 0.0F ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE);
} else if (vy < 0.0F) { } else if (vy < 0.0F) {
facing_ = Facing::UP; facing_ = Facing::NORTH;
setFlip(SDL_FLIP_NONE);
} else if (vy > 0.0F) { } else if (vy > 0.0F) {
facing_ = Facing::DOWN; facing_ = Facing::SOUTH;
setFlip(SDL_FLIP_NONE);
} }
} }
@@ -331,11 +354,18 @@ Player::Player(const std::string& anim_key, int id, float x, float y, const Cont
paint_tile_index_(paint_tile_index) { paint_tile_index_(paint_tile_index) {
setPos(x, y); setPos(x, y);
// Postura inicial mirant cap al jugador (cara avall), no de costat: // Postura inicial mirant cap al jugador (cara avall), no de costat:
// forcem facing_ a DOWN i l'animació `stand_down` abans del primer // forcem facing_ a SOUTH i l'animació `stand_south` abans del primer
// frame d'input — el primer `update` la preservarà si l'usuari no // frame d'input — el primer `update` la preservarà si l'usuari no
// prem cap tecla. // prem cap tecla.
facing_ = Facing::DOWN; facing_ = Facing::SOUTH;
setCurrentAnimation("stand_down"); setCurrentAnimation("stand_south");
// Comptador d'intents inicialitzat amb el valor del YAML (default 3
// si el bloc `death:` manca). Fora del cicle DYING no toca a ningú.
lives_remaining_ = death_config.initial_lives;
// Spawn point inicial: la posició del ctor val per defecte; el
// GameScene el reescriu via `setSpawnPoint` quan canvia de sala.
spawn_x_ = x;
spawn_y_ = y;
} }
void Player::update(float delta_time) { void Player::update(float delta_time) {
@@ -371,9 +401,13 @@ auto Player::collidesWithWorld(const SDL_FRect& box) const -> bool {
const bool MY_AIR = (state_ == State::AIRBORNE); const bool MY_AIR = (state_ == State::AIRBORNE);
const bool MY_HURT = (state_ == State::HURT); const bool MY_HURT = (state_ == State::HURT);
const float MY_Z = jump_z_; const float MY_Z = jump_z_;
return std::ranges::any_of(*all_players_, [this, &PROPOSED_HIT, MY_AIR, MY_HURT, MY_Z](const Player* p) { const bool MY_DYING = (state_ == State::DYING);
return std::ranges::any_of(*all_players_, [this, &PROPOSED_HIT, MY_AIR, MY_HURT, MY_DYING, MY_Z](const Player* p) {
if (p == nullptr || p == this) { return false; } if (p == nullptr || p == this) { return false; }
if (p->isOutOfPlay()) { return false; } if (p->isOutOfPlay()) { return false; }
// DYING és intangible per a PvP: el jugador mort no actua de
// mur, i un altre jugador en DYING no pot empentar tampoc.
if (MY_DYING || p->isDying()) { return false; }
if (MY_HURT || p->isHurt()) { return false; } if (MY_HURT || p->isHurt()) { return false; }
if (MY_AIR != p->isAirborne()) { return false; } if (MY_AIR != p->isAirborne()) { return false; }
// Aquí els dos estats coincideixen. Si tots dos volen, el filtre // Aquí els dos estats coincideixen. Si tots dos volen, el filtre
@@ -429,6 +463,25 @@ void Player::tick(float delta_time) {
// que GameScene el reactiva (respawn / spawnFromAir). // que GameScene el reactiva (respawn / spawnFromAir).
if (isOutOfPlay()) { return; } if (isOutOfPlay()) { return; }
// DYING: animació `death_spinning` + pausa de respawn. Ignorem tot
// l'input (ni moviment, ni salt, ni atac). El moviment queda
// congelat al lloc de la mort; `applyMovement` avança l'animació i
// detecta la fi per disparar el respawn. Mantenim els flancs de
// botó actualitzats perquè una tecla sostinguda no es comporte com
// a flanc nou en sortir del cicle.
if (state_ == State::DYING) {
(void)consumeJumpEdge();
(void)consumeAttackEdge();
input_vx_ = 0.0F;
input_vy_ = 0.0F;
intended_vx_ = 0.0F;
intended_vy_ = 0.0F;
motion_time_ms_ = 0.0F;
setVelX(0.0F);
setVelY(0.0F);
return;
}
// Reset per frame: el resolver del GameScene el tornarà a posar a // Reset per frame: el resolver del GameScene el tornarà a posar a
// true si aquest frame hi ha push actiu. Així quan el push acaba // true si aquest frame hi ha push actiu. Així quan el push acaba
// (un deixa d'empentar, l'altre comença a caminar), l'animació torna // (un deixa d'empentar, l'altre comença a caminar), l'animació torna
@@ -527,6 +580,12 @@ void Player::tick(float delta_time) {
return; return;
} }
// Refresca les vinculacions des d'Options en calent: així un remap fet
// al menú de servei té efecte immediatament, sense esperar al pròxim
// GameScene::onEnter. Replica el comportament d'AEE
// (`KeyRemap::update` que llig `Options::keys_game` cada frame).
refreshControlsFromOptions();
// Via `InputState` en lloc de `SDL_GetKeyboardState` directe: quan el // Via `InputState` en lloc de `SDL_GetKeyboardState` directe: quan el
// service menu (o qualsevol UI modal) està obert, el wrapper retorna // service menu (o qualsevol UI modal) està obert, el wrapper retorna
// un buffer tot-a-zero perquè el jugador no es mogui mentre l'usuari // un buffer tot-a-zero perquè el jugador no es mogui mentre l'usuari
@@ -602,6 +661,18 @@ void Player::tick(float delta_time) {
} }
} }
void Player::refreshControlsFromOptions() {
if (id_ != 0 && id_ != 1) { return; }
const auto& kb = (id_ == 0) ? Options::controls().keyboard_p1
: Options::controls().keyboard_p2;
controls_.up = kb.up;
controls_.down = kb.down;
controls_.left = kb.left;
controls_.right = kb.right;
controls_.jump = kb.jump;
controls_.attack = kb.attack;
}
auto Player::consumeJumpEdge() -> bool { auto Player::consumeJumpEdge() -> bool {
// Edge-detection independent per teclat i mando: si qualsevol dels // Edge-detection independent per teclat i mando: si qualsevol dels
// dos passa de no-premut a premut aquest frame, comptem com a salt. // dos passa de no-premut a premut aquest frame, comptem com a salt.
@@ -613,7 +684,11 @@ auto Player::consumeJumpEdge() -> bool {
jump_held_keyboard_ = KEY_NOW; jump_held_keyboard_ = KEY_NOW;
SDL_Gamepad* pad = GamepadManager::get().getForPlayer(id_); SDL_Gamepad* pad = GamepadManager::get().getForPlayer(id_);
const bool PAD_NOW = (pad != nullptr) && InputState::getGamepadButton(pad, SDL_GAMEPAD_BUTTON_SOUTH); const auto& slot = (id_ == 0) ? Options::controls().gamepad_p1
: Options::controls().gamepad_p2;
const int JUMP_BTN = GamepadBindings::get().getButton(slot.name, slot.path, GamepadBindings::Action::JUMP);
const bool PAD_NOW = (pad != nullptr) && !InputState::isBlocked() &&
GamepadBindings::isPressed(pad, JUMP_BTN);
if (PAD_NOW && !jump_held_gamepad_) { edge = true; } if (PAD_NOW && !jump_held_gamepad_) { edge = true; }
jump_held_gamepad_ = PAD_NOW; jump_held_gamepad_ = PAD_NOW;
@@ -621,16 +696,20 @@ auto Player::consumeJumpEdge() -> bool {
} }
auto Player::consumeAttackEdge() -> bool { auto Player::consumeAttackEdge() -> bool {
// Mateix patró que `consumeJumpEdge` però per al botó d'atac. Mando: // Mateix patró que `consumeJumpEdge` però per al botó d'atac. El botó
// SDL_GAMEPAD_BUTTON_WEST (X a Xbox / Quadrat a PlayStation), oposat // físic ve d'`GamepadBindings`, persistit per dispositiu — l'usuari
// a SOUTH per evitar topades semàntiques. // l'ha pogut remapejar des del menú de servei.
const bool* keys = InputState::getKeyboardState(); const bool* keys = InputState::getKeyboardState();
const bool KEY_NOW = keys[controls_.attack]; const bool KEY_NOW = keys[controls_.attack];
bool edge = (KEY_NOW && !attack_held_keyboard_); bool edge = (KEY_NOW && !attack_held_keyboard_);
attack_held_keyboard_ = KEY_NOW; attack_held_keyboard_ = KEY_NOW;
SDL_Gamepad* pad = GamepadManager::get().getForPlayer(id_); SDL_Gamepad* pad = GamepadManager::get().getForPlayer(id_);
const bool PAD_NOW = (pad != nullptr) && InputState::getGamepadButton(pad, SDL_GAMEPAD_BUTTON_WEST); const auto& slot = (id_ == 0) ? Options::controls().gamepad_p1
: Options::controls().gamepad_p2;
const int ATK_BTN = GamepadBindings::get().getButton(slot.name, slot.path, GamepadBindings::Action::ATTACK);
const bool PAD_NOW = (pad != nullptr) && !InputState::isBlocked() &&
GamepadBindings::isPressed(pad, ATK_BTN);
if (PAD_NOW && !attack_held_gamepad_) { edge = true; } if (PAD_NOW && !attack_held_gamepad_) { edge = true; }
attack_held_gamepad_ = PAD_NOW; attack_held_gamepad_ = PAD_NOW;
@@ -639,16 +718,16 @@ auto Player::consumeAttackEdge() -> bool {
auto Player::currentFacingCardinal() const -> Cardinal { auto Player::currentFacingCardinal() const -> Cardinal {
switch (facing_) { switch (facing_) {
case Facing::UP: case Facing::NORTH:
return Cardinal::NORTH; return Cardinal::NORTH;
case Facing::DOWN: case Facing::SOUTH:
return Cardinal::SOUTH; return Cardinal::SOUTH;
case Facing::SIDE: case Facing::EAST:
break; return Cardinal::EAST;
case Facing::WEST:
return Cardinal::WEST;
} }
// Lateral: el flip discrimina E/W. Sense flip = mira a la dreta return Cardinal::EAST;
// (convenció estable a tots els assets actuals).
return (getFlip() == SDL_FLIP_HORIZONTAL) ? Cardinal::WEST : Cardinal::EAST;
} }
void Player::enterAttacking(Cardinal facing) { void Player::enterAttacking(Cardinal facing) {
@@ -662,6 +741,13 @@ void Player::enterAttacking(Cardinal facing) {
if (facing == Cardinal::NONE) { return; } if (facing == Cardinal::NONE) { return; }
// So del swing — disparat ací (no des del WhipAttack) perquè és el
// jugador qui executa l'acció; si demà el spawn falla per qualsevol
// motiu, el so del gest segueix sonant.
if (!whip_config.sound_key.empty() && Audio::get() != nullptr) {
Audio::get()->playSound(whip_config.sound_key, Audio::Group::GAME);
}
// Mapa de Cardinal → int al payload (vegeu `WhipSpawnPayload`). // Mapa de Cardinal → int al payload (vegeu `WhipSpawnPayload`).
int dir_int = 0; int dir_int = 0;
switch (facing) { switch (facing) {
@@ -707,7 +793,7 @@ void Player::startJump() {
// Un gir de 180° en curs es compromet immediatament: si l'usuari // Un gir de 180° en curs es compromet immediatament: si l'usuari
// ha disparat un giro (oest→est, p.ex.) i salta abans que s'esgote // ha disparat un giro (oest→est, p.ex.) i salta abans que s'esgote
// el timer, l'animació del salt ha de mostrar la direcció final // el timer, l'animació del salt ha de mostrar la direcció final
// (jump lateral), no la intermèdia (jump_down). Aplem el pending // (jump lateral), no la intermèdia (jump_south). Aplem el pending
// ara mateix i resetem l'estat del gir. Si no hi havia gir en // ara mateix i resetem l'estat del gir. Si no hi havia gir en
// curs, és no-op. // curs, és no-op.
if (turning_remaining_ms_ > 0.0F) { if (turning_remaining_ms_ > 0.0F) {
@@ -802,6 +888,9 @@ void Player::enterHurt(Cardinal preferred) {
// Activem l'estat i fixem la velocitat segons la direcció triada. // Activem l'estat i fixem la velocitat segons la direcció triada.
state_ = State::HURT; state_ = State::HURT;
if (!hurt_config.sound_key.empty() && Audio::get() != nullptr) {
Audio::get()->playSound(hurt_config.sound_key, Audio::Group::GAME);
}
applyCardinal(CHOSEN); applyCardinal(CHOSEN);
last_input_cardinal_ = CHOSEN; last_input_cardinal_ = CHOSEN;
turning_remaining_ms_ = 0.0F; turning_remaining_ms_ = 0.0F;
@@ -860,7 +949,9 @@ void Player::resolveLandingOverlap() {
if (other == nullptr || other == this) { continue; } if (other == nullptr || other == this) { continue; }
// Salta jugadors fora del joc: un caigut sobre un slot DISABLED // Salta jugadors fora del joc: un caigut sobre un slot DISABLED
// (o sobre un EXITED encara al vector) no els ha de projectar. // (o sobre un EXITED encara al vector) no els ha de projectar.
if (other->isOutOfPlay()) { continue; } // També DYING — un jugador en cicle de mort no rep cap més
// efecte fins que respawneja.
if (other->isOutOfPlay() || other->isDying()) { continue; }
// Test amb la hit-box (8×8 de contacte precís), no la base-box. // Test amb la hit-box (8×8 de contacte precís), no la base-box.
// Coherent amb la resta de col·lisions actor-actor del joc: // Coherent amb la resta de col·lisions actor-actor del joc:
// momies, fuetades futures, resolver PvP… tots usen hit-box. Un // momies, fuetades futures, resolver PvP… tots usen hit-box. Un
@@ -908,6 +999,11 @@ void Player::applyMovement(float delta_time) {
resolveLandingOverlap(); resolveLandingOverlap();
} }
if (state_ == State::DYING) {
applyDyingMovement(delta_time);
return;
}
if (state_ == State::HURT) { if (state_ == State::HURT) {
applyHurtMovement(delta_time); applyHurtMovement(delta_time);
return; return;
@@ -979,6 +1075,75 @@ void Player::applyMovement(float delta_time) {
if (state_ == State::GROUNDED) { publishTileEnteredIfChanged(); } if (state_ == State::GROUNDED) { publishTileEnteredIfChanged(); }
} }
void Player::enterDying() {
// Idempotent. Un segon contacte de momia mentre el jugador ja està
// en DYING no reinicia l'animació ni el comptador. Tampoc disparem
// el cicle si el jugador està fora de joc (EXITED al passar la
// porta, DISABLED durant les transicions de sala).
if (state_ == State::DYING || isOutOfPlay()) { return; }
state_ = State::DYING;
intended_vx_ = 0.0F;
intended_vy_ = 0.0F;
input_vx_ = 0.0F;
input_vy_ = 0.0F;
motion_time_ms_ = 0.0F;
being_pushed_ = false;
jump_z_ = 0.0F;
jump_vz_ = 0.0F;
landing_stun_remaining_ms_ = 0.0F;
attacking_remaining_ms_ = 0.0F;
turning_remaining_ms_ = 0.0F;
pending_cardinal_ = Cardinal::NONE;
intermediate_cardinal_ = Cardinal::NONE;
last_input_cardinal_ = Cardinal::NONE;
setVelX(0.0F);
setVelY(0.0F);
death_animation_complete_ = false;
respawn_remaining_ms_ = 0.0F;
setCurrentAnimation("death_spinning");
// So del cop letal: reaprofitem `hurt.wav` (ja precarregat per
// `readHurtBlock`). El bloc `death:` no té camp de so propi —
// semànticament l'impacte mortal és la versió forta de `hurt`.
if (!hurt_config.sound_key.empty() && Audio::get() != nullptr) {
Audio::get()->playSound(hurt_config.sound_key, Audio::Group::GAME);
}
}
void Player::applyDyingMovement(float delta_time) {
// Avancem l'animació; el sprite no es mou (la posició queda
// congelada al punt de la mort). `animationIsCompleted()` flanqueja
// un cop la roda arriba al darrer frame (loop:false). Llavors
// descomptem `respawn_delay_ms` mantenint el sprite al darrer frame
// i, en arribar a 0, disparem el respawn.
animate(delta_time);
if (!death_animation_complete_ && animationIsCompleted()) {
death_animation_complete_ = true;
respawn_remaining_ms_ = static_cast<float>(death_config.respawn_delay_ms);
}
if (death_animation_complete_) {
respawn_remaining_ms_ -= delta_time * 1000.0F;
if (respawn_remaining_ms_ <= 0.0F) {
respawnFromDying();
}
}
}
void Player::respawnFromDying() {
// Vides: decreix sempre. Pot anar a negatius — no hi ha game over.
--lives_remaining_;
death_animation_complete_ = false;
respawn_remaining_ms_ = 0.0F;
// Cinemàtica idèntica a la d'INTRO de sala: el jugador cau des de
// dalt fins al passadís d'spawn (`spawnFromAir` reseteja primer i
// després posa AIRBORNE amb l'altura derivada de la geometria).
// Així el respawn post-mort comparteix tota la sensació visual i
// física del primer spawn — incloent el LANDING_STUN final.
spawnFromAir(spawn_x_, spawn_y_);
}
void Player::applyHurtMovement(float delta_time) { void Player::applyHurtMovement(float delta_time) {
// En HURT el jugador es desplaça segons `intended_v*` (escrit a // En HURT el jugador es desplaça segons `intended_v*` (escrit a
// `enterHurt` i decremental a `tick`). Si `tryMove` no avança // `enterHurt` i decremental a `tick`). Si `tryMove` no avança
@@ -1031,6 +1196,13 @@ void Player::render() {
// vector. // vector.
if (isOutOfPlay()) { return; } if (isOutOfPlay()) { return; }
// Limbo post-mort: durant `respawn_delay_ms` entre la fi de
// `death_spinning` i el `spawnFromAir`, el jugador queda invisible.
// Així el cicle complet és: animació rodant → desapareix → ressuscita
// caient des de dalt. Sense aquest gat, el sprite quedaria pintat al
// darrer frame de la roda mentre passa el delay.
if (state_ == State::DYING && death_animation_complete_) { return; }
// Mentre el jugador està enlaire, el sprite es desplaça visualment // Mentre el jugador està enlaire, el sprite es desplaça visualment
// cap amunt `jump_z_` píxels. Modifiquem `pos_.y` (la posició entera // cap amunt `jump_z_` píxels. Modifiquem `pos_.y` (la posició entera
// de render) abans de cridar el render base, i la restaurem després // de render) abans de cridar el render base, i la restaurem després
@@ -1085,91 +1257,56 @@ void Player::renderShadow() const {
Screen::get().blendRect(SX, SY, SHADOW_W, SHADOW_H, SHADOW_ARGB, SHADOW_ALPHA); Screen::get().blendRect(SX, SY, SHADOW_W, SHADOW_H, SHADOW_ARGB, SHADOW_ALPHA);
} }
auto Player::animationName(bool moving) const -> const char* { // Sufix d'animació per cardinal — empat 1:1 amb l'enum `Facing` i el
// En l'aire forcem la variant `jump_*` (per direcció), independent de // sufix declarat als YAMLs de jugador (sam.yaml, morcus.yaml).
// si hi ha velocitat horitzontal: per al jugador, l'estat visual del auto Player::facingSuffix(Facing facing) -> const char* {
// salt és per direcció, no per velocitat. switch (facing) {
if (state_ == State::AIRBORNE) { case Facing::NORTH:
switch (facing_) { return "_north";
case Facing::UP: case Facing::SOUTH:
return "jump_up"; return "_south";
case Facing::DOWN: case Facing::EAST:
return "jump_down"; return "_east";
case Facing::SIDE: case Facing::WEST:
break; return "_west";
}
return "jump";
} }
// En LANDING_STUN, `stun_*` (per direcció): mostrem el frame return "_east";
// d'aterratge fins que s'esgota el timer. La direcció ve fixada del }
// moment del despegue (no es canvia mentre estem en l'aire).
if (state_ == State::LANDING_STUN) { // Prefix d'animació per estat. ATTACKING té dues sub-fases: durant
switch (facing_) { // `swing_ms` és `attack_*` (animació del braç llançant el látigo); durant
case Facing::UP: // `cooldown_ms` posterior, `recover_*`. A AIRBORNE/LANDING_STUN/HURT, la
return "stun_up"; // variant és per direcció, no per velocitat: l'estat bloqueja el `moving`
case Facing::DOWN: // argument i no fa servir walk/stand.
return "stun_down"; auto Player::statePrefix(State state, bool moving, bool in_attack_swing) -> const char* {
case Facing::SIDE: switch (state) {
break; case State::AIRBORNE:
} return "jump";
return "stun"; case State::LANDING_STUN:
} return "stun";
// En HURT, `hurt_*` (per direcció): el jugador llisca sense case State::HURT:
// control en la direcció triada per `enterHurt`. Mateixa estructura return "hurt";
// que walk per facilitat (ara mateix els frames són clons). case State::ATTACKING:
if (state_ == State::HURT) { return in_attack_swing ? "attack" : "recover";
switch (facing_) { case State::DYING:
case Facing::UP: // L'animació concreta (`death_spinning`) la fixa `enterDying`;
return "hurt_up"; // `applyDyingMovement` no torna a cridar `setCurrentAnimation`,
case Facing::DOWN: // així que aquest valor no s'usa mai. El retornem només per
return "hurt_down"; // exhaustivitat del switch.
case Facing::SIDE: return "death";
break; case State::GROUNDED:
} case State::EXITED:
return "hurt"; case State::DISABLED:
}
// ATTACKING té dues sub-fases: durant `swing_ms` el sprite és
// `attack_*` (animació del braç llançant el látigo); durant
// `cooldown_ms` posterior canviem a `recover_*` per a frames
// pròpis de recuperació. Mentre l'art no estiga dibuixat, els
// YAMLs apunten `recover_*` als mateixos frames que `stand_*` —
// visualment és idèntic, però la separació deixa l'arquitectura
// preparada perquè en afegir frames reals només calga tocar el
// YAML, no el codi.
if (state_ == State::ATTACKING) {
const bool IN_SWING = (attacking_remaining_ms_ > static_cast<float>(whip_config.cooldown_ms));
if (IN_SWING) {
switch (facing_) {
case Facing::UP:
return "attack_up";
case Facing::DOWN:
return "attack_down";
case Facing::SIDE:
break;
}
return "attack";
}
switch (facing_) {
case Facing::UP:
return "recover_up";
case Facing::DOWN:
return "recover_down";
case Facing::SIDE:
break;
}
return "recover";
}
switch (facing_) {
case Facing::UP:
return moving ? "walk_up" : "stand_up";
case Facing::DOWN:
return moving ? "walk_down" : "stand_down";
case Facing::SIDE:
break; break;
} }
return moving ? "walk" : "stand"; return moving ? "walk" : "stand";
} }
auto Player::animationName(bool moving) const -> std::string {
const bool IN_ATTACK_SWING = (attacking_remaining_ms_ > static_cast<float>(whip_config.cooldown_ms));
return std::string(statePrefix(state_, moving, IN_ATTACK_SWING)) + facingSuffix(facing_);
}
auto Player::getCurrentTile() const -> std::pair<int, int> { auto Player::getCurrentTile() const -> std::pair<int, int> {
// El grid del tilemap és de `tile_size` píxels i està 16-alineat // El grid del tilemap és de `tile_size` píxels i està 16-alineat
// respecte a l'origen del tilemap (pot ser negatiu per overscan). El // respecte a l'origen del tilemap (pot ser negatiu per overscan). El
@@ -1223,16 +1360,21 @@ void Player::respawn(float x, float y) {
jump_z_ = 0.0F; jump_z_ = 0.0F;
jump_vz_ = 0.0F; jump_vz_ = 0.0F;
landing_stun_remaining_ms_ = 0.0F; landing_stun_remaining_ms_ = 0.0F;
attacking_remaining_ms_ = 0.0F;
turning_remaining_ms_ = 0.0F; turning_remaining_ms_ = 0.0F;
pending_cardinal_ = Cardinal::NONE; pending_cardinal_ = Cardinal::NONE;
intermediate_cardinal_ = Cardinal::NONE; intermediate_cardinal_ = Cardinal::NONE;
last_input_cardinal_ = Cardinal::NONE; last_input_cardinal_ = Cardinal::NONE;
// Flags del cicle DYING (que `respawnFromDying` també neteja, però
// si algú crida `respawn` directament des d'un altre estat, han de
// quedar coherents per al pròxim cicle de mort).
death_animation_complete_ = false;
respawn_remaining_ms_ = 0.0F;
// Reseteig del facing al d'spawn original (mirant cap a la càmera): // Reseteig del facing al d'spawn original (mirant cap a la càmera):
// sense això, un EXITED que entrava per la porta amb facing UP // sense això, un EXITED que entrava per la porta amb facing NORTH
// re-aparix d'esquena al pròxim cicle. Mateixa lògica que el ctor. // re-aparix d'esquena al pròxim cicle. Mateixa lògica que el ctor.
facing_ = Facing::DOWN; facing_ = Facing::SOUTH;
setFlip(SDL_FLIP_NONE); setCurrentAnimation("stand_south");
setCurrentAnimation("stand_down");
setPosX(x); setPosX(x);
setPosY(y); setPosY(y);
} }
@@ -1279,10 +1421,10 @@ void Player::spawnFromAir(float x, float y) {
intended_vy_ = 0.0F; intended_vy_ = 0.0F;
// Mirant cap avall durant la caiguda — `animationName(AIRBORNE)` amb // Mirant cap avall durant la caiguda — `animationName(AIRBORNE)` amb
// facing DOWN ja resol a `jump_down`. `respawn` ja ha posat facing a // facing SOUTH ja resol a `jump_south`. `respawn` ja ha posat facing a
// DOWN; ho deixem explícit ací per claredat de la cinemàtica. // SOUTH; ho deixem explícit ací per claredat de la cinemàtica.
facing_ = Facing::DOWN; facing_ = Facing::SOUTH;
setCurrentAnimation("jump_down"); setCurrentAnimation("jump_south");
} }
auto Player::getBaseBox() const -> SDL_FRect { auto Player::getBaseBox() const -> SDL_FRect {
+113 -17
View File
@@ -68,6 +68,13 @@ class Player : public AnimatedSprite {
// tots els slots durant les transicions de sala abans del spawn // tots els slots durant les transicions de sala abans del spawn
// aeri, i (c) en el futur, l'estat post-mort abans del respawn. // aeri, i (c) en el futur, l'estat post-mort abans del respawn.
DISABLED, DISABLED,
// Mort en curs: animació `death_spinning` (3 voltes horàries) +
// pausa `respawn_delay_ms` abans del respawn. Bloqueja input i
// moviment; no rep contacte amb hazards (cf. `isDying`). Quan el
// timer s'esgota, `respawn(spawn_x_, spawn_y_)` torna a GROUNDED
// i decrementa el comptador de vides (pot anar a negatius — no
// hi ha game over).
DYING,
}; };
// Geometria carregada des del YAML del player (p.ex. `data/player/sam.yaml`). // Geometria carregada des del YAML del player (p.ex. `data/player/sam.yaml`).
@@ -203,6 +210,7 @@ class Player : public AnimatedSprite {
[[nodiscard]] auto isHurt() const -> bool { return state_ == State::HURT; } [[nodiscard]] auto isHurt() const -> bool { return state_ == State::HURT; }
[[nodiscard]] auto isExited() const -> bool { return state_ == State::EXITED; } [[nodiscard]] auto isExited() const -> bool { return state_ == State::EXITED; }
[[nodiscard]] auto isDisabled() const -> bool { return state_ == State::DISABLED; } [[nodiscard]] auto isDisabled() const -> bool { return state_ == State::DISABLED; }
[[nodiscard]] auto isDying() const -> bool { return state_ == State::DYING; }
// Resum dels estats "fora del món": EXITED i DISABLED comparteixen el // Resum dels estats "fora del món": EXITED i DISABLED comparteixen el
// mateix perfil físic (no es pinten, no llegeixen input, no // mateix perfil físic (no es pinten, no llegeixen input, no
// col·lisionen, no participen al resolver PvP). Tots dos gates de // col·lisionen, no participen al resolver PvP). Tots dos gates de
@@ -240,6 +248,28 @@ class Player : public AnimatedSprite {
being_pushed_ = false; being_pushed_ = false;
} }
// Mata el jugador d'un sol toc: transició a DYING, animació
// `death_spinning`, posició congelada, input ignorat. Idempotent —
// si ja està DYING, EXITED o DISABLED, és no-op (un segon toc de
// momia mentre la roda no la reinicia). Quan l'animació acaba i
// `respawn_delay_ms` expira, `respawn(spawn_x_, spawn_y_)` torna a
// GROUNDED i `lives_remaining_` decreix en 1 (pot anar a negatius).
void enterDying();
// Coordenades del spawn de respawn (cantó superior-esquerra del
// sprite, mateixa convenció que `respawn(x, y)`). El GameScene les
// assigna després de `computeSpawn` al ctor i a cada `loadRoom`. Sense
// això, el respawn enviaria el jugador a (0, 0).
void setSpawnPoint(float x, float y) {
spawn_x_ = x;
spawn_y_ = y;
}
// Comptador d'intents (estil Mario, NO HP). Decreix a cada respawn;
// no es clampa a 0 — permet anar a negatius perquè avui no hi ha
// game over. Públic per a HUD i tests.
[[nodiscard]] auto getLivesRemaining() const -> int { return lives_remaining_; }
// Reset complet a l'estat inicial: posició a (x, y), velocitats a 0, // Reset complet a l'estat inicial: posició a (x, y), velocitats a 0,
// estat GROUNDED, qualsevol flag de control reinicialitzat. Cridat // estat GROUNDED, qualsevol flag de control reinicialitzat. Cridat
// per GameScene a la transició ROOM_OUTRO → ROOM_INTRO. Idempotent // per GameScene a la transició ROOM_OUTRO → ROOM_INTRO. Idempotent
@@ -351,9 +381,12 @@ class Player : public AnimatedSprite {
void snapToHorizontalLane(float delta_time); void snapToHorizontalLane(float delta_time);
void snapToVerticalLane(float delta_time); void snapToVerticalLane(float delta_time);
// Nom d'animació per al `facing_` actual. Si `moving` és cert s'usa // Nom d'animació per al `facing_` actual. Composat com a `prefix +
// la variant `walk*`, altrament la variant `stand*`. // suffix` via els dos helpers estàtics (declarats junt amb `Facing`
[[nodiscard]] auto animationName(bool moving) const -> const char*; // més avall) — el prefix depèn de l'estat (walk/stand/jump/stun/hurt/
// attack/recover), el sufix del cardinal (`_north`/`_south`/`_east`/
// `_west`).
[[nodiscard]] auto animationName(bool moving) const -> std::string;
void snapToLane( void snapToLane(
float delta_time, float delta_time,
float base, float base,
@@ -374,6 +407,12 @@ class Player : public AnimatedSprite {
float (Player::*get_pos)() const, float (Player::*get_pos)() const,
void (Player::*set_pos)(float)) -> bool; void (Player::*set_pos)(float)) -> bool;
// Refresca `controls_` des d'`Options::controls()` cada frame. Replica
// el patró d'AEE on `KeyRemap::update` reflecteix Options::keys_game al
// buffer del teclat cada tick: així un remap fet al menú de servei té
// efecte immediat sense haver de tornar a entrar a la GameScene.
void refreshControlsFromOptions();
int id_{-1}; int id_{-1};
Controls controls_; Controls controls_;
@@ -428,19 +467,24 @@ class Player : public AnimatedSprite {
// reinstanciar-lo. // reinstanciar-lo.
Map::Info map_; Map::Info map_;
// Direcció "cap on mira" el jugador. Governa quin conjunt d'animacions // Direcció "cap on mira" el jugador. Una entrada per cada cardinal: el
// s'aplica (lateral, amunt, avall) i si el sprite es flipeja o no. Els // codi del joc no toca mai el flip — l'oest reprodueix `*_west`, que al
// moviments purament horitzontals o diagonals prenen `SIDE` (prevalença // YAML declara `flip_h: true`, i és el motor d'animació qui aplica el
// lateral), els purament verticals fan `UP`/`DOWN`. En aturar-se es // flip al sprite. Els moviments diagonals prenen el component lateral
// preserva l'última orientació perquè la versió stand coincideixi. // (EAST/WEST); en aturar-se es preserva l'última orientació perquè la
enum class Facing : std::uint8_t { SIDE, // versió stand coincideixi.
UP, enum class Facing : std::uint8_t { EAST,
DOWN }; WEST,
Facing facing_{Facing::SIDE}; NORTH,
SOUTH };
Facing facing_{Facing::EAST};
// Helpers de composició de `animationName`. Han de viure ací (després
// de la declaració de `Facing`) perquè la signatura el referencia.
static auto facingSuffix(Facing facing) -> const char*;
static auto statePrefix(State state, bool moving, bool in_attack_swing) -> const char*;
// Cardinal complet (N/S/E/W) usat per a la detecció de girs de 180°. // Cardinal complet (N/S/E/W) usat per a la detecció de girs de 180°.
// El parell (Facing, flip) només té 3+1 estats; aquesta enum n'afegeix
// la 4a sense reorganitzar el sistema d'animacions.
// Pública perquè el `WhipAttack` la passa a `enterHurt` quan colpeja // Pública perquè el `WhipAttack` la passa a `enterHurt` quan colpeja
// un altre jugador (friendly fire). // un altre jugador (friendly fire).
public: public:
@@ -511,6 +555,11 @@ class Player : public AnimatedSprite {
struct HurtConfig { struct HurtConfig {
float initial_speed_px_per_s{0.0F}; // Velocitat amb què surt projectat (px/s). float initial_speed_px_per_s{0.0F}; // Velocitat amb què surt projectat (px/s).
int brake_time_ms{0}; // Temps en que la velocitat decau a 0. int brake_time_ms{0}; // Temps en que la velocitat decau a 0.
// Fitxer .wav reproduït en entrar a HURT (relatiu a `data/sounds/`).
// Mateixa convenció que la resta de sons del jugador: el basename val
// de clau al Resource::Cache i de path. Buit o absent = entrada
// silenciosa.
std::string sound_key;
}; };
static HurtConfig hurt_config; static HurtConfig hurt_config;
@@ -529,6 +578,10 @@ class Player : public AnimatedSprite {
int swing_ms{0}; int swing_ms{0};
int cooldown_ms{0}; int cooldown_ms{0};
int damage{0}; int damage{0};
// Fitxer .wav reproduït al moment de disparar el swing (relatiu a
// `data/sounds/`). Convenció: el basename val tant de clau al
// Resource::Cache com de path. Buit o absent = swing silenciós.
std::string sound_key;
}; };
static WhipConfig whip_config; static WhipConfig whip_config;
@@ -537,15 +590,29 @@ class Player : public AnimatedSprite {
// d'input es perd silenciosament). // d'input es perd silenciosament).
static void readWhipBlock(const fkyaml::node& yaml); static void readWhipBlock(const fkyaml::node& yaml);
// Configuració de mort/respawn llegida del bloc `death:` del YAML.
// `respawn_delay_ms` és la pausa entre la fi de `death_spinning` i la
// reaparició al spawn; `initial_lives` és el comptador d'intents amb
// què naix cada Player. Si el bloc manca, defaults segurs (0/3).
struct DeathConfig {
int respawn_delay_ms{0};
int initial_lives{3};
};
static DeathConfig death_config;
// Llig el bloc `death:` del YAML i n'omple `death_config`. Si manca,
// `respawn_delay_ms` queda a 0 (el respawn és immediat al final de
// l'animació) i `initial_lives` es manté al default (3).
static void readDeathBlock(const fkyaml::node& yaml);
// Activa l'estat ATTACKING en la direcció cardinal indicada (la // Activa l'estat ATTACKING en la direcció cardinal indicada (la
// direcció cap on mira el jugador) i publica `whip_spawn` al bus // direcció cap on mira el jugador) i publica `whip_spawn` al bus
// perquè el GameScene cree el `WhipAttack` corresponent. Posa el // perquè el GameScene cree el `WhipAttack` corresponent. Posa el
// timer `attacking_remaining_ms_` a `swing_ms + cooldown_ms`. // timer `attacking_remaining_ms_` a `swing_ms + cooldown_ms`.
void enterAttacking(Cardinal facing); void enterAttacking(Cardinal facing);
// Cardinal que correspon al `(facing_, flip_)` actual del sprite — // Cardinal corresponent al `facing_` actual del sprite — la direcció
// la direcció cap on mira el jugador. Si `facing_ == SIDE` el // cap on mira el jugador. Mapeig 1:1 amb `Facing` (mai NONE).
// resultat depèn del flip; UP/DOWN són trivials. Mai retorna NONE.
[[nodiscard]] auto currentFacingCardinal() const -> Cardinal; [[nodiscard]] auto currentFacingCardinal() const -> Cardinal;
// Activa l'estat HURT: el jugador queda projectat amb velocitat // Activa l'estat HURT: el jugador queda projectat amb velocitat
@@ -608,6 +675,35 @@ class Player : public AnimatedSprite {
// bloquejat i no rep input direccional ni de salt ni d'atac. // bloquejat i no rep input direccional ni de salt ni d'atac.
float attacking_remaining_ms_{0.0F}; float attacking_remaining_ms_{0.0F};
// Estat de DYING. `death_animation_complete_` es marca quan
// `animationIsCompleted()` retorna true per primera vegada al cicle
// actual; només llavors comença a descomptar `respawn_remaining_ms_`.
// Quan el delay arriba a 0, `respawnFromDying()` aplica `respawn(...)`
// i decreix el comptador de vides.
bool death_animation_complete_{false};
float respawn_remaining_ms_{0.0F};
// Comptador d'intents (estil Mario). Inicialitzat a `death_config.
// initial_lives` al ctor; decreix una vegada per cada respawn
// post-mort (no per respawns de canvi de sala). Pot anar a negatius:
// avui no hi ha game over.
int lives_remaining_{0};
// Coordenades del spawn de respawn (cantó superior-esquerra del
// sprite). Les assigna el GameScene via `setSpawnPoint`. 0/0 fins que
// el GameScene n'invoque el setter al ctor / `loadRoom`.
float spawn_x_{0.0F};
float spawn_y_{0.0F};
// Aplica el respawn al final del cicle DYING: decreix vides (sense
// clamp), neteja els flags interns del cicle i delega a `respawn`
// amb les coordenades del spawn.
void respawnFromDying();
// Branca de `applyMovement` per a DYING. Avança l'animació, detecta
// la fi i descompta el delay; al final, dispara el respawn.
void applyDyingMovement(float delta_time);
// Llig el botó de salt aquest frame (teclat + mando assignat) i, // Llig el botó de salt aquest frame (teclat + mando assignat) i,
// amb edge-detection, retorna true si s'ha de despegar el salt. // amb edge-detection, retorna true si s'ha de despegar el salt.
[[nodiscard]] auto consumeJumpEdge() -> bool; [[nodiscard]] auto consumeJumpEdge() -> bool;
+68
View File
@@ -169,11 +169,51 @@ namespace {
readField(node, "path", dst.path); readField(node, "path", dst.path);
} }
// Llegeix una tecla del YAML pel nom (com `SDL_GetScancodeName`). Si la
// clau no hi és o és invàlida, deixa `dst` intacte per preservar el
// default. Replica la política d'AEE (`KeyConfig::applyOverrides`).
void readScancodeField(const fkyaml::node& node, const char* key, SDL_Scancode& dst) {
if (!node.contains(key)) { return; }
const auto NAME = node[key].get_value<std::string>();
const SDL_Scancode SC = SDL_GetScancodeFromName(NAME.c_str());
if (SC != SDL_SCANCODE_UNKNOWN) { dst = SC; }
}
void parseKeyboard(const fkyaml::node& parent, const char* key, Options::Keyboard& dst) {
if (!parent.contains(key)) { return; }
const auto& node = parent[key];
readScancodeField(node, "up", dst.up);
readScancodeField(node, "down", dst.down);
readScancodeField(node, "left", dst.left);
readScancodeField(node, "right", dst.right);
readScancodeField(node, "jump", dst.jump);
readScancodeField(node, "attack", dst.attack);
}
void parseControls(const fkyaml::node& yaml) { void parseControls(const fkyaml::node& yaml) {
if (!yaml.contains("controls")) { return; } if (!yaml.contains("controls")) { return; }
const auto& node = yaml["controls"]; const auto& node = yaml["controls"];
parseGamepadAssignment(node, "gamepad_p1", g_controls.gamepad_p1); parseGamepadAssignment(node, "gamepad_p1", g_controls.gamepad_p1);
parseGamepadAssignment(node, "gamepad_p2", g_controls.gamepad_p2); parseGamepadAssignment(node, "gamepad_p2", g_controls.gamepad_p2);
parseKeyboard(node, "keyboard_p1", g_controls.keyboard_p1);
parseKeyboard(node, "keyboard_p2", g_controls.keyboard_p2);
}
// Escriu una tecla com a string del nom SDL ("Up", "W", ...). Si
// l'scancode és UNKNOWN o sense nom registrat, escriu "" — no en farem
// override en càrrega gràcies a `readScancodeField`.
void writeKeyboard(std::ostringstream& out, const char* key, const Options::Keyboard& kb) {
const auto NAME_OR_EMPTY = [](SDL_Scancode sc) -> std::string {
const char* n = SDL_GetScancodeName(sc);
return (n != nullptr) ? std::string{n} : std::string{};
};
out << " " << key << ":\n";
out << " up: \"" << NAME_OR_EMPTY(kb.up) << "\"\n";
out << " down: \"" << NAME_OR_EMPTY(kb.down) << "\"\n";
out << " left: \"" << NAME_OR_EMPTY(kb.left) << "\"\n";
out << " right: \"" << NAME_OR_EMPTY(kb.right) << "\"\n";
out << " jump: \"" << NAME_OR_EMPTY(kb.jump) << "\"\n";
out << " attack: \"" << NAME_OR_EMPTY(kb.attack) << "\"\n";
} }
// Aplica les assignacions persistides al GamepadManager. Ordre garantit // Aplica les assignacions persistides al GamepadManager. Ordre garantit
@@ -561,6 +601,32 @@ namespace Options {
saveToFile(); saveToFile();
} }
void setKeyboardBinding(int player_id, KeyboardAction action, SDL_Scancode scancode) {
if (player_id < 0 || player_id > 1) { return; }
auto& kb = (player_id == 0) ? g_controls.keyboard_p1 : g_controls.keyboard_p2;
switch (action) {
case KeyboardAction::UP:
kb.up = scancode;
break;
case KeyboardAction::DOWN:
kb.down = scancode;
break;
case KeyboardAction::LEFT:
kb.left = scancode;
break;
case KeyboardAction::RIGHT:
kb.right = scancode;
break;
case KeyboardAction::JUMP:
kb.jump = scancode;
break;
case KeyboardAction::ATTACK:
kb.attack = scancode;
break;
}
saveToFile();
}
void setConfigFile(const std::string& path) { void setConfigFile(const std::string& path) {
g_config_file_path = path; g_config_file_path = path;
} }
@@ -666,6 +732,8 @@ namespace Options {
out << " gamepad_p2:\n"; out << " gamepad_p2:\n";
out << " name: \"" << g_controls.gamepad_p2.name << "\"\n"; out << " name: \"" << g_controls.gamepad_p2.name << "\"\n";
out << " path: \"" << g_controls.gamepad_p2.path << "\"\n"; out << " path: \"" << g_controls.gamepad_p2.path << "\"\n";
writeKeyboard(out, "keyboard_p1", g_controls.keyboard_p1);
writeKeyboard(out, "keyboard_p2", g_controls.keyboard_p2);
return writeFile(g_config_file_path, out.str()); return writeFile(g_config_file_path, out.str());
} }
+49
View File
@@ -1,5 +1,8 @@
#pragma once #pragma once
#include <SDL3/SDL.h>
#include <cstdint>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -114,9 +117,49 @@ namespace Options {
std::string path; std::string path;
}; };
// Tecles de joc d'un jugador (vinculacions remapeables des del menú).
// Cada camp és un `SDL_Scancode` literal; els defaults venen de
// `Defaults::KeysGame*` segons el slot (P1 fletxes, P2 WASD).
struct Keyboard {
SDL_Scancode up{SDL_SCANCODE_UNKNOWN};
SDL_Scancode down{SDL_SCANCODE_UNKNOWN};
SDL_Scancode left{SDL_SCANCODE_UNKNOWN};
SDL_Scancode right{SDL_SCANCODE_UNKNOWN};
SDL_Scancode jump{SDL_SCANCODE_UNKNOWN};
SDL_Scancode attack{SDL_SCANCODE_UNKNOWN};
};
// Identificadors d'acció per als setters genèrics: una sola funció
// `setKeyboardBinding(player_id, action, scancode)` cobreix les 12
// possibles vinculacions sense haver de generar 12 setters individuals.
enum class KeyboardAction : std::uint8_t {
UP,
DOWN,
LEFT,
RIGHT,
JUMP,
ATTACK,
};
struct Controls { struct Controls {
GamepadAssignment gamepad_p1{}; GamepadAssignment gamepad_p1{};
GamepadAssignment gamepad_p2{}; GamepadAssignment gamepad_p2{};
Keyboard keyboard_p1{
.up = Defaults::KeysGame::UP,
.down = Defaults::KeysGame::DOWN,
.left = Defaults::KeysGame::LEFT,
.right = Defaults::KeysGame::RIGHT,
.jump = Defaults::KeysGame::JUMP,
.attack = Defaults::KeysGame::ATTACK,
};
Keyboard keyboard_p2{
.up = Defaults::KeysGameP2::UP,
.down = Defaults::KeysGameP2::DOWN,
.left = Defaults::KeysGameP2::LEFT,
.right = Defaults::KeysGameP2::RIGHT,
.jump = Defaults::KeysGameP2::JUMP,
.attack = Defaults::KeysGameP2::ATTACK,
};
}; };
// --- Getters de l'estat (read-only) --- // --- Getters de l'estat (read-only) ---
@@ -153,6 +196,12 @@ namespace Options {
// = desassignació. `player_id`: 0 = P1, 1 = P2. // = desassignació. `player_id`: 0 = P1, 1 = P2.
void setGamepadAssignment(int player_id, const std::string& name, const std::string& path); void setGamepadAssignment(int player_id, const std::string& name, const std::string& path);
// Assigna un nou scancode a una acció d'un jugador i persisteix a
// `config.yaml`. `player_id`: 0 = P1, 1 = P2. Sense validació de
// conflictes — replicant la referència AEE, el mateix scancode pot
// quedar associat a múltiples accions si l'usuari ho elegeix.
void setKeyboardBinding(int player_id, KeyboardAction action, SDL_Scancode scancode);
// --- API pública: càrrega/desament de fitxers --- // --- API pública: càrrega/desament de fitxers ---
void setConfigFile(const std::string& path); void setConfigFile(const std::string& path);
auto loadFromFile() -> bool; auto loadFromFile() -> bool;
+175 -85
View File
@@ -298,6 +298,14 @@ GameScene::GameScene(Config config)
std::cout << "[PLAYER HIT] player_id=" << ev.player_id std::cout << "[PLAYER HIT] player_id=" << ev.player_id
<< " dmg=" << (dmg != nullptr ? *dmg : 0) << '\n'; << " dmg=" << (dmg != nullptr ? *dmg : 0) << '\n';
}); });
// `player_killed` el publica un hazard letal (mòmia amb
// `kills_outright`) després del `player_hit`. Aquí resolem el
// player_id a la instància concreta i disparem el cicle DYING. El
// hazard no toca cap símbol de Player directament, així els binaris
// de tests d'entitats no han d'arrossegar tot l'stack del jugador.
entity_manager_.getBus().subscribe("player_killed", [this](const Event& ev) {
dispatchPlayerKilled(ev.player_id);
});
// Polvet: precarreguem el surface del Dust una sola vegada perquè // Polvet: precarreguem el surface del Dust una sola vegada perquè
// els spawns runtime de l'entitat no facin disc al primer salt. // els spawns runtime de l'entitat no facin disc al primer salt.
@@ -326,24 +334,27 @@ GameScene::GameScene(Config config)
if (p != nullptr) { spawnWhip(*p); } if (p != nullptr) { spawnWhip(*p); }
}); });
// Controls per jugador: P1 amb fletxes, P2 amb WASD. Cada Player // Controls per jugador: P1 amb fletxes, P2 amb WASD per defecte. Les
// escolta les seues pròpies tecles dins del seu `update`, no hi ha // vinculacions actives venen d'`Options::controls()`, que s'inicialitza
// estat global d'input. // amb els defaults i pot ser sobreescrita pel menú de servei (i
// persistida a `config.yaml`).
const auto& kb_p1 = Options::controls().keyboard_p1;
const auto& kb_p2 = Options::controls().keyboard_p2;
const Player::Controls CONTROLS_P1{ const Player::Controls CONTROLS_P1{
.up = Defaults::KeysGame::UP, .up = kb_p1.up,
.down = Defaults::KeysGame::DOWN, .down = kb_p1.down,
.left = Defaults::KeysGame::LEFT, .left = kb_p1.left,
.right = Defaults::KeysGame::RIGHT, .right = kb_p1.right,
.jump = Defaults::KeysGame::JUMP, .jump = kb_p1.jump,
.attack = Defaults::KeysGame::ATTACK, .attack = kb_p1.attack,
}; };
const Player::Controls CONTROLS_P2{ const Player::Controls CONTROLS_P2{
.up = Defaults::KeysGameP2::UP, .up = kb_p2.up,
.down = Defaults::KeysGameP2::DOWN, .down = kb_p2.down,
.left = Defaults::KeysGameP2::LEFT, .left = kb_p2.left,
.right = Defaults::KeysGameP2::RIGHT, .right = kb_p2.right,
.jump = Defaults::KeysGameP2::JUMP, .jump = kb_p2.jump,
.attack = Defaults::KeysGameP2::ATTACK, .attack = kb_p2.attack,
}; };
// Spawn lligat a la porta: tots els jugadors apareixen davant del // Spawn lligat a la porta: tots els jugadors apareixen davant del
// passadís de sortida. `computeSpawn` deriva les coordenades de // passadís de sortida. `computeSpawn` deriva les coordenades de
@@ -378,11 +389,10 @@ GameScene::GameScene(Config config)
players_.emplace_back(std::make_unique<Player>(ANIM_P1, ID_P1, p1_x, SPAWN.y, CONTROLS_P1, Map::makeInfo(), P1_PAINT)); players_.emplace_back(std::make_unique<Player>(ANIM_P1, ID_P1, p1_x, SPAWN.y, CONTROLS_P1, Map::makeInfo(), P1_PAINT));
players_.emplace_back(std::make_unique<Player>(ANIM_P2, ID_P2, p2_x, SPAWN.y, CONTROLS_P2, Map::makeInfo(), P2_PAINT)); players_.emplace_back(std::make_unique<Player>(ANIM_P2, ID_P2, p2_x, SPAWN.y, CONTROLS_P2, Map::makeInfo(), P2_PAINT));
// Tots els slots arrenquen DISABLED. Els actius els reactivarà // Tots els slots arrenquen DISABLED. Els actius els reactivarà el
// `updateRoomTransition` quan s'esgote el delay corresponent dins // callback de fi de música d'INTRO via `completeIntro()` (spawn aeri
// de ROOM_INTRO (`player_spawn_delay_ms` per al primer, i // simultani) en arribar a la fi de la pista `room_intro.ogg`. Els
// `player2_spawn_delay_ms` per al segon en mode BOTH). Els inactius // inactius es queden DISABLED tot el cicle de sala.
// es queden DISABLED tot el cicle de sala.
for (auto& p : players_) { p->disable(); } for (auto& p : players_) { p->disable(); }
// PaintLayer: font única del pintat del nivell. Es crea després de // PaintLayer: font única del pintat del nivell. Es crea després de
@@ -465,21 +475,12 @@ GameScene::GameScene(Config config)
// vegada; canvis requereixen reiniciar el joc. // vegada; canvis requereixen reiniciar el joc.
transition_config_ = RoomFsm::loadConfig(Defaults::GameScene::CONFIG_YAML); transition_config_ = RoomFsm::loadConfig(Defaults::GameScene::CONFIG_YAML);
// Preload del so del rètol d'INTRO (no hi ha manifest de sons; el // Cinemàtica d'entrada de la primera sala: arrenca la pista
// pack agafa tot `data/` automàticament i ací registrem la clau // `room_intro.ogg` i arma el callback de fi que farà spawn + salt
// al Resource::Cache perquè Audio::playSound la trobe). Si el camp // a PLAYING. Cal cridar `enterRoomIntro` ací (i no només des
// del YAML està buit, no carreguem res — el dispar consultarà el // d'`advanceToNextRoom`) perquè la primera entrada a la GameScene
// cache i caurà al silenci sense crash. // no passa pel flux d'avanç de sala.
if (!transition_config_.intro_text_sound.empty()) { enterRoomIntro();
Resource::Cache::get().loadSound(
transition_config_.intro_text_sound,
"data/sounds/" + transition_config_.intro_text_sound);
}
// Log d'estat inicial: la FSM arrenca a ROOM_INTRO. En aquesta fita
// les transicions són instantànies (sense fades ni text) i el primer
// tick d'`updateRoomTransition` ja saltarà a PLAYING.
std::cout << "[ROOM] state=ROOM_INTRO (initial)" << '\n';
} }
GameScene::~GameScene() = default; GameScene::~GameScene() = default;
@@ -514,12 +515,18 @@ void GameScene::loadRoom(const std::string& yaml_path) {
// CLOSED amb visited buit, momies i altres actors a la posició inicial. // CLOSED amb visited buit, momies i altres actors a la posició inicial.
config_.map_yaml = yaml_path; config_.map_yaml = yaml_path;
Map::loadFrom(yaml_path); Map::loadFrom(yaml_path);
// Tilemap pre-blita els tiles a un buffer ARGB intern al ctor — sense
// reconstruir-lo, la imatge de la sala anterior es queda fixada per a
// sempre. PaintLayer captura `tileset_path_`/`tileset_cols_` per valor
// del Tilemap a la seua construcció, així que també cal recrear-lo
// perquè el rastre del jugador faça servir el tileset nou.
tilemap_ = Tilemap(yaml_path);
paint_layer_ = std::make_unique<PaintLayer>(tilemap_, &entity_manager_.getBus());
entity_manager_.reload(yaml_path); entity_manager_.reload(yaml_path);
// Tornem a fixar el comptador de "esperats" amb només els slots // Tornem a fixar el comptador de "esperats" amb només els slots
// actius — un slot DISABLED permanent (single-player) no compta. // actius — un slot DISABLED permanent (single-player) no compta.
const int ACTIVE_COUNT = std::ranges::count(players_starts_active_, true); const int ACTIVE_COUNT = std::ranges::count(players_starts_active_, true);
entity_manager_.setExpectedPlayerCount(ACTIVE_COUNT); entity_manager_.setExpectedPlayerCount(ACTIVE_COUNT);
if (paint_layer_) { paint_layer_->clear(); }
// Tornem tots els slots a DISABLED. Els actius els reactivarà el // Tornem tots els slots a DISABLED. Els actius els reactivarà el
// trigger d'spawn aeri dins de `updateRoomTransition` quan torni a // trigger d'spawn aeri dins de `updateRoomTransition` quan torni a
@@ -563,19 +570,41 @@ void GameScene::enterRoomIntro() {
} }
state_ = RoomFsm::State::ROOM_INTRO; state_ = RoomFsm::State::ROOM_INTRO;
transition_ = {}; transition_ = {};
room_intro_elapsed_ms_ = 0; intro_completed_ = false;
intro_spawn_elapsed_ms_ = 0;
players_spawned_in_intro_.fill(false); players_spawned_in_intro_.fill(false);
intro_text_sound_played_ = false; outro_music_ended_ = false;
// La cinemàtica d'entrada és silenciosa per disseny: és INTRO qui
// posseeix l'aturada. L'OUTRO no toca la música — així el cicle // INTRO arrenca amb la pista `room_intro.ogg` (loop=0, una sola
// OUTRO → INTRO transmet la sensació de "tall" en entrar a la sala // passada). El callback de fi de música és l'únic responsable de
// nova, no de "fade-out" anterior. Si ja no sona res, stopMusic és // disparar el spawn dels jugadors i el salt a PLAYING — la durada
// no-op. // real ve marcada per la pista, no per cap timer numèric.
if (Audio::get() != nullptr) { Audio::get()->stopMusic(); } auto* audio = Audio::get();
if (audio == nullptr) {
// Sense Audio inicialitzat, no podem esperar cap callback.
// Completem síncronament perquè la sala no quede bloquejada.
std::cout << "[ROOM] INTRO: Audio absent — completant síncronament\n";
completeIntro();
return;
}
audio->stopMusic();
audio->setOnMusicEnded([this]() { completeIntro(); });
audio->playMusic("room_intro.ogg", 0);
if (Audio::getMusicState() != Audio::MusicState::PLAYING) {
// Fallback: la pista no s'ha pogut arrencar (resource absent,
// música deshabilitada, etc.). Netegem el callback per no
// deixar-lo penjat i fem el spawn + transició com si el track
// hagués acabat ja. Així cap fallada d'asset deixa la sala
// congelada al fons negre de l'INTRO.
std::cout << "[ROOM] INTRO: música no arrencada — completant síncronament\n";
audio->setOnMusicEnded({});
completeIntro();
}
} }
void GameScene::triggerPlayerSpawn(std::size_t slot_index) { void GameScene::triggerPlayerSpawn(std::size_t slot_index) {
if (slot_index >= players_spawned_in_intro_.size()) { return; } if (slot_index >= players_.size()) { return; }
if (!players_starts_active_[slot_index]) { return; }
if (players_spawned_in_intro_[slot_index]) { return; } if (players_spawned_in_intro_[slot_index]) { return; }
players_spawned_in_intro_[slot_index] = true; players_spawned_in_intro_[slot_index] = true;
@@ -590,6 +619,11 @@ void GameScene::triggerPlayerSpawn(std::size_t slot_index) {
float x = SINGLE_X; float x = SINGLE_X;
if (BOTH_ACTIVE) { x = (slot_index == 0) ? SPAWN.p1_x : SPAWN.p2_x; } if (BOTH_ACTIVE) { x = (slot_index == 0) ? SPAWN.p1_x : SPAWN.p2_x; }
// Memoritzem aquestes coordenades com a punt de respawn post-mort.
// El Player les usa al final del cicle DYING per teleportar-se. Les
// refresquem cada vegada que disparem un spawn aeri perquè la sala
// pot canviar de geometria de porta entre cicles.
players_[slot_index]->setSpawnPoint(x, SPAWN.y);
players_[slot_index]->spawnFromAir(x, SPAWN.y); players_[slot_index]->spawnFromAir(x, SPAWN.y);
// Arma el reverb del primer LAND com a part de la cinemàtica // Arma el reverb del primer LAND com a part de la cinemàtica
// d'INTRO. Una sola vegada per spawn aeri: el respawn post-mort // d'INTRO. Una sola vegada per spawn aeri: el respawn post-mort
@@ -600,23 +634,84 @@ void GameScene::triggerPlayerSpawn(std::size_t slot_index) {
std::cout << "[ROOM] spawn cinematic P" << (slot_index + 1) << ": airborne entry\n"; std::cout << "[ROOM] spawn cinematic P" << (slot_index + 1) << ": airborne entry\n";
} }
void GameScene::dispatchPlayerKilled(int player_id) {
std::cout << "[PLAYER KILLED] player_id=" << player_id << '\n';
const auto IT = std::ranges::find_if(players_, [player_id](const auto& p) {
return p && p->getId() == player_id;
});
if (IT != players_.end()) { (*IT)->enterDying(); }
}
void GameScene::completeIntro() {
if (intro_completed_) { return; }
intro_completed_ = true;
intro_spawn_elapsed_ms_ = 0;
// Spawn dels slots amb delay <= 0 (el cas habitual de P1). Els
// altres cauen frame a frame dins `updateRoomTransition` a mesura
// que `intro_spawn_elapsed_ms_` supera el seu delay. Així P1 cau
// alhora que la música acaba i la transició a PLAYING; P2 (en
// mode BOTH) entra uns frames més tard amb el seu offset propi.
spawnDuePlayers();
setState(RoomFsm::State::PLAYING);
}
void GameScene::spawnDuePlayers() {
if (!intro_completed_) { return; }
const bool BOTH_ACTIVE = players_starts_active_[0] && players_starts_active_[1];
for (std::size_t i = 0; i < players_starts_active_.size(); ++i) {
if (!players_starts_active_[i]) { continue; }
if (players_spawned_in_intro_[i]) { continue; }
// Single-player: el slot actiu (P1 o P2) sempre fa servir
// `player_spawn_delay_ms`. Mode BOTH: P1 fa player_spawn_delay_ms,
// P2 fa player2_spawn_delay_ms (offset respecte P1).
const int DELAY = (i == 1 && BOTH_ACTIVE)
? transition_config_.player2_spawn_delay_ms
: transition_config_.player_spawn_delay_ms;
if (intro_spawn_elapsed_ms_ >= DELAY) { triggerPlayerSpawn(i); }
}
}
void GameScene::setState(RoomFsm::State new_state) { void GameScene::setState(RoomFsm::State new_state) {
if (new_state == state_) { return; } if (new_state == state_) { return; }
std::cout << "[ROOM] " << RoomFsm::stateName(state_) << " -> " << RoomFsm::stateName(new_state) << '\n'; std::cout << "[ROOM] " << RoomFsm::stateName(state_) << " -> " << RoomFsm::stateName(new_state) << '\n';
state_ = new_state; state_ = new_state;
transition_ = {}; transition_ = {};
// En entrar a un nou cicle d'INTRO, resetegem el timer i el flag de // Reset defensiu dels flags de cicle quan tornem a INTRO via aquest
// l'spawn aeri perquè es torne a disparar al delay configurat. // camí (no canònic — `enterRoomIntro` és el punt d'entrada normal).
if (new_state == RoomFsm::State::ROOM_INTRO) { if (new_state == RoomFsm::State::ROOM_INTRO) {
room_intro_elapsed_ms_ = 0; intro_completed_ = false;
intro_spawn_elapsed_ms_ = 0;
players_spawned_in_intro_.fill(false); players_spawned_in_intro_.fill(false);
intro_text_sound_played_ = false; outro_music_ended_ = false;
} }
// En sortir d'INTRO cap a PLAYING, la música arrenca: és el moment auto* audio = Audio::get();
// en què la cinemàtica d'entrada acaba i el jugador agafa control. // PLAYING: arrenca la pista de joc en loop infinit. Netegem el
// L'OUTRO no atura la música; ho fa el següent `enterRoomIntro`. // callback de fi (residual d'INTRO, ja consumit) perquè un futur
if (new_state == RoomFsm::State::PLAYING && Audio::get() != nullptr) { // stopMusic no dispari lògica obsoleta.
Audio::get()->playMusic(config_.music_track, -1); if (new_state == RoomFsm::State::PLAYING && audio != nullptr) {
audio->setOnMusicEnded({});
audio->playMusic(config_.music_track, -1);
}
// ROOM_OUTRO: corte sec de `playing.ogg`, arrenca `room_outro.ogg`
// (loop=0). El callback de fi marca `outro_music_ended_ = true`,
// que junt amb el final del text_exit gata el fade visual a negre
// dins `updateRoomTransition`. Si la música no arrenca, marquem el
// flag immediatament perquè el fade quedi gata només contra el text.
if (new_state == RoomFsm::State::ROOM_OUTRO) {
outro_music_ended_ = false;
if (audio == nullptr) {
std::cout << "[ROOM] OUTRO: Audio absent — fade gata només contra text\n";
outro_music_ended_ = true;
} else {
audio->stopMusic();
audio->setOnMusicEnded([this]() { outro_music_ended_ = true; });
audio->playMusic("room_outro.ogg", 0);
if (Audio::getMusicState() != Audio::MusicState::PLAYING) {
std::cout << "[ROOM] OUTRO: música no arrencada — fade gata només contra text\n";
audio->setOnMusicEnded({});
outro_music_ended_ = true;
}
}
} }
// L'avanç de sala (OUTRO → INTRO) NO viu ací: el dispara // L'avanç de sala (OUTRO → INTRO) NO viu ací: el dispara
// `updateRoomTransition` quan la FSM detecta el final de l'OUTRO, // `updateRoomTransition` quan la FSM detecta el final de l'OUTRO,
@@ -627,43 +722,38 @@ void GameScene::setState(RoomFsm::State new_state) {
} }
void GameScene::updateRoomTransition(int delta_ms) { void GameScene::updateRoomTransition(int delta_ms) {
// Disparador d'spawn aeri: timer propi (independent de les fases de // Tick del timer d'spawn aeri escalonat. Comença al callback de fi
// la FSM) que arrenca a l'entrada de ROOM_INTRO. La separació deixa // de música d'INTRO (`completeIntro` posa intro_completed_=true) i
// el delay desacoblat del fade-in i, demà, reutilitzable per al // segueix corrent cap a PLAYING fins que tots els slots actius hagin
// respawn post-mort sense que la FSM de sala hi haja d'intervenir. // caigut. Cap cost extra un cop tots han caigut (`spawnDuePlayers`
if (state_ == RoomFsm::State::ROOM_INTRO) { // és no-op llavors).
room_intro_elapsed_ms_ += delta_ms; if (intro_completed_) {
// Cada slot actiu pot caure en un moment distint quan els dos intro_spawn_elapsed_ms_ += delta_ms;
// estan actius (mode BOTH): P1 amb player_spawn_delay_ms i P2 spawnDuePlayers();
// amb player2_spawn_delay_ms. En single-player (P1 o P2 sols)
// l'slot actiu fa servir player_spawn_delay_ms — el segon valor
// queda inactiu, com ha demanat el disseny.
const bool BOTH_ACTIVE = players_starts_active_[0] && players_starts_active_[1];
for (std::size_t i = 0; i < players_starts_active_.size(); ++i) {
if (!players_starts_active_[i]) { continue; }
if (players_spawned_in_intro_[i]) { continue; }
const int DELAY = (i == 1 && BOTH_ACTIVE)
? transition_config_.player2_spawn_delay_ms
: transition_config_.player_spawn_delay_ms;
if (room_intro_elapsed_ms_ >= DELAY) {
triggerPlayerSpawn(i);
}
}
} }
const auto NEXT = RoomFsm::advance(state_, transition_, transition_config_, delta_ms); const auto NEXT = RoomFsm::advance(state_, transition_, transition_config_, delta_ms);
// Disparador del so del rètol: la FSM acaba d'avançar; si estem a // OUTRO: gate del fade visual sobre la doble condició (música
// INTRO i ja hem deixat enrere phase 0 (fade_in), reproduïm el so // acabada AND text fora). Si la FSM ha avançat a phase 3 (fade_out)
// una sola vegada. El so es dispara al final del fade-in, anticipant // pel timer del text però el callback de fi de música encara no
// el text que apareix a phase 2 (text_enter): així sembla que el so // ha disparat, congelem el comptador a 0 perquè el fade no comence
// "anuncia" l'arribada del rètol enlloc de coincidir amb la seva // a pintar el negre. Quan el callback alliberi `outro_music_ended_`,
// entrada visual. // les pròximes crides a `advance` tornaran a acumular delta_ms i
if (state_ == RoomFsm::State::ROOM_INTRO && transition_.phase >= 1 && !intro_text_sound_played_ && !transition_config_.intro_text_sound.empty() && Audio::get() != nullptr) { // el fade arrencarà des de zero, polit.
Audio::get()->playSound(transition_config_.intro_text_sound, Audio::Group::GAME); if (state_ == RoomFsm::State::ROOM_OUTRO &&
intro_text_sound_played_ = true; transition_.phase == 3 &&
!outro_music_ended_) {
transition_.elapsed_ms = 0;
} }
if (NEXT != state_) { if (NEXT != state_) {
// INTRO → PLAYING per timer: ignorat. El callback de fi de
// música d'INTRO és l'únic responsable d'aquesta transició.
if (state_ == RoomFsm::State::ROOM_INTRO &&
NEXT == RoomFsm::State::PLAYING) {
return;
}
// El final de l'OUTRO no es resol amb un setState normal: cal // El final de l'OUTRO no es resol amb un setState normal: cal
// demanar al LevelManager el següent nivell i carregar-lo abans // demanar al LevelManager el següent nivell i carregar-lo abans
// d'entrar a INTRO. `advanceToNextRoom` fa tot el bloc i acaba // d'entrar a INTRO. `advanceToNextRoom` fa tot el bloc i acaba
+54 -24
View File
@@ -104,30 +104,59 @@ class GameScene : public Scene {
// queda DISABLED tot el cicle de sala. // queda DISABLED tot el cicle de sala.
std::array<bool, 2> players_starts_active_{false, false}; std::array<bool, 2> players_starts_active_{false, false};
// Mil·lisegons des de l'entrada en ROOM_INTRO, per disparar la
// cinemàtica de spawn aeri quan s'arriba al delay corresponent al
// slot. Es reseteja a `setState(ROOM_INTRO)`. Cada slot té el seu
// propi flag de "ja he caigut" — així podem caure en moments
// distints en mode BOTH (`player_spawn_delay_ms` per a P1 i
// `player2_spawn_delay_ms` per a P2).
int room_intro_elapsed_ms_{0};
std::array<bool, 2> players_spawned_in_intro_{false, false};
// One-shot del so del rètol d'INTRO (`text_sound` a game.yaml).
// Es disparen quan la FSM passa a phase 2 (text_enter); el flag
// evita repeticions si phase 2 s'estén multi-frame. Es reseteja a
// `enterRoomIntro` / `setState(ROOM_INTRO)`. Si la clau del so és
// buida o el cache no l'ha carregat, queda inutilitzat.
bool intro_text_sound_played_{false};
// Cinemàtica de spawn d'un slot concret. Posa el slot indicat en // Cinemàtica de spawn d'un slot concret. Posa el slot indicat en
// AIRBORNE des d'una altura fora de pantalla. Cridat per // AIRBORNE des d'una altura fora de pantalla. Cridat per
// `updateRoomTransition` quan `room_intro_elapsed_ms_` supera el // `spawnDuePlayers` quan el timer post-callback supera el delay
// delay específic de l'slot. Idempotent (consulta el flag corresponent // del slot. Idempotent (consulta `players_spawned_in_intro_`).
// a `players_spawned_in_intro_`). També és el punt al qual es // També és el punt al qual es connectarà el respawn post-mort en
// connectarà el respawn post-mort en el futur. // el futur.
void triggerPlayerSpawn(std::size_t slot_index); void triggerPlayerSpawn(std::size_t slot_index);
// Cos del callback de fi de música d'INTRO. Idempotent: si ja s'ha
// disparat aquest cicle (`intro_completed_`), no fa res. Marca el
// flag, reseteja el timer post-callback i intenta spawn dels slots
// actius amb delay <= 0 ara mateix; els altres cauen frame a frame
// dins `updateRoomTransition` quan el timer els assoleix. Finalment
// commuta a PLAYING — la música del joc arrenca amb el primer slot.
void completeIntro();
// Dispatch del cicle DYING al jugador identificat per `player_id`.
// Crida `Player::enterDying()` (idempotent — un segon impacte mentre
// ja és DYING és no-op). Subscriptor de l'event `player_killed`
// publicat pels hazards letals (mòmia amb `kills_outright`).
// Extret a un mètode per acotar la complexitat cognitiva del ctor.
void dispatchPlayerKilled(int player_id);
// Itera els slots actius i, si el delay configurat ha expirat des
// del callback de fi de música d'INTRO, dispara `triggerPlayerSpawn`.
// Cridada cada frame durant la fase d'spawn escalonat (entre el
// callback i la caiguda de l'últim slot). No fa res si ja han
// caigut tots o si encara no estem dins la finestra (intro_completed_).
void spawnDuePlayers();
// Hi ha un únic slot global de callback de fi de música a `Ja::Engine`.
// Cada estat (INTRO, OUTRO) ha de registrar el seu en entrar i pot
// sobreescriure'l amb el del següent estat. Aquests flags marquen
// si el callback corresponent ja ha disparat: són la frontera entre
// el thread d'audio (que invoca el callback dins `Engine::update`)
// i la lògica de transició (`updateRoomTransition`).
bool intro_completed_{false};
bool outro_music_ended_{false};
// Mil·lisegons acumulats des del callback de fi de música d'INTRO,
// per gatejar l'spawn aeri escalonat dels slots actius. P1 fa
// servir `player_spawn_delay_ms` (típicament 0); P2 en mode BOTH
// fa servir `player2_spawn_delay_ms` (offset respecte P1). Es
// reseteja a 0 dins `completeIntro` i no torna a córrer fins al
// següent cicle d'INTRO.
int intro_spawn_elapsed_ms_{0};
// Per slot: ja ha caigut aquest cicle d'INTRO? Garanteix
// idempotència de `triggerPlayerSpawn` davant múltiples ticks del
// timer mentre el delay no s'ha mogut. Es reseteja a `enterRoomIntro`
// i a `setState(ROOM_INTRO)` (camí defensiu no canònic).
std::array<bool, 2> players_spawned_in_intro_{false, false};
// Vista plana de punters crus — la fa servir Player com a llista // Vista plana de punters crus — la fa servir Player com a llista
// d'obstacles mòbils a la callback de col·lisió. Es construeix un sol // d'obstacles mòbils a la callback de col·lisió. Es construeix un sol
// cop al ctor (els punters són estables perquè `players_` no es // cop al ctor (els punters són estables perquè `players_` no es
@@ -252,10 +281,11 @@ class GameScene : public Scene {
void advanceToNextRoom(); void advanceToNextRoom();
// Inicia (o reinicia) el cicle d'entrada de sala: posa l'estat a // Inicia (o reinicia) el cicle d'entrada de sala: posa l'estat a
// ROOM_INTRO i reseteja `transition_`, `room_intro_elapsed_ms_` i el // ROOM_INTRO, reseteja `transition_` i `intro_completed_`, i comença
// flag d'spawn aeri. Idempotent quan ja s'estava a INTRO (els // la pista `room_intro.ogg` amb un callback de fi que dispararà
// timers tornen a començar des de zero — útil per als salts debug // `completeIntro()`. Si la càrrega de la música falla, dispara
// que recarreguen la sala estant ja en INTRO). // `completeIntro()` síncronament perquè la sala no quedi bloquejada.
// Idempotent quan ja s'estava a INTRO — útil per als salts debug.
void enterRoomIntro(); void enterRoomIntro();
#ifdef _DEBUG #ifdef _DEBUG
-3
View File
@@ -50,9 +50,6 @@ namespace RoomFsm {
if (n.contains("land_reverb")) { if (n.contains("land_reverb")) {
cfg.intro_land_reverb_preset = n["land_reverb"].get_value<std::string>(); cfg.intro_land_reverb_preset = n["land_reverb"].get_value<std::string>();
} }
if (n.contains("text_sound")) {
cfg.intro_text_sound = n["text_sound"].get_value<std::string>();
}
} }
if (yaml.contains("room_outro")) { if (yaml.contains("room_outro")) {
const auto& n = yaml["room_outro"]; const auto& n = yaml["room_outro"];
+10 -21
View File
@@ -47,23 +47,19 @@ namespace RoomFsm {
// sense recompilar — útil per donar respir abans del nou rètol. // sense recompilar — útil per donar respir abans del nou rètol.
int outro_black_hold_ms{0}; int outro_black_hold_ms{0};
// Mil·lisegons des de l'inici de ROOM_INTRO en què cau la // Mil·lisegons des del callback de fi de la música d'INTRO
// cinemàtica d'spawn aeri dels jugadors actius. Específic per a // (`room_intro.ogg`) en què cau l'spawn aeri del primer slot
// l'entrada de sala — el respawn post-mort tindrà el seu propi // actiu. 0 = cau al moment exacte que la música acaba. El
// timing quan toqui dissenyar-lo. Un valor habitual: la suma de // respawn post-mort tindrà el seu propi timing quan toqui
// fade_in + text_delay + text_enter + text_hold, perquè els // dissenyar-lo.
// players caiguin just quan el rètol comença a sortir. El int player_spawn_delay_ms{0};
// fallback compilat (500 ms) deixa el spawn al final del fade-in
// si el YAML no defineix res — només pensat com a degradació.
int player_spawn_delay_ms{500};
// Delay independent per al segon jugador, **només quan els dos // Delay independent per al segon jugador, **només quan els dos
// slots estan actius** (mode BOTH). En single-player (P1 o P2 // slots estan actius** (mode BOTH). En single-player (P1 o P2
// sols) s'usa sempre `player_spawn_delay_ms` per al jugador // sols) s'usa sempre `player_spawn_delay_ms`. Si el YAML no
// actiu. Si el YAML no fixa aquest valor, el `loadConfig` el // fixa aquest valor, `loadConfig` el copia de `player_spawn_delay_ms`
// copia de `player_spawn_delay_ms` perquè els dos players caiguin // perquè els dos players caiguin alhora — comportament neutre.
// alhora — comportament històric del joc. int player2_spawn_delay_ms{0};
int player2_spawn_delay_ms{500};
// Píxels extra entre les dues línies del text d'INTRO (piràmide // Píxels extra entre les dues línies del text d'INTRO (piràmide
// damunt, sala davall). Es sumen al `lineHeight` natural del // damunt, sala davall). Es sumen al `lineHeight` natural del
@@ -87,13 +83,6 @@ namespace RoomFsm {
// cinemàtica d'entrada sona igual que un salt qualsevol). // cinemàtica d'entrada sona igual que un salt qualsevol).
std::string intro_land_reverb_preset; std::string intro_land_reverb_preset;
// Clau d'un so puntual (data/sounds/<key>) que GameScene
// dispara just quan la cinemàtica d'INTRO arriba a la fase
// d'entrada del text (phase == 2). Buit o absent = sense so.
// Convenció Resource::Cache: el basename val tant de clau com
// de path (loadSound ho fa servir igual que el land_sound).
std::string intro_text_sound;
// Panel translúcid sota el rètol de transició (INTRO i OUTRO). // Panel translúcid sota el rètol de transició (INTRO i OUTRO).
// Centrat verticalment al centre del playfield, ocupa tota // Centrat verticalment al centre del playfield, ocupa tota
// l'amplada del canvas. Creix d'altura 0 → height_px durant la // l'amplada del canvas. Creix d'altura 0 → height_px durant la
+227 -54
View File
@@ -7,6 +7,7 @@
#include <utility> #include <utility>
#include "core/audio/audio.hpp" #include "core/audio/audio.hpp"
#include "core/input/gamepad_bindings.hpp"
#include "core/input/gamepad_manager.hpp" #include "core/input/gamepad_manager.hpp"
#include "core/input/input_state.hpp" #include "core/input/input_state.hpp"
#include "core/input/key_config.hpp" #include "core/input/key_config.hpp"
@@ -230,6 +231,9 @@ void ServiceMenu::init(std::shared_ptr<Text> title_font,
font_ = std::move(title_font); font_ = std::move(title_font);
items_font_ = std::move(items_font); items_font_ = std::move(items_font);
ui_palette_ = ui_palette; ui_palette_ = ui_palette;
// Comparteix fonts/paleta amb el sub-mòdul de captura de botons —
// aquest pinta el seu propi overlay quan està actiu.
define_buttons_.init(font_, items_font_, ui_palette_);
} }
void ServiceMenu::toggle() { void ServiceMenu::toggle() {
@@ -253,6 +257,7 @@ void ServiceMenu::close() {
if (!open_) { return; } if (!open_) { return; }
open_ = false; open_ = false;
closing_ = true; // `open_anim_` decaurà a update; la caixa es veu mentre dura. closing_ = true; // `open_anim_` decaurà a update; la caixa es veu mentre dura.
capturing_ = false;
// Allibera el polling de seguida — la closing animation és purament // Allibera el polling de seguida — la closing animation és purament
// visual i no ha d'impedir que el jugador es mogui un cop ha tancat. // visual i no ha d'impedir que el jugador es mogui un cop ha tancat.
InputState::setBlocked(false); InputState::setBlocked(false);
@@ -266,6 +271,16 @@ auto ServiceMenu::isVisible() const -> bool {
} }
void ServiceMenu::update(int delta_ms) { void ServiceMenu::update(int delta_ms) {
// El sub-mòdul de captura de botons té el seu propi cicle (timer post
// assignació). Una vegada `isReadyToClose`, el desactivem perquè el
// menú principal recuperi el control de l'input.
if (define_buttons_.isEnabled()) {
define_buttons_.update(delta_ms);
if (define_buttons_.isReadyToClose()) {
define_buttons_.disable();
}
}
const float DT = static_cast<float>(delta_ms) / MS_PER_SECOND; const float DT = static_cast<float>(delta_ms) / MS_PER_SECOND;
if (closing_) { if (closing_) {
@@ -419,50 +434,125 @@ void ServiceMenu::activateCurrent(int direction) {
if (direction > 0 && item.action) { item.action(); } if (direction > 0 && item.action) { item.action(); }
break; break;
case Item::Kind::KEY_BIND: case Item::Kind::KEY_BIND:
// TODO: entrar en mode captura quan hi hagi `set_scancode`. // Entra en mode captura: el Director redirigirà el pròxim
// De moment no fem res — els items sense setter ja no són // KEY_DOWN cap a `captureKey()`. Només té sentit amb RETURN /
// seleccionables (veure `makeKeyBind`), per tant aquest case // ENTER / RIGHT (direction +1) — LEFT no fa res, com a la
// només es trepitja si algú en fa un amb setter en el futur. // resta de Kinds que requereixen confirmació explícita.
if (direction > 0 && item.set_scancode) { capturing_ = true; }
break; break;
} }
} }
namespace {
// Resol el botó SERVICE configurat per al mando que ha emès aquest
// event. -1 si no troba cap match (mando desconegut o desconnectat
// mentrestant).
auto serviceButtonForEvent(SDL_JoystickID jid) -> int {
for (const auto& info : GamepadManager::get().getAll()) {
if (info.pad == nullptr) { continue; }
if (SDL_GetJoystickID(SDL_GetGamepadJoystick(info.pad)) != jid) { continue; }
return GamepadBindings::get().getButton(info.name, info.path, GamepadBindings::Action::SERVICE);
}
return -1;
}
} // namespace
void ServiceMenu::handleGamepadButtonDown(const SDL_GamepadButtonEvent& ev) {
// El botó SERVICE (per defecte BACK; remapejable per dispositiu via
// `GamepadBindings`) tanca el menú sencer. Cada mando pot tenir el
// seu propi mapeig — resolem-lo a partir del joystick instance ID.
const int SERVICE_BTN = serviceButtonForEvent(ev.which);
if (SERVICE_BTN >= 0 && static_cast<int>(ev.button) == SERVICE_BTN) {
close();
return;
}
switch (ev.button) {
case SDL_GAMEPAD_BUTTON_DPAD_UP:
moveCursor(-1);
break;
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
moveCursor(+1);
break;
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
activateCurrent(-1);
break;
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
case SDL_GAMEPAD_BUTTON_EAST:
case SDL_GAMEPAD_BUTTON_NORTH:
activateCurrent(+1);
break;
case SDL_GAMEPAD_BUTTON_WEST:
case SDL_GAMEPAD_BUTTON_SOUTH:
popPage();
break;
default:
break;
}
}
void ServiceMenu::captureKey(SDL_Scancode sc) {
if (!capturing_) { return; }
// ESC cancel·la la captura sense assignar — replica AEE
// (`Menu::captureKey`). Qualsevol altra tecla queda assignada.
if (sc == SDL_SCANCODE_ESCAPE) {
capturing_ = false;
return;
}
if (stack_.empty()) {
capturing_ = false;
return;
}
const auto& page = topConst();
if (page.cursor >= page.items.size()) {
capturing_ = false;
return;
}
const auto& item = page.items[page.cursor];
if (item.kind == Item::Kind::KEY_BIND && item.set_scancode) {
item.set_scancode(sc);
}
capturing_ = false;
}
// --- Input --- // --- Input ---
auto ServiceMenu::handleEvent(const SDL_Event& ev) -> bool { auto ServiceMenu::handleEvent(const SDL_Event& ev) -> bool {
if (!open_) { return false; } if (!open_) { return false; }
// Mode de captura de botons del mando: redirigeix tots els events
// del gamepad (BUTTON_DOWN/UP, AXIS_MOTION) al sub-mòdul. ESCAPE de
// teclat cancel·la la sessió i tanca el sub-overlay sense persistir.
if (define_buttons_.isEnabled()) {
if (ev.type == SDL_EVENT_KEY_DOWN && !ev.key.repeat &&
ev.key.scancode == SDL_SCANCODE_ESCAPE) {
define_buttons_.disable();
return true;
}
define_buttons_.handleEvent(ev);
return true;
}
// Mode de captura per a un KEY_BIND. Mentre dura, el menú consumeix
// TOTS els events: el primer KEY_DOWN s'assigna a l'item seleccionat
// (o ESC cancel·la), i la resta queden bloquejats perquè cap tecla
// s'escapi al joc. Replica el comportament d'AEE
// (`Menu::isCapturing` + `Menu::captureKey`).
if (capturing_) {
if (ev.type == SDL_EVENT_KEY_DOWN && !ev.key.repeat) {
captureKey(ev.key.scancode);
}
return true;
}
// Botons del gamepad: cruceta per navegar, EAST/NORTH com a OK, // Botons del gamepad: cruceta per navegar, EAST/NORTH com a OK,
// WEST/SOUTH com a BACK de pàgina, BACK tanca el menú sencer. Quan // WEST/SOUTH com a BACK de pàgina, BACK tanca el menú sencer. Quan
// el menú està obert consumim TOTS els BUTTON_DOWN perquè cap botó // el menú està obert consumim TOTS els BUTTON_DOWN perquè cap botó
// arribi als consumidors de gameplay. Paral·lel al consum total de // arribi als consumidors de gameplay. Paral·lel al consum total de
// KEY_DOWN més avall. // KEY_DOWN més avall.
if (ev.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) { if (ev.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
switch (ev.gbutton.button) { handleGamepadButtonDown(ev.gbutton);
case SDL_GAMEPAD_BUTTON_DPAD_UP:
moveCursor(-1);
break;
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
moveCursor(+1);
break;
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
activateCurrent(-1);
break;
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
case SDL_GAMEPAD_BUTTON_EAST:
case SDL_GAMEPAD_BUTTON_NORTH:
activateCurrent(+1);
break;
case SDL_GAMEPAD_BUTTON_WEST:
case SDL_GAMEPAD_BUTTON_SOUTH:
popPage();
break;
case SDL_GAMEPAD_BUTTON_BACK:
close();
break;
default:
break;
}
return true; return true;
} }
@@ -732,37 +822,62 @@ auto ServiceMenu::buildControls() -> Page {
return page; return page;
} }
namespace {
auto getKeyboardScancode(int player_id, Options::KeyboardAction action) -> SDL_Scancode {
const auto& kb = (player_id == 0) ? Options::controls().keyboard_p1
: Options::controls().keyboard_p2;
switch (action) {
case Options::KeyboardAction::UP:
return kb.up;
case Options::KeyboardAction::DOWN:
return kb.down;
case Options::KeyboardAction::LEFT:
return kb.left;
case Options::KeyboardAction::RIGHT:
return kb.right;
case Options::KeyboardAction::JUMP:
return kb.jump;
case Options::KeyboardAction::ATTACK:
return kb.attack;
}
return SDL_SCANCODE_UNKNOWN;
}
} // namespace
auto ServiceMenu::makeKeyboardItem(const std::string& label_key, int player_id, std::uint8_t action) -> Item {
const auto KA = static_cast<Options::KeyboardAction>(action);
return makeKeyBind(
label_key,
[player_id, KA]() { return getKeyboardScancode(player_id, KA); },
[player_id, KA](SDL_Scancode sc) { Options::setKeyboardBinding(player_id, KA, sc); });
}
auto ServiceMenu::buildKeyboardControls() -> Page { auto ServiceMenu::buildKeyboardControls() -> Page {
Page page; Page page;
page.title_key = "menu.titles.keyboard"; page.title_key = "menu.titles.keyboard";
// Etiqueta divisòria per al jugador 1. using KA = Options::KeyboardAction;
const auto AS_U8 = [](KA k) { return static_cast<std::uint8_t>(k); };
// Bloc del jugador 1. Ordre: moviment → atacar → saltar.
page.items.push_back(makeLabel(Locale::get().text("menu.labels.player_1"))); page.items.push_back(makeLabel(Locale::get().text("menu.labels.player_1")));
page.items.push_back(makeKeyboardItem("menu.items.move_up", 0, AS_U8(KA::UP)));
page.items.push_back(makeKeyboardItem("menu.items.move_down", 0, AS_U8(KA::DOWN)));
page.items.push_back(makeKeyboardItem("menu.items.move_left", 0, AS_U8(KA::LEFT)));
page.items.push_back(makeKeyboardItem("menu.items.move_right", 0, AS_U8(KA::RIGHT)));
page.items.push_back(makeKeyboardItem("menu.items.attack", 0, AS_U8(KA::ATTACK)));
page.items.push_back(makeKeyboardItem("menu.items.jump", 0, AS_U8(KA::JUMP)));
// Vinculacions actuals: les llegim directament de `Defaults::KeysGame` // Bloc del jugador 2.
// (P1) i `Defaults::KeysGameP2` (P2). Encara no hi ha setter, per
// tant els items surten com a informatius (no seleccionables). La
// lògica de captura i persistència arribarà en una segona iteració
// amb `Options::keysGameP1()` / `keysGameP2()`.
page.items.push_back(makeKeyBind("menu.items.move_up",
[]() { return Defaults::KeysGame::UP; }));
page.items.push_back(makeKeyBind("menu.items.move_down",
[]() { return Defaults::KeysGame::DOWN; }));
page.items.push_back(makeKeyBind("menu.items.move_left",
[]() { return Defaults::KeysGame::LEFT; }));
page.items.push_back(makeKeyBind("menu.items.move_right",
[]() { return Defaults::KeysGame::RIGHT; }));
// Bloc del jugador 2: mateixa forma, llegint de KeysGameP2.
page.items.push_back(makeLabel(Locale::get().text("menu.labels.player_2"))); page.items.push_back(makeLabel(Locale::get().text("menu.labels.player_2")));
page.items.push_back(makeKeyBind("menu.items.move_up", page.items.push_back(makeKeyboardItem("menu.items.move_up", 1, AS_U8(KA::UP)));
[]() { return Defaults::KeysGameP2::UP; })); page.items.push_back(makeKeyboardItem("menu.items.move_down", 1, AS_U8(KA::DOWN)));
page.items.push_back(makeKeyBind("menu.items.move_down", page.items.push_back(makeKeyboardItem("menu.items.move_left", 1, AS_U8(KA::LEFT)));
[]() { return Defaults::KeysGameP2::DOWN; })); page.items.push_back(makeKeyboardItem("menu.items.move_right", 1, AS_U8(KA::RIGHT)));
page.items.push_back(makeKeyBind("menu.items.move_left", page.items.push_back(makeKeyboardItem("menu.items.attack", 1, AS_U8(KA::ATTACK)));
[]() { return Defaults::KeysGameP2::LEFT; })); page.items.push_back(makeKeyboardItem("menu.items.jump", 1, AS_U8(KA::JUMP)));
page.items.push_back(makeKeyBind("menu.items.move_right",
[]() { return Defaults::KeysGameP2::RIGHT; }));
return page; return page;
} }
@@ -874,6 +989,37 @@ namespace {
} // namespace } // namespace
namespace {
// Acció: arranca DefineButtons per al mando assignat al jugador.
// Si no hi ha mando, no fa res — l'item ja s'amaga via `visible`.
void startDefineButtonsForPlayer(int player_id) {
SDL_Gamepad* pad = GamepadManager::get().getForPlayer(player_id);
if (pad == nullptr) { return; }
const auto& slot = (player_id == 0)
? Options::controls().gamepad_p1
: Options::controls().gamepad_p2;
// Anem a buscar el nom/path actuals del mando físic, no els
// persistits — així podem captar correctament el mateix mando
// si l'usuari l'ha mogut de port USB i només en queda el nom.
std::string name = slot.name;
std::string path = slot.path;
const auto ALL = GamepadManager::get().getAll();
const auto IT = std::ranges::find_if(ALL,
[pad](const auto& info) { return info.pad == pad; });
if (IT != ALL.end()) {
name = IT->name;
path = IT->path;
}
ServiceMenu::get().startDefineButtons(pad, name, path);
}
auto playerHasGamepad(int player_id) -> bool {
return GamepadManager::get().getForPlayer(player_id) != nullptr;
}
} // namespace
auto ServiceMenu::buildGamepadControls() -> Page { auto ServiceMenu::buildGamepadControls() -> Page {
Page page; Page page;
page.title_key = "menu.titles.gamepad"; page.title_key = "menu.titles.gamepad";
@@ -896,9 +1042,29 @@ auto ServiceMenu::buildGamepadControls() -> Page {
[](std::size_t idx) { applyPadSelection(1, idx); }, [](std::size_t idx) { applyPadSelection(1, idx); },
[](std::size_t idx) { return padLabel(1, idx); })); [](std::size_t idx) { return padLabel(1, idx); }));
// Accions per a llançar `DefineButtons` per a cada slot assignat.
// Només visibles si el jugador té un mando vinculat — equivalent al
// `define_buttons_->enable(...)` que dispara l'acció a coffee_crisis.
Item define_p1 = makeAction("menu.items.define_buttons_p1",
[]() { startDefineButtonsForPlayer(0); });
define_p1.visible = []() { return playerHasGamepad(0); };
page.items.push_back(std::move(define_p1));
Item define_p2 = makeAction("menu.items.define_buttons_p2",
[]() { startDefineButtonsForPlayer(1); });
define_p2.visible = []() { return playerHasGamepad(1); };
page.items.push_back(std::move(define_p2));
return page; return page;
} }
void ServiceMenu::startDefineButtons(SDL_Gamepad* pad, const std::string& name, const std::string& path) {
// Wrapper públic perquè els items del menú (factories estàtiques)
// puguin atacar el sub-mòdul sense exposar-lo. Evita haver de fer
// `define_buttons_` `public`.
define_buttons_.enable(pad, name, path);
}
auto ServiceMenu::buildSystem() -> Page { auto ServiceMenu::buildSystem() -> Page {
Page page; Page page;
page.title_key = "menu.titles.system"; page.title_key = "menu.titles.system";
@@ -1038,10 +1204,13 @@ void ServiceMenu::renderPage(const Page& page, int box_x, int box_y, int box_w)
first_item = false; first_item = false;
const bool SELECTED = item.selectable && (i == page.cursor); const bool SELECTED = item.selectable && (i == page.cursor);
const bool CAPTURING_THIS = capturing_ && SELECTED && (item.kind == Item::Kind::KEY_BIND);
const std::string LABEL_TEXT = (item.kind == Item::Kind::LABEL) const std::string LABEL_TEXT = (item.kind == Item::Kind::LABEL)
? item.label_key ? item.label_key
: Locale::get().text(item.label_key); : Locale::get().text(item.label_key);
const std::string VALUE = formatValue(item); const std::string VALUE = CAPTURING_THIS
? Locale::get().text("menu.values.press_key")
: formatValue(item);
Uint8 label_color = UiConfig::get().menuLabel(); Uint8 label_color = UiConfig::get().menuLabel();
if (item.kind == Item::Kind::LABEL) { if (item.kind == Item::Kind::LABEL) {
@@ -1118,4 +1287,8 @@ void ServiceMenu::render() {
}); });
renderPage(page, BOX_X, BOX_Y, BOX_WIDTH); renderPage(page, BOX_X, BOX_Y, BOX_WIDTH);
Rendering::popClip(); Rendering::popClip();
// Overlay de captura de botons al damunt del menú. Es pinta sense
// clip perquè ocupa la seua pròpia caixa centrada.
define_buttons_.render();
} }
+37
View File
@@ -3,11 +3,13 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <cstddef> #include <cstddef>
#include <cstdint>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include "core/input/define_buttons.hpp"
#include "core/rendering/surface.hpp" // Palette #include "core/rendering/surface.hpp" // Palette
class Text; class Text;
@@ -56,6 +58,21 @@ class ServiceMenu {
// false; és el caller qui decideix obrir-lo amb la hotkey. // false; és el caller qui decideix obrir-lo amb la hotkey.
auto handleEvent(const SDL_Event& ev) -> bool; auto handleEvent(const SDL_Event& ev) -> bool;
// True mentre estem esperant que l'usuari pulse una tecla per assignar
// a l'item KEY_BIND seleccionat. El Director redirigeix els KEY_DOWN
// a `captureKey` mentre dura — replica `Menu::isCapturing` d'AEE.
[[nodiscard]] auto isCapturing() const -> bool { return capturing_; }
// Aplica l'scancode capturat a l'item seleccionat (si encara és KEY_BIND
// i té setter). ESCAPE cancel·la sense assignar.
void captureKey(SDL_Scancode sc);
// Arranca el sub-mòdul `DefineButtons` per al mando indicat. El menú
// queda "obert" però els events del mando es redirigeixen al sub-mòdul
// fins que acabi la seqüència o l'usuari pulse ESC. Replica el patró
// `define_buttons_->enable(...)` de coffee_crisis_arcade_edition.
void startDefineButtons(SDL_Gamepad* pad, const std::string& name, const std::string& path);
private: private:
// --- Struct d'un item d'una pàgina --- // --- Struct d'un item d'una pàgina ---
// //
@@ -185,6 +202,11 @@ class ServiceMenu {
// --- Navegació i estat --- // --- Navegació i estat ---
void pushPage(Page page); void pushPage(Page page);
void popPage(); void popPage();
// Tractament del bloc d'events de gamepad (BUTTON_DOWN). Extret a
// mètode propi per a mantenir la complexitat cognitiva d'`handleEvent`
// sota el llindar de clang-tidy.
void handleGamepadButtonDown(const SDL_GamepadButtonEvent& ev);
[[nodiscard]] auto top() -> Page&; [[nodiscard]] auto top() -> Page&;
[[nodiscard]] auto topConst() const -> const Page&; [[nodiscard]] auto topConst() const -> const Page&;
[[nodiscard]] static auto isItemVisible(const Item& item) -> bool; [[nodiscard]] static auto isItemVisible(const Item& item) -> bool;
@@ -211,6 +233,14 @@ class ServiceMenu {
[[nodiscard]] static auto makeShaderTypeItem() -> Item; [[nodiscard]] static auto makeShaderTypeItem() -> Item;
[[nodiscard]] static auto makeShaderPresetItem() -> Item; [[nodiscard]] static auto makeShaderPresetItem() -> Item;
// Fàbrica genèrica per a un KEY_BIND vinculat a una acció del jugador
// `player_id`. Llegeix d'`Options::controls().keyboard_p*` i delega
// la mutació a `Options::setKeyboardBinding(...)` (persisteix).
[[nodiscard]] static auto makeKeyboardItem(
const std::string& label_key,
int player_id,
std::uint8_t action) -> Item;
// --- Render --- // --- Render ---
// "Visible" inclou l'animació de tancament: retorna true mentre la caixa // "Visible" inclou l'animació de tancament: retorna true mentre la caixa
// encara ha de dibuixar-se, encara que open_ siga false. // encara ha de dibuixar-se, encara que open_ siga false.
@@ -232,4 +262,11 @@ class ServiceMenu {
float animated_h_{0.0F}; // Alçada suavitzada cap a `targetBoxHeight`. float animated_h_{0.0F}; // Alçada suavitzada cap a `targetBoxHeight`.
bool transition_active_{false}; // Push/pop de pàgina en curs. bool transition_active_{false}; // Push/pop de pàgina en curs.
float transition_progress_{0.0F}; // 0 → 1 durant la transició de pàgina. float transition_progress_{0.0F}; // 0 → 1 durant la transició de pàgina.
bool capturing_{false}; // True mentre esperem una tecla per a un KEY_BIND.
// Sub-mòdul de captura de botons del mando. Quan està actiu absorbeix
// tots els events de mando (BUTTON_DOWN/UP, AXIS_MOTION) i, en acabar,
// persisteix la nova configuració via `GamepadBindings`. Replica el
// patró `define_buttons_` de coffee_crisis_arcade_edition.
DefineButtons define_buttons_;
}; };
+1 -1
View File
@@ -260,7 +260,7 @@ namespace Defaults {
// així que Player::loadFrom només cal cridar-la amb un dels dos YAMLs. // així que Player::loadFrom només cal cridar-la amb un dels dos YAMLs.
constexpr const char* PLAYER_SPRITE_P2 = "data/player/morcus.gif"; constexpr const char* PLAYER_SPRITE_P2 = "data/player/morcus.gif";
constexpr const char* PLAYER_YAML_P2 = "data/player/morcus.yaml"; constexpr const char* PLAYER_YAML_P2 = "data/player/morcus.yaml";
constexpr const char* MUSIC_TRACK = "pyramid_1.ogg"; constexpr const char* MUSIC_TRACK = "playing.ogg";
} // namespace Pyramid } // namespace Pyramid
} // namespace Defaults } // namespace Defaults
+1 -1
View File
@@ -3,7 +3,7 @@
namespace Texts { namespace Texts {
constexpr const char* WINDOW_TITLE = "© 2026 Aventures en Egipte — JailDesigner"; constexpr const char* WINDOW_TITLE = "© 2026 Aventures en Egipte — JailDesigner";
constexpr const char* COPYRIGHT = "© 2026 JailDesigner"; constexpr const char* COPYRIGHT = "© 2026 JailDesigner";
constexpr const char* VERSION = "0.29.0"; // Single source of truth — Makefile el grep constexpr const char* VERSION = "0.30.0"; // Single source of truth — Makefile el grep
} // namespace Texts } // namespace Texts
namespace GameCanvas { namespace GameCanvas {
+256
View File
@@ -0,0 +1,256 @@
// Smoke test del parser d'animacions amb suport de flip H/V. Stand-alone:
// només enllaça `animated_sprite_parser.cpp` (la part del motor que NO toca
// Resource::Cache ni Surface), perquè la cobertura interessa el contracte
// de càrrega del YAML, no el render real.
//
// Tests coberts:
// T1 default — YAML sense cap flip declarat carrega tots els frames
// amb (flip_h=false, flip_v=false). L'aplicació al sprite
// resulta en `SDL_FLIP_NONE`.
// T2 flip a nivell d'animació — `flip_h: true` amb frames `int` →
// tots els frames hereten flip_h=true.
// T3 override per-frame — frame mapping `{ index: N, flip_h: true }`
// en una animació sense flip → només aquest frame
// té el flag.
// T4 mapping no hereta — animació amb `flip_h: true` però un frame
// `{ index: N }` (mapping sense flip) → flip_h=false
// en aquest frame (el mapping declara el seu propi
// estat, sense heretar).
// T5 combinat V+H — frame `{ index, flip_h: true, flip_v: true }` →
// ambdós flags actius.
#include <cstdio>
#include <iostream>
#include <string>
#include "core/rendering/sprite/animated_sprite.hpp"
#include "external/fkyaml_node.hpp"
namespace {
int g_failures = 0;
void check(bool cond, const char* msg, const char* file, int line) {
if (!cond) {
std::cerr << " FAIL: " << msg << " (" << file << ":" << line << ")\n";
++g_failures;
}
}
#define REQUIRE(cond) check((cond), #cond, __FILE__, __LINE__)
// Paràmetres comuns per a tots els tests: spritesheet hipotètic 64×64
// amb tiles de 16×16 → grid 4×4, max_tiles=16.
auto sheetParams() -> AnimatedSprite::SheetParams {
return AnimatedSprite::SheetParams{
.frame_width = 16.0F,
.frame_height = 16.0F,
.frames_per_row = 4,
.max_tiles = 16,
};
}
auto parse(const std::string& yaml_str) {
return AnimatedSprite::parseAnimationsFromYaml(
fkyaml::node::deserialize(yaml_str), sheetParams());
}
void testDefaultNoFlip() {
std::cout << "[T1] default: cap flip declarat → frames sense flip\n";
const std::string YAML = R"(
animations:
walk: { frames: [0, 1, 2, 3], frame_ms: 100 }
stand: { frames: [4] }
)";
const auto ANIMS = parse(YAML);
REQUIRE(ANIMS.size() == 2);
const auto WALK_IT = ANIMS.find("walk");
REQUIRE(WALK_IT != ANIMS.end());
if (WALK_IT != ANIMS.end()) {
const auto& WALK = WALK_IT->second;
REQUIRE(WALK.frames.size() == 4);
for (const auto& F : WALK.frames) {
REQUIRE(!F.flip_h);
REQUIRE(!F.flip_v);
}
}
const auto STAND_IT = ANIMS.find("stand");
REQUIRE(STAND_IT != ANIMS.end());
if (STAND_IT != ANIMS.end()) {
const auto& STAND = STAND_IT->second;
REQUIRE(STAND.frames.size() == 1);
REQUIRE(!STAND.frames[0].flip_h);
REQUIRE(!STAND.frames[0].flip_v);
}
}
void testFlipAtAnimationLevel() {
std::cout << "[T2] flip a nivell d'anim hereten frames int\n";
const std::string YAML = R"(
animations:
walk_west: { frames: [8, 9, 10, 11], frame_ms: 100, flip_h: true }
)";
const auto ANIMS = parse(YAML);
const auto IT = ANIMS.find("walk_west");
REQUIRE(IT != ANIMS.end());
if (IT != ANIMS.end()) {
const auto& A = IT->second;
REQUIRE(A.frames.size() == 4);
for (const auto& F : A.frames) {
REQUIRE(F.flip_h);
REQUIRE(!F.flip_v);
}
}
}
void testOverridePerFrame() {
std::cout << "[T3] override per-frame en animació sense flip\n";
const std::string YAML = R"(
animations:
death_spin:
frames:
- 0
- 4
- 8
- { index: 8, flip_h: true }
frame_ms: 100
)";
const auto ANIMS = parse(YAML);
const auto IT = ANIMS.find("death_spin");
REQUIRE(IT != ANIMS.end());
if (IT != ANIMS.end()) {
const auto& A = IT->second;
REQUIRE(A.frames.size() == 4);
// Els tres primers (int, sense flip a l'anim) → tot a false.
REQUIRE(!A.frames[0].flip_h);
REQUIRE(!A.frames[0].flip_v);
REQUIRE(!A.frames[1].flip_h);
REQUIRE(!A.frames[1].flip_v);
REQUIRE(!A.frames[2].flip_h);
REQUIRE(!A.frames[2].flip_v);
// El quart (mapping amb flip_h: true) → només flip_h.
REQUIRE(A.frames[3].flip_h);
REQUIRE(!A.frames[3].flip_v);
}
}
void testMappingDoesNotInherit() {
std::cout << "[T4] mapping NO hereta el flip de l'animació\n";
const std::string YAML = R"(
animations:
mixed:
frames:
- 0
- { index: 4 }
frame_ms: 100
flip_h: true
)";
const auto ANIMS = parse(YAML);
const auto IT = ANIMS.find("mixed");
REQUIRE(IT != ANIMS.end());
if (IT != ANIMS.end()) {
const auto& A = IT->second;
REQUIRE(A.frames.size() == 2);
// Frame int hereta el flip_h true de l'animació.
REQUIRE(A.frames[0].flip_h);
REQUIRE(!A.frames[0].flip_v);
// Frame mapping declara els seus propis flags (cap declarat
// → false) i NO hereta del flip a nivell d'anim.
REQUIRE(!A.frames[1].flip_h);
REQUIRE(!A.frames[1].flip_v);
}
}
void testDeathSpinning() {
std::cout << "[T6] death_spinning: 12 frames + frame_ms list + loop:false\n";
// Mateix bloc que sam.yaml/morcus.yaml. 3 voltes horàries: per
// cada volta, sud (idx 0) → oest (idx 8 amb flip_h) → nord (idx 4)
// → est (idx 8). 12 frames totals; rampa lineal de durades; sense
// loop (es queda al darrer frame).
const std::string YAML = R"(
animations:
death_spinning:
frames:
- 0
- { index: 8, flip_h: true }
- 4
- 8
- 0
- { index: 8, flip_h: true }
- 4
- 8
- 0
- { index: 8, flip_h: true }
- 4
- 8
frame_ms: [60, 80, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280]
loop: false
)";
const auto ANIMS = parse(YAML);
const auto IT = ANIMS.find("death_spinning");
REQUIRE(IT != ANIMS.end());
if (IT == ANIMS.end()) { return; }
const auto& A = IT->second;
REQUIRE(A.frames.size() == 12);
REQUIRE(A.speeds.size() == 12);
REQUIRE(A.loop_from == -1); // loop:false → -1
// Patró cíclic: posicions 1,5,9 (oest) tenen flip_h=true; la resta no.
for (size_t i = 0; i < A.frames.size(); ++i) {
const bool IS_WEST = (i % 4 == 1);
REQUIRE(A.frames[i].flip_h == IS_WEST);
REQUIRE(!A.frames[i].flip_v);
}
// Rampa de durades creixent (acceleració negativa visual).
for (size_t i = 1; i < A.speeds.size(); ++i) {
REQUIRE(A.speeds[i] > A.speeds[i - 1]);
}
}
void testCombinedVerticalAndHorizontal() {
std::cout << "[T5] flip combinat V+H per-frame\n";
const std::string YAML = R"(
animations:
upside_down:
frames:
- { index: 5, flip_h: true, flip_v: true }
frame_ms: 100
)";
const auto ANIMS = parse(YAML);
const auto IT = ANIMS.find("upside_down");
REQUIRE(IT != ANIMS.end());
if (IT != ANIMS.end()) {
const auto& A = IT->second;
REQUIRE(A.frames.size() == 1);
REQUIRE(A.frames[0].flip_h);
REQUIRE(A.frames[0].flip_v);
}
}
} // namespace
auto main() -> int {
std::cout << "== smoke tests: animation flip parser ==\n";
testDefaultNoFlip();
testFlipAtAnimationLevel();
testOverridePerFrame();
testMappingDoesNotInherit();
testCombinedVerticalAndHorizontal();
testDeathSpinning();
if (g_failures == 0) {
std::cout << "ALL OK\n";
return 0;
}
std::cerr << g_failures << " smoke check(s) failed\n";
return 1;
}
+2 -2
View File
@@ -444,7 +444,7 @@ namespace {
// Direcció preferida: dreta. Mòmia ha d'entrar en HURT i avançar // Direcció preferida: dreta. Mòmia ha d'entrar en HURT i avançar
// a la dreta (la velocitat de hurt és més alta que la base). // a la dreta (la velocitat de hurt és més alta que la base).
mummy.enterHurt(Mummy::Direction::RIGHT); mummy.enterHurt(Mummy::Direction::EAST);
REQUIRE(mummy.getState() == Mummy::State::HURT); REQUIRE(mummy.getState() == Mummy::State::HURT);
REQUIRE(mummy.getDirX() > 0.0F); REQUIRE(mummy.getDirX() > 0.0F);
@@ -488,7 +488,7 @@ namespace {
// timer. Aquesta variant del test NO comprova la sortida // timer. Aquesta variant del test NO comprova la sortida
// per col·lisió; només verifica que entrar a HURT a la // per col·lisió; només verifica que entrar a HURT a la
// paret no peta. // paret no peta.
mummy.enterHurt(Mummy::Direction::RIGHT); mummy.enterHurt(Mummy::Direction::EAST);
REQUIRE(mummy.getState() == Mummy::State::HURT); REQUIRE(mummy.getState() == Mummy::State::HURT);
// Update llarg → expira el timer i torna a ROAMING. // Update llarg → expira el timer i torna a ROAMING.
mummy.update(1.0F); mummy.update(1.0F);