Compare commits

..
64 Commits
Author SHA1 Message Date
JailDesigner 3442b9b481 chore: bump v0.32.0 — Notifier configurable, F5/F6 gatejades i CRTPI passthrough 2026-05-02 15:16:40 +02:00
JailDesigner 8746ee9ec9 Merge fix/shader-keys-and-service-menu — passthrough real i F5/F6 gatejades 2026-05-02 15:13:41 +02:00
JailDesigner ef7fbf4027 fix(shader): force passthrough quan està apagat; F5/F6 ignorats sense shader actiu 2026-05-02 15:12:58 +02:00
JailDesigner 964c32dd0d Merge feat/notifier-polish — geometria i animació del Notifier al ui.yaml 2026-05-02 14:59:28 +02:00
JailDesigner 970fe2b4db feat(notifier): entrada/eixida per temps amb easing, configurable a ui.yaml 2026-05-02 14:58:25 +02:00
JailDesigner f816e75cc0 feat(input): KP_2 dispara notificació llarga per a provar multilinia 2026-05-02 14:51:29 +02:00
JailDesigner 2e8df45b38 feat(notifier): geometria al ui.yaml; +1px alçada, text 1px més avall, marge superior 3 2026-05-02 14:43:04 +02:00
JailDesigner 643e5442c3 Merge fix/lane-snap-and-door-corridor — snap a lane i corridor amb porta oberta 2026-05-02 14:31:57 +02:00
JailDesigner 82ccf65ce1 fix(player): no snap a lane si pica contra mur extern; corridor només si porta oberta 2026-05-02 14:30:09 +02:00
JailDesigner 1ae6912be5 Merge feat/entity-editor — editor d'entitats complet 2026-05-02 13:36:34 +02:00
JailDesigner c003e6665e feat(editor): mou la porta i persisteix geometry.door_col 2026-05-02 13:35:01 +02:00
JailDesigner a254fdd669 feat(editor): hand pick per base, z-sort, amaga entitats en TILE, HUD 2-files centrat 2026-05-02 12:42:47 +02:00
JailDesigner c0362abf67 fix(editor): inverteix posició des del collision_rect i restaura mòmies r1 2026-05-02 12:22:14 +02:00
JailDesigner d13313503c fix(editor): preferDisk en Debug + reload de sala al entrar i sortir 2026-05-02 12:20:16 +02:00
JailDesigner 2a78c78e77 feat(editor): franja HUD amb mode, brush/sel, cursor i recompte de constraints 2026-05-02 11:41:25 +02:00
JailDesigner 10e16d7905 feat(editor): valida 1 porta i ≥1 sarc en sortida i en borrar 2026-05-02 11:37:57 +02:00
JailDesigner ecb4625465 feat(editor): EntityWriter — persisteix el bloc entities: al sortir 2026-05-02 11:35:10 +02:00
JailDesigner b4e36944d7 feat(editor): mode mà — selecció, mou, borra i marc d'entitats 2026-05-02 11:28:41 +02:00
JailDesigner 9b09ccb765 feat(editor): col·locació d'entitats amb snap per tipus i bloqueig de solapaments 2026-05-02 11:21:05 +02:00
JailDesigner 94213f9fd6 feat(editor): entity picker amb noms del factory i flag editable per plantilla 2026-05-02 11:07:02 +02:00
JailDesigner f591aa82fd fix(whip_attack): treure entity_manager_ duplicat (ja viu a Entity) 2026-05-02 11:01:53 +02:00
JailDesigner 2ef5968e0d feat(editor): màquina d'estats Mode {TILE,ENTITY,HAND} + Picker 2026-05-02 10:58:24 +02:00
JailDesigner ca03a1307e feat(editor): bindings per E (entity picker), H (mà), Supr (borrar) 2026-05-02 10:53:50 +02:00
JailDesigner fe916d263f Merge feat/controls — sticks al menú, accept/cancel remappable, facing real 2026-05-01 10:25:06 +02:00
JailDesigner de7233a012 fix(player): facing del sprite des del desplaçament real, no de l'input 2026-05-01 10:24:13 +02:00
JailDesigner d26ecec04f feat(menu): acceptar amb ATTACK i cancel·lar amb JUMP del mando 2026-05-01 09:07:22 +02:00
JailDesigner fddea0c449 feat(menu): navegació del menú de servei amb sticks 2026-05-01 09:03:20 +02:00
JailDesigner 2c1c1a442b Merge feat/notifier-tweaks — vora externa al notificador 2026-05-01 08:57:02 +02:00
JailDesigner d1948afe4b feat(notifier): vora externa del color del text 2026-05-01 08:56:53 +02:00
JailDesigner ac06b05332 chore(audio): defaults a música 100% i so 20% 2026-04-30 20:45:03 +02:00
JailDesigner 7d11dde0b1 Merge feat/menu-pause — pausa del món i sons al menú de servei 2026-04-30 20:40:04 +02:00
JailDesigner 73add013dd art: stun de sam/morcus refinat i mòmia més baixa per saltar millor 2026-04-30 20:22:09 +02:00
JailDesigner 4e2ba827e3 Merge feat/mummy-ai — generalització del filtre de cruces de la mòmia 2026-04-30 20:06:54 +02:00
JailDesigner 9ac0c0cb74 refactor(mummy): generalitza el filtre de cruces a isBlockedAheadInDir 2026-04-30 20:05:36 +02:00
JailDesigner 11a909b58d Merge feat/hazard-hole — hazard Hole, evitació per mòmies i broadcast de decals al load 2026-04-30 19:29:57 +02:00
JailDesigner 17cf6e0636 chore: bump v0.31.0 — hazard Hole, IA d'evitació de mòmies i broadcast de decals 2026-04-30 19:29:53 +02:00
JailDesigner 7d0aca506a feat(decals): tiles de decal de sòl compten com a pintats des del load 2026-04-30 19:16:21 +02:00
JailDesigner 7cd7b4dc4f chore(hole): neteja de comentaris obsolets i notes sobre riscos latents 2026-04-30 18:52:49 +02:00
JailDesigner 6d4f606eeb feat(mummy): evita forats per detecció reactiva al canvi de tile 2026-04-30 18:30:03 +02:00
JailDesigner 1ccd444aad fix(hazard): el jugador només cau al Hole si la hit-box hi cap del tot 2026-04-30 16:56:56 +02:00
JailDesigner 3bfca35f3a feat(mummy): evita tiles amb decal trigger (Hole) en decidir direcció 2026-04-30 16:50:26 +02:00
JailDesigner 3e5217066f fix(hazard): aterrar damunt d'un Hole salta el LANDING_STUN 2026-04-30 13:53:26 +02:00
JailDesigner 4da50571eb feat(hazard): nou hazard Hole — caiguda per forat amb clip rect 2026-04-30 13:32:17 +02:00
JailDesigner d4593d0686 Merge feat/tilepicker-layout — tilepicker amb editor.yaml 2026-04-30 09:52:51 +02:00
JailDesigner a53cf4b797 feat(editor): spacing, border i centrat vertical del tilepicker via editor.yaml 2026-04-30 09:51:30 +02:00
JailDesigner bcbf990a5f feat(editor): tilepicker respecta l'amplada del tileset si hi cap 2026-04-30 09:36:24 +02:00
JailDesigner 45514ae2f4 feat(menu): sons de navegació i acceptació al menú de servei 2026-04-29 20:27:01 +02:00
JailDesigner 0847e9c544 feat(menu): congela el món mentre el menú de servei està obert 2026-04-29 20:19:20 +02:00
JailDesigner 27cf2f032d chore(audio): normalitzat el volum dels efectes 2026-04-29 20:00:07 +02:00
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
119 changed files with 6019 additions and 1594 deletions

No files matched your search

+31 -1
View File
@@ -50,6 +50,7 @@ set(ENTITY_SOURCES_BASE
source/game/entities/concrete/rolling_rock.cpp source/game/entities/concrete/rolling_rock.cpp
source/game/entities/concrete/wall_trap.cpp source/game/entities/concrete/wall_trap.cpp
source/game/entities/concrete/floor_trap.cpp source/game/entities/concrete/floor_trap.cpp
source/game/entities/concrete/hole.cpp
source/game/entities/concrete/dust.cpp source/game/entities/concrete/dust.cpp
) )
@@ -74,6 +75,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 +91,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
@@ -112,12 +116,19 @@ set(APP_SOURCES
source/game/map/map.cpp source/game/map/map.cpp
source/game/map/tilemap.cpp source/game/map/tilemap.cpp
source/game/map/tilemap_writer.cpp source/game/map/tilemap_writer.cpp
# Writer surgical de claus dins de `geometry:` (door_col), paral·lel
# al TilemapWriter. Només Debug.
source/game/map/map_writer.cpp
source/game/map/paint_layer.cpp source/game/map/paint_layer.cpp
source/game/levels/level_manager.cpp source/game/levels/level_manager.cpp
# Mòdul editor (només activa contingut en Debug via `#ifdef _DEBUG`). # Mòdul editor (només activa contingut en Debug via `#ifdef _DEBUG`).
# En Release el fitxer compila a un object buit i l'enllaçador no # En Release els fitxers compilen a object buit i l'enllaçador no
# arrossega cap símbol del joc final. # arrossega cap símbol del joc final.
source/game/editor/editor.cpp source/game/editor/editor.cpp
source/game/editor/editor_config.cpp
# Writer del bloc `entities:` del YAML del nivell, paral·lel al
# TilemapWriter pero per a la persistència de l'editor d'entitats.
source/game/entities/entity_writer.cpp
${ENTITY_SOURCES} ${ENTITY_SOURCES}
# Fitxer només del binari principal: `loadFromLevel` depèn de # Fitxer només del binari principal: `loadFromLevel` depèn de
# Resource::Cache (→ Surface → GIF → àudio). Separat per no arrossegar # Resource::Cache (→ Surface → GIF → àudio). Separat per no arrossegar
@@ -345,6 +356,7 @@ option(BUILD_TESTS "Build smoke tests" ON)
if(BUILD_TESTS) if(BUILD_TESTS)
add_executable(test_entities_smoke add_executable(test_entities_smoke
tests/test_entities_smoke.cpp tests/test_entities_smoke.cpp
tests/hole_player_stubs.cpp
source/core/resources/resource_helper.cpp source/core/resources/resource_helper.cpp
source/core/resources/resource_pack.cpp source/core/resources/resource_pack.cpp
source/game/map/map.cpp source/game/map/map.cpp
@@ -368,6 +380,7 @@ if(BUILD_TESTS)
add_executable(test_whip_attack add_executable(test_whip_attack
tests/test_whip_attack.cpp tests/test_whip_attack.cpp
tests/whip_attack_player_stubs.cpp tests/whip_attack_player_stubs.cpp
tests/hole_player_stubs.cpp
source/core/resources/resource_helper.cpp source/core/resources/resource_helper.cpp
source/core/resources/resource_pack.cpp source/core/resources/resource_pack.cpp
source/game/map/map.cpp source/game/map/map.cpp
@@ -444,4 +457,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()
+13
View File
@@ -0,0 +1,13 @@
# Tema visual de l'editor de tilemap (només Debug). Carregat per
# EditorConfig a l'arrencada; les claus absents cauen als valors compilats
# a Defaults::Editor (veure source/utils/defaults.hpp).
picker:
margin: 8 # px entre canvas i caixa del picker
spacing: 1 # px de separació entre tiles dins del picker
# Colors ARGB en hex. Accepta enter (0xFF2244FF) o cadena ("#FF2244FF").
colors:
border: "0xFF2244FF" # vora 1 px que envolta canvas i picker
picker_bg: "0xFF101824" # fons del picker
picker_hover: "0xFFFFFFFF" # vora 1 px del tile sota el cursor
+1
View File
@@ -1,5 +1,6 @@
name: door name: door
type: interactable type: interactable
editable: true
sprite: sprite:
path: door.gif path: door.gif
size: [48, 32] size: [48, 32]
Binary file not shown.

After

Width:  |  Height:  |  Size: 182 B

+27
View File
@@ -0,0 +1,27 @@
name: hole
type: static_hazard
editable: true
sprite:
path: hole.gif
size: [16, 16]
tile_size: [1, 1]
collision:
# Forat: ocupa el tile sencer (16×16). `height: 0` deixa que un salt
# AIRBORNE el creui sense disparar el contacte (el filtre d'altura de
# `checkPlayerCollisions` el descarta quan `jump_z_ > height`). El
# bucle de contacte admet l'entitat per `flags.triggers_on_overlap`,
# encara que `flags.collides` sigui false.
size: [16, 16]
height: 0
flags:
collides: false
interactable: false
# Avui el loader no llig el bloc `flags:` (és declaratiu); el flag
# real es fixa al constructor C++ de `Hole`. Mantenim el camp per
# coherència amb la resta de plantilles i per quan es cablege la
# lectura genèrica de flags.
triggers_on_overlap: true
animations:
idle: { frames: [0], fps: 1, loop: true }
stats:
damage: 0
+18 -8
View File
@@ -1,5 +1,6 @@
name: mummy name: mummy
type: dynamic_hazard type: dynamic_hazard
editable: true
sprite: sprite:
path: mummy.gif path: mummy.gif
size: [16, 32] size: [16, 32]
@@ -18,26 +19,35 @@ collision:
# jugador amb jump_z > height no col·lisiona amb la momia (la salta # jugador amb jump_z > height no col·lisiona amb la momia (la salta
# per damunt). Amb impulse=240 + gravity=600 el pic ronda els 48 px, # per damunt). Amb impulse=240 + gravity=600 el pic ronda els 48 px,
# així que 24 deixa marge per esquivar amb un salt no perfecte. # així que 24 deixa marge per esquivar amb un salt no perfecte.
height: 24 height: 12
flags: 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
@@ -1,5 +1,6 @@
name: sarcophagus name: sarcophagus
type: interactable type: interactable
editable: true
sprite: sprite:
path: sarcophagus.gif path: sarcophagus.gif
size: [48, 32] size: [48, 32]
+1
View File
@@ -1,5 +1,6 @@
name: torch name: torch
type: decoration type: decoration
editable: true
sprite: sprite:
path: torch.gif path: torch.gif
size: [16, 16] size: [16, 16]
+10 -1
View File
@@ -44,9 +44,18 @@ keys:
- id: editor_toggle - id: editor_toggle
code: "9" code: "9"
desc: "Entra/surt del mode editor (només Debug)" desc: "Entra/surt del mode editor (només Debug)"
- id: editor_picker - id: editor_tile_picker
code: "T" code: "T"
desc: "Obri/tanca el tilepicker dins de l'editor" desc: "Obri/tanca el tilepicker dins de l'editor"
- id: editor_entity_picker
code: "E"
desc: "Obri/tanca l'entity picker dins de l'editor"
- id: editor_hand
code: "H"
desc: "Activa el mode mà de l'editor (selecciona/mou/borra entitats)"
- id: editor_delete
code: "Delete"
desc: "Esborra l'entitat seleccionada en mode mà"
- id: prev_room - id: prev_room
code: "[" code: "["
desc: "Salta a la sala anterior del levels.yaml (només Debug)" desc: "Salta a la sala anterior del levels.yaml (només Debug)"
+12
View File
@@ -27,6 +27,9 @@ input:
editor_on: "Editor ON" editor_on: "Editor ON"
editor_off: "Editor OFF" editor_off: "Editor OFF"
editor_save_error: "Editor: error guardant el mapa" editor_save_error: "Editor: error guardant el mapa"
editor_keep_door: "Editor: cal almenys una porta"
editor_keep_sarc: "Editor: cal almenys un sarcòfag"
editor_invalid_level: "Editor: nivell incomplet (porta i sarcs)"
hud: hud:
p1: "P1" p1: "P1"
@@ -71,8 +74,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 +112,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"
+17
View File
@@ -27,6 +27,9 @@ input:
editor_on: "Editor ON" editor_on: "Editor ON"
editor_off: "Editor OFF" editor_off: "Editor OFF"
editor_save_error: "Editor: map save failed" editor_save_error: "Editor: map save failed"
editor_keep_door: "Editor: at least one door required"
editor_keep_sarc: "Editor: at least one sarcophagus required"
editor_invalid_level: "Editor: level incomplete (door and sarcs)"
hud: hud:
p1: "P1" p1: "P1"
@@ -57,6 +60,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 +73,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 +109,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"
+29 -28
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
@@ -49,24 +49,24 @@ geometry:
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per # 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent # on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna. # mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
door_col: 10 door_col: 11
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:
@@ -82,8 +82,6 @@ data:
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat # d'obertura (262/263). La momia al corredor central. L'antorxa al costat
# superior esquerre per verificar render d'una entitat decorativa. # superior esquerre per verificar render d'una entitat decorativa.
entities: entities:
- type: torch
position: [4, 2]
- type: torch - type: torch
position: [8, 2] position: [8, 2]
- type: torch - type: torch
@@ -130,11 +128,14 @@ entities:
sarc_position: [2, 4] sarc_position: [2, 4]
- type: sarcophagus - type: sarcophagus
sarc_position: [3, 4] sarc_position: [3, 4]
- type: hole
position: [4, 7]
- type: hole
position: [8, 11]
- type: hole
position: [15, 11]
- type: hole
position: [15, 5]
- type: hole
position: [8, 5]
- type: door - type: door
# Sense `position`: la porta sempre viu al mur trasser. La columna
# transitable la dicta `geometry.door_col`; el render i la caixa
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
- type: mummy
position: [10, 7]
- type: mummy
position: [7, 9]
+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

+88 -122
View File
@@ -1,143 +1,104 @@
# 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: # Mort per caiguda a un agujero (DeathCause::HOLE). `loop: false` perquè
frames: [4, 5, 6, 7] # `animationIsCompleted()` dispari el respawn timer determinísticament.
frame_ms: 50 death_hole:
frames: [15]
frame_ms: 60
loop: false
attack: # Inèrcia de frenada en soltar la direcció. `threshold_ms` filtra
frames: [8, 9, 10, 11] # tocs curts (passets d'un tile sense rellisquera).
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 +107,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

+88 -127
View File
@@ -1,143 +1,104 @@
# 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: # Mort per caiguda a un agujero (DeathCause::HOLE). `loop: false` perquè
frames: [24, 25, 26, 27] # `animationIsCompleted()` dispari el respawn timer determinísticament.
frame_ms: 50 death_hole:
frames: [15]
frame_ms: 60
loop: false
attack: # Inèrcia de frenada en soltar la direcció. `threshold_ms` filtra
frames: [20, 21, 22, 23] # tocs curts (passets d'un tile sense rellisquera).
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 +107,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.
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

+23
View File
@@ -65,6 +65,22 @@ hud:
flash_ms: 250 # durada del flash quan un jugador rep un hit flash_ms: 250 # durada del flash quan un jugador rep un hit
shake_ms: 300 # durada del shake que acompanya el flash shake_ms: 300 # durada del shake que acompanya el flash
# Geometria del popup superior (Notifier). En píxels del canvas, llevat de
# slide_ms (durada en ms de l'animació d'entrada i d'eixida; constant
# independentment de la mida de la caixa). El padding vertical és asimètric a
# propòsit: padding_top > padding_bottom puja una mica el text dins la caixa.
# Corbes disponibles: linear, outQuad, inQuad, inOutQuad, outCubic, inCubic.
notifier:
padding_h: 6
padding_top: 4
padding_bottom: 3
line_gap: 1
margin_top: 3
border_px: 1
slide_ms: 300
slide_in_curve: outCubic
slide_out_curve: inCubic
# Pausa graduada de la GameScene: el món no para d'un colp, s'atura amb # Pausa graduada de la GameScene: el món no para d'un colp, s'atura amb
# easing i torna a arrancar amb una altra corba. Els timings es poden # easing i torna a arrancar amb una altra corba. Els timings es poden
# afinar independentment, i les corbes permeten estils diferents entrant # afinar independentment, i les corbes permeten estils diferents entrant
@@ -75,3 +91,10 @@ pause:
exit_ms: 250 exit_ms: 250
enter_curve: outCubic enter_curve: outCubic
exit_curve: inCubic exit_curve: inCubic
# Sons del menú de servei. Els fitxers viuen a data/sounds/. SELECT
# acompanya navegació (amunt/avall) i back; ACCEPT acompanya modificació
# d'opció (esquerra/dreta/retorn) o entrada a un submenú.
sounds:
menu_select: "menu_select.wav"
menu_accept: "menu_accept.wav"
+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_;
};
+16
View File
@@ -89,6 +89,9 @@ namespace GlobalInputs {
} }
void onNextShader() { void onNextShader() {
// F5/F6 només tenen sentit quan els shaders estan actius. Si no,
// ignorem la pulsació en silenci — l'usuari ha de prémer F4 abans.
if (!Options::video().shader.enabled) { return; }
const std::string NEW_NAME = (Options::video().shader.current_shader == "crtpi") ? "postfx" : "crtpi"; const std::string NEW_NAME = (Options::video().shader.current_shader == "crtpi") ? "postfx" : "crtpi";
Options::setActiveShaderName(NEW_NAME); Options::setActiveShaderName(NEW_NAME);
OptionsApply::reapplyShader(); OptionsApply::reapplyShader();
@@ -98,6 +101,7 @@ namespace GlobalInputs {
// Busca l'entrada actual al vector de presets i passa a la següent // Busca l'entrada actual al vector de presets i passa a la següent
// (wrap-around). Si no en troba cap s'evita el canvi silenciosament. // (wrap-around). Si no en troba cap s'evita el canvi silenciosament.
void onNextPreset() { void onNextPreset() {
if (!Options::video().shader.enabled) { return; }
const bool IS_CRTPI = Options::video().shader.current_shader == "crtpi"; const bool IS_CRTPI = Options::video().shader.current_shader == "crtpi";
std::string new_preset; std::string new_preset;
if (IS_CRTPI) { if (IS_CRTPI) {
@@ -154,6 +158,14 @@ namespace GlobalInputs {
Options::setVideoRenderInfoPosition(static_cast<Defaults::RenderInfoPosition>(NEXT)); Options::setVideoRenderInfoPosition(static_cast<Defaults::RenderInfoPosition>(NEXT));
} }
// Debug helper: dispara una notificació prou llarga perquè
// `wrapToWidth` la parteixi en (almenys) dues línies. Útil per a
// verificar visualment el padding asimètric, el line_gap i la caixa
// multilinia després de tocar la geometria a `data/ui.yaml`.
void onDebugLongNotification() {
Notification::notify("Notificació de prova llarga per a comprovar que el text es parteix correctament en dues línies dins la caixa.");
}
void onExit() { void onExit() {
if (exit_window_s > 0.0F) { if (exit_window_s > 0.0F) {
quit_requested = true; quit_requested = true;
@@ -193,6 +205,10 @@ namespace GlobalInputs {
onCycleRenderInfo(); onCycleRenderInfo();
} else if (SC == KeyConfig::get().scancode("exit")) { } else if (SC == KeyConfig::get().scancode("exit")) {
onExit(); onExit();
} else if (SC == SDL_SCANCODE_KP_2) {
// Tecla de debug fora del KeyConfig: dispara una notificació
// multilinia per a inspeccionar el layout de la caixa.
onDebugLongNotification();
} }
} }
} }
+50 -18
View File
@@ -6,6 +6,7 @@
#include "core/rendering/screen.hpp" #include "core/rendering/screen.hpp"
#include "core/rendering/text.hpp" #include "core/rendering/text.hpp"
#include "core/ui/ui_config.hpp"
#include "utils/defines.hpp" #include "utils/defines.hpp"
namespace { namespace {
@@ -67,26 +68,39 @@ void Notifier::init(std::shared_ptr<Text> text, const Palette& ui_palette) {
} }
void Notifier::update(int delta_ms) { void Notifier::update(int delta_ms) {
const float DT = static_cast<float>(delta_ms) / 1000.0F; const auto& geo = UiConfig::get().notifier();
switch (status_) { switch (status_) {
case Status::RISING: case Status::RISING: {
y_ += SLIDE_SPEED * DT; slide_elapsed_ms_ += delta_ms;
if (y_ >= static_cast<float>(MARGIN_TOP)) { if (slide_elapsed_ms_ >= geo.slide_ms) {
y_ = static_cast<float>(MARGIN_TOP); y_ = slide_y_on_;
status_ = Status::ACTIVE; status_ = Status::ACTIVE;
} else {
const float T = static_cast<float>(slide_elapsed_ms_) / static_cast<float>(geo.slide_ms);
const float K = geo.slide_in_curve(T);
y_ = slide_y_off_ + ((slide_y_on_ - slide_y_off_) * K);
} }
break; break;
}
case Status::ACTIVE: case Status::ACTIVE:
remaining_ms_ -= delta_ms; remaining_ms_ -= delta_ms;
if (remaining_ms_ <= 0) { status_ = Status::VANISHING; } if (remaining_ms_ <= 0) {
break; slide_elapsed_ms_ = 0;
case Status::VANISHING: status_ = Status::VANISHING;
y_ -= SLIDE_SPEED * DT;
if (y_ <= static_cast<float>(-current_box_h_)) {
y_ = static_cast<float>(-current_box_h_ - MARGIN_TOP);
status_ = Status::HIDDEN;
} }
break; break;
case Status::VANISHING: {
slide_elapsed_ms_ += delta_ms;
if (slide_elapsed_ms_ >= geo.slide_ms) {
y_ = slide_y_off_;
status_ = Status::HIDDEN;
} else {
const float T = static_cast<float>(slide_elapsed_ms_) / static_cast<float>(geo.slide_ms);
const float K = geo.slide_out_curve(T);
y_ = slide_y_on_ + ((slide_y_off_ - slide_y_on_) * K);
}
break;
}
default: default:
break; break;
} }
@@ -95,6 +109,7 @@ void Notifier::update(int delta_ms) {
void Notifier::render() { void Notifier::render() {
if (status_ == Status::HIDDEN || text_ == nullptr || current_lines_.empty()) { return; } if (status_ == Status::HIDDEN || text_ == nullptr || current_lines_.empty()) { return; }
const auto& geo = UiConfig::get().notifier();
const int BOX_X = (GameCanvas::WIDTH - current_box_w_) / 2; const int BOX_X = (GameCanvas::WIDTH - current_box_w_) / 2;
const int BOX_Y = static_cast<int>(y_); const int BOX_Y = static_cast<int>(y_);
@@ -102,8 +117,17 @@ void Notifier::render() {
// (ARGB directe al canvas, sense tocar cap surface paletada). // (ARGB directe al canvas, sense tocar cap surface paletada).
Screen::get().fillRect(BOX_X, BOX_Y, current_box_w_, current_box_h_, ui_palette_[current_bg_color_]); Screen::get().fillRect(BOX_X, BOX_Y, current_box_w_, current_box_h_, ui_palette_[current_bg_color_]);
const int LINE_STEP = Rendering::Hud::FONT_SIZE_PX + LINE_GAP; // Vora externa: 4 línies d'1 px del color del text, paral·leles a cada
int line_y = BOX_Y + PADDING_V; // costat i una mica fora de la caixa. Cada segment es queda dins del
// rang del costat corresponent perquè les cantonades no es cobrisquen.
const std::uint32_t BORDER_ARGB = ui_palette_[current_text_color_];
Screen::get().fillRect(BOX_X, BOX_Y - geo.border_px, current_box_w_, geo.border_px, BORDER_ARGB);
Screen::get().fillRect(BOX_X, BOX_Y + current_box_h_, current_box_w_, geo.border_px, BORDER_ARGB);
Screen::get().fillRect(BOX_X - geo.border_px, BOX_Y, geo.border_px, current_box_h_, BORDER_ARGB);
Screen::get().fillRect(BOX_X + current_box_w_, BOX_Y, geo.border_px, current_box_h_, BORDER_ARGB);
const int LINE_STEP = Rendering::Hud::FONT_SIZE_PX + geo.line_gap;
int line_y = BOX_Y + geo.padding_top;
for (const auto& line : current_lines_) { for (const auto& line : current_lines_) {
text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
GameCanvas::CENTER_X, GameCanvas::CENTER_X,
@@ -120,18 +144,26 @@ void Notifier::notify(const std::string& text, Uint8 text_color, Uint8 bg_color,
// gestionar-ne una cua, canviar ací per una FIFO quan calga. // gestionar-ne una cua, canviar ací per una FIFO quan calga.
if (text_ == nullptr) { return; } if (text_ == nullptr) { return; }
const auto& geo = UiConfig::get().notifier();
const int MAX_TEXT_WIDTH = GameCanvas::WIDTH - (2 * SIDE_MARGIN) - (2 * geo.padding_h);
current_lines_ = wrapToWidth(text, MAX_TEXT_WIDTH, *text_, 1); current_lines_ = wrapToWidth(text, MAX_TEXT_WIDTH, *text_, 1);
// Amplada = max de les línies + padding. Alçada = N*glif + (N-1)*gap + padding. // Amplada = max de les línies + padding. Alçada = N*glif + (N-1)*gap + padding asimètric.
const int MAX_LINE_W = std::accumulate(current_lines_.begin(), current_lines_.end(), 0, [this](int acc, const std::string& s) { return std::max(acc, text_->length(s, 1)); }); const int MAX_LINE_W = std::accumulate(current_lines_.begin(), current_lines_.end(), 0, [this](int acc, const std::string& s) { return std::max(acc, text_->length(s, 1)); });
current_box_w_ = MAX_LINE_W + (2 * PADDING_H); current_box_w_ = MAX_LINE_W + (2 * geo.padding_h);
const int N = static_cast<int>(current_lines_.size()); const int N = static_cast<int>(current_lines_.size());
current_box_h_ = (N * Rendering::Hud::FONT_SIZE_PX) + ((N - 1) * LINE_GAP) + (2 * PADDING_V); current_box_h_ = (N * Rendering::Hud::FONT_SIZE_PX) + ((N - 1) * geo.line_gap) + geo.padding_top + geo.padding_bottom;
current_text_color_ = text_color; current_text_color_ = text_color;
current_bg_color_ = bg_color; current_bg_color_ = bg_color;
remaining_ms_ = time_ms; remaining_ms_ = time_ms;
y_ = static_cast<float>(-current_box_h_ - MARGIN_TOP);
// Posicions extremes en y (top-left de la caixa). Off = totalment fora
// del canvas per dalt; On = posició final amb la vora respectada.
slide_y_off_ = static_cast<float>(-current_box_h_ - geo.border_px - geo.margin_top);
slide_y_on_ = static_cast<float>(geo.margin_top + geo.border_px);
slide_elapsed_ms_ = 0;
y_ = slide_y_off_;
status_ = Status::RISING; status_ = Status::RISING;
} }
+12 -9
View File
@@ -43,15 +43,11 @@ class Notifier {
void notify(const std::string& text, Uint8 text_color, Uint8 bg_color, int time_ms); void notify(const std::string& text, Uint8 text_color, Uint8 bg_color, int time_ms);
static constexpr int PADDING_H = 6; // Marge lateral entre la caixa i les vores esquerra/dreta del canvas.
static constexpr int PADDING_V = 3; // Limita l'amplada disponible al text en multilinia (combinat amb el
static constexpr int LINE_GAP = 1; // Separació vertical entre línies multilinia. // padding horitzontal llegit d'UiConfig). No està exposat al YAML
static constexpr int MARGIN_TOP = 2; // Separació amb la vora superior del canvas. // perquè en pantalles 240 px no té sentit retallar més.
static constexpr float SLIDE_SPEED = 120.0F; static constexpr int SIDE_MARGIN = 10;
// Amplada màxima (sense comptar padding) que pot ocupar el text abans que
// `wrapToWidth` el parteixi en línies. Deixem marge respecte a la vora del
// canvas perquè la caixa no toqui els laterals.
static constexpr int MAX_TEXT_WIDTH = GameCanvas::WIDTH - 20 - (2 * PADDING_H);
private: private:
enum class Status : std::uint8_t { HIDDEN, enum class Status : std::uint8_t { HIDDEN,
@@ -72,4 +68,11 @@ class Notifier {
Uint8 current_text_color_{0}; Uint8 current_text_color_{0};
Uint8 current_bg_color_{0}; Uint8 current_bg_color_{0};
int remaining_ms_{0}; int remaining_ms_{0};
// Animació d'entrada/eixida per temps. `slide_elapsed_ms_` avança
// amb el delta dins de RISING/VANISHING; els extrems s'avaluen a
// `notify()` perquè depenen de la mida de la caixa.
int slide_elapsed_ms_{0};
float slide_y_off_{0.0F};
float slide_y_on_{0.0F};
}; };
@@ -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;
}
+45 -6
View File
@@ -15,12 +15,18 @@ namespace Resource::Helper {
Pack g_pack; Pack g_pack;
bool g_pack_loaded = false; bool g_pack_loaded = false;
bool g_fallback_enabled = true; bool g_fallback_enabled = true;
// Inverteix la prioritat (disc abans que pack). Activat en Debug
// per defecte; veure setPreferDisk al header.
bool g_prefer_disk = false;
auto readFromDisk(const std::string& filepath) -> std::vector<std::uint8_t> { auto readFromDisk(const std::string& filepath, bool verbose = true) -> std::vector<std::uint8_t> {
const auto FULL = resolve(filepath); const auto FULL = resolve(filepath);
std::ifstream file(FULL, std::ios::binary | std::ios::ate); std::ifstream file(FULL, std::ios::binary | std::ios::ate);
if (!file) { if (!file) {
std::cerr << "Resource::Helper: cannot open " << FULL << '\n'; // Silenciem si veníem de prefer_disk: aleshores caurem
// al pack i no és cap error real que el fitxer no estigui
// al disc.
if (verbose) { std::cerr << "Resource::Helper: cannot open " << FULL << '\n'; }
return {}; return {};
} }
std::streamsize size = file.tellg(); std::streamsize size = file.tellg();
@@ -37,6 +43,11 @@ namespace Resource::Helper {
auto initializeResourceSystem(const std::string& pack_file, bool enable_fallback) -> bool { auto initializeResourceSystem(const std::string& pack_file, bool enable_fallback) -> bool {
g_fallback_enabled = enable_fallback; g_fallback_enabled = enable_fallback;
// En el flux Debug típic (fallback actiu, source tree disponible)
// preferim sempre disc: el desenvolupador edita YAML/GIF a `data/`
// i ho veu reflectit al següent reload sense re-packejar. En
// Release el fallback està desactivat i aquest flag és inert.
g_prefer_disk = enable_fallback;
g_pack_loaded = g_pack.loadPack(pack_file); g_pack_loaded = g_pack.loadPack(pack_file);
if (g_pack_loaded) { if (g_pack_loaded) {
@@ -76,17 +87,45 @@ namespace Resource::Helper {
} }
auto loadFile(const std::string& filepath) -> std::vector<std::uint8_t> { auto loadFile(const std::string& filepath) -> std::vector<std::uint8_t> {
if (g_pack_loaded) { // Helper local: lookup al pack normalitzant la clau (els callers
// El pack està indexat relatiu a `data/` (la carpeta que // passen "data/foo.yaml" però l'índex del pack és relatiu a la
// s'empaqueta), però els callers passen "data/foo.yaml" amb // carpeta `data/`).
// prefix. Fem el strip ací perquè no hagin de tenir-ho en compte. const auto FROM_PACK = [&filepath]() -> std::vector<std::uint8_t> {
if (!g_pack_loaded) { return {}; }
constexpr std::string_view PREFIX = "data/"; constexpr std::string_view PREFIX = "data/";
std::string key = filepath; std::string key = filepath;
if (key.starts_with(PREFIX)) { key.erase(0, PREFIX.size()); } if (key.starts_with(PREFIX)) { key.erase(0, PREFIX.size()); }
if (g_pack.hasResource(key)) { return g_pack.getResource(key); } if (g_pack.hasResource(key)) { return g_pack.getResource(key); }
return {};
};
if (g_prefer_disk && g_fallback_enabled) {
// En Debug volem que el disc guanyi: així les edicions del
// editor in-game (i les manuals del repo) es vegen al pròxim
// reload sense rebuild del pack. Si el fitxer no és al disc,
// caiem al pack — útil per a recursos que només s'han
// empaquetat (cas estrany). El silenci a `readFromDisk` evita
// emplenar el log amb "cannot open" en aquest camí esperat.
auto bytes = readFromDisk(filepath, false);
if (!bytes.empty()) { return bytes; }
return FROM_PACK();
} }
// Política original: pack primer; disc com a fallback si el
// recurs no està empaquetat (típic d'arrencada en Debug abans
// del primer pack).
auto packed = FROM_PACK();
if (!packed.empty()) { return packed; }
if (g_fallback_enabled) { return readFromDisk(filepath); } if (g_fallback_enabled) { return readFromDisk(filepath); }
return {}; return {};
} }
void setPreferDisk(bool prefer) {
g_prefer_disk = prefer;
}
auto preferDisk() -> bool {
return g_prefer_disk;
}
} // namespace Resource::Helper } // namespace Resource::Helper
+11 -1
View File
@@ -32,7 +32,17 @@ namespace Resource::Helper {
// Carrega un recurs pel seu camí relatiu (p.ex. "fonts/hud.gif", // Carrega un recurs pel seu camí relatiu (p.ex. "fonts/hud.gif",
// "input/keys.yaml"). Prova primer el pack; si no hi és i el fallback // "input/keys.yaml"). Prova primer el pack; si no hi és i el fallback
// està actiu, cau a disc (data root + ruta). Retorna vector buit si // està actiu, cau a disc (data root + ruta). Retorna vector buit si
// no el troba. // no el troba. Si `preferDisk()` és true (Debug), inverteix la
// prioritat: prova disc abans del pack.
auto loadFile(const std::string& filepath) -> std::vector<std::uint8_t>; auto loadFile(const std::string& filepath) -> std::vector<std::uint8_t>;
// Inverteix la prioritat de `loadFile` (disc abans que pack). En Debug
// s'activa per defecte al `initializeResourceSystem` perquè cada
// canvi al YAML/GIF al source tree es vegi sense re-packejar — és el
// que necessita l'editor in-game per veure les seues edicions
// immediatament al recarregar la sala. En Release no té efecte
// (el fallback està desactivat).
void setPreferDisk(bool prefer);
[[nodiscard]] auto preferDisk() -> bool;
} // namespace Resource::Helper } // namespace Resource::Helper
+54 -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"
@@ -28,6 +29,9 @@
#include "core/system/event_buffer.hpp" #include "core/system/event_buffer.hpp"
#include "core/ui/ui_config.hpp" #include "core/ui/ui_config.hpp"
#include "game/debug_config.hpp" #include "game/debug_config.hpp"
#ifdef _DEBUG
#include "game/editor/editor_config.hpp"
#endif
#include "game/levels/level_manager.hpp" #include "game/levels/level_manager.hpp"
#include "game/options.hpp" #include "game/options.hpp"
#include "game/scenes/game_scene.hpp" #include "game/scenes/game_scene.hpp"
@@ -82,6 +86,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);
@@ -113,6 +122,12 @@ Director::Director() {
// data/ui.yaml cau al valor compilat a Defaults::UI. // data/ui.yaml cau al valor compilat a Defaults::UI.
UiConfig::get().load("data/ui.yaml"); UiConfig::get().load("data/ui.yaml");
#ifdef _DEBUG
// Tema visual de l'editor de tilemap (Debug-only). Claus absents
// cauen als valors compilats a Defaults::Editor.
EditorConfig::get().load("data/editor/editor.yaml");
#endif
// Recursos compartits d'UI (paleta + fonts). Es carreguen abans que els // Recursos compartits d'UI (paleta + fonts). Es carreguen abans que els
// overlays perquè tots els reaprofiten sense duplicar la lògica. Els // overlays perquè tots els reaprofiten sense duplicar la lògica. Els
// noms de font viuen al fitxer data/ui.yaml via UiConfig. // noms de font viuen al fitxer data/ui.yaml via UiConfig.
@@ -132,9 +147,20 @@ 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");
// Sons del menú de servei. Els noms venen de `data/ui.yaml` (amb
// fallback a `Defaults::UI::MENU_*_SOUND`); el ServiceMenu els
// dispara via Audio::Group::INTERFACE.
const auto& menu_select = UiConfig::get().menuSelectSound();
const auto& menu_accept = UiConfig::get().menuAcceptSound();
Resource::Cache::get().loadSound(menu_select, "data/sounds/" + menu_select);
Resource::Cache::get().loadSound(menu_accept, "data/sounds/" + menu_accept);
// 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 +319,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 +349,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);
+33 -1
View File
@@ -96,11 +96,24 @@ UiConfig::UiConfig()
pause_exit_ms_(Defaults::UI::PAUSE_EXIT_MS), pause_exit_ms_(Defaults::UI::PAUSE_EXIT_MS),
pause_enter_curve_(curveByName(Defaults::UI::PAUSE_ENTER_CURVE)), pause_enter_curve_(curveByName(Defaults::UI::PAUSE_ENTER_CURVE)),
pause_exit_curve_(curveByName(Defaults::UI::PAUSE_EXIT_CURVE)), pause_exit_curve_(curveByName(Defaults::UI::PAUSE_EXIT_CURVE)),
menu_select_sound_(Defaults::UI::MENU_SELECT_SOUND),
menu_accept_sound_(Defaults::UI::MENU_ACCEPT_SOUND),
hud_font_{.name = Defaults::UI::Font::HUD_NAME, .gif = Defaults::UI::Font::HUD_GIF, .fnt = Defaults::UI::Font::HUD_FNT, .kerning = Defaults::UI::Font::HUD_KERNING}, hud_font_{.name = Defaults::UI::Font::HUD_NAME, .gif = Defaults::UI::Font::HUD_GIF, .fnt = Defaults::UI::Font::HUD_FNT, .kerning = Defaults::UI::Font::HUD_KERNING},
placeholder_font_{.name = Defaults::UI::Font::PLACEHOLDER_NAME, .gif = Defaults::UI::Font::PLACEHOLDER_GIF, .fnt = Defaults::UI::Font::PLACEHOLDER_FNT, .kerning = Defaults::UI::Font::PLACEHOLDER_KERNING}, placeholder_font_{.name = Defaults::UI::Font::PLACEHOLDER_NAME, .gif = Defaults::UI::Font::PLACEHOLDER_GIF, .fnt = Defaults::UI::Font::PLACEHOLDER_FNT, .kerning = Defaults::UI::Font::PLACEHOLDER_KERNING},
room_text_font_{.name = Defaults::UI::Font::ROOM_TEXT_NAME, .gif = Defaults::UI::Font::ROOM_TEXT_GIF, .fnt = Defaults::UI::Font::ROOM_TEXT_FNT, .kerning = Defaults::UI::Font::ROOM_TEXT_KERNING}, room_text_font_{.name = Defaults::UI::Font::ROOM_TEXT_NAME, .gif = Defaults::UI::Font::ROOM_TEXT_GIF, .fnt = Defaults::UI::Font::ROOM_TEXT_FNT, .kerning = Defaults::UI::Font::ROOM_TEXT_KERNING},
menu_title_font_{.name = Defaults::UI::Font::MENU_TITLE_NAME, .gif = Defaults::UI::Font::MENU_TITLE_GIF, .fnt = Defaults::UI::Font::MENU_TITLE_FNT, .kerning = Defaults::UI::Font::MENU_TITLE_KERNING}, menu_title_font_{.name = Defaults::UI::Font::MENU_TITLE_NAME, .gif = Defaults::UI::Font::MENU_TITLE_GIF, .fnt = Defaults::UI::Font::MENU_TITLE_FNT, .kerning = Defaults::UI::Font::MENU_TITLE_KERNING},
menu_items_font_{.name = Defaults::UI::Font::MENU_ITEMS_NAME, .gif = Defaults::UI::Font::MENU_ITEMS_GIF, .fnt = Defaults::UI::Font::MENU_ITEMS_FNT, .kerning = Defaults::UI::Font::MENU_ITEMS_KERNING} { menu_items_font_{.name = Defaults::UI::Font::MENU_ITEMS_NAME, .gif = Defaults::UI::Font::MENU_ITEMS_GIF, .fnt = Defaults::UI::Font::MENU_ITEMS_FNT, .kerning = Defaults::UI::Font::MENU_ITEMS_KERNING},
notifier_{
.padding_h = Defaults::UI::Notifier::PADDING_H,
.padding_top = Defaults::UI::Notifier::PADDING_TOP,
.padding_bottom = Defaults::UI::Notifier::PADDING_BOTTOM,
.line_gap = Defaults::UI::Notifier::LINE_GAP,
.margin_top = Defaults::UI::Notifier::MARGIN_TOP,
.border_px = Defaults::UI::Notifier::BORDER_PX,
.slide_ms = Defaults::UI::Notifier::SLIDE_MS,
.slide_in_curve = curveByName(Defaults::UI::Notifier::SLIDE_IN_CURVE),
.slide_out_curve = curveByName(Defaults::UI::Notifier::SLIDE_OUT_CURVE),
} {
} }
auto UiConfig::get() -> UiConfig& { auto UiConfig::get() -> UiConfig& {
@@ -185,6 +198,25 @@ void UiConfig::load(const std::string& file_path) {
readCurve(p, "exit_curve", pause_exit_curve_); readCurve(p, "exit_curve", pause_exit_curve_);
} }
if (yaml.contains("sounds")) {
const auto& s = yaml["sounds"];
readField(s, "menu_select", menu_select_sound_);
readField(s, "menu_accept", menu_accept_sound_);
}
if (yaml.contains("notifier")) {
const auto& n = yaml["notifier"];
readField(n, "padding_h", notifier_.padding_h);
readField(n, "padding_top", notifier_.padding_top);
readField(n, "padding_bottom", notifier_.padding_bottom);
readField(n, "line_gap", notifier_.line_gap);
readField(n, "margin_top", notifier_.margin_top);
readField(n, "border_px", notifier_.border_px);
readField(n, "slide_ms", notifier_.slide_ms);
readCurve(n, "slide_in_curve", notifier_.slide_in_curve);
readCurve(n, "slide_out_curve", notifier_.slide_out_curve);
}
std::cout << "UiConfig: carregat des de " << file_path << '\n'; std::cout << "UiConfig: carregat des de " << file_path << '\n';
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::cerr << "UiConfig: error parsejant " << file_path << ": " << e.what() << '\n'; std::cerr << "UiConfig: error parsejant " << file_path << ": " << e.what() << '\n';
+34 -5
View File
@@ -20,6 +20,27 @@ class UiConfig {
int kerning; int kerning;
}; };
// Signatura d'una funció d'easing: t ∈ [0, 1] → valor ∈ [0, 1].
// Permet triar corbes des de data/ui.yaml per nom (cadena) i guardar
// el punter a la corresponent funció de `utils/easing.hpp`.
using CurveFn = auto (*)(float) -> float;
// Geometria del Notifier (popup superior). Carregada des de
// data/ui.yaml secció `notifier:`; els valors per defecte vénen de
// Defaults::UI::Notifier. El moviment és per temps (slide_ms) i no per
// velocitat, perquè una caixa més gran no triga més a entrar/eixir.
struct Notifier {
int padding_h;
int padding_top;
int padding_bottom;
int line_gap;
int margin_top;
int border_px;
int slide_ms;
CurveFn slide_in_curve;
CurveFn slide_out_curve;
};
static auto get() -> UiConfig&; static auto get() -> UiConfig&;
UiConfig(const UiConfig&) = delete; UiConfig(const UiConfig&) = delete;
@@ -51,11 +72,6 @@ class UiConfig {
[[nodiscard]] auto roomText() const -> Uint8 { return room_text_; } [[nodiscard]] auto roomText() const -> Uint8 { return room_text_; }
[[nodiscard]] auto roomTextOutline() const -> Uint8 { return room_text_outline_; } [[nodiscard]] auto roomTextOutline() const -> Uint8 { return room_text_outline_; }
// Signatura d'una funció d'easing: t ∈ [0, 1] → valor ∈ [0, 1].
// Permet triar corbes des de data/ui.yaml per nom (cadena) i guardar
// el punter a la corresponent funció de `utils/easing.hpp`.
using CurveFn = auto (*)(float) -> float;
// Timings (ms). // Timings (ms).
[[nodiscard]] auto notificationMs() const -> int { return notification_ms_; } [[nodiscard]] auto notificationMs() const -> int { return notification_ms_; }
[[nodiscard]] auto exitMs() const -> int { return exit_ms_; } [[nodiscard]] auto exitMs() const -> int { return exit_ms_; }
@@ -76,6 +92,11 @@ class UiConfig {
// retorna Easing::linear i deixa un warning al log. // retorna Easing::linear i deixa un warning al log.
[[nodiscard]] static auto curveByName(const std::string& name) -> CurveFn; [[nodiscard]] static auto curveByName(const std::string& name) -> CurveFn;
// Sons del menú de servei. Claus de Resource::Cache::loadSound (els
// fitxers viuen a `data/sounds/`).
[[nodiscard]] auto menuSelectSound() const -> const std::string& { return menu_select_sound_; }
[[nodiscard]] auto menuAcceptSound() const -> const std::string& { return menu_accept_sound_; }
// Fonts de UI. // Fonts de UI.
[[nodiscard]] auto hudFont() const -> const Font& { return hud_font_; } [[nodiscard]] auto hudFont() const -> const Font& { return hud_font_; }
[[nodiscard]] auto placeholderFont() const -> const Font& { return placeholder_font_; } [[nodiscard]] auto placeholderFont() const -> const Font& { return placeholder_font_; }
@@ -83,6 +104,9 @@ class UiConfig {
[[nodiscard]] auto menuTitleFont() const -> const Font& { return menu_title_font_; } [[nodiscard]] auto menuTitleFont() const -> const Font& { return menu_title_font_; }
[[nodiscard]] auto menuItemsFont() const -> const Font& { return menu_items_font_; } [[nodiscard]] auto menuItemsFont() const -> const Font& { return menu_items_font_; }
// Geometria del Notifier.
[[nodiscard]] auto notifier() const -> const Notifier& { return notifier_; }
// Flags visuals. // Flags visuals.
[[nodiscard]] auto hudBgEnabled() const -> bool { return hud_bg_enabled_; } [[nodiscard]] auto hudBgEnabled() const -> bool { return hud_bg_enabled_; }
@@ -121,11 +145,16 @@ class UiConfig {
CurveFn pause_enter_curve_; CurveFn pause_enter_curve_;
CurveFn pause_exit_curve_; CurveFn pause_exit_curve_;
std::string menu_select_sound_;
std::string menu_accept_sound_;
Font hud_font_; Font hud_font_;
Font placeholder_font_; Font placeholder_font_;
Font room_text_font_; Font room_text_font_;
Font menu_title_font_; Font menu_title_font_;
Font menu_items_font_; Font menu_items_font_;
Notifier notifier_;
bool hud_bg_enabled_{false}; bool hud_bg_enabled_{false};
}; };
File diff suppressed because it is too large. Load diff
+158 -28
View File
@@ -2,6 +2,15 @@
#ifdef _DEBUG #ifdef _DEBUG
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
class Entity;
class EntityManager;
class Surface;
class Text;
class Tilemap; class Tilemap;
// Mode editor modal del GameScene. Només disponible en Debug — en Release // Mode editor modal del GameScene. Només disponible en Debug — en Release
@@ -11,55 +20,176 @@ class Tilemap;
// L'editor NO gestiona la tecla de toggle pròpia (`editor_toggle`): això // L'editor NO gestiona la tecla de toggle pròpia (`editor_toggle`): això
// ho fa GameScene per poder decidir la coexistència amb la pausa F11 // ho fa GameScene per poder decidir la coexistència amb la pausa F11
// (mútuament excloents). L'editor s'encarrega de: // (mútuament excloents). L'editor s'encarrega de:
// - Estat propi (actiu/no actiu, brush, picker obert). // - Estat propi (mode actiu, brush per mode, picker obert).
// - Input del ratolí i tecles internes (`T`, `ESC`). // - Input del ratolí i tecles internes (`T`, `E`, `H`, `Supr`, `ESC`).
// - Render del border blau + brush preview + picker. // - Render del border blau + brush preview + picker.
// - Persistència del tilemap al YAML del repo en sortir. // - Persistència del tilemap i de les entitats al YAML del repo en sortir.
// //
// GameScene, quan `active()` és true, salta el tick de jugadors/entitats // Modes del cursor (`Mode`):
// i no els pinta; el tilemap sí es pinta perquè és el que s'està editant. // - TILE : el clic esquerre pinta el `brush_tile_` al tilemap.
// - ENTITY : el clic esquerre col·loca una instància de `brush_entity_`.
// - HAND : selecciona/mou/borra entitats existents.
//
// Pickers (`Picker`):
// - TILE_PICKER (`T`) : selecciona índex de tile.
// - ENTITY_PICKER (`E`) : selecciona tipus d'entitat (nom registrat al
// factory).
// Mútuament excloents: obrir un tanca l'altre.
class Editor { class Editor {
public: public:
// Entra al mode editor: notifica i marca `active=true`. La congelació // Entra al mode editor: notifica i marca `active=true`. La congelació
// d'entitats / jugador la gestiona GameScene consultant `active()`. // d'entitats / jugador la gestiona GameScene consultant `active()`.
void enter(); void enter();
// Surt del mode editor: persisteix el tilemap al YAML (writer surgical) // Surt del mode editor: persisteix el tilemap i les entitats al YAML
// i notifica. Si la persistència falla, emet la notificació d'error. // (writers surgicals) i notifica. Si la persistència falla, emet la
void exit(Tilemap& tilemap); // notificació d'error.
void exit(Tilemap& tilemap, const EntityManager& entities);
// Helper: enter() si no actiu, exit() si actiu. Útil des de GameScene // Helper: enter() si no actiu, exit() si actiu. Útil des de GameScene
// quan detecta la tecla de toggle. // quan detecta la tecla de toggle.
void toggle(Tilemap& tilemap); void toggle(Tilemap& tilemap, const EntityManager& entities);
[[nodiscard]] auto active() const -> bool { return active_; } [[nodiscard]] auto active() const -> bool { return active_; }
// Processa input propi (brush, picker). No-op si no actiu. // Cert quan, en mode editor, GameScene ha de pintar les entitats
void update(int delta_ms, Tilemap& tilemap); // de la sala. Falses en mode TILE (volem veure el tilemap pelat
// per pintar-lo) i quan hi ha un picker obert (la caixa del picker
// ja tapa la pantalla i el soroll visual addicional sobra).
[[nodiscard]] auto shouldShowEntities() const -> bool {
return active_ && mode_ != Mode::TILE && picker_ == Picker::NONE;
}
// Pinta el border blau i, més endavant, brush preview + picker. // Processa input propi (brush, picker). No-op si no actiu.
// No-op si no actiu. // `entities` cal perquè els modes ENTITY i HAND modifiquen el manager
void render(const Tilemap& tilemap) const; // en directe (col·loca/mou/borra entitats); en TILE no s'usa.
void update(int delta_ms, Tilemap& tilemap, EntityManager& entities);
// Pinta el border blau i, segons mode i picker, brush preview, marc
// d'entitat seleccionada o el picker. No-op si no actiu.
// `entities` és necessari per al marc d'entitat hovered/seleccionada
// en Mode::HAND; en altres modes no s'usa.
void render(const Tilemap& tilemap, const EntityManager& entities) const;
private: private:
// Les branques d'update / render segons el mode (picker obert o // Mode del cursor: determina què fa el clic esquerre sobre el mapa.
// pintura al mapa) queden separades per claredat; no formen part enum class Mode : std::uint8_t { TILE,
// de l'API pública perquè només té sentit cridar-les des d'`update`. ENTITY,
void updateMap(Tilemap& tilemap, int canvas_x, int canvas_y); HAND };
void updatePicker(const Tilemap& tilemap, int canvas_x, int canvas_y);
void renderPicker(const Tilemap& tilemap) const; // Quin picker està obert (si n'hi ha). Mútuament excloents.
enum class Picker : std::uint8_t { NONE,
TILE,
ENTITY };
// Consum de hotkeys pròpies (T, E, H, ESC). Modifica `mode_` / `picker_`.
// Es crida al principi d'`update` perquè altres handlers no vegin
// tecles ja interpretades pel propi editor.
void handleHotkeys();
// Aplica una tecla i retorna true si l'editor l'ha consumit. Aïllat
// per mantenir baixa la complexitat cognitiva de `handleHotkeys` —
// l'iteració del buffer queda neta i la lògica de transició viu en
// un sol lloc.
auto applyHotkey(int scancode) -> bool;
// Branques d'update segons (mode, picker). Només té sentit cridar-les
// des d'`update`.
void updateTileBrush(Tilemap& tilemap, int canvas_x, int canvas_y);
void updateEntityBrush(EntityManager& entities, int canvas_x, int canvas_y);
void updateHand(const Tilemap& tilemap, EntityManager& entities, int canvas_x, int canvas_y);
void updateTilePicker(const Tilemap& tilemap, int canvas_x, int canvas_y);
void updateEntityPicker(int canvas_x, int canvas_y);
// Intent de col·locació segons brush_entity_ a la cel·la del cursor.
// Construeix un node spawn sintètic, fa el snap concret per tipus
// (sarc → rejilla 4×5; door → fila del mur i, de moment, columna
// canònica) i comprova solapaments. No-op si la cel·la està ocupada
// o el snap no és vàlid. Retorna true si s'ha col·locat.
auto tryPlaceEntity(EntityManager& entities) -> bool;
// Mou l'entitat seleccionada a la cel·la del cursor: borra l'antiga
// i crida `addSpawn` amb la nova posició. Reusa la mateixa lògica
// de snap/solapament que la col·locació nova, excloent l'entitat
// que es mou del check de solapament. Cas especial per a la porta:
// el sprite ocupa 3 cols (col-1, col, col+1) sobre el mur superior;
// per tant la columna central vàlida és [1, cols-2] — `tilemap` és
// necessari per saber `cols`.
auto tryMoveSelected(const Tilemap& tilemap, EntityManager& entities) const -> bool;
// Sub-handlers de `updateHand`. Aïllats per mantenir baixa la
// complexitat cognitiva del bucle d'esdeveniments.
void handleHandLeftClick(const Tilemap& tilemap, EntityManager& entities);
void handleHandRightClick();
// Borra l'entitat seleccionada. La validació de constraints
// (1 puerta, ≥1 sarc) es fa al pas 8.
void deleteSelected(EntityManager& entities);
// Render del marc d'una entitat: 1 px sobre el seu pixel_rect.
static void renderEntityFrame(const Entity& e, std::uint32_t argb);
// HUD d'edició: franja superior amb mode, brush, cursor i comptadors
// de constraints. Substitueix el HUD del joc mentre l'editor és actiu.
void renderEditorHud(const EntityManager& entities) const;
// Carrega la font 8bithud del cache. Lazy: al primer enter().
void ensureHudFont();
void renderTilePicker(const Tilemap& tilemap) const;
void renderEntityPicker(const Tilemap& tilemap) const;
bool active_{false}; bool active_{false};
Mode mode_{Mode::TILE};
Picker picker_{Picker::NONE};
// Estat de l'edició (brush + cursor + picker). Es poblen al pas // Cursor en cel·les del tilemap. -1/-1 = fora del mapa.
// següent; els declarem ara perquè `update()` pugui mutar-los i les int cursor_col_{-1};
// signatures queden estables entre hitos.
int brush_{-1}; // Índex de tile al tileset; -1 = sense brush.
int cursor_col_{-1}; // Cel·la del mapa sota el cursor (-1 = fora).
int cursor_row_{-1}; int cursor_row_{-1};
bool picker_open_{false};
int picker_scroll_{0}; // Fila superior visible del picker (linealitzat). // Brush actiu per Mode::TILE (índex al tileset; -1 = sense brush).
int picker_hover_{-1}; // Índex del tile sota el cursor dins del picker. int brush_tile_{-1};
// Estat del TilePicker.
int tile_picker_scroll_{0};
int tile_picker_hover_{-1};
// Brush actiu per Mode::ENTITY (nom registrat al factory; "" = cap).
std::string brush_entity_;
// Estat de l'EntityPicker.
int entity_picker_scroll_{0};
int entity_picker_hover_{-1}; // Índex a la llista de noms.
// Entrada editable del picker. Cada item recorda el nom (clau de
// factory + YAML), el surface del sprite (frame 0 com a icona) i la
// mida de cada frame al GIF — necessària per clipar el primer frame
// quan el GIF és un strip multi-frame.
struct EditableEntity {
std::string name;
std::shared_ptr<Surface> sprite;
int frame_w{0};
int frame_h{0};
};
// Construïda lazy al primer enter() i preservada entre sessions:
// el cost és I/O + parse de N plantilles YAML, però `EntityLoader`
// ja les memoritza, així que rellegir només resoldria per sortida i
// entrada al editor — no val la pena.
void ensureEditableEntities();
std::vector<EditableEntity> editable_entities_;
// Font compartida amb el Notifier/HUD: el surface el carrega Director
// a l'arrencada, ací només prenem la referència del cache. shared_ptr
// perquè `Text` accepta el surface per shared_ptr i així no copiem.
std::shared_ptr<Text> hud_font_;
// Selecció en Mode::HAND. Treballem amb índexs sobre el snapshot de
// `EntityManager::getEntities()` perquè és el que ens dóna l'API
// pública. Després de cada mutació (move/delete) ho recalculem; com
// que l'editor congela el joc, el snapshot només canvia per accions
// pròpies.
int hand_hover_idx_{-1};
int hand_selected_idx_{-1};
}; };
#endif // _DEBUG #endif // _DEBUG
+85
View File
@@ -0,0 +1,85 @@
#include "game/editor/editor_config.hpp"
#ifdef _DEBUG
#include <cstdint>
#include <iostream>
#include <stdexcept>
#include <string>
#include "core/resources/resource_helper.hpp"
#include "external/fkyaml_node.hpp"
#include "utils/defaults.hpp"
namespace {
template <typename T>
void readField(const fkyaml::node& node, const char* key, T& dst) {
if (node.contains(key)) { dst = node[key].get_value<T>(); }
}
// Llegeix un color ARGB. Accepta enter (0xFF2244FF al YAML) o cadena
// ("0xFF2244FF" / "#FF2244FF"). std::stoul amb base=0 detecta el prefix
// "0x" automàticament; si el valor és invàlid, deixem `dst` intacte i
// avisem perquè l'error no passi en silenci.
void readArgb(const fkyaml::node& node, const char* key, std::uint32_t& dst) {
if (!node.contains(key)) { return; }
try {
const auto& v = node[key];
if (v.is_integer()) {
dst = static_cast<std::uint32_t>(v.get_value<std::uint64_t>());
return;
}
auto s = v.get_value<std::string>();
if (!s.empty() && s.front() == '#') { s.erase(0, 1); }
dst = static_cast<std::uint32_t>(std::stoul(s, nullptr, 0));
} catch (const std::exception& e) {
std::cerr << "EditorConfig: color '" << key << "' invàlid (" << e.what() << ") — ignorat\n";
}
}
} // namespace
EditorConfig::EditorConfig()
: picker_margin_(Defaults::Editor::PICKER_MARGIN),
picker_spacing_(Defaults::Editor::PICKER_SPACING),
border_argb_(Defaults::Editor::BORDER_ARGB),
picker_bg_argb_(Defaults::Editor::PICKER_BG_ARGB),
picker_hover_argb_(Defaults::Editor::PICKER_HOVER_ARGB) {
}
auto EditorConfig::get() -> EditorConfig& {
static EditorConfig instance_;
return instance_;
}
void EditorConfig::load(const std::string& file_path) {
auto bytes = Resource::Helper::loadFile(file_path);
if (bytes.empty()) {
std::cerr << "EditorConfig: no s'ha pogut obrir " << file_path << " — s'usaran els defaults compilats\n";
return;
}
try {
const auto* begin = reinterpret_cast<const char*>(bytes.data());
const auto* end = begin + bytes.size();
auto yaml = fkyaml::node::deserialize(begin, end);
if (yaml.contains("picker")) {
const auto& p = yaml["picker"];
readField(p, "margin", picker_margin_);
readField(p, "spacing", picker_spacing_);
}
if (yaml.contains("colors")) {
const auto& c = yaml["colors"];
readArgb(c, "border", border_argb_);
readArgb(c, "picker_bg", picker_bg_argb_);
readArgb(c, "picker_hover", picker_hover_argb_);
}
std::cout << "EditorConfig: carregat des de " << file_path << '\n';
} catch (const std::exception& e) {
std::cerr << "EditorConfig: error parsejant " << file_path << ": " << e.what() << '\n';
}
}
#endif // _DEBUG
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#ifdef _DEBUG
#include <cstdint>
#include <string>
// Configuració visual de l'editor de tilemap (Debug-only). Carregada des
// de data/editor/editor.yaml; les claus absents cauen als valors compilats
// a Defaults::Editor. Mateix patró que UiConfig (Meyers singleton + load).
class EditorConfig {
public:
static auto get() -> EditorConfig&;
EditorConfig(const EditorConfig&) = delete;
EditorConfig(EditorConfig&&) = delete;
auto operator=(const EditorConfig&) -> EditorConfig& = delete;
auto operator=(EditorConfig&&) -> EditorConfig& = delete;
void load(const std::string& file_path);
[[nodiscard]] auto pickerMargin() const -> int { return picker_margin_; }
[[nodiscard]] auto pickerSpacing() const -> int { return picker_spacing_; }
[[nodiscard]] auto borderArgb() const -> std::uint32_t { return border_argb_; }
[[nodiscard]] auto pickerBgArgb() const -> std::uint32_t { return picker_bg_argb_; }
[[nodiscard]] auto pickerHoverArgb() const -> std::uint32_t { return picker_hover_argb_; }
private:
EditorConfig();
~EditorConfig() = default;
int picker_margin_;
int picker_spacing_;
std::uint32_t border_argb_;
std::uint32_t picker_bg_argb_;
std::uint32_t picker_hover_argb_;
};
#endif // _DEBUG
+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};
@@ -10,6 +10,10 @@ FloorTrap::FloorTrap(const fkyaml::node& yaml) {
name_ = yaml.contains("name") ? yaml["name"].get_value<std::string>() : std::string("floor_trap"); name_ = yaml.contains("name") ? yaml["name"].get_value<std::string>() : std::string("floor_trap");
collides_ = false; collides_ = false;
interactable_ = false; interactable_ = false;
// Trigger d'àrea: la trampa no bloqueja el pas però vol rebre
// `onPlayerContact` quan el jugador la solapa. Tanca el gap latent
// que la deixava muda (`checkPlayerCollisions` filtrava pel `collides`).
triggers_on_overlap_ = true;
if (yaml.contains("cycle")) { if (yaml.contains("cycle")) {
const auto& c = yaml["cycle"]; const auto& c = yaml["cycle"];
+54
View File
@@ -0,0 +1,54 @@
#include "game/entities/concrete/hole.hpp"
#include <SDL3/SDL.h>
#include <string>
#include "game/entities/entity_factory.hpp"
#include "game/entities/player.hpp"
Hole::Hole(const fkyaml::node& yaml) {
name_ = yaml.contains("name") ? yaml["name"].get_value<std::string>() : std::string("hole");
// Sense xoc físic ni interacció — només trigger d'àrea. El bucle de
// contacte ens admet via `triggers_on_overlap_=true`. Damage queda a
// 0 (no publiquem `player_hit`); la mort la dispara `integrateJump`
// del jugador quan supera `HOLE_FALL_DEPTH`.
collides_ = false;
interactable_ = false;
triggers_on_overlap_ = true;
// Decal de sòl: `Entity::getFeetY()` torna sentinel mínim si aquest
// flag està actiu — així el forat es pinta abans que qualsevol actor.
is_floor_decal_ = true;
setDamage(0);
}
void Hole::onPlayerContact(Player* player) {
if (player == nullptr) { return; }
// Només mossega quan el jugador trepitja el forat de peu (GROUNDED).
// En l'aire, el filtre d'altura del bucle ja l'ha rebutjat (height_=0
// i jump_z_>0 prou per superar-ho); però mantenim el gat per defensa
// davant de futurs canvis al filtre.
if (player->getState() != Player::State::GROUNDED) { return; }
// Re-verificació estricta: la hit-box interna del jugador (8×8) ha
// d'estar COMPLETAMENT continguda dins del rect del forat (16×16). El
// bucle de col·lisió del manager ja ens ha cridat per qualsevol solape;
// ací filtrem perquè la caiguda només es dispari quan els "dos peus"
// estan dins del forat, no quan només la vora frega — sinó es percep
// com un imant que succiona el jugador al centre.
const SDL_FRect HIT = player->getHitBox();
const SDL_FRect BOX = getCollisionRect();
const bool CONTAINED = HIT.x >= BOX.x && HIT.y >= BOX.y && HIT.x + HIT.w <= BOX.x + BOX.w && HIT.y + HIT.h <= BOX.y + BOX.h;
if (!CONTAINED) { return; }
// Snap al centre XY del forat + Y de la base del tile (per al clip
// rect del sprite mentre cau). `enterHoleFall` rep coordenades
// absolutes en el canvas — el player s'encarrega de convertir a la
// seua convenció (cantó superior-esquerra del sprite).
const float CENTER_X = BOX.x + (BOX.w * 0.5F);
const float CENTER_Y = BOX.y + (BOX.h * 0.5F);
const float FLOOR_Y = BOX.y + BOX.h;
player->enterHoleFall(CENTER_X, CENTER_Y, FLOOR_Y);
}
REGISTER_ENTITY("hole", Hole)
+40
View File
@@ -0,0 +1,40 @@
#pragma once
#include "external/fkyaml_node.hpp"
#include "game/entities/hazard.hpp"
// Forat al sòl: hazard estàtic de 16×16 que NO bloqueja el pas però
// detecta quan el jugador hi cau a sobre. Característiques:
// - `collides_=false` + `triggers_on_overlap_=true`: rep `onPlayerContact`
// pel bucle de `EntityManager::checkPlayerCollisions` sense bloquejar
// el moviment del jugador.
// - `height_=0`: el filtre per altura del bucle (`jump_z > height`) NO
// s'activa amb height a zero, així que el bucle de contacte SÍ crida
// `onPlayerContact` també per a jugadors AIRBORNE. Qui rebutja els
// estats distints de GROUNDED (saltant, caient, HURT, DYING...) és el
// guard explícit dins de `Hole::onPlayerContact`. El filtre d'altura
// és una optimització ortogonal del bucle, no la defensa principal.
// - Damage 0: el contacte no publica `player_hit`. En lloc d'això,
// `onPlayerContact` invoca `Player::enterHoleFall`, que transiciona
// el jugador a AIRBORNE amb subestat `falling_through_hole_`.
// - Verificació amb hit-box interna: `onPlayerContact` re-verifica
// l'overlap amb `Player::getHitBox()` (8×8 centrada al sprite),
// així només es dispara quan el centre del jugador entra al tile.
// - Z-order: `is_floor_decal_=true` (heretat) — `Entity::getFeetY()`
// retorna sentinel mínim, així el forat es pinta abans que qualsevol
// actor i les momies passen per davant. L'efecte "el sòl es traga el
// jugador" no es fa amb Y-sort: el `Player::render` aplica un clip
// rect mentre `falling_through_hole_=true` que retalla el sprite per
// sota de la base del tile del forat.
class Hole : public StaticHazard {
public:
explicit Hole(const fkyaml::node& yaml);
// No publica `player_hit`. En el seu lloc, dispara
// `Player::enterHoleFall(center_x, center_y, floor_y)` perquè el
// jugador armi el clip rect a la Y de la base del forat.
void onPlayerContact(Player* player) override;
};
// TODO(hole-feature): afegir reset de `falling_through_hole_` i `hole_clip_bottom_y_` a `Player::disable()` per simetria.
// TODO(hole-feature): afegir tests dedicats del comportament de la mòmia amb forats (no requereixen Player real).
+265 -79
View File
@@ -2,12 +2,14 @@
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <cmath>
#include <optional> #include <optional>
#include <random> #include <random>
#include <string> #include <string>
#include <utility> #include <utility>
#include "game/entities/entity_factory.hpp" #include "game/entities/entity_factory.hpp"
#include "game/entities/entity_manager.hpp"
#include "game/entities/event_bus.hpp" #include "game/entities/event_bus.hpp"
#include "game/entities/player.hpp" #include "game/entities/player.hpp"
#include "game/map/map.hpp" #include "game/map/map.hpp"
@@ -26,11 +28,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 +49,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 +81,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 +138,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 {
@@ -181,7 +199,15 @@ void Mummy::snapToPlayfieldEdge(float dx, float dy) {
} }
void Mummy::step(float delta_time) { void Mummy::step(float delta_time) {
// Desplaçament nominal en aquest frame segons la direcció actual. // Detecció reactiva al canvi de tile: si la mòmia ha entrat aquest
// tick a un tile nou amb un decal trigger (Hole), dispara mitja volta
// abans de calcular el desplaçament — així el frame ja consumeix la
// nova direcció sense esperar al següent. La primera crida només
// registra el tile de spawn (lazy init).
reactToCurrentTile();
// Desplaçament nominal en aquest frame segons la direcció actual
// (potser ja invertida per `reactToCurrentTile`).
const float DX = dir_x_ * speed_ * delta_time; const float DX = dir_x_ * speed_ * delta_time;
const float DY = dir_y_ * speed_ * delta_time; const float DY = dir_y_ * speed_ * delta_time;
@@ -195,6 +221,9 @@ void Mummy::step(float delta_time) {
// centre; el remanent queda per al pròxim frame, ja amb la (potser // centre; el remanent queda per al pròxim frame, ja amb la (potser
// nova) direcció aplicada. Així la caixa 16×16 queda sempre alineada // nova) direcció aplicada. Així la caixa 16×16 queda sempre alineada
// a la graella de corredors i els girs no xoquen per drift sub-píxel. // a la graella de corredors i els girs no xoquen per drift sub-píxel.
// L'evitació de forats en pasillos rectes la cobreix
// `reactToCurrentTile` (al començament d'aquest mètode), no aquesta
// branca — aquí només es decideix entre direccions geomètriques.
if (const auto SNAP = detectLaneCrossing(DX, DY); SNAP.has_value()) { if (const auto SNAP = detectLaneCrossing(DX, DY); SNAP.has_value()) {
if (DX != 0.0F) { if (DX != 0.0F) {
pixel_rect_.x += *SNAP; pixel_rect_.x += *SNAP;
@@ -217,14 +246,86 @@ void Mummy::step(float delta_time) {
// Bloqueig per la vora del playfield: amarrem la caja a la vora exacta. // Bloqueig per la vora del playfield: amarrem la caja a la vora exacta.
if (!Map::insidePlayfield(CANDIDATE)) { snapToPlayfieldEdge(DX, DY); } if (!Map::insidePlayfield(CANDIDATE)) { snapToPlayfieldEdge(DX, DY); }
// Cal girar. Preferim perpendiculars; només fem U-turn si no hi ha cap // Cal girar. Cascada de tres nivells:
// altra opció (comportament típic d'Oh Mummy!: no es recula sense motiu). // Nivell 1 — perpendiculars sense decal (preferides; el comportament
const auto POOLS = collectDirectionPools(speed_ * delta_time); // "natural" d'Oh Mummy!: girar abans que recular).
const auto& pool = !POOLS.perp.empty() ? POOLS.perp : POOLS.uturn; // Nivell 2 — mitja volta sense decal (rescat: callejón sense salida o
applyChoice(chooseDirection(pool)); // tots els girs porten a forat). Preferim recular abans
// que caure al forat — un forat al costat NO ha de fer
// que la mòmia salti per damunt seu cap a una perpendicular
// amb forat només perquè la mitja volta era l'única neta.
// Nivell 3 — pool sense filtre de decals (cas degenerat: rodejada de
// forats). Permet caure al forat com a últim recurs; les
// mòmies no moren pels forats, així que és comportament
// tolerable en sales mal dissenyades.
// TODO(refactor): mateixa cascada a `decideAtIntersection` amb la
// variant "afegir recta a Nivell 1". Factoritzable
// quan tinga sentit (3+ callsites).
auto pools = collectDirectionPools(speed_ * delta_time, /*avoid_blocked=*/true);
const std::vector<Direction>* chosen = nullptr;
if (!pools.perp.empty()) {
chosen = &pools.perp;
} else if (!pools.uturn.empty()) {
chosen = &pools.uturn;
}
DirectionPools pools_unfiltered;
if (chosen == nullptr) {
pools_unfiltered = collectDirectionPools(speed_ * delta_time, /*avoid_blocked=*/false);
if (!pools_unfiltered.perp.empty()) {
chosen = &pools_unfiltered.perp;
} else if (!pools_unfiltered.uturn.empty()) {
chosen = &pools_unfiltered.uturn;
}
}
applyChoice((chosen != nullptr) ? chooseDirection(*chosen) : Direction::NONE);
// No avancem aquest frame: la direcció nova s'aplicarà al pròxim step. // No avancem aquest frame: la direcció nova s'aplicarà al pròxim step.
} }
void Mummy::reactToCurrentTile() {
// Calculem el tile que toca la **vora davantera** de la BOX en la
// direcció de marxa, no el centre. Així la mitja volta es dispara
// tan aviat com el primer píxel del cos de la mòmia entra al tile-
// forat ("asoma el peu"), no quan ja hi ha mig cos a dins. Per a una
// direcció positiva en un eix, la vora davantera és el costat major
// de l'AABB (`x + w - 1`, exclusive→inclusive convention en píxels);
// per a una direcció negativa, el menor (`x`); per a zero, qualsevol
// (no hi ha avenç en aquest eix).
const auto BOX = getCollisionRect();
const float LEAD_X = (dir_x_ > 0.0F) ? (BOX.x + BOX.w - 1.0F) : BOX.x;
const float LEAD_Y = (dir_y_ > 0.0F) ? (BOX.y + BOX.h - 1.0F) : BOX.y;
const auto [CUR_COL, CUR_ROW] = Map::pixelToTile(LEAD_X, LEAD_Y);
// Sense canvi de tile no hi ha res a fer — això filtra també tots els
// ticks intra-tile (la majoria, ~16 frames per tile a velocitats típiques).
if (CUR_COL == last_tile_col_ && CUR_ROW == last_tile_row_) { return; }
// Primera crida després del spawn: registrem el tile sense reaccionar.
// Així no disparem mitja volta espuria si la mòmia spawneja sobre un
// decal (defensa contra mapes mal dissenyats) o si pixel_rect_ encara
// no estava posat al constructor.
const bool FIRST_TIME = (last_tile_col_ == INT_MIN || last_tile_row_ == INT_MIN);
last_tile_col_ = CUR_COL;
last_tile_row_ = CUR_ROW;
if (FIRST_TIME) { return; }
// Sense manager injectat (smoke tests), no podem consultar decals.
if (entity_manager_ == nullptr) { return; }
// Si el tile nou conté un decal trigger (Hole o futur pinchos plans,
// plaques, etc.), mitja volta immediata. `changeDirection` també
// selecciona l'animació cardinal corresponent.
const auto TS = static_cast<float>(Map::tile_size);
const SDL_FRect TILE_RECT{
.x = (static_cast<float>(CUR_COL) * TS) + static_cast<float>(Map::tilemap_origin_x),
.y = (static_cast<float>(CUR_ROW) * TS) + static_cast<float>(Map::tilemap_origin_y),
.w = TS,
.h = TS,
};
if (entity_manager_->findFloorTriggerAt(TILE_RECT) != nullptr) {
changeDirection(-dir_x_, -dir_y_);
}
}
auto Mummy::detectLaneCrossing(float dx, float dy) const -> std::optional<float> { auto Mummy::detectLaneCrossing(float dx, float dy) const -> std::optional<float> {
const auto BOX = getCollisionRect(); const auto BOX = getCollisionRect();
if (dx != 0.0F) { if (dx != 0.0F) {
@@ -234,44 +335,72 @@ 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;
} }
void Mummy::decideAtIntersection(float step_px) { void Mummy::decideAtIntersection(float step_px) {
// Pool base: perpendiculars lliures (collectDirectionPools ja exclou la // Cascada de tres nivells (paral·lela a la branca de bloqueig de
// direcció actual i la mitja volta va al seu propi calaix, que ací // `step`). Aquí el Nivell 1 inclou la recta com a candidata addicional
// ignorem — mai fem U-turn a un creuament). // (a un creuament la mòmia pot continuar recta si segueix lliure i no
auto pools = collectDirectionPools(step_px); // apunta a un decal); el Nivell 2 admet la mitja volta — abans no es
std::vector<Direction> pool = std::move(pools.perp); // permetia mai en creuaments, però és preferible recular que caure al
// forat quan totes les altres surten a decal.
// TODO(refactor): cascada paral·lela a la branca de bloqueig de `step`.
auto pools = collectDirectionPools(step_px, /*avoid_blocked=*/true);
// Afegim la recta si segueix essent lliure. A un creuament "net" ho és // Nivell 1: perpendiculars (filtrades) + recta si lliure i sense decal.
// sempre (la mòmia hi ha arribat per aquest corredor), però als creua- std::vector<Direction> level1 = std::move(pools.perp);
// ments de la vora del playfield la recta pot estar al límit i no
// permetre un pas més — en aquest cas la descartem.
const auto STRAIGHT_BOX = candidateBox(dir_x_ * step_px, dir_y_ * step_px); const auto STRAIGHT_BOX = candidateBox(dir_x_ * step_px, dir_y_ * step_px);
if (Map::insidePlayfield(STRAIGHT_BOX) && !Map::collides(STRAIGHT_BOX)) { const bool STRAIGHT_OPEN = Map::insidePlayfield(STRAIGHT_BOX) && !Map::collides(STRAIGHT_BOX);
pool.push_back(currentDirection()); if (STRAIGHT_OPEN && !isBlockedAheadInDir(dir_x_, dir_y_)) {
level1.push_back(currentDirection());
} }
if (pool.empty()) { return; } // Nivell 2: mitja volta sense decal — rescat quan Nivell 1 ja no té cap
applyChoice(chooseDirection(pool)); // opció neta. Un forat al final del passadís ha de fer recular la
// mòmia, no convertir-se en l'opció triada per defecte.
const std::vector<Direction>* chosen = nullptr;
if (!level1.empty()) {
chosen = &level1;
} else if (!pools.uturn.empty()) {
chosen = &pools.uturn;
}
// Nivell 3: cas degenerat (rodejada de forats). Pool sense filtre amb
// perpendiculars, recta si lliure i mitja volta. Permet caure al forat
// com a últim recurs.
DirectionPools pools_unfiltered;
std::vector<Direction> level3;
if (chosen == nullptr) {
pools_unfiltered = collectDirectionPools(step_px, /*avoid_blocked=*/false);
level3 = std::move(pools_unfiltered.perp);
if (STRAIGHT_OPEN) { level3.push_back(currentDirection()); }
if (!level3.empty()) {
chosen = &level3;
} else if (!pools_unfiltered.uturn.empty()) {
chosen = &pools_unfiltered.uturn;
}
}
if (chosen == nullptr) { return; }
applyChoice(chooseDirection(*chosen));
} }
auto Mummy::collectDirectionPools(float step) const -> DirectionPools { auto Mummy::collectDirectionPools(float step, bool avoid_blocked) const -> DirectionPools {
struct Candidate { struct Candidate {
float dx; float dx;
float dy; float dy;
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) {
@@ -281,6 +410,11 @@ auto Mummy::collectDirectionPools(float step) const -> DirectionPools {
if (IS_CURRENT) { continue; } if (IS_CURRENT) { continue; }
const auto TEST = candidateBox(c.dx, c.dy); const auto TEST = candidateBox(c.dx, c.dy);
if (!Map::insidePlayfield(TEST) || Map::collides(TEST)) { continue; } if (!Map::insidePlayfield(TEST) || Map::collides(TEST)) { continue; }
// Filtre de decals (Hole i futurs decals trigger): comprova el
// tile cardinal contigu; si conté un trigger, descartem la
// direcció. El caller pot demanar `avoid_blocked=false` com a
// fallback quan totes les filtrades estan bloquejades.
if (avoid_blocked && isBlockedAheadInDir(c.dx, c.dy)) { continue; }
const bool IS_UTURN = (c.dx * dir_x_ < 0.0F) || (c.dy * dir_y_ < 0.0F); const bool IS_UTURN = (c.dx * dir_x_ < 0.0F) || (c.dy * dir_y_ < 0.0F);
if (IS_UTURN) { if (IS_UTURN) {
pools.uturn.push_back(c.dir); pools.uturn.push_back(c.dir);
@@ -291,18 +425,56 @@ auto Mummy::collectDirectionPools(float step) const -> DirectionPools {
return pools; return pools;
} }
auto Mummy::isBlockedAheadInDir(float dx_signed, float dy_signed) const -> bool {
if (dx_signed == 0.0F && dy_signed == 0.0F) { return false; }
// El rect a consultar és el AABB del tile cardinal contigu (mida
// `Map::tile_size`), ancorat al tile actual de la mòmia. Calculem el
// tile a partir del **centre** de la BOX, no de la cantonada superior-
// esquerra: amb una BOX de mida `tile_size`, la cantonada esquerra
// entra al tile següent un sub-píxel abans que el cos de la mòmia hi
// sigui majoritàriament. Si féssim servir `BOX.x` directament, una
// mòmia a `BOX.x = 199.5` (cobrint [199.5, 215.5], 0.5 px al tile 12
// i 15.5 px al tile 13) seria classificada al tile 12 i giraria un
// tile abans del veritablement adjacent al decal.
const auto BOX = getCollisionRect();
const auto TS = static_cast<float>(Map::tile_size);
const auto SIGN_OF = [](float v) -> float {
if (v > 0.0F) { return 1.0F; }
if (v < 0.0F) { return -1.0F; }
return 0.0F;
};
const float CENTER_X = BOX.x + (BOX.w * 0.5F);
const float CENTER_Y = BOX.y + (BOX.h * 0.5F);
const auto [CUR_COL, CUR_ROW] = Map::pixelToTile(CENTER_X, CENTER_Y);
const float TILE_X = (static_cast<float>(CUR_COL) * TS) + static_cast<float>(Map::tilemap_origin_x);
const float TILE_Y = (static_cast<float>(CUR_ROW) * TS) + static_cast<float>(Map::tilemap_origin_y);
const SDL_FRect NEXT{
.x = TILE_X + (SIGN_OF(dx_signed) * TS),
.y = TILE_Y + (SIGN_OF(dy_signed) * TS),
.w = TS,
.h = TS,
};
// Ordre per cost creixent: comparació de coordenades, després iteració
// sobre sarcs i porta, i finalment cerca a l'index d'entitats. Sense
// manager (smoke tests) saltem només l'últim filtre.
if (!Map::insidePlayfield(NEXT)) { return true; }
if (Map::collides(NEXT)) { return true; }
if (entity_manager_ != nullptr && entity_manager_->findFloorTriggerAt(NEXT) != nullptr) { return true; }
return false;
}
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 +484,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 +533,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 +559,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;
+64 -15
View File
@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <climits>
#include <cstdint> #include <cstdint>
#include <optional> #include <optional>
#include <vector> #include <vector>
@@ -16,10 +17,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 +70,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).
@@ -84,17 +86,37 @@ class Mummy : public DynamicHazard {
// Recull les direccions alternatives viables des de la posició actual: // Recull les direccions alternatives viables des de la posició actual:
// `perpendiculars` (girs) i `uturn` (inversa). La direcció actual s'exclou // `perpendiculars` (girs) i `uturn` (inversa). La direcció actual s'exclou
// perquè ja sabem que està bloquejada. Cada candidata es valida amb // perquè ja sabem que està bloquejada. Cada candidata es valida amb
// `Map::insidePlayfield` + `Map::collides` sobre la caja resultant. // `Map::insidePlayfield` + `Map::collides` sobre la caja resultant. Si
// `avoid_blocked` és true i hi ha `EntityManager` injectat, també es
// descarten les direccions que apunten a un tile amb un decal trigger
// (Hole o futurs pinchos plans/charcos). El caller és responsable de
// re-cridar amb `avoid_blocked=false` com a fallback si totes les
// direccions filtrades estan bloquejades.
struct DirectionPools { struct DirectionPools {
std::vector<Direction> perp; std::vector<Direction> perp;
std::vector<Direction> uturn; std::vector<Direction> uturn;
}; };
[[nodiscard]] auto collectDirectionPools(float step) const -> DirectionPools; [[nodiscard]] auto collectDirectionPools(float step, bool avoid_blocked = true) const -> DirectionPools;
// Canvi de direcció + aplicació de l'animació i flip corresponents. // True si el tile cardinal contigu en la direcció (signe de dx, signe
// Tots els camins que trien nova direcció (ctor amb `initial_direction`, // de dy) està bloquejat per qualsevol motiu: fora del playfield, ocupat
// gir després d'un bloqueig) passen per ací per mantenir anim_ i // per geometria estàtica (sarcs, door_corridor tancat) via `Map::collides`,
// flip_ en sincronia amb (dir_x_, dir_y_). // o conté un decal trigger (`is_floor_decal_ && triggers_on_overlap_`)
// segons `EntityManager::findFloorTriggerAt`. Captura `Hole` i qualsevol
// futur decal trigger sense hardcodejar tipus, i evita que la mòmia
// triï a un creuament una direcció que el frame següent toparia (cas
// típic: a la intersecció anterior al límit nord del playfield, on el
// pas immediat encara hi cap però el tile sencer següent ja està fora).
// Si `entity_manager_` no està injectat (smoke tests sense manager),
// el filtre de decals és silenciós però els altres dos (playfield i
// collides) segueixen actius.
[[nodiscard]] auto isBlockedAheadInDir(float dx_signed, float dy_signed) const -> bool;
// Canvi de direcció + selecció de l'animació corresponent (un cardinal
// per branch). Tots els camins que trien nova direcció (ctor amb
// `initial_direction`, gir després d'un bloqueig) passen per ací; el
// 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 +150,24 @@ 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 // Detecció reactiva al canvi de tile (substitueix el lookahead sub-tile
// només en cal una (`walk`) i el flip ja diferencia esquerra/dreta. // entre creuaments). Es crida al principi de cada `step()`. Si el tile
AnimationComponent anim_walk_down_; // que ocupa la mòmia (calculat des del centre de la BOX) ha canviat
AnimationComponent anim_walk_up_; // respecte a l'última crida, comprova si el tile nou conté un decal
AnimationComponent anim_walk_lateral_; // trigger; si el conté, dispara mitja volta immediata. La primera crida
// (last_tile_col_ == INT_MIN) només registra el tile actual sense
// reaccionar — evita un falç positiu si la mòmia spawneja sobre un
// decal o si el setup encara no és estable.
void reactToCurrentTile();
// Animacions carregades del YAML, una per cada cardinal. La variant
// `walk_west` reaprofita els frames de l'est però amb `flip_h: true`
// declarat al YAML — el motor d'animació aplica el flip al sprite
// automàticament; aquesta classe no toca mai `flip_` directament.
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 +177,18 @@ 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};
// Tile que ocupava la mòmia a l'última crida a `reactToCurrentTile`.
// Inicialitzats a `INT_MIN` perquè la primera crida (lazy init: no
// podem fer-ho al constructor perquè `pixel_rect_` encara no té
// posició; setPixelRect del manager arriba després) registra el tile
// de spawn sense reaccionar.
int last_tile_col_{INT_MIN};
int last_tile_row_{INT_MIN};
}; };
@@ -10,6 +10,9 @@ WallTrap::WallTrap(const fkyaml::node& yaml) {
name_ = yaml.contains("name") ? yaml["name"].get_value<std::string>() : std::string("wall_trap"); name_ = yaml.contains("name") ? yaml["name"].get_value<std::string>() : std::string("wall_trap");
collides_ = false; collides_ = false;
interactable_ = false; interactable_ = false;
// Trigger d'àrea: igual que FloorTrap. Tanca el gap latent del filtre
// de `checkPlayerCollisions` que ho deixava mut.
triggers_on_overlap_ = true;
if (yaml.contains("cycle")) { if (yaml.contains("cycle")) {
const auto& c = yaml["cycle"]; const auto& c = yaml["cycle"];
@@ -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;
} }
@@ -152,7 +152,7 @@ class WhipAttack : public Attack {
// del nostre `getFeetY()` per delegar el bucket d'y-sort al jugador. // del nostre `getFeetY()` per delegar el bucket d'y-sort al jugador.
float owner_feet_y_{0.0F}; float owner_feet_y_{0.0F};
EntityManager* entity_manager_{nullptr}; // `entity_manager_` viu a Entity (protected): l'utilitzem d'allà.
const std::vector<Player*>* players_{nullptr}; const std::vector<Player*>* players_{nullptr};
DirSprite sprite_north_; DirSprite sprite_north_;
+7
View File
@@ -1,5 +1,6 @@
#include "game/entities/entity.hpp" #include "game/entities/entity.hpp"
#include <limits>
#include <utility> #include <utility>
Entity::Entity(std::string name, TilePos tile, SDL_FRect pixel_rect) Entity::Entity(std::string name, TilePos tile, SDL_FRect pixel_rect)
@@ -38,6 +39,12 @@ auto Entity::getCollisionRect() const -> SDL_FRect {
} }
auto Entity::getFeetY() const -> float { auto Entity::getFeetY() const -> float {
// Decal de sòl: sentinel mínim per quedar al principi del Y-sort
// ascendent de `GameScene` (stable_sort) i així pintar-se ABANS de
// qualsevol actor. Sense passades de render extra.
if (is_floor_decal_) {
return std::numeric_limits<float>::lowest();
}
const auto RECT = getCollisionRect(); const auto RECT = getCollisionRect();
return RECT.y + RECT.h; return RECT.y + RECT.h;
} }
+41 -4
View File
@@ -12,9 +12,13 @@
// `source/game/entities/player.hpp` i només es referencia per referència // `source/game/entities/player.hpp` i només es referencia per referència
// dins `onPlayerContact`; `EventBus` és opcional a cada entitat i viu al // dins `onPlayerContact`; `EventBus` és opcional a cada entitat i viu al
// seu header propi. `Surface` és el backend de render paletitzat del motor. // seu header propi. `Surface` és el backend de render paletitzat del motor.
// `EntityManager` només es referencia per punter (injectat opcionalment
// per a entitats que necessiten consultar el món d'entitats — p.ex. la
// mòmia per saber si el pròxim tile té un decal trigger tipus Hole).
class Player; class Player;
class EventBus; class EventBus;
class Surface; class Surface;
class EntityManager;
// Entitat genèrica del món. Actua com a dada en comú per a tots els actors // Entitat genèrica del món. Actua com a dada en comú per a tots els actors
// del joc (decoratius, interactables, perills mòbils i estàtics): manté // del joc (decoratius, interactables, perills mòbils i estàtics): manté
@@ -88,6 +92,27 @@ class Entity {
[[nodiscard]] auto collides() const -> bool { return collides_; } [[nodiscard]] auto collides() const -> bool { return collides_; }
[[nodiscard]] auto interactable() const -> bool { return interactable_; } [[nodiscard]] auto interactable() const -> bool { return interactable_; }
// Trigger d'àrea: l'entitat NO bloqueja el moviment (`collides_=false`)
// però sí vol rebre `onPlayerContact` quan el jugador la solapa. Pensat
// per a hazards "no físics" (forats al sòl, picos retràctils, plaques
// de pressió). El bucle de contacte (`checkPlayerCollisions`) admet
// l'entitat si compleix `collides_ || triggers_on_overlap_`. El filtre
// d'altura per salt (`height_`) i la caixa de col·lisió funcionen igual.
[[nodiscard]] auto triggersOnOverlap() const -> bool { return triggers_on_overlap_; }
void setTriggersOnOverlap(bool v) { triggers_on_overlap_ = v; }
// Decal de sòl: hazards estàtics sense presència vertical (forats,
// plaques de pressió, charcos). Es pinten ABANS de qualsevol actor
// dins del Y-sort de `GameScene` perquè conceptualment són "pintura
// sobre el tile". Implementació: `getFeetY()` retorna un sentinel
// mínim (`std::numeric_limits<float>::lowest()`) per quedar al
// principi del `stable_sort` ascendent — així no calen passades de
// render extra. Les subclasses (p.ex. `Hole`) poden sobreescriure
// `getFeetY` per saltar-se el sentinel en moments concrets (p.ex.
// un forat que està tragant un jugador i ha de pintar-se al damunt).
[[nodiscard]] auto isFloorDecal() const -> bool { return is_floor_decal_; }
void setFloorDecal(bool v) { is_floor_decal_ = v; }
// Marca per a entitats transitòries: efectes (polvet, espurnes, // Marca per a entitats transitòries: efectes (polvet, espurnes,
// explosions...) que viuen una sola animació i s'autoeliminen quan // explosions...) que viuen una sola animació i s'autoeliminen quan
// acaba. L'`EntityManager` purga totes les entitats amb `dead_=true` // acaba. L'`EntityManager` purga totes les entitats amb `dead_=true`
@@ -101,6 +126,14 @@ class Entity {
// estàtiques de moment) el poden ignorar. // estàtiques de moment) el poden ignorar.
void setEventBus(EventBus* bus) { bus_ = bus; } void setEventBus(EventBus* bus) { bus_ = bus; }
// Injecció opcional del manager d'entitats (mateix patró que
// `setEventBus`: el manager s'auto-injecta a `add`/`setDoor`/
// `addSarcophagus`). El consumeixen entitats que volen consultar el
// món d'entitats — avui la mòmia, per saber si el tile següent
// conté un decal trigger (Hole) i evitar caure-hi. Entitats que no
// l'usen el poden ignorar.
void setEntityManager(EntityManager* manager) { entity_manager_ = manager; }
// Assigna la caixa de render en píxels. La invoca `EntityManager` durant // Assigna la caixa de render en píxels. La invoca `EntityManager` durant
// `loadFromLevel` un cop sap la posició definitiva de l'entitat al mapa. // `loadFromLevel` un cop sap la posició definitiva de l'entitat al mapa.
void setPixelRect(SDL_FRect rect) { pixel_rect_ = rect; } void setPixelRect(SDL_FRect rect) { pixel_rect_ = rect; }
@@ -155,10 +188,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; }
@@ -171,8 +205,11 @@ class Entity {
std::shared_ptr<Surface> surface_{nullptr}; std::shared_ptr<Surface> surface_{nullptr};
bool collides_{false}; bool collides_{false};
bool interactable_{false}; bool interactable_{false};
bool triggers_on_overlap_{false};
bool is_floor_decal_{false};
int height_{0}; int height_{0};
bool dead_{false}; bool dead_{false};
SDL_FlipMode flip_{SDL_FLIP_NONE}; SDL_FlipMode flip_{SDL_FLIP_NONE};
EventBus* bus_{nullptr}; EventBus* bus_{nullptr};
EntityManager* entity_manager_{nullptr};
}; };
+39 -1
View File
@@ -72,6 +72,7 @@ void EntityManager::forceOpenDoor() {
auto EntityManager::add(std::unique_ptr<Entity> entity) -> Entity* { auto EntityManager::add(std::unique_ptr<Entity> entity) -> Entity* {
if (entity == nullptr) { return nullptr; } if (entity == nullptr) { return nullptr; }
entity->setEventBus(&bus_); entity->setEventBus(&bus_);
entity->setEntityManager(this);
Entity* raw = entity.get(); Entity* raw = entity.get();
entities_.push_back(std::move(entity)); entities_.push_back(std::move(entity));
// cppcheck-suppress returnDanglingLifetime // cppcheck-suppress returnDanglingLifetime
@@ -80,12 +81,31 @@ auto EntityManager::add(std::unique_ptr<Entity> entity) -> Entity* {
void EntityManager::setDoor(Door* door) { void EntityManager::setDoor(Door* door) {
door_ = door; door_ = door;
if (door_ != nullptr) { door_->setEventBus(&bus_); } if (door_ != nullptr) {
door_->setEventBus(&bus_);
door_->setEntityManager(this);
}
}
auto EntityManager::removeEntity(Entity* observer) -> bool {
if (observer == nullptr) { return false; }
const bool WAS_DOOR = (door_ == observer);
const bool WAS_SARC = (observer->getName() == "sarcophagus");
auto it = std::ranges::find_if(entities_, [observer](const auto& up) { return up.get() == observer; });
if (it == entities_.end()) { return false; }
entities_.erase(it);
if (WAS_DOOR) { door_ = nullptr; }
if (WAS_SARC && total_sarcs_ > 0) { --total_sarcs_; }
// No tocar `opened_sarcs_` aquí: l'editor congela el joc i no pot
// disparar-se cap esdeveniment derivat. Quan el GameScene faça el
// reload posterior, els comptadors es re-deriven d'inici.
return true;
} }
auto EntityManager::addSarcophagus(std::unique_ptr<Sarcophagus> sarc) -> Sarcophagus* { auto EntityManager::addSarcophagus(std::unique_ptr<Sarcophagus> sarc) -> Sarcophagus* {
if (sarc == nullptr) { return nullptr; } if (sarc == nullptr) { return nullptr; }
sarc->setEventBus(&bus_); sarc->setEventBus(&bus_);
sarc->setEntityManager(this);
Sarcophagus* raw = sarc.get(); Sarcophagus* raw = sarc.get();
entities_.push_back(std::move(sarc)); entities_.push_back(std::move(sarc));
++total_sarcs_; ++total_sarcs_;
@@ -110,6 +130,24 @@ void EntityManager::notifyPlayerTilePainted(int col, int row) {
for (auto& e : entities_) { e->notifyTilePainted(col, row); } for (auto& e : entities_) { e->notifyTilePainted(col, row); }
} }
auto EntityManager::findFloorTriggerAt(const SDL_FRect& rect) const -> const Entity* {
// Cerca lineal sobre `entities_`: cost menyspreable amb poques
// desenes d'entitats per sala i una crida per frame des dels
// consumidors actius (Player a l'aterratge, Mòmia a la decisió de
// direcció). Si esdevé calent, es podria indexar per cel·la del
// tilemap. La doble condició filtra exclusivament decals de sòl que
// volen rebre triggers — avui Hole; en el futur pinchos plans,
// plaques de pressió, charcos.
for (const auto& entity : entities_) {
if (!entity->triggersOnOverlap()) { continue; }
if (!entity->isFloorDecal()) { continue; }
const auto BOX = entity->getCollisionRect();
const bool OVERLAP = rect.x + rect.w > BOX.x && BOX.x + BOX.w > rect.x && rect.y + rect.h > BOX.y && BOX.y + BOX.h > rect.y;
if (OVERLAP) { return entity.get(); }
}
return nullptr;
}
auto EntityManager::getEntities() const -> std::vector<Entity*> { auto EntityManager::getEntities() const -> std::vector<Entity*> {
std::vector<Entity*> out; std::vector<Entity*> out;
out.reserve(entities_.size()); out.reserve(entities_.size());
+29
View File
@@ -1,10 +1,13 @@
#pragma once #pragma once
#include <SDL3/SDL.h>
#include <cstddef> #include <cstddef>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include "external/fkyaml_node.hpp"
#include "game/entities/event_bus.hpp" #include "game/entities/event_bus.hpp"
class Entity; class Entity;
@@ -41,6 +44,13 @@ class EntityManager {
// una. Si ja n'hi ha una assignada, la substitueix (útil a tests). // una. Si ja n'hi ha una assignada, la substitueix (útil a tests).
void setDoor(Door* door); void setDoor(Door* door);
// Treu i destrueix l'entitat observada. Si era la porta o un sarc,
// ajusta `door_` / `total_sarcs_` en conseqüència. Pensat per al
// editor: no toca `opened_sarcs_` ni publica events — el joc està
// congelat i, en sortir del editor, GameScene torna a fer reload(),
// així que l'estat es deriva net.
auto removeEntity(Entity* observer) -> bool;
// Marca una entitat com a sarcòfag — si no es passa per `add` amb // Marca una entitat com a sarcòfag — si no es passa per `add` amb
// type=sarcophagus no es compta al total. Útil en combinació amb // type=sarcophagus no es compta al total. Útil en combinació amb
// `add` quan es fa addSarcophagus(unique_ptr<Sarcophagus>). // `add` quan es fa addSarcophagus(unique_ptr<Sarcophagus>).
@@ -74,6 +84,16 @@ class EntityManager {
// reflecteixi l'estat del frame actual. // reflecteixi l'estat del frame actual.
void checkPlayerCollisions(const std::vector<Player*>& players); void checkPlayerCollisions(const std::vector<Player*>& players);
// Cerca la primera entitat que (a) sigui decal de sòl
// (`is_floor_decal_=true`), (b) admeti triggers d'àrea
// (`triggers_on_overlap_=true`), i (c) la seva caixa de col·lisió
// solape amb el rect donat. Pensat per a `Player::integrateJump`,
// que la consulta a l'aterratge per detectar caigudes sobre Hole
// (i en el futur pinchos plans, plaques de pressió, charcos...) i
// saltar-se el LANDING_STUN normal. Retorna `nullptr` si no hi ha
// cap match — el caller usa el camí d'aterratge habitual.
[[nodiscard]] auto findFloorTriggerAt(const SDL_FRect& rect) const -> const Entity*;
// Fan-out del hook de pintat: per a cada sarcòfag, cridar // Fan-out del hook de pintat: per a cada sarcòfag, cridar
// notifyTilePainted. Les entitats no-sarcòfag ho ignoren per defecte // notifyTilePainted. Les entitats no-sarcòfag ho ignoren per defecte
// (veure Entity::notifyTilePainted). Invocat com a reacció a // (veure Entity::notifyTilePainted). Invocat com a reacció a
@@ -90,6 +110,15 @@ class EntityManager {
// `Map::tile_size`, `tilemap_origin_*` i `getSarcRect`). // `Map::tile_size`, `tilemap_origin_*` i `getSarcRect`).
void loadFromLevel(const std::string& level_yaml_path); void loadFromLevel(const std::string& level_yaml_path);
// Crea i afegeix una entitat a partir d'un node spawn (mateix format
// que els items del bloc `entities:` del YAML del nivell). Retorna el
// punter observacional a l'entitat creada, o `nullptr` si la plantilla
// no es pot carregar, el tipus no està registrat o el spawn no té els
// camps requerits (`position`, `sarc_position`, etc. segons el tipus).
// Pensat per al editor de Debug: construeix un node sintètic des
// d'una posició del cursor i el passa.
auto addSpawn(const fkyaml::node& spawn) -> Entity*;
// Reset complet del nivell: destrueix totes les entitats, reseteja // Reset complet del nivell: destrueix totes les entitats, reseteja
// els comptadors interns (sarcòfags oberts, total, exited, flag de // els comptadors interns (sarcòfags oberts, total, exited, flag de
// level_complete) i torna a cridar `loadFromLevel` per regenerar les // level_complete) i torna a cridar `loadFromLevel` per regenerar les
@@ -61,11 +61,32 @@ void EntityManager::renderAll() {
void EntityManager::checkPlayerCollisions(const std::vector<Player*>& players) { void EntityManager::checkPlayerCollisions(const std::vector<Player*>& players) {
if (players.empty()) { return; } if (players.empty()) { return; }
for (const auto& entity : entities_) { for (const auto& entity : entities_) {
if (!entity->collides()) { continue; } // Acceptem entitats sòlides (xoc físic) i triggers d'àrea (no
// bloquegen però volen rebre `onPlayerContact`). Tanquem el gap
// latent de FloorTrap/WallTrap (que tenien `collides_=false` i
// mai no es disparaven) i obrim la porta a hazards no-físics
// com `Hole`.
if (!entity->collides() && !entity->triggersOnOverlap()) { continue; }
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; }
// Mentre el jugador cau per un forat, ignorem qualsevol altre
// hazard: la transició a `enterDying()` ja la dispara
// `integrateJump` quan supera `HOLE_FALL_DEPTH`.
//
// NOTA: aquest skip és la defensa que evita que un jugador
// caient per un forat reba contacte d'altres hazards
// "subterranis" durant la caiguda. La cadena de mort per
// forat (`enterDying(HOLE)` → respawn) depén de no ser
// interrompuda per un event `player_hit`/`player_killed`
// mentre `falling_through_hole_=true`. Si el dia que es
// toque aquest skip s'elimina, cal revisar la cadena
// mort/respawn per forat.
if (p->isFallingThroughHole()) { 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.
@@ -374,31 +395,58 @@ void EntityManager::reload(const std::string& level_yaml_path) {
loadFromLevel(level_yaml_path); loadFromLevel(level_yaml_path);
} }
auto EntityManager::addSpawn(const fkyaml::node& spawn) -> Entity* {
if (!spawn.contains("type")) { return nullptr; }
const auto TYPE = spawn["type"].get_value<std::string>();
SpriteInfo sprite;
std::string anchor;
auto entity = buildEntity(TYPE, sprite, anchor);
if (entity == nullptr) { return nullptr; }
Entity* observer = entity.get();
bool ok = false;
if (TYPE == "sarcophagus") {
ok = placeSarcophagus(*this, std::move(entity), sprite, spawn);
} else if (TYPE == "door") {
ok = placeDoor(*this, std::move(entity), sprite);
} else {
ok = placeGeneric(*this, std::move(entity), TYPE, sprite, anchor, spawn);
}
return ok ? observer : nullptr;
}
void EntityManager::loadFromLevel(const std::string& level_yaml_path) { void EntityManager::loadFromLevel(const std::string& level_yaml_path) {
const auto SPAWNS = EntityLoader::readSpawns(level_yaml_path); const auto SPAWNS = EntityLoader::readSpawns(level_yaml_path);
int loaded = 0; const int LOADED = static_cast<int>(std::ranges::count_if(SPAWNS,
[this](const fkyaml::node& spawn) { return addSpawn(spawn) != nullptr; }));
for (const auto& spawn : SPAWNS) { // Broadcast inicial: marca com a "pintats" tots els tiles ocupats per
if (!spawn.contains("type")) { continue; } // decals de sòl (Hole, i futurs pinchos plans, plaques, charcos...).
const auto TYPE = spawn["type"].get_value<std::string>(); // Imprescindible perquè el jugador no pot trepitjar aquests tiles
// (cauria al forat) i els sarcòfags vecins no podrien completar el
SpriteInfo sprite; // perímetre — la sala seria incompletable. Usa el canal semàntic
std::string anchor; // `tile_painted` (lògica de joc), no `on_tile_entered` (presència
auto entity = buildEntity(TYPE, sprite, anchor); // física del jugador). Ho fem aquí, després del bucle de spawns,
if (entity == nullptr) { continue; } // perquè tots els sarcòfags ja estan registrats al fan-out (la
// subscripció del manager és al ctor, anterior a qualsevol load).
bool ok = false; int decal_broadcast = 0;
if (TYPE == "sarcophagus") { for (const auto& entity : entities_) {
ok = placeSarcophagus(*this, std::move(entity), sprite, spawn); if (!entity->isFloorDecal()) { continue; }
} else if (TYPE == "door") { const auto BOX = entity->getCollisionRect();
ok = placeDoor(*this, std::move(entity), sprite); const auto [COL, ROW] = Map::pixelToTile(BOX.x, BOX.y);
} else { bus_.publish(Event{
ok = placeGeneric(*this, std::move(entity), TYPE, sprite, anchor, spawn); .name = "tile_painted",
} .payload = TilePos{.col = COL, .row = ROW},
const bool OK = ok; .player_id = -1,
if (OK) { ++loaded; } });
++decal_broadcast;
}
if (decal_broadcast > 0) {
std::cout << "EntityManager: " << decal_broadcast
<< " tile_painted broadcast inicial (decals de sòl)\n";
} }
std::cout << "EntityManager: " << loaded << " entitats carregades des de " std::cout << "EntityManager: " << LOADED << " entitats carregades des de "
<< level_yaml_path << '\n'; << level_yaml_path << '\n';
} }
+115
View File
@@ -0,0 +1,115 @@
#include "game/entities/entity_writer.hpp"
#ifdef _DEBUG
#include <cstddef>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <string>
#include <system_error>
namespace EntityWriter {
namespace {
// Cerca la línia que comença per `<key>:` a columna 0. Mateix
// patró que TilemapWriter::findDataLine però parametritzat. Si
// un dia paga la pena, podem extreure'l a un helper compartit.
auto findKeyLine(const std::string& content, const std::string& key) -> std::size_t {
const std::string PREFIX = key + ":";
std::size_t pos = 0;
while (pos < content.size()) {
const bool AT_LINE_START = (pos == 0 || content[pos - 1] == '\n');
if (AT_LINE_START && content.compare(pos, PREFIX.size(), PREFIX) == 0) {
const std::size_t AFTER = pos + PREFIX.size();
if (AFTER >= content.size() || content[AFTER] == '\n' || content[AFTER] == '\r' || content[AFTER] == ' ' || content[AFTER] == '\t' || content[AFTER] == '#') {
return pos;
}
}
const auto NL = content.find('\n', pos);
if (NL == std::string::npos) { break; }
pos = NL + 1;
}
return std::string::npos;
}
// Avança mentre trobe línies indentades (espai/tab) o línies buides.
// A diferència del TilemapWriter, ací incloem línies buides dins
// del bloc: l'editor de YAML deixa sovint blanc entre grups
// d'entitats, i volem absorbir-lo per no deixar restes orfes
// entre el bloc nou i el següent top-level.
auto advancePastBlock(const std::string& content, std::size_t pos) -> std::size_t {
while (pos < content.size()) {
const char C = content[pos];
const bool IS_INDENT = (C == ' ' || C == '\t');
const bool IS_BLANK = (C == '\n' || C == '\r');
if (!IS_INDENT && !IS_BLANK) { break; }
const auto NL = content.find('\n', pos);
pos = (NL == std::string::npos) ? content.size() : NL + 1;
}
return pos;
}
auto buildBlock(const std::vector<Spawn>& spawns) -> std::string {
std::string out;
for (const auto& s : spawns) {
out += " - type: " + s.type + "\n";
if (s.sarc_col.has_value() && s.sarc_row.has_value()) {
out += " sarc_position: [" + std::to_string(*s.sarc_col) + ", " + std::to_string(*s.sarc_row) + "]\n";
} else if (s.col.has_value() && s.row.has_value()) {
out += " position: [" + std::to_string(*s.col) + ", " + std::to_string(*s.row) + "]\n";
}
}
return out;
}
} // namespace
auto saveEntitiesBlock(const std::string& path, const std::vector<Spawn>& spawns) -> bool {
std::ifstream in(path, std::ios::binary);
if (!in) { return false; }
std::ostringstream ss;
ss << in.rdbuf();
const std::string CONTENT = ss.str();
in.close();
const auto KEY_POS = findKeyLine(CONTENT, "entities");
if (KEY_POS == std::string::npos) { return false; }
const auto LINE_END = CONTENT.find('\n', KEY_POS);
const auto AFTER_KEY = (LINE_END == std::string::npos) ? CONTENT.size() : LINE_END + 1;
const auto BLOCK_END = advancePastBlock(CONTENT, AFTER_KEY);
// Mantenim una línia en blanc darrere del bloc si abans n'hi
// havia: ajuda a separar visualment el `entities:` de la pròxima
// secció. Si no n'hi havia, no afegim res.
const std::string TAIL = CONTENT.substr(BLOCK_END);
const std::string NEW_CONTENT =
CONTENT.substr(0, AFTER_KEY) + buildBlock(spawns) + TAIL;
const auto TMP_PATH = path + ".tmp";
{
std::ofstream out(TMP_PATH, std::ios::binary | std::ios::trunc);
if (!out) { return false; }
out.write(NEW_CONTENT.data(), static_cast<std::streamsize>(NEW_CONTENT.size()));
out.close();
if (!out) {
std::error_code ec;
std::filesystem::remove(TMP_PATH, ec);
return false;
}
}
std::error_code ec;
std::filesystem::rename(TMP_PATH, path, ec);
if (ec) {
std::filesystem::remove(TMP_PATH, ec);
return false;
}
return true;
}
} // namespace EntityWriter
#endif // _DEBUG
+50
View File
@@ -0,0 +1,50 @@
#pragma once
#ifdef _DEBUG
#include <optional>
#include <string>
#include <vector>
// Reescriu NOMÉS el bloc `entities:` d'un YAML de nivell, preservant byte
// a byte la resta del fitxer: comentaris, ordre de claus, format del
// `geometry:` i `data:`, encoding i line endings entre línies no tocades.
// Pensat exclusivament per a l'editor (Debug).
//
// Format de sortida (estable, llegible a l'editor de text):
// entities:
// - type: torch
// position: [4, 2]
// - type: sarcophagus
// sarc_position: [0, 0]
// - type: door
//
// Semàntica del bloc origen:
// - Línia de capçalera: primera aparició de "entities:" a columna 0.
// Si manca, el writer FALLA i no toca el fitxer.
// - Contingut: línies indentades (espai o tab). Una línia buida o una
// línia a columna 0 marquen la fi del bloc.
//
// Si el fitxer original no té el bloc `entities:`, el writer no inventa
// ubicació: retorna false per que el caller decidisca (mostrar error,
// avortar, etc.) — no és funció del writer escollir l'estil del YAML.
//
// Escriptura atòmica: via `<path>.tmp` + `std::filesystem::rename`. Si
// l'escriptura falla a meitat, el YAML original queda intacte.
namespace EntityWriter {
// Cada spawn duplica l'estructura mínima del que el lector espera:
// type sempre, i un dels (position, sarc_position) o cap (cas door).
struct Spawn {
std::string type;
std::optional<int> col;
std::optional<int> row;
std::optional<int> sarc_col;
std::optional<int> sarc_row;
};
auto saveEntitiesBlock(const std::string& path, const std::vector<Spawn>& spawns) -> bool;
} // namespace EntityWriter
#endif // _DEBUG
+505 -162
View File
@@ -9,13 +9,18 @@
#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/canvas.hpp"
#include "core/rendering/screen.hpp" #include "core/rendering/screen.hpp"
#include "core/resources/resource_cache.hpp" #include "core/resources/resource_cache.hpp"
#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/entity.hpp"
#include "game/entities/entity_manager.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 +74,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 +126,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 +190,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 +213,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 +254,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");
} }
@@ -301,18 +330,18 @@ void Player::updateFacingAndTurn(float delta_time, float vx, float vy) {
intermediate_cardinal_ = intermediateFor(INPUT_CARD); intermediate_cardinal_ = intermediateFor(INPUT_CARD);
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) {
// Tria de facing original: prevalença lateral, flip segons el
// signe de vx; verticals purs van a UP/DOWN amb flip NONE; en
// aturar-se conservem `facing_` per a l'animació stand.
facing_ = Facing::SIDE;
setFlip(vx < 0.0F ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE);
} else if (vy < 0.0F) { } else if (vy < 0.0F) {
facing_ = Facing::UP; // Tria de facing amb prevalença vertical: si hi ha component
setFlip(SDL_FLIP_NONE); // vertical, el sprite mira NORTH/SOUTH encara que també hi
// haja horitzontal. Així una diagonal (down+right, up+left,
// etc.) mostra el sprite mirant amunt o avall, no de costat.
// Només quan el moviment és purament horitzontal anem a
// EAST/WEST. En aturar-se conservem `facing_` per a stand.
facing_ = Facing::NORTH;
} else if (vy > 0.0F) { } else if (vy > 0.0F) {
facing_ = Facing::DOWN; facing_ = Facing::SOUTH;
setFlip(SDL_FLIP_NONE); } else if (vx != 0.0F) {
facing_ = (vx < 0.0F) ? Facing::WEST : Facing::EAST;
} }
} }
@@ -331,11 +360,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 +407,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 +469,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 +586,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 +667,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 +690,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 +702,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 +724,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 +747,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 +799,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 +894,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;
@@ -821,7 +916,40 @@ auto Player::integrateJump(float delta_time) -> bool {
if (state_ != State::AIRBORNE) { return false; } if (state_ != State::AIRBORNE) { return false; }
jump_vz_ -= jump_config.gravity_px_per_s2 * delta_time; jump_vz_ -= jump_config.gravity_px_per_s2 * delta_time;
jump_z_ += jump_vz_ * delta_time; jump_z_ += jump_vz_ * delta_time;
// Caiguda per agujero: el sòl ja no és present sota el jugador, així
// que NO clampem `jump_z_` a 0. Deixem baixar a negatius (visualment
// el sprite "s'enfonsa" sota el sòl) i, en superar `HOLE_FALL_DEPTH`,
// disparem la mort. La cinemàtica de respawn (`enterDying →
// spawnFromAir`) reseteja el flag i la posició al spawn point.
if (falling_through_hole_) {
if (jump_z_ <= -HOLE_FALL_DEPTH) {
enterDying(DeathCause::HOLE);
}
return false;
}
if (jump_z_ <= 0.0F) { if (jump_z_ <= 0.0F) {
// Aterratge sobre decal-trigger (Hole, futurs pinchos plans...):
// saltem el LANDING_STUN i transicionem directament a la caiguda
// pel forat. Sense aquesta detecció, l'aterratge entraria a
// LANDING_STUN (~200 ms flotant sobre el forat) i només quan
// tornés a GROUNDED `Hole::onPlayerContact` ho dispararia. La
// hit-box (8×8 centrada al sprite) és la mateixa que `Hole`
// usa al seu re-check; coherent. Si el manager no s'ha
// injectat (tests stand-alone), el camí d'aterratge segueix sent
// el habitual.
if (entity_manager_ != nullptr) {
const Entity* trigger = entity_manager_->findFloorTriggerAt(getHitBox());
if (trigger != nullptr) {
const auto BOX = trigger->getCollisionRect();
const float CENTER_X = BOX.x + (BOX.w * 0.5F);
const float CENTER_Y = BOX.y + (BOX.h * 0.5F);
const float FLOOR_Y = BOX.y + BOX.h;
enterHoleFall(CENTER_X, CENTER_Y, FLOOR_Y);
// No retornem `true`: no és un aterratge "normal" i
// `applyMovement` no ha de cridar `resolveLandingOverlap`.
return false;
}
}
jump_z_ = 0.0F; jump_z_ = 0.0F;
jump_vz_ = 0.0F; jump_vz_ = 0.0F;
state_ = State::LANDING_STUN; state_ = State::LANDING_STUN;
@@ -860,7 +988,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 +1038,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;
@@ -965,6 +1100,31 @@ void Player::applyMovement(float delta_time) {
clampToPlayfield(); clampToPlayfield();
// Refinem el `facing_` amb el desplaçament REAL del frame (post
// col·lisió/snap), no amb la velocitat d'entrada: si l'usuari prem
// diagonal en un pasillo, `tryMove` ja haurà anul·lat l'eix tapat
// per la paret, així que el sprite mira en la direcció realment
// efectuada (la d'input cru no ho sap). En AIRBORNE el facing es
// preserva des del despegue (vegeu el comentari de `integrateJump`),
// i durant un gir actiu el cardinal intermedi mana, així que els
// saltem. Si DX==DY==0 (parat o totalment bloquejat) preservem el
// facing previ per a l'animació stand. La prioritat vertical en
// diagonals lliures és intranscendent en pasillos (un dels dos eixos
// serà sempre 0); manté coherència amb la tria d'`updateFacingAndTurn`.
if (!AIRBORNE && turning_remaining_ms_ <= 0.0F) {
const float DX = getPosX() - FRAME_START_X;
const float DY = getPosY() - FRAME_START_Y;
if (DY < 0.0F) {
facing_ = Facing::NORTH;
} else if (DY > 0.0F) {
facing_ = Facing::SOUTH;
} else if (DX < 0.0F) {
facing_ = Facing::WEST;
} else if (DX > 0.0F) {
facing_ = Facing::EAST;
}
}
const bool MOVED = (getPosX() != FRAME_START_X) || (getPosY() != FRAME_START_Y); const bool MOVED = (getPosX() != FRAME_START_X) || (getPosY() != FRAME_START_Y);
// `being_pushed_` força `stand_*`: el jugador es desplaça sense input, // `being_pushed_` força `stand_*`: el jugador es desplaça sense input,
// animar-lo com a caminant faria creure que està caminant quan de // animar-lo com a caminant faria creure que està caminant quan de
@@ -979,6 +1139,88 @@ void Player::applyMovement(float delta_time) {
if (state_ == State::GROUNDED) { publishTileEnteredIfChanged(); } if (state_ == State::GROUNDED) { publishTileEnteredIfChanged(); }
} }
void Player::enterDying(DeathCause cause) {
// 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;
falling_through_hole_ = false;
hole_clip_bottom_y_ = 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;
// Presentació segons la causa. Cada `DeathCause` té la seua animació
// dedicada al YAML del player; `applyDyingMovement` arma el respawn
// timer quan `animationIsCompleted()` flanqueja, igual per a totes.
switch (cause) {
case DeathCause::HOLE:
setCurrentAnimation("death_hole");
break;
case DeathCause::GENERIC:
default:
setCurrentAnimation("death_spinning");
break;
}
// 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,20 +1273,54 @@ 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 // `jump_z_` píxels: cap amunt si és positiu (salt), cap avall si és
// negatiu (caiguda per `Hole`, que deixa baixar `jump_z_` a negatius
// fins a `-HOLE_FALL_DEPTH`). 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
// perquè l'estat seguent del frame ho recalcule des de `y_` (la font // perquè l'estat seguent del frame ho recalcule des de `y_` (la font
// de veritat float). El sòl sota el jugador no es pinta des d'ací: // de veritat float). El sòl sota el jugador no es pinta des d'ací:
// això ho fa el GameScene en una passada de sombres a banda. // això ho fa el GameScene en una passada de sombres a banda. El cas
if (jump_z_ <= 0.0F) { // `jump_z_ == 0` (a terra) surt per l'early-return per evitar tocar
AnimatedSprite::render(); // `pos_.y` innecessàriament.
return; // Caiguda per agujero: el sprite es "menja" pel sòl. Fixem un clip
// global a la franja [0, hole_clip_bottom_y_] del canvas (el blitter
// de `Surface` respecta `Rendering::activeClip()`); a mesura que
// `pos_.y - jump_z_` baixa, els píxels del sprite que creuen la
// línia deixen de pintar-se. Fem el push/pop al voltant de tot el
// render del sprite per cobrir tant el cas `jump_z_==0` (caiguda
// just iniciada) com els frames posteriors. `pushClip` ja clamp
// a la mida del canvas, així que un `h` negatiu es traduiria en
// una franja buida i el sprite quedaria invisible — comportament
// correcte si el clip ja ha "tapat" tota la pantalla.
const bool CLIP_HOLE = falling_through_hole_;
if (CLIP_HOLE) {
const auto CANVAS = Rendering::activeCanvas();
Rendering::pushClip({
.x = 0,
.y = 0,
.w = CANVAS.width,
.h = static_cast<int>(hole_clip_bottom_y_),
});
} }
const float SAVED_Y = pos_.y;
pos_.y = SAVED_Y - jump_z_; if (jump_z_ == 0.0F) {
AnimatedSprite::render(); AnimatedSprite::render();
pos_.y = SAVED_Y; } else {
const float SAVED_Y = pos_.y;
pos_.y = SAVED_Y - jump_z_;
AnimatedSprite::render();
pos_.y = SAVED_Y;
}
if (CLIP_HOLE) { Rendering::popClip(); }
} }
void Player::renderShadow() const { void Player::renderShadow() const {
@@ -1052,6 +1328,10 @@ void Player::renderShadow() const {
// que poden cridar `renderShadow` per a tots els jugadors sense // que poden cridar `renderShadow` per a tots els jugadors sense
// discriminar. // discriminar.
if (!isAirborne()) { return; } if (!isAirborne()) { return; }
// Caient per un agujero: el cos s'enfonsa sota el sòl, dibuixar una
// sombra al ras del sòl trenca la il·lusió. Suprimim la sombra fins
// que `enterDying` netegi el flag.
if (falling_through_hole_) { return; }
// Sombra centrada al sprite, recolzada a la línia dels peus reals // Sombra centrada al sprite, recolzada a la línia dels peus reals
// (ignorant `jump_z_`). L'amplada es redueix proporcional a l'altura // (ignorant `jump_z_`). L'amplada es redueix proporcional a l'altura
@@ -1085,91 +1365,55 @@ 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` o `death_hole`) la
return "hurt_up"; // selecciona `enterDying` segons `DeathCause`; aquest retorn
case Facing::DOWN: // només cobreix exhaustivitat del switch i no s'usa al runtime.
return "hurt_down"; return "death";
case Facing::SIDE: case State::GROUNDED:
break; case State::EXITED:
} case State::DISABLED:
return "hurt";
}
// 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
@@ -1222,17 +1466,24 @@ void Player::respawn(float x, float y) {
being_pushed_ = false; being_pushed_ = false;
jump_z_ = 0.0F; jump_z_ = 0.0F;
jump_vz_ = 0.0F; jump_vz_ = 0.0F;
falling_through_hole_ = false;
hole_clip_bottom_y_ = 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);
} }
@@ -1254,6 +1505,57 @@ void Player::disable() {
last_input_cardinal_ = Cardinal::NONE; last_input_cardinal_ = Cardinal::NONE;
} }
void Player::enterHoleFall(float center_x, float center_y, float floor_y) {
// Cap guard de state ací: la responsabilitat la té el caller perquè
// hi ha dos callers vàlids amb states distints:
// - `Hole::onPlayerContact` (state GROUNDED, el jugador camina i
// trepitja el tile del forat).
// - `Player::integrateJump` (state AIRBORNE, en aterrar damunt
// d'un decal-trigger en lloc d'entrar a LANDING_STUN).
// Posar `if (state_ != GROUNDED) return` ací bloquejaria el segon
// cas i obligaria a hacks tipus "forçar GROUNDED abans de cridar".
// Snap del centre de la hit-box a la coordenada central del forat.
// Resolem `pos = center - (hit_offset + hit_size/2)` per a cada eix:
// així el centre exacte de la caja de contacte queda alineat amb
// (center_x, center_y), independentment de la geometria interna del
// sprite. `setPosX/Y` és l'API canònica per teleportar el jugador
// (la usen `respawn` i `spawnFromAir`).
const float HIT_HALF = static_cast<float>(hit_size) * 0.5F;
setPosX(center_x - static_cast<float>(hit_x_offset) - HIT_HALF);
setPosY(center_y - static_cast<float>(hit_y_offset) - HIT_HALF);
// Frenem qualsevol inèrcia prèvia: la caiguda ha de ser estàtica al
// centre del forat.
intended_vx_ = 0.0F;
intended_vy_ = 0.0F;
input_vx_ = 0.0F;
input_vy_ = 0.0F;
motion_time_ms_ = 0.0F;
being_pushed_ = false;
setVelX(0.0F);
setVelY(0.0F);
jump_z_ = 0.0F;
jump_vz_ = 0.0F;
state_ = State::AIRBORNE;
falling_through_hole_ = true;
// Línia visual on el clip rect ha de retallar el sprite. Restem
// `SPRITE_FLOOR_OFFSET` perquè la base del sprite té píxels
// transparents — el retall ha de coincidir amb la silueta visible,
// no amb la vora del rect del sprite.
hole_clip_bottom_y_ = floor_y - SPRITE_FLOOR_OFFSET;
// Animació de caiguda: respectem el `facing_` que ja portava el jugador
// (no té sentit reorientar-lo cap al sud només perquè caigui en un
// forat). Com `state_` ja és AIRBORNE, `animationName(false)` resol el
// prefix `jump_` i hi afegeix el sufix cardinal correcte —
// `jump_north`, `jump_south`, `jump_east` o `jump_west` segons el
// facing actual. Si demà cal una animació de "cara de pànic", només
// cal canviar aquí pel `setCurrentAnimation` corresponent.
setCurrentAnimation(animationName(false));
}
void Player::spawnFromAir(float x, float y) { void Player::spawnFromAir(float x, float y) {
// Net total: equival a un `respawn` seguit d'una entrada en l'aire. // Net total: equival a un `respawn` seguit d'una entrada en l'aire.
// Així el flag de gir, l'animació, els flancs del salt i les // Així el flag de gir, l'animació, els flancs del salt i les
@@ -1279,10 +1581,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 {
@@ -1315,24 +1617,13 @@ auto Player::hitBoxFromBase(const SDL_FRect& base) -> SDL_FRect {
}; };
} }
void Player::snapToLane( void Player::snapTowardsLane(
float delta_time, float delta_time,
float base, float target_lane,
float pitch,
int max_idx,
float (Player::*get_pos)() const, float (Player::*get_pos)() const,
void (Player::*set_pos)(float)) { void (Player::*set_pos)(float)) {
// Lanes en pos_axis: base + i·pitch per a i ∈ [0, max_idx]. Correspon a
// la caixa de col·lisió ocupant exactament un passadís perpendicular.
const auto THRESHOLD = static_cast<float>(collision_size) / 2.0F; const auto THRESHOLD = static_cast<float>(collision_size) / 2.0F;
const float DELTA = target_lane - (this->*get_pos)();
const auto IDX = std::clamp(
static_cast<int>(std::round(((this->*get_pos)() - base) / pitch)),
0,
max_idx);
const float LANE = base + (static_cast<float>(IDX) * pitch);
const float DELTA = LANE - (this->*get_pos)();
if (std::abs(DELTA) <= 0.0F || std::abs(DELTA) > THRESHOLD) { return; } if (std::abs(DELTA) <= 0.0F || std::abs(DELTA) > THRESHOLD) { return; }
const float FULL_STEP = std::min(std::abs(DELTA), SPEED_PX_PER_S * delta_time); const float FULL_STEP = std::min(std::abs(DELTA), SPEED_PX_PER_S * delta_time);
@@ -1417,20 +1708,72 @@ auto Player::tryPushSnapBlockers(
return true; return true;
} }
auto Player::wouldHitOuterWall(float dx, float dy) const -> bool {
SDL_FRect probe = getBaseBox();
probe.x += dx;
probe.y += dy;
if (Map::insidePlayfield(probe)) { return false; }
// Excepció: corridor de la porta OBERTA quan l'usuari prem cap
// amunt (dy < 0). No demanem que la probe ja estigui dins del
// corridor — encara no ho està, perquè el snap és justament qui
// ha d'aliniar-la. En lloc d'això comprovem si la base box està
// prou prop del centre del corridor com per a que el snap
// (mateix THRESHOLD = collision_size/2) puga completar-lo.
if (Map::door_open && dy < 0.0F && map_.door_corridor_w > 0) {
const float CORRIDOR_TARGET = static_cast<float>(map_.door_corridor_x) + (static_cast<float>(map_.door_corridor_w - collision_size) / 2.0F);
const auto THRESHOLD = static_cast<float>(collision_size) / 2.0F;
if (std::abs(CORRIDOR_TARGET - probe.x) <= THRESHOLD) { return false; }
}
return true;
}
void Player::snapToHorizontalLane(float delta_time) { void Player::snapToHorizontalLane(float delta_time) {
snapToLane(delta_time, // Si el moviment principal del frame (eix X) està blocat per un mur
static_cast<float>(map_.playfield_y - collision_y_offset), // extern, no fem snap perpendicular: l'usuari està picant contra el
static_cast<float>(map_.pitch_y), // mur i no té sentit arrossegar-lo a un carril. Step probe d'1 px.
map_.grid_rows, if (intended_vx_ != 0.0F && wouldHitOuterWall(std::copysign(1.0F, intended_vx_), 0.0F)) {
&Player::getPosY, return;
&Player::setPosY); }
const auto BASE_Y = static_cast<float>(map_.playfield_y - collision_y_offset);
const auto PITCH_Y = static_cast<float>(map_.pitch_y);
const int IDX = std::clamp(
static_cast<int>(std::round((getPosY() - BASE_Y) / PITCH_Y)),
0,
map_.grid_rows);
const float LANE_REG = BASE_Y + (static_cast<float>(IDX) * PITCH_Y);
snapTowardsLane(delta_time, LANE_REG, &Player::getPosY, &Player::setPosY);
} }
void Player::snapToVerticalLane(float delta_time) { void Player::snapToVerticalLane(float delta_time) {
snapToLane(delta_time, // Mateixa idea que la versió horitzontal: si el moviment principal
static_cast<float>(map_.playfield_x - collision_x_offset), // (eix Y) està blocat per un mur extern, no aliniem en X. La
static_cast<float>(map_.pitch_x), // excepció del corridor de la porta hi aplica naturalment dins
map_.grid_cols, // `wouldHitOuterWall` (només quan és OBERT).
&Player::getPosX, if (intended_vy_ != 0.0F && wouldHitOuterWall(0.0F, std::copysign(1.0F, intended_vy_))) {
&Player::setPosX); return;
}
// Candidat 1: lane regular del laberint.
const auto BASE_X = static_cast<float>(map_.playfield_x - collision_x_offset);
const auto PITCH_X = static_cast<float>(map_.pitch_x);
const int IDX = std::clamp(
static_cast<int>(std::round((getPosX() - BASE_X) / PITCH_X)),
0,
map_.grid_cols);
const float LANE_REG = BASE_X + (static_cast<float>(IDX) * PITCH_X);
// Candidat 2: corridor de la porta. NOMÉS si la porta està oberta —
// si està tancada no té sentit arrossegar el jugador cap a un
// forat per on no pot passar.
float best_lane = LANE_REG;
if (Map::door_open && map_.door_corridor_w > 0) {
const float CORRIDOR_CENTER = static_cast<float>(map_.door_corridor_x) + (static_cast<float>(map_.door_corridor_w - collision_size) / 2.0F);
const float LANE_DOOR = CORRIDOR_CENTER - static_cast<float>(collision_x_offset);
const float DELTA_REG = LANE_REG - getPosX();
const float DELTA_DOOR = LANE_DOOR - getPosX();
if (std::abs(DELTA_DOOR) < std::abs(DELTA_REG)) { best_lane = LANE_DOOR; }
}
snapTowardsLane(delta_time, best_lane, &Player::getPosX, &Player::setPosX);
} }
Loaded 100 of 119 files, more files were not shown because too many files have changed in this diff. Show more