2 Commits

Author SHA1 Message Date
b3f6e2fcf0 Metal post-processing pipeline implementation
- Add custom Metal render target creation
- Implement post-processing without GPU-CPU-GPU copies
- Create Metal texture extraction system
- Add Metal CRT shader pipeline integration
- Modify screen rendering to use Metal when available
- Enable shaders by default for testing

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-16 07:42:03 +02:00
7f00942517 treballant en metal 2025-09-10 20:44:10 +02:00
153 changed files with 4229 additions and 6350 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,4 @@
.vscode .vscode
.claude
build/ build/
data/config/config.txt data/config/config.txt
*.DS_Store *.DS_Store
@@ -18,4 +17,3 @@ debug.txt
cppcheck-result* cppcheck-result*
desktop.ini desktop.ini
ccae_release/ ccae_release/
resources.pack

View File

@@ -14,22 +14,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED True)
cmake_policy(SET CMP0072 NEW) cmake_policy(SET CMP0072 NEW)
set(OpenGL_GL_PREFERENCE GLVND) set(OpenGL_GL_PREFERENCE GLVND)
# --- GENERACIÓN DE VERSIÓN AUTOMÁTICA ---
find_package(Git QUIET)
if(GIT_FOUND)
execute_process(
COMMAND ${GIT_EXECUTABLE} rev-parse --short=7 HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
else()
set(GIT_HASH "unknown")
endif()
# Configurar archivo de versión
configure_file(${CMAKE_SOURCE_DIR}/source/version.h.in ${CMAKE_BINARY_DIR}/version.h @ONLY)
# --- 1. LISTA EXPLÍCITA DE FUENTES --- # --- 1. LISTA EXPLÍCITA DE FUENTES ---
set(APP_SOURCES set(APP_SOURCES
@@ -64,7 +48,6 @@ set(APP_SOURCES
source/balloon_manager.cpp source/balloon_manager.cpp
source/balloon.cpp source/balloon.cpp
source/bullet.cpp source/bullet.cpp
source/bullet_manager.cpp
source/enter_name.cpp source/enter_name.cpp
source/explosions.cpp source/explosions.cpp
source/game_logo.cpp source/game_logo.cpp
@@ -96,7 +79,6 @@ set(APP_SOURCES
# --- Otros --- # --- Otros ---
source/color.cpp source/color.cpp
source/demo.cpp
source/define_buttons.cpp source/define_buttons.cpp
source/difficulty.cpp source/difficulty.cpp
source/input_types.cpp source/input_types.cpp
@@ -110,37 +92,28 @@ set(APP_SOURCES
# Fuentes de librerías de terceros # Fuentes de librerías de terceros
set(EXTERNAL_SOURCES set(EXTERNAL_SOURCES
source/external/jail_audio.cpp source/external/jail_shader.cpp
source/external/json.hpp source/external/json.hpp
source/external/gif.cpp source/external/gif.cpp
) )
# Fuentes del sistema de renderizado # Añadir jail_audio.cpp solo si el audio está habilitado
set(RENDERING_SOURCES if(NOT DISABLE_AUDIO)
source/rendering/opengl/opengl_shader.cpp list(APPEND EXTERNAL_SOURCES source/external/jail_audio.cpp)
)
# Añadir backend de Metal en macOS
if(APPLE)
list(APPEND RENDERING_SOURCES
source/rendering/metal/metal_shader.mm
)
message(STATUS "Metal backend habilitado para macOS")
endif() endif()
# Configuración de SDL3 # Configuración de SDL3
find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3) find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3)
message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}") message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}")
# --- 2. AÑADIR EJECUTABLE --- # --- 2. AÑADIR EJECUTABLE ---
add_executable(${PROJECT_NAME} ${APP_SOURCES} ${EXTERNAL_SOURCES} ${RENDERING_SOURCES}) add_executable(${PROJECT_NAME} ${APP_SOURCES} ${EXTERNAL_SOURCES})
# --- 3. DIRECTORIOS DE INCLUSIÓN --- # --- 3. DIRECTORIOS DE INCLUSIÓN ---
target_include_directories(${PROJECT_NAME} PUBLIC target_include_directories(${PROJECT_NAME} PUBLIC
"${CMAKE_SOURCE_DIR}/source" "${CMAKE_SOURCE_DIR}/source"
"${CMAKE_SOURCE_DIR}/source/external" "${CMAKE_SOURCE_DIR}/source/external"
"${CMAKE_SOURCE_DIR}/source/rendering"
"${CMAKE_BINARY_DIR}"
) )
# Enlazar la librería SDL3 # Enlazar la librería SDL3
@@ -155,9 +128,16 @@ target_compile_options(${PROJECT_NAME} PRIVATE $<$<CONFIG:RELEASE>:-Os -ffunctio
# Definir _DEBUG en modo Debug # Definir _DEBUG en modo Debug
target_compile_definitions(${PROJECT_NAME} PRIVATE $<$<CONFIG:DEBUG>:_DEBUG>) target_compile_definitions(${PROJECT_NAME} PRIVATE $<$<CONFIG:DEBUG>:_DEBUG>)
# Descomentar la siguiente línea para activar el modo grabación de demos # Opción para habilitar/deshabilitar audio
# target_compile_definitions(${PROJECT_NAME} PRIVATE RECORDING) option(DISABLE_AUDIO "Disable audio system" OFF)
# Definir NO_AUDIO si la opción está activada
if(DISABLE_AUDIO)
target_compile_definitions(${PROJECT_NAME} PRIVATE NO_AUDIO)
message(STATUS "Audio deshabilitado - NO_AUDIO definido")
else()
message(STATUS "Audio habilitado")
endif()
# Configuración específica para cada plataforma # Configuración específica para cada plataforma
if(WIN32) if(WIN32)
@@ -167,11 +147,6 @@ elseif(APPLE)
target_compile_definitions(${PROJECT_NAME} PRIVATE MACOS_BUILD) target_compile_definitions(${PROJECT_NAME} PRIVATE MACOS_BUILD)
target_compile_options(${PROJECT_NAME} PRIVATE -Wno-deprecated) target_compile_options(${PROJECT_NAME} PRIVATE -Wno-deprecated)
set(CMAKE_OSX_ARCHITECTURES "arm64") set(CMAKE_OSX_ARCHITECTURES "arm64")
# Enlazar frameworks de Metal
target_link_libraries(${PROJECT_NAME} PRIVATE
"-framework Metal"
"-framework QuartzCore"
)
elseif(UNIX AND NOT APPLE) elseif(UNIX AND NOT APPLE)
target_compile_definitions(${PROJECT_NAME} PRIVATE LINUX_BUILD) target_compile_definitions(${PROJECT_NAME} PRIVATE LINUX_BUILD)
endif() endif()

View File

@@ -3,7 +3,6 @@ DIR_ROOT := $(dir $(abspath $(MAKEFILE_LIST)))
DIR_SOURCES := $(addsuffix /, $(DIR_ROOT)source) DIR_SOURCES := $(addsuffix /, $(DIR_ROOT)source)
DIR_BIN := $(addsuffix /, $(DIR_ROOT)) DIR_BIN := $(addsuffix /, $(DIR_ROOT))
DIR_BUILD := $(addsuffix /, $(DIR_ROOT)build) DIR_BUILD := $(addsuffix /, $(DIR_ROOT)build)
DIR_TOOLS := $(addsuffix /, $(DIR_ROOT)tools)
# Variables # Variables
TARGET_NAME := coffee_crisis_arcade_edition TARGET_NAME := coffee_crisis_arcade_edition
@@ -13,17 +12,6 @@ RELEASE_FOLDER := ccae_release
RELEASE_FILE := $(RELEASE_FOLDER)/$(TARGET_NAME) RELEASE_FILE := $(RELEASE_FOLDER)/$(TARGET_NAME)
RESOURCE_FILE := release/coffee.res RESOURCE_FILE := release/coffee.res
# Variables para herramienta de empaquetado
ifeq ($(OS),Windows_NT)
PACK_TOOL := $(DIR_TOOLS)pack_resources.exe
PACK_CXX := $(CXX)
else
PACK_TOOL := $(DIR_TOOLS)pack_resources
PACK_CXX := $(CXX)
endif
PACK_SOURCES := $(DIR_TOOLS)pack_resources.cpp $(DIR_SOURCES)resource_pack.cpp
PACK_INCLUDES := -I$(DIR_ROOT)
# Versión automática basada en la fecha actual (específica por SO) # Versión automática basada en la fecha actual (específica por SO)
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
VERSION := $(shell powershell -Command "Get-Date -Format 'yyyy-MM-dd'") VERSION := $(shell powershell -Command "Get-Date -Format 'yyyy-MM-dd'")
@@ -147,19 +135,6 @@ else
endif endif
endif endif
# Reglas para herramienta de empaquetado y resources.pack
$(PACK_TOOL): $(PACK_SOURCES)
@echo "Compilando herramienta de empaquetado..."
$(PACK_CXX) -std=c++17 -Wall -Os $(PACK_INCLUDES) $(PACK_SOURCES) -o $(PACK_TOOL)
@echo "✓ Herramienta de empaquetado lista: $(PACK_TOOL)"
pack_tool: $(PACK_TOOL)
resources.pack: $(PACK_TOOL)
@echo "Generando resources.pack desde directorio data/..."
$(PACK_TOOL) data resources.pack
@echo "✓ resources.pack generado exitosamente"
# Reglas para compilación # Reglas para compilación
windows: windows:
@echo off @echo off
@@ -178,7 +153,7 @@ windows_debug:
@echo Compilando version debug para Windows: "$(APP_NAME)_debug.exe" @echo Compilando version debug para Windows: "$(APP_NAME)_debug.exe"
$(CXX) $(APP_SOURCES) $(INCLUDES) -DDEBUG -DVERBOSE $(CXXFLAGS_DEBUG) $(LDFLAGS) -o "$(WIN_TARGET_FILE)_debug.exe" $(CXX) $(APP_SOURCES) $(INCLUDES) -DDEBUG -DVERBOSE $(CXXFLAGS_DEBUG) $(LDFLAGS) -o "$(WIN_TARGET_FILE)_debug.exe"
windows_release: resources.pack windows_release:
@echo off @echo off
@echo Creando release para Windows - Version: $(VERSION) @echo Creando release para Windows - Version: $(VERSION)
@@ -216,7 +191,7 @@ macos_debug:
@echo "Compilando version debug para macOS: $(TARGET_NAME)_debug" @echo "Compilando version debug para macOS: $(TARGET_NAME)_debug"
$(CXX) $(APP_SOURCES) $(INCLUDES) -DDEBUG -DVERBOSE $(CXXFLAGS_DEBUG) $(LDFLAGS) -o "$(TARGET_FILE)_debug" $(CXX) $(APP_SOURCES) $(INCLUDES) -DDEBUG -DVERBOSE $(CXXFLAGS_DEBUG) $(LDFLAGS) -o "$(TARGET_FILE)_debug"
macos_release: resources.pack macos_release:
@echo "Creando release para macOS - Version: $(VERSION)" @echo "Creando release para macOS - Version: $(VERSION)"
# Elimina datos de compilaciones anteriores # Elimina datos de compilaciones anteriores
$(RMDIR) "$(RELEASE_FOLDER)" $(RMDIR) "$(RELEASE_FOLDER)"
@@ -283,7 +258,7 @@ linux_debug:
@echo "Compilando version debug para Linux: $(TARGET_NAME)_debug" @echo "Compilando version debug para Linux: $(TARGET_NAME)_debug"
$(CXX) $(APP_SOURCES) $(INCLUDES) -DDEBUG -DVERBOSE $(CXXFLAGS_DEBUG) $(LDFLAGS) -o "$(TARGET_FILE)_debug" $(CXX) $(APP_SOURCES) $(INCLUDES) -DDEBUG -DVERBOSE $(CXXFLAGS_DEBUG) $(LDFLAGS) -o "$(TARGET_FILE)_debug"
linux_release: resources.pack linux_release:
@echo "Creando release para Linux - Version: $(VERSION)" @echo "Creando release para Linux - Version: $(VERSION)"
# Elimina carpetas previas # Elimina carpetas previas
$(RMDIR) "$(RELEASE_FOLDER)" $(RMDIR) "$(RELEASE_FOLDER)"
@@ -309,7 +284,7 @@ linux_release: resources.pack
# Elimina la carpeta temporal # Elimina la carpeta temporal
$(RMDIR) "$(RELEASE_FOLDER)" $(RMDIR) "$(RELEASE_FOLDER)"
linux_release_desktop: resources.pack linux_release_desktop:
@echo "Creando release con integracion desktop para Linux - Version: $(VERSION)" @echo "Creando release con integracion desktop para Linux - Version: $(VERSION)"
# Elimina carpetas previas # Elimina carpetas previas
$(RMDIR) "$(RELEASE_FOLDER)" $(RMDIR) "$(RELEASE_FOLDER)"
@@ -414,7 +389,7 @@ raspi_debug:
@echo "Compilando version debug para Raspberry Pi: $(TARGET_NAME)_debug" @echo "Compilando version debug para Raspberry Pi: $(TARGET_NAME)_debug"
$(CXX) $(APP_SOURCES) $(INCLUDES) -DVERBOSE -DDEBUG $(CXXFLAGS_DEBUG) $(LDFLAGS) -o "$(TARGET_FILE)_debug" $(CXX) $(APP_SOURCES) $(INCLUDES) -DVERBOSE -DDEBUG $(CXXFLAGS_DEBUG) $(LDFLAGS) -o "$(TARGET_FILE)_debug"
raspi_release: resources.pack raspi_release:
@echo "Creando release para Raspberry Pi - Version: $(VERSION)" @echo "Creando release para Raspberry Pi - Version: $(VERSION)"
# Elimina carpetas previas # Elimina carpetas previas
$(RMDIR) "$(RELEASE_FOLDER)" $(RMDIR) "$(RELEASE_FOLDER)"
@@ -440,7 +415,7 @@ raspi_release: resources.pack
# Elimina la carpeta temporal # Elimina la carpeta temporal
$(RMDIR) "$(RELEASE_FOLDER)" $(RMDIR) "$(RELEASE_FOLDER)"
anbernic: resources.pack anbernic:
@echo "Compilando para Anbernic: $(TARGET_NAME)" @echo "Compilando para Anbernic: $(TARGET_NAME)"
# Elimina carpetas previas # Elimina carpetas previas
$(RMDIR) "$(RELEASE_FOLDER)"_anbernic $(RMDIR) "$(RELEASE_FOLDER)"_anbernic
@@ -482,9 +457,7 @@ help:
@echo " raspi_release - Crear release completo para Raspberry Pi" @echo " raspi_release - Crear release completo para Raspberry Pi"
@echo " anbernic - Compilar para Anbernic" @echo " anbernic - Compilar para Anbernic"
@echo " no_audio - Compilar sin sistema de audio" @echo " no_audio - Compilar sin sistema de audio"
@echo " pack_tool - Compilar herramienta de empaquetado"
@echo " resources.pack - Generar pack de recursos desde data/"
@echo " show_version - Mostrar version actual ($(VERSION))" @echo " show_version - Mostrar version actual ($(VERSION))"
@echo " help - Mostrar esta ayuda" @echo " help - Mostrar esta ayuda"
.PHONY: windows windows_rec windows_debug windows_release macos macos_debug macos_release linux linux_debug linux_release linux_release_desktop raspi raspi_debug raspi_release anbernic no_audio show_version help pack_tool .PHONY: windows windows_rec windows_debug windows_release macos macos_debug macos_release linux linux_debug linux_release linux_release_desktop raspi raspi_debug raspi_release anbernic no_audio show_version help

61
TODO.md
View File

@@ -1,61 +0,0 @@
# TODO
## Tareas pendientes
- [ ] Revisar todas las variables static de los métodos para ver si se resetean correctamente
## Mejoras arquitecturales (refactoring)
### Eliminar variables static locales y usar patrones profesionales:
**Opción 1: Máquina de Estados**
```cpp
class GameCompletedState {
bool start_celebrations_done = false;
bool end_celebrations_done = false;
float timer = 0.0f;
public:
void reset() {
start_celebrations_done = false;
end_celebrations_done = false;
timer = 0.0f;
}
void update(float deltaTime) {
timer += deltaTime;
// lógica aquí
}
};
```
**Opción 2: Sistema de Eventos/Callbacks**
```cpp
// Al entrar en COMPLETED state
eventSystem.scheduleEvent(6.0f, []{ startCelebrations(); });
eventSystem.scheduleEvent(14.0f, []{ endCelebrations(); });
```
**Opción 3: Flags como miembros privados**
```cpp
class Game {
private:
struct GameOverState {
bool game_over_triggered = false;
bool start_celebrations_triggered = false;
bool end_celebrations_triggered = false;
void reset() {
game_over_triggered = false;
start_celebrations_triggered = false;
end_celebrations_triggered = false;
}
} game_over_state_;
};
```
**Ventajas:**
- Más fáciles de testear
- Más fáciles de debugear
- Más fáciles de entender y mantener
- No tienen "estado oculto"

View File

@@ -4,7 +4,7 @@
# Variables: ${PREFIX}, ${SYSTEM_FOLDER} # Variables: ${PREFIX}, ${SYSTEM_FOLDER}
# Archivos de configuración del sistema (absolutos y opcionales) # Archivos de configuración del sistema (absolutos y opcionales)
DATA|${SYSTEM_FOLDER}/config_v2.txt|optional,absolute DATA|${SYSTEM_FOLDER}/config.txt|optional,absolute
DATA|${SYSTEM_FOLDER}/controllers.json|optional,absolute DATA|${SYSTEM_FOLDER}/controllers.json|optional,absolute
DATA|${SYSTEM_FOLDER}/score.bin|optional,absolute DATA|${SYSTEM_FOLDER}/score.bin|optional,absolute
@@ -20,10 +20,8 @@ DATA|${PREFIX}/config/stages.txt
# Archivos con los datos de la demo # Archivos con los datos de la demo
DEMODATA|${PREFIX}/data/demo/demo1.bin DEMODATA|${PREFIX}/data/demo/demo1.bin
DEMODATA|${PREFIX}/data/demo/demo2.bin DEMODATA|${PREFIX}/data/demo/demo2.bin
DEMODATA|${PREFIX}/data/demo/demo3.bin
# Música # Música
MUSIC|${PREFIX}/data/music/congratulations.ogg
MUSIC|${PREFIX}/data/music/credits.ogg MUSIC|${PREFIX}/data/music/credits.ogg
MUSIC|${PREFIX}/data/music/intro.ogg MUSIC|${PREFIX}/data/music/intro.ogg
MUSIC|${PREFIX}/data/music/playing.ogg MUSIC|${PREFIX}/data/music/playing.ogg
@@ -38,8 +36,7 @@ SOUND|${PREFIX}/data/sound/balloon_pop0.wav
SOUND|${PREFIX}/data/sound/balloon_pop1.wav SOUND|${PREFIX}/data/sound/balloon_pop1.wav
SOUND|${PREFIX}/data/sound/balloon_pop2.wav SOUND|${PREFIX}/data/sound/balloon_pop2.wav
SOUND|${PREFIX}/data/sound/balloon_pop3.wav SOUND|${PREFIX}/data/sound/balloon_pop3.wav
SOUND|${PREFIX}/data/sound/bullet1p.wav SOUND|${PREFIX}/data/sound/bullet.wav
SOUND|${PREFIX}/data/sound/bullet2p.wav
SOUND|${PREFIX}/data/sound/clock.wav SOUND|${PREFIX}/data/sound/clock.wav
SOUND|${PREFIX}/data/sound/coffee_out.wav SOUND|${PREFIX}/data/sound/coffee_out.wav
SOUND|${PREFIX}/data/sound/continue_clock.wav SOUND|${PREFIX}/data/sound/continue_clock.wav
@@ -51,7 +48,6 @@ SOUND|${PREFIX}/data/sound/item_drop.wav
SOUND|${PREFIX}/data/sound/item_pickup.wav SOUND|${PREFIX}/data/sound/item_pickup.wav
SOUND|${PREFIX}/data/sound/jump.wav SOUND|${PREFIX}/data/sound/jump.wav
SOUND|${PREFIX}/data/sound/logo.wav SOUND|${PREFIX}/data/sound/logo.wav
SOUND|${PREFIX}/data/sound/name_input_accept.wav
SOUND|${PREFIX}/data/sound/notify.wav SOUND|${PREFIX}/data/sound/notify.wav
SOUND|${PREFIX}/data/sound/player_collision.wav SOUND|${PREFIX}/data/sound/player_collision.wav
SOUND|${PREFIX}/data/sound/power_ball_explosion.wav SOUND|${PREFIX}/data/sound/power_ball_explosion.wav
@@ -66,7 +62,6 @@ SOUND|${PREFIX}/data/sound/title.wav
SOUND|${PREFIX}/data/sound/voice_aw_aw_aw.wav SOUND|${PREFIX}/data/sound/voice_aw_aw_aw.wav
SOUND|${PREFIX}/data/sound/voice_coffee.wav SOUND|${PREFIX}/data/sound/voice_coffee.wav
SOUND|${PREFIX}/data/sound/voice_credit_thankyou.wav SOUND|${PREFIX}/data/sound/voice_credit_thankyou.wav
SOUND|${PREFIX}/data/sound/voice_game_over.wav
SOUND|${PREFIX}/data/sound/voice_get_ready.wav SOUND|${PREFIX}/data/sound/voice_get_ready.wav
SOUND|${PREFIX}/data/sound/voice_no.wav SOUND|${PREFIX}/data/sound/voice_no.wav
SOUND|${PREFIX}/data/sound/voice_power_up.wav SOUND|${PREFIX}/data/sound/voice_power_up.wav
@@ -74,13 +69,9 @@ SOUND|${PREFIX}/data/sound/voice_recover.wav
SOUND|${PREFIX}/data/sound/voice_thankyou.wav SOUND|${PREFIX}/data/sound/voice_thankyou.wav
SOUND|${PREFIX}/data/sound/walk.wav SOUND|${PREFIX}/data/sound/walk.wav
# Shaders OpenGL (Windows/Linux) # Shaders
DATA|${PREFIX}/data/shaders/crtpi_vertex.glsl DATA|${PREFIX}/data/shaders/crtpi_240.glsl
DATA|${PREFIX}/data/shaders/crtpi_fragment.glsl DATA|${PREFIX}/data/shaders/crtpi_256.glsl
# Shaders Metal (macOS) - opcionales
DATA|${PREFIX}/data/shaders/crtpi_vertex.metal|optional
DATA|${PREFIX}/data/shaders/crtpi_fragment.metal|optional
# Texturas - Balloons # Texturas - Balloons
ANIMATION|${PREFIX}/data/gfx/balloon/balloon0.ani ANIMATION|${PREFIX}/data/gfx/balloon/balloon0.ani

View File

@@ -8,270 +8,270 @@
formation: 0 formation: 0
# Dos enemigos BALLOON3 uno a cada extremo # Dos enemigos BALLOON3 uno a cada extremo
X3_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, EXTRALARGE, 0.0000 X3_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, EXTRALARGE, 0
X3_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, EXTRALARGE, 0.0000 X3_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, EXTRALARGE, 0
formation: 1 formation: 1
# Dos enemigos BALLOON3 uno a cada cuarto. Ambos van hacia el centro # Dos enemigos BALLOON3 uno a cada cuarto. Ambos van hacia el centro
X3_25, 0, DEFAULT_POS_Y, RIGHT, BALLOON, EXTRALARGE, 0.0000 X3_25, 0, DEFAULT_POS_Y, RIGHT, BALLOON, EXTRALARGE, 0
X3_75, 0, DEFAULT_POS_Y, LEFT, BALLOON, EXTRALARGE, 0.0000 X3_75, 0, DEFAULT_POS_Y, LEFT, BALLOON, EXTRALARGE, 0
formation: 2 formation: 2
# Cuatro enemigos BALLOON1 uno detrás del otro. A la izquierda y hacia el centro # Cuatro enemigos BALLOON1 uno detrás del otro. A la izquierda y hacia el centro
X1_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 0.5000 X1_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 30
X1_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 0.3333 X1_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 20
X1_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 0.1667 X1_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 10
X1_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 0.0000 X1_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 0
formation: 3 formation: 3
# Cuatro enemigos BALLOON1 uno detrás del otro. A la derecha y hacia el centro # Cuatro enemigos BALLOON1 uno detrás del otro. A la derecha y hacia el centro
X1_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 0.5000 X1_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 30
X1_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 0.3333 X1_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 20
X1_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 0.1667 X1_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 10
X1_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 0.0000 X1_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 0
formation: 4 formation: 4
# Tres enemigos BALLOON2. 0, 25, 50. Hacia la derecha # Tres enemigos BALLOON2. 0, 25, 50. Hacia la derecha
X2_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.3333 X2_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 20
X2_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.1667 X2_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 10
X2_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.0000 X2_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0
formation: 5 formation: 5
# Tres enemigos BALLOON2. 50, 75, 100. Hacia la izquierda # Tres enemigos BALLOON2. 50, 75, 100. Hacia la izquierda
X2_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.3333 X2_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 20
X2_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.1667 X2_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 10
X2_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.0000 X2_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0
formation: 6 formation: 6
# Tres enemigos BALLOON2. 0, 0, 0. Hacia la derecha # Tres enemigos BALLOON2. 0, 0, 0. Hacia la derecha
X2_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.3333 X2_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 20
X2_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.1667 X2_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 10
X2_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.0000 X2_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0
formation: 7 formation: 7
# Tres enemigos BALLOON2. 100, 100, 100. Hacia la izquierda # Tres enemigos BALLOON2. 100, 100, 100. Hacia la izquierda
X2_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.3333 X2_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 20
X2_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.1667 X2_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 10
X2_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.0000 X2_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0
formation: 8 formation: 8
# Seis enemigos BALLOON0. 0, 0, 0, 0, 0, 0. Hacia la derecha # Seis enemigos BALLOON0. 0, 0, 0, 0, 0, 0. Hacia la derecha
X0_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.8333 X0_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 50
X0_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.6667 X0_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 40
X0_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.5000 X0_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 30
X0_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.3333 X0_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 20
X0_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.1667 X0_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 10
X0_0, 5, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.0000 X0_0, 5, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0
formation: 9 formation: 9
# Seis enemigos BALLOON0. 100, 100, 100, 100, 100, 100. Hacia la izquierda # Seis enemigos BALLOON0. 100, 100, 100, 100, 100, 100. Hacia la izquierda
X0_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.8333 X0_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 50
X0_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.6667 X0_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 40
X0_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.5000 X0_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 30
X0_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.3333 X0_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 20
X0_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.1667 X0_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 10
X0_100, -5, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.0000 X0_100, -5, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0
formation: 10 formation: 10
# Tres enemigos BALLOON3 seguidos desde la izquierda. Hacia la derecha # Tres enemigos BALLOON3 seguidos desde la izquierda. Hacia la derecha
X3_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, EXTRALARGE, 0.5000 X3_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, EXTRALARGE, 30
X3_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, EXTRALARGE, 0.2500 X3_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, EXTRALARGE, 15
X3_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, EXTRALARGE, 0.0000 X3_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, EXTRALARGE, 0
formation: 11 formation: 11
# Tres enemigos BALLOON3 seguidos desde la derecha. Hacia la izquierda # Tres enemigos BALLOON3 seguidos desde la derecha. Hacia la izquierda
X3_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, EXTRALARGE, 0.5000 X3_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, EXTRALARGE, 30
X3_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, EXTRALARGE, 0.2500 X3_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, EXTRALARGE, 15
X3_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, EXTRALARGE, 0.0000 X3_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, EXTRALARGE, 0
formation: 12 formation: 12
# Seis enemigos BALLOON1 uno detrás del otro. A la izquierda y hacia el centro # Seis enemigos BALLOON1 uno detrás del otro. A la izquierda y hacia el centro
X1_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 0.8333 X1_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 50
X1_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 0.6667 X1_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 40
X1_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 0.5000 X1_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 30
X1_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 0.3333 X1_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 20
X1_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 0.1667 X1_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 10
X1_0, 5, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 0.0000 X1_0, 5, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 0
formation: 13 formation: 13
# Seis enemigos BALLOON1 uno detrás del otro. A la derecha y hacia el centro # Seis enemigos BALLOON1 uno detrás del otro. A la derecha y hacia el centro
X1_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 0.8333 X1_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 50
X1_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 0.6667 X1_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 40
X1_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 0.5000 X1_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 30
X1_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 0.3333 X1_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 20
X1_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 0.1667 X1_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 10
X1_100, -5, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 0.0000 X1_100, -5, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 0
formation: 14 formation: 14
# Cinco enemigos BALLOON2. Hacia la derecha. Separados # Cinco enemigos BALLOON2. Hacia la derecha. Separados
X2_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.6667 X2_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 40
X2_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.5000 X2_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 30
X2_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.3333 X2_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 20
X2_0, 6, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.1667 X2_0, 6, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 10
X2_0, 8, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.0000 X2_0, 8, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0
formation: 15 formation: 15
# Cinco enemigos BALLOON2. Hacia la izquierda. Separados # Cinco enemigos BALLOON2. Hacia la izquierda. Separados
X2_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.6667 X2_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 40
X2_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.5000 X2_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 30
X2_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.3333 X2_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 20
X2_100, -6, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.1667 X2_100, -6, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 10
X2_100, -8, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.0000 X2_100, -8, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0
formation: 16 formation: 16
# Cinco enemigos BALLOON2. Hacia la derecha. Juntos # Cinco enemigos BALLOON2. Hacia la derecha. Juntos
X2_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.6667 X2_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 40
X2_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.5000 X2_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 30
X2_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.3333 X2_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 20
X2_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.1667 X2_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 10
X2_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.0000 X2_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0
formation: 17 formation: 17
# Cinco enemigos BALLOON2. Hacia la izquierda. Juntos # Cinco enemigos BALLOON2. Hacia la izquierda. Juntos
X2_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.6667 X2_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 40
X2_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.5000 X2_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 30
X2_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.3333 X2_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 20
X2_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.1667 X2_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 10
X2_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.0000 X2_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0
formation: 18 formation: 18
# Doce enemigos BALLOON0. Hacia la derecha. Juntos # Doce enemigos BALLOON0. Hacia la derecha. Juntos
X0_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 1.8333 X0_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 110
X0_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 1.6667 X0_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 100
X0_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 1.5000 X0_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 90
X0_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 1.3333 X0_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 80
X0_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 1.1667 X0_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 70
X0_0, 5, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 1.0000 X0_0, 5, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 60
X0_0, 6, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.8333 X0_0, 6, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 50
X0_0, 7, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.6667 X0_0, 7, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 40
X0_0, 8, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.5000 X0_0, 8, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 30
X0_0, 9, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.3333 X0_0, 9, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 20
X0_0, 10, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.1667 X0_0, 10, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 10
X0_0, 11, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.0000 X0_0, 11, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0
formation: 19 formation: 19
# Doce enemigos BALLOON0. Hacia la izquierda. Juntos # Doce enemigos BALLOON0. Hacia la izquierda. Juntos
X0_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 1.8333 X0_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 110
X0_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 1.6667 X0_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 100
X0_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 1.5000 X0_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 90
X0_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 1.3333 X0_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 80
X0_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 1.1667 X0_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 70
X0_100, -5, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 1.0000 X0_100, -5, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 60
X0_100, -6, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.8333 X0_100, -6, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 50
X0_100, -7, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.6667 X0_100, -7, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 40
X0_100, -8, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.5000 X0_100, -8, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 30
X0_100, -9, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.3333 X0_100, -9, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 20
X0_100, -10, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.1667 X0_100, -10, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 10
X0_100, -11, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.0000 X0_100, -11, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0
formation: 20 formation: 20
# Cuatro enemigos BALLOON3 seguidos desde la izquierda/derecha. Simétricos # Cuatro enemigos BALLOON3 seguidos desde la izquierda/derecha. Simétricos
X3_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, EXTRALARGE, 0.0000 X3_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, EXTRALARGE, 0
X3_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, EXTRALARGE, 0.0000 X3_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, EXTRALARGE, 0
X3_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, EXTRALARGE, 0.0000 X3_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, EXTRALARGE, 0
X3_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, EXTRALARGE, 0.0000 X3_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, EXTRALARGE, 0
formation: 21 formation: 21
# Diez enemigos BALLOON1 uno detrás del otro. Izquierda/derecha. Simétricos # Diez enemigos BALLOON1 uno detrás del otro. Izquierda/derecha. Simétricos
X1_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 0.2000 X1_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 12
X1_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 0.1500 X1_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 9
X1_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 0.1000 X1_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 6
X1_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 0.0500 X1_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 3
X1_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 0.0000 X1_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 0
X1_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 0.2000 X1_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 12
X1_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 0.1500 X1_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 9
X1_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 0.1000 X1_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 6
X1_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 0.0500 X1_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 3
X1_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 0.0000 X1_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 0
formation: 22 formation: 22
# Diez enemigos BALLOON2. Hacia la derecha/izquierda. Separados. Simétricos # Diez enemigos BALLOON2. Hacia la derecha/izquierda. Separados. Simétricos
X2_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.6667 X2_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 40
X2_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.5000 X2_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 30
X2_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.3333 X2_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 20
X2_0, 6, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.1667 X2_0, 6, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 10
X2_0, 8, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.0000 X2_0, 8, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0
X2_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.6667 X2_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 40
X2_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.5000 X2_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 30
X2_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.3333 X2_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 20
X2_100, -6, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.1667 X2_100, -6, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 10
X2_100, -8, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.0000 X2_100, -8, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0
formation: 23 formation: 23
# Diez enemigos BALLOON2. Hacia la derecha. Juntos. Simétricos # Diez enemigos BALLOON2. Hacia la derecha. Juntos. Simétricos
X2_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.6667 X2_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 40
X2_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.5000 X2_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 30
X2_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.3333 X2_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 20
X2_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.1667 X2_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 10
X2_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0.0000 X2_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0
X2_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.6667 X2_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 40
X2_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.5000 X2_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 30
X2_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.3333 X2_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 20
X2_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.1667 X2_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 10
X2_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0.0000 X2_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0
formation: 24 formation: 24
# Treinta enemigos BALLOON0. Del centro hacia los extremos. Juntos. Simétricos # Treinta enemigos BALLOON0. Del centro hacia los extremos. Juntos. Simétricos
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.0000 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.0833 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 5
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.1667 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 10
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.2500 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 15
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.3333 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 20
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.4167 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 25
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.5000 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 30
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.5833 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 35
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.6667 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 40
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.7500 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 45
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.8333 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 50
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.9167 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 55
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 1.0000 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 60
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 1.0833 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 65
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 1.1667 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 70
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.0000 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.0833 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 5
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.1667 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 10
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.2500 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 15
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.3333 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 20
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.4167 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 25
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.5000 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 30
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.5833 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 35
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.6667 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 40
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.7500 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 45
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.8333 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 50
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.9167 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 55
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 1.0000 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 60
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 1.0833 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 65
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 1.1667 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 70
formation: 25 formation: 25
# Treinta enemigos BALLOON0. Del centro hacia adentro. Juntos. Simétricos # Treinta enemigos BALLOON0. Del centro hacia adentro. Juntos. Simétricos
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 1.1667 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 70
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 1.0833 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 65
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 1.0000 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 60
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.9167 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 55
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.8333 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 50
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.7500 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 45
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.6667 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 40
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.5833 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 35
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.5000 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 30
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.4167 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 25
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.3333 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 20
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.2500 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 15
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.1667 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 10
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.0833 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 5
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0.0000 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 1.1667 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 70
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 1.0833 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 65
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 1.0000 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 60
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.9167 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 55
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.8333 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 50
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.7500 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 45
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.6667 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 40
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.5833 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 35
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.5000 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 30
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.4167 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 25
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.3333 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 20
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.2500 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 15
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.1667 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 10
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.0833 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 5
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0.0000 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0

View File

@@ -2,6 +2,7 @@
# Formato: PARAMETRO VALOR # Formato: PARAMETRO VALOR
# --- GAME --- # --- GAME ---
game.item_size 20 # Tamaño de los items del juego (en píxeles)
game.item_text_outline_color E0E0E0F0 # Color del outline del texto de los items (RGBA hex) game.item_text_outline_color E0E0E0F0 # Color del outline del texto de los items (RGBA hex)
game.width 320 # Ancho de la resolución nativa del juego (en píxeles) game.width 320 # Ancho de la resolución nativa del juego (en píxeles)
game.height 240 # Alto de la resolución nativa del juego (en píxeles) game.height 240 # Alto de la resolución nativa del juego (en píxeles)
@@ -39,7 +40,7 @@ scoreboard.skip_countdown_value 8 # Valor para saltar la cuenta atrás (
# --- TITLE --- # --- TITLE ---
title.press_start_position 180 # Posición Y del texto "Press Start" title.press_start_position 180 # Posición Y del texto "Press Start"
title.title_duration 14 # Duración de la pantalla de título (segundos) title.title_duration 800 # Duración de la pantalla de título (frames)
title.arcade_edition_position 123 # Posición Y del subtítulo "Arcade Edition" title.arcade_edition_position 123 # Posición Y del subtítulo "Arcade Edition"
title.title_c_c_position 80 # Posición Y del título principal title.title_c_c_position 80 # Posición Y del título principal
title.bg_color 41526F # Color de fondo en la sección titulo title.bg_color 41526F # Color de fondo en la sección titulo
@@ -47,15 +48,15 @@ title.bg_color 41526F # Color de fondo en la sección titulo
# --- BACKGROUND --- # --- BACKGROUND ---
background.attenuate_color FFFFFF00 # Color de atenuación del fondo (RGBA hexadecimal) background.attenuate_color FFFFFF00 # Color de atenuación del fondo (RGBA hexadecimal)
# --- BALLOONS --- (deltaTime en segundos: vel en pixels/s, grav en pixels/s²) # --- BALLOONS ---
balloon.settings[0].vel 165.0f # Velocidad inicial del globo 1 (pixels/s) balloon.settings[0].vel 2.75f # Velocidad inicial del globo 1
balloon.settings[0].grav 320.0f # Gravedad aplicada al globo 1 (pixels/s²) balloon.settings[0].grav 0.09f # Gravedad aplicada al globo 1
balloon.settings[1].vel 222.0f # Velocidad inicial del globo 2 (pixels/s) balloon.settings[1].vel 3.70f # Velocidad inicial del globo 2
balloon.settings[1].grav 360.0f # Gravedad aplicada al globo 2 (pixels/s²) balloon.settings[1].grav 0.10f # Gravedad aplicada al globo 2
balloon.settings[2].vel 282.0f # Velocidad inicial del globo 3 (pixels/s) balloon.settings[2].vel 4.70f # Velocidad inicial del globo 3
balloon.settings[2].grav 360.0f # Gravedad aplicada al globo 3 (pixels/s²) balloon.settings[2].grav 0.10f # Gravedad aplicada al globo 3
balloon.settings[3].vel 327.0f # Velocidad inicial del globo 4 (pixels/s) balloon.settings[3].vel 5.45f # Velocidad inicial del globo 4
balloon.settings[3].grav 360.0f # Gravedad aplicada al globo 4 (pixels/s²) balloon.settings[3].grav 0.10f # Gravedad aplicada al globo 4
balloon.color[0] blue # Color de creación del globo normal balloon.color[0] blue # Color de creación del globo normal
balloon.color[1] orange # Color del globo normal balloon.color[1] orange # Color del globo normal

View File

@@ -40,7 +40,7 @@ scoreboard.skip_countdown_value 8 # Valor para saltar la cuenta atrás (
# --- TITLE --- # --- TITLE ---
title.press_start_position 180 # Posición Y del texto "Press Start" title.press_start_position 180 # Posición Y del texto "Press Start"
title.title_duration 14 # Duración de la pantalla de título (segundos) title.title_duration 800 # Duración de la pantalla de título (frames)
title.arcade_edition_position 123 # Posición Y del subtítulo "Arcade Edition" title.arcade_edition_position 123 # Posición Y del subtítulo "Arcade Edition"
title.title_c_c_position 80 # Posición Y del título principal title.title_c_c_position 80 # Posición Y del título principal
title.bg_color 41526F # Color de fondo en la sección titulo title.bg_color 41526F # Color de fondo en la sección titulo
@@ -48,15 +48,15 @@ title.bg_color 41526F # Color de fondo en la sección titulo
# --- BACKGROUND --- # --- BACKGROUND ---
background.attenuate_color FFFFFF00 # Color de atenuación del fondo (RGBA hexadecimal) background.attenuate_color FFFFFF00 # Color de atenuación del fondo (RGBA hexadecimal)
# --- BALLOONS --- (deltaTime en segundos: vel en pixels/s, grav en pixels/s²) # --- BALLOONS ---
balloon.settings[0].vel 165.0f # Velocidad inicial del globo 1 (pixels/s) balloon.settings[0].vel 2.75f # Velocidad inicial del globo 1
balloon.settings[0].grav 320.0f # Gravedad aplicada al globo 1 (pixels/s²) balloon.settings[0].grav 0.09f # Gravedad aplicada al globo 1
balloon.settings[1].vel 222.0f # Velocidad inicial del globo 2 (pixels/s) balloon.settings[1].vel 3.70f # Velocidad inicial del globo 2
balloon.settings[1].grav 360.0f # Gravedad aplicada al globo 2 (pixels/s²) balloon.settings[1].grav 0.10f # Gravedad aplicada al globo 2
balloon.settings[2].vel 282.0f # Velocidad inicial del globo 3 (pixels/s) balloon.settings[2].vel 4.70f # Velocidad inicial del globo 3
balloon.settings[2].grav 360.0f # Gravedad aplicada al globo 3 (pixels/s²) balloon.settings[2].grav 0.10f # Gravedad aplicada al globo 3
balloon.settings[3].vel 327.0f # Velocidad inicial del globo 4 (pixels/s) balloon.settings[3].vel 5.45f # Velocidad inicial del globo 4
balloon.settings[3].grav 360.0f # Gravedad aplicada al globo 4 (pixels/s²) balloon.settings[3].grav 0.10f # Gravedad aplicada al globo 4
balloon.color[0] blue # Color de creación del globo normal balloon.color[0] blue # Color de creación del globo normal
balloon.color[1] orange # Color del globo normal balloon.color[1] orange # Color del globo normal

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -3,28 +3,28 @@ frame_height=10
[animation] [animation]
name=orange name=orange
speed=0.1667 speed=10
loop=0 loop=0
frames=0,1,2,3,4,5,6,7,8,9 frames=0,1,2,3,4,5,6,7,8,9
[/animation] [/animation]
[animation] [animation]
name=blue name=blue
speed=0.3333 speed=20
loop=0 loop=0
frames=10,11,12,13,14,15,16,17,18,19 frames=10,11,12,13,14,15,16,17,18,19
[/animation] [/animation]
[animation] [animation]
name=green name=green
speed=0.1667 speed=10
loop=0 loop=0
frames=20,21,22,23,24,25,26,27,28,29 frames=20,21,22,23,24,25,26,27,28,29
[/animation] [/animation]
[animation] [animation]
name=red name=red
speed=0.3333 speed=20
loop=0 loop=0
frames=30,31,32,33,34,35,36,37,38,39 frames=30,31,32,33,34,35,36,37,38,39
[/animation] [/animation]

View File

@@ -3,28 +3,28 @@ frame_height=16
[animation] [animation]
name=orange name=orange
speed=0.1667 speed=10
loop=0 loop=0
frames=0,1,2,3,4,5,6,7,8,9 frames=0,1,2,3,4,5,6,7,8,9
[/animation] [/animation]
[animation] [animation]
name=blue name=blue
speed=0.3333 speed=20
loop=0 loop=0
frames=10,11,12,13,14,15,16,17,18,19 frames=10,11,12,13,14,15,16,17,18,19
[/animation] [/animation]
[animation] [animation]
name=green name=green
speed=0.1667 speed=10
loop=0 loop=0
frames=20,21,22,23,24,25,26,27,28,29 frames=20,21,22,23,24,25,26,27,28,29
[/animation] [/animation]
[animation] [animation]
name=red name=red
speed=0.3333 speed=20
loop=0 loop=0
frames=30,31,32,33,34,35,36,37,38,39 frames=30,31,32,33,34,35,36,37,38,39
[/animation] [/animation]

View File

@@ -3,28 +3,28 @@ frame_height=26
[animation] [animation]
name=orange name=orange
speed=0.1667 speed=10
loop=0 loop=0
frames=0,1,2,3,4,5,6,7,8,9 frames=0,1,2,3,4,5,6,7,8,9
[/animation] [/animation]
[animation] [animation]
name=blue name=blue
speed=0.3333 speed=20
loop=0 loop=0
frames=10,11,12,13,14,15,16,17,18,19 frames=10,11,12,13,14,15,16,17,18,19
[/animation] [/animation]
[animation] [animation]
name=green name=green
speed=0.1667 speed=10
loop=0 loop=0
frames=20,21,22,23,24,25,26,27,28,29 frames=20,21,22,23,24,25,26,27,28,29
[/animation] [/animation]
[animation] [animation]
name=red name=red
speed=0.3333 speed=20
loop=0 loop=0
frames=30,31,32,33,34,35,36,37,38,39 frames=30,31,32,33,34,35,36,37,38,39
[/animation] [/animation]

View File

@@ -3,28 +3,28 @@ frame_height=48
[animation] [animation]
name=orange name=orange
speed=0.1667 speed=10
loop=0 loop=0
frames=0,1,2,3,4,5,6,7,8,9 frames=0,1,2,3,4,5,6,7,8,9
[/animation] [/animation]
[animation] [animation]
name=blue name=blue
speed=0.3333 speed=20
loop=0 loop=0
frames=10,11,12,13,14,15,16,17,18,19 frames=10,11,12,13,14,15,16,17,18,19
[/animation] [/animation]
[animation] [animation]
name=green name=green
speed=0.1667 speed=10
loop=0 loop=0
frames=20,21,22,23,24,25,26,27,28,29 frames=20,21,22,23,24,25,26,27,28,29
[/animation] [/animation]
[animation] [animation]
name=red name=red
speed=0.3333 speed=20
loop=0 loop=0
frames=30,31,32,33,34,35,36,37,38,39 frames=30,31,32,33,34,35,36,37,38,39
[/animation] [/animation]

View File

@@ -3,7 +3,7 @@ frame_height=10
[animation] [animation]
name=default name=default
speed=0.0833 speed=5
loop=-1 loop=-1
frames=0,1,2,3,4,5,6,7,8,9 frames=0,1,2,3,4,5,6,7,8,9
[/animation] [/animation]

View File

@@ -3,7 +3,7 @@ frame_height=16
[animation] [animation]
name=default name=default
speed=0.0833 speed=5
loop=-1 loop=-1
frames=0,1,2,3,4,5,6,7,8,9 frames=0,1,2,3,4,5,6,7,8,9
[/animation] [/animation]

View File

@@ -3,7 +3,7 @@ frame_height=26
[animation] [animation]
name=default name=default
speed=0.0833 speed=5
loop=-1 loop=-1
frames=0,1,2,3,4,5,6,7,8,9 frames=0,1,2,3,4,5,6,7,8,9
[/animation] [/animation]

View File

@@ -3,7 +3,7 @@ frame_height=48
[animation] [animation]
name=default name=default
speed=0.0833 speed=5
loop=-1 loop=-1
frames=0,1,2,3,4,5,6,7,8,9 frames=0,1,2,3,4,5,6,7,8,9
[/animation] [/animation]

View File

@@ -3,7 +3,7 @@ frame_height=49
[animation] [animation]
name=powerball name=powerball
speed=0.0167 speed=10
loop=-1 loop=-1
frames=1 frames=1
[/animation] [/animation]

View File

@@ -2,85 +2,43 @@ frame_width=12
frame_height=12 frame_height=12
[animation] [animation]
name=yellow_up name=normal_up
speed=20 speed=5
loop=-1 loop=0
frames=0 frames=0,1,2
[/animation] [/animation]
[animation] [animation]
name=yellow_left name=normal_left
speed=20 speed=5
loop=-1 loop=0
frames=1 frames=3,4,5,5,4,3
[/animation] [/animation]
[animation] [animation]
name=yellow_right name=normal_right
speed=20 speed=5
loop=-1 loop=0
frames=2 frames=6,7,8,8,7,6
[/animation] [/animation]
[animation] [animation]
name=green_up name=powered_up
speed=20 speed=5
loop=-1 loop=0
frames=3 frames=9,10,11,11,10,9
[/animation] [/animation]
[animation] [animation]
name=green_left name=powered_left
speed=20 speed=5
loop=-1 loop=0
frames=4 frames=12,13,14,14,13,12
[/animation] [/animation]
[animation] [animation]
name=green_right name=powered_right
speed=20 speed=5
loop=-1 loop=0
frames=5 frames=15,16,17,17,26,15
[/animation]
[animation]
name=red_up
speed=20
loop=-1
frames=6
[/animation]
[animation]
name=red_left
speed=20
loop=-1
frames=7
[/animation]
[animation]
name=red_right
speed=20
loop=-1
frames=8
[/animation]
[animation]
name=purple_up
speed=20
loop=-1
frames=9
[/animation]
[animation]
name=purple_left
speed=20
loop=-1
frames=10
[/animation]
[animation]
name=purple_right
speed=20
loop=-1
frames=11
[/animation] [/animation]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -3,7 +3,7 @@ frame_height=20
[animation] [animation]
name=default name=default
speed=0.1333 speed=8
loop=0 loop=0
frames=0,0,1 frames=0,0,1
[/animation] [/animation]

View File

@@ -3,7 +3,7 @@ frame_height=20
[animation] [animation]
name=default name=default
speed=0.1333 speed=8
loop=0 loop=0
frames=0,0,1 frames=0,0,1
[/animation] [/animation]

View File

@@ -3,7 +3,7 @@ frame_height=39
[animation] [animation]
name=default name=default
speed=0.1 speed=6
loop=0 loop=0
frames=0,1,2,3,4,5,6,7,8 frames=0,1,2,3,4,5,6,7,8
[/animation] [/animation]

View File

@@ -3,7 +3,7 @@ frame_height=20
[animation] [animation]
name=default name=default
speed=0.1333 speed=8
loop=0 loop=0
frames=0,0,1 frames=0,0,1
[/animation] [/animation]

View File

@@ -3,7 +3,7 @@ frame_height=20
[animation] [animation]
name=default name=default
speed=0.1333 speed=8
loop=0 loop=0
frames=0,0,1 frames=0,0,1
[/animation] [/animation]

View File

@@ -3,7 +3,7 @@ frame_height=20
[animation] [animation]
name=default name=default
speed=0.1333 speed=8
loop=0 loop=0
frames=0,0,1 frames=0,0,1
[/animation] [/animation]

View File

@@ -3,7 +3,7 @@ frame_height=20
[animation] [animation]
name=default name=default
speed=0.1333 speed=8
loop=0 loop=0
frames=0,0,1 frames=0,0,1
[/animation] [/animation]

View File

@@ -3,133 +3,133 @@ frame_height=32
[animation] [animation]
name=walk name=walk
speed=0.0833 speed=5
loop=0 loop=0
frames=0,1,2,3 frames=0,1,2,3
[/animation] [/animation]
[animation] [animation]
name=stand name=stand
speed=0.167 speed=10
loop=0 loop=0
frames=4,5,6,7 frames=4,5,6,7
[/animation] [/animation]
[animation] [animation]
name=walk-fire-side name=walk-fire-side
speed=0.0833 speed=5
loop=0 loop=0
frames=8,9,10,11 frames=8,9,10,11
[/animation] [/animation]
[animation] [animation]
name=walk-recoil-side name=walk-recoil-side
speed=0.0833 speed=5
loop=0 loop=0
frames=12,13,14,15 frames=12,13,14,15
[/animation] [/animation]
[animation] [animation]
name=walk-cool-side name=walk-cool-side
speed=0.0833 speed=5
loop=0 loop=0
frames=16,17,18,19 frames=16,17,18,19
[/animation] [/animation]
[animation] [animation]
name=stand-fire-side name=stand-fire-side
speed=0.0833 speed=5
loop=0 loop=0
frames=20 frames=20
[/animation] [/animation]
[animation] [animation]
name=stand-recoil-side name=stand-recoil-side
speed=0.0833 speed=5
loop=0 loop=0
frames=21 frames=21
[/animation] [/animation]
[animation] [animation]
name=stand-cool-side name=stand-cool-side
speed=0.0833 speed=5
loop=0 loop=0
frames=22 frames=22
[/animation] [/animation]
[animation] [animation]
name=walk-fire-center name=walk-fire-center
speed=0.0833 speed=5
loop=0 loop=0
frames=23,24,25,26 frames=23,24,25,26
[/animation] [/animation]
[animation] [animation]
name=walk-recoil-center name=walk-recoil-center
speed=0.0833 speed=5
loop=0 loop=0
frames=27,28,29,30 frames=27,28,29,30
[/animation] [/animation]
[animation] [animation]
name=walk-cool-center name=walk-cool-center
speed=0.0833 speed=5
loop=0 loop=0
frames=31,32,33,34 frames=31,32,33,34
[/animation] [/animation]
[animation] [animation]
name=stand-fire-center name=stand-fire-center
speed=0.0833 speed=5
loop=0 loop=0
frames=35 frames=35
[/animation] [/animation]
[animation] [animation]
name=stand-recoil-center name=stand-recoil-center
speed=0.0833 speed=5
loop=0 loop=0
frames=36 frames=36
[/animation] [/animation]
[animation] [animation]
name=stand-cool-center name=stand-cool-center
speed=0.0833 speed=5
loop=0 loop=0
frames=37 frames=37
[/animation] [/animation]
[animation] [animation]
name=rolling name=rolling
speed=0.167 speed=10
loop=0 loop=0
frames=38,39,40,41 frames=38,39,40,41
[/animation] [/animation]
[animation] [animation]
name=celebration name=celebration
speed=0.167 speed=10
loop=0 loop=-1
frames=42,42,42,42,42,42,43,44,45,46,46,46,46,46,46,45,45,45,46,46,46,45,45,45,44,43,42,42,42 frames=42,42,42,42,42,42,43,44,45,46,46,46,46,46,46,45,45,45,46,46,46,45,45,45,44,43,42,42,42
[/animation] [/animation]
[animation] [animation]
name=dizzy name=dizzy
speed=0.0833 speed=5
loop=0 loop=0
frames=47,48,49,50,51,52,53 frames=47,48,49,50,51,52,53
[/animation] [/animation]
[animation] [animation]
name=recover name=recover
speed=0.05 speed=3
loop=-1 loop=-1
frames=54,54,54,54,55,56,57,58,58,58,59,60,61,58,59,60,61,58,59,60,61,62,62,62,62 frames=54,54,54,54,55,56,57,58,58,58,59,60,61,58,59,60,61,58,59,60,61,62,62,62,62
[/animation] [/animation]
[animation] [animation]
name=hello name=hello
speed=0.05 speed=3
loop=-1 loop=-1
frames=63,64,65,66,67,68,69,70,71,72,73,73,73,73,73,73,73,73,73,73,73,73,73,74,75,76,77,78,79,80,81,82,82,81,80,79,79,80,81,82,82,81,80,79,79,80,81,82,82,81,80,79,79,80,81,82,82,81,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63 frames=63,64,65,66,67,68,69,70,71,72,73,73,73,73,73,73,73,73,73,73,73,73,73,74,75,76,77,78,79,80,81,82,82,81,80,79,79,80,81,82,82,81,80,79,79,80,81,82,82,81,80,79,79,80,81,82,82,81,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63
[/animation] [/animation]

View File

@@ -3,7 +3,7 @@ frame_height=44
[animation] [animation]
name=default name=default
speed=0.0833 speed=5
loop=0 loop=0
frames=0,1,2,3 frames=0,1,2,3
[/animation] [/animation]

View File

@@ -3,14 +3,14 @@ frame_height=32
[animation] [animation]
name=fly name=fly
speed=0.0333 speed=2
loop=0 loop=0
frames=0,1 frames=0,1
[/animation] [/animation]
[animation] [animation]
name=hit name=hit
speed=0.0333 speed=2
loop=0 loop=0
frames=2,3 frames=2,3
[/animation] [/animation]

View File

@@ -3,7 +3,7 @@ frame_height=16
[animation] [animation]
name=default name=default
speed=0.1333 speed=8
loop=-1 loop=-1
frames=0,1,2,3,4,5,6 frames=0,1,2,3,4,5,6
[/animation] [/animation]

View File

@@ -27,7 +27,6 @@
"[GAME_TEXT] 7": "Endavant!", "[GAME_TEXT] 7": "Endavant!",
"[GAME_TEXT] 8": "1.000.000 de punts!", "[GAME_TEXT] 8": "1.000.000 de punts!",
"[GAME_TEXT] THANK_YOU": "Gracies!", "[GAME_TEXT] THANK_YOU": "Gracies!",
"[GAME_TEXT] NEW_RECORD": "Nou record!",
"[HIGHSCORE_TABLE] CAPTION": "Millors puntuacions", "[HIGHSCORE_TABLE] CAPTION": "Millors puntuacions",

View File

@@ -26,7 +26,6 @@
"[GAME_TEXT] 7": "Get Ready!", "[GAME_TEXT] 7": "Get Ready!",
"[GAME_TEXT] 8": "1,000,000 points!", "[GAME_TEXT] 8": "1,000,000 points!",
"[GAME_TEXT] THANK_YOU": "Thank you!", "[GAME_TEXT] THANK_YOU": "Thank you!",
"[GAME_TEXT] NEW_RECORD": "New record!",
"[HIGHSCORE_TABLE] CAPTION": "Best scores", "[HIGHSCORE_TABLE] CAPTION": "Best scores",

View File

@@ -26,7 +26,6 @@
"[GAME_TEXT] 7": "Adelante!", "[GAME_TEXT] 7": "Adelante!",
"[GAME_TEXT] 8": "1.000.000 de puntos!", "[GAME_TEXT] 8": "1.000.000 de puntos!",
"[GAME_TEXT] THANK_YOU": "Gracias!", "[GAME_TEXT] THANK_YOU": "Gracias!",
"[GAME_TEXT] NEW_RECORD": "Nuevo record!",
"[HIGHSCORE_TABLE] CAPTION": "Mejores puntuaciones", "[HIGHSCORE_TABLE] CAPTION": "Mejores puntuaciones",

Binary file not shown.

234
data/shaders/crtpi_240.glsl Normal file
View File

@@ -0,0 +1,234 @@
/*
crt-pi - A Raspberry Pi friendly CRT shader.
Copyright (C) 2015-2016 davej
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your option)
any later version.
Notes:
This shader is designed to work well on Raspberry Pi GPUs (i.e. 1080P @ 60Hz on a game with a 4:3 aspect ratio). It pushes the Pi's GPU hard and enabling some features will slow it down so that it is no longer able to match 1080P @ 60Hz. You will need to overclock your Pi to the fastest setting in raspi-config to get the best results from this shader: 'Pi2' for Pi2 and 'Turbo' for original Pi and Pi Zero. Note: Pi2s are slower at running the shader than other Pis, this seems to be down to Pi2s lower maximum memory speed. Pi2s don't quite manage 1080P @ 60Hz - they drop about 1 in 1000 frames. You probably won't notice this, but if you do, try enabling FAKE_GAMMA.
SCANLINES enables scanlines. You'll almost certainly want to use it with MULTISAMPLE to reduce moire effects. SCANLINE_WEIGHT defines how wide scanlines are (it is an inverse value so a higher number = thinner lines). SCANLINE_GAP_BRIGHTNESS defines how dark the gaps between the scan lines are. Darker gaps between scan lines make moire effects more likely.
GAMMA enables gamma correction using the values in INPUT_GAMMA and OUTPUT_GAMMA. FAKE_GAMMA causes it to ignore the values in INPUT_GAMMA and OUTPUT_GAMMA and approximate gamma correction in a way which is faster than true gamma whilst still looking better than having none. You must have GAMMA defined to enable FAKE_GAMMA.
CURVATURE distorts the screen by CURVATURE_X and CURVATURE_Y. Curvature slows things down a lot.
By default the shader uses linear blending horizontally. If you find this too blury, enable SHARPER.
BLOOM_FACTOR controls the increase in width for bright scanlines.
MASK_TYPE defines what, if any, shadow mask to use. MASK_BRIGHTNESS defines how much the mask type darkens the screen.
*/
#pragma parameter CURVATURE_X "Screen curvature - horizontal" 0.10 0.0 1.0 0.01
#pragma parameter CURVATURE_Y "Screen curvature - vertical" 0.15 0.0 1.0 0.01
#pragma parameter MASK_BRIGHTNESS "Mask brightness" 0.70 0.0 1.0 0.01
#pragma parameter SCANLINE_WEIGHT "Scanline weight" 6.0 0.0 15.0 0.1
#pragma parameter SCANLINE_GAP_BRIGHTNESS "Scanline gap brightness" 0.12 0.0 1.0 0.01
#pragma parameter BLOOM_FACTOR "Bloom factor" 1.5 0.0 5.0 0.01
#pragma parameter INPUT_GAMMA "Input gamma" 2.4 0.0 5.0 0.01
#pragma parameter OUTPUT_GAMMA "Output gamma" 2.2 0.0 5.0 0.01
// Haven't put these as parameters as it would slow the code down.
#define SCANLINES
#define MULTISAMPLE
#define GAMMA
//#define FAKE_GAMMA
//#define CURVATURE
//#define SHARPER
// MASK_TYPE: 0 = none, 1 = green/magenta, 2 = trinitron(ish)
#define MASK_TYPE 2
#ifdef GL_ES
#define COMPAT_PRECISION mediump
precision mediump float;
#else
#define COMPAT_PRECISION
#endif
#ifdef PARAMETER_UNIFORM
uniform COMPAT_PRECISION float CURVATURE_X;
uniform COMPAT_PRECISION float CURVATURE_Y;
uniform COMPAT_PRECISION float MASK_BRIGHTNESS;
uniform COMPAT_PRECISION float SCANLINE_WEIGHT;
uniform COMPAT_PRECISION float SCANLINE_GAP_BRIGHTNESS;
uniform COMPAT_PRECISION float BLOOM_FACTOR;
uniform COMPAT_PRECISION float INPUT_GAMMA;
uniform COMPAT_PRECISION float OUTPUT_GAMMA;
#else
#define CURVATURE_X 0.05
#define CURVATURE_Y 0.1
#define MASK_BRIGHTNESS 0.80
#define SCANLINE_WEIGHT 6.0
#define SCANLINE_GAP_BRIGHTNESS 0.12
#define BLOOM_FACTOR 3.5
#define INPUT_GAMMA 2.4
#define OUTPUT_GAMMA 2.2
#endif
/* COMPATIBILITY
- GLSL compilers
*/
//uniform vec2 TextureSize;
#if defined(CURVATURE)
varying vec2 screenScale;
#endif
varying vec2 TEX0;
varying float filterWidth;
#if defined(VERTEX)
//uniform mat4 MVPMatrix;
//attribute vec4 VertexCoord;
//attribute vec2 TexCoord;
//uniform vec2 InputSize;
//uniform vec2 OutputSize;
void main()
{
#if defined(CURVATURE)
screenScale = vec2(1.0, 1.0); //TextureSize / InputSize;
#endif
filterWidth = (768.0 / 240.0) / 3.0;
TEX0 = vec2(gl_MultiTexCoord0.x, 1.0-gl_MultiTexCoord0.y)*1.0001;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#elif defined(FRAGMENT)
uniform sampler2D Texture;
#if defined(CURVATURE)
vec2 Distort(vec2 coord)
{
vec2 CURVATURE_DISTORTION = vec2(CURVATURE_X, CURVATURE_Y);
// Barrel distortion shrinks the display area a bit, this will allow us to counteract that.
vec2 barrelScale = 1.0 - (0.23 * CURVATURE_DISTORTION);
coord *= screenScale;
coord -= vec2(0.5);
float rsq = coord.x * coord.x + coord.y * coord.y;
coord += coord * (CURVATURE_DISTORTION * rsq);
coord *= barrelScale;
if (abs(coord.x) >= 0.5 || abs(coord.y) >= 0.5)
coord = vec2(-1.0); // If out of bounds, return an invalid value.
else
{
coord += vec2(0.5);
coord /= screenScale;
}
return coord;
}
#endif
float CalcScanLineWeight(float dist)
{
return max(1.0-dist*dist*SCANLINE_WEIGHT, SCANLINE_GAP_BRIGHTNESS);
}
float CalcScanLine(float dy)
{
float scanLineWeight = CalcScanLineWeight(dy);
#if defined(MULTISAMPLE)
scanLineWeight += CalcScanLineWeight(dy-filterWidth);
scanLineWeight += CalcScanLineWeight(dy+filterWidth);
scanLineWeight *= 0.3333333;
#endif
return scanLineWeight;
}
void main()
{
vec2 TextureSize = vec2(320.0, 240.0);
#if defined(CURVATURE)
vec2 texcoord = Distort(TEX0);
if (texcoord.x < 0.0)
gl_FragColor = vec4(0.0);
else
#else
vec2 texcoord = TEX0;
#endif
{
vec2 texcoordInPixels = texcoord * TextureSize;
#if defined(SHARPER)
vec2 tempCoord = floor(texcoordInPixels) + 0.5;
vec2 coord = tempCoord / TextureSize;
vec2 deltas = texcoordInPixels - tempCoord;
float scanLineWeight = CalcScanLine(deltas.y);
vec2 signs = sign(deltas);
deltas.x *= 2.0;
deltas = deltas * deltas;
deltas.y = deltas.y * deltas.y;
deltas.x *= 0.5;
deltas.y *= 8.0;
deltas /= TextureSize;
deltas *= signs;
vec2 tc = coord + deltas;
#else
float tempY = floor(texcoordInPixels.y) + 0.5;
float yCoord = tempY / TextureSize.y;
float dy = texcoordInPixels.y - tempY;
float scanLineWeight = CalcScanLine(dy);
float signY = sign(dy);
dy = dy * dy;
dy = dy * dy;
dy *= 8.0;
dy /= TextureSize.y;
dy *= signY;
vec2 tc = vec2(texcoord.x, yCoord + dy);
#endif
vec3 colour = texture2D(Texture, tc).rgb;
#if defined(SCANLINES)
#if defined(GAMMA)
#if defined(FAKE_GAMMA)
colour = colour * colour;
#else
colour = pow(colour, vec3(INPUT_GAMMA));
#endif
#endif
scanLineWeight *= BLOOM_FACTOR;
colour *= scanLineWeight;
#if defined(GAMMA)
#if defined(FAKE_GAMMA)
colour = sqrt(colour);
#else
colour = pow(colour, vec3(1.0/OUTPUT_GAMMA));
#endif
#endif
#endif
#if MASK_TYPE == 0
gl_FragColor = vec4(colour, 1.0);
#else
#if MASK_TYPE == 1
float whichMask = fract((gl_FragCoord.x*1.0001) * 0.5);
vec3 mask;
if (whichMask < 0.5)
mask = vec3(MASK_BRIGHTNESS, 1.0, MASK_BRIGHTNESS);
else
mask = vec3(1.0, MASK_BRIGHTNESS, 1.0);
#elif MASK_TYPE == 2
float whichMask = fract((gl_FragCoord.x*1.0001) * 0.3333333);
vec3 mask = vec3(MASK_BRIGHTNESS, MASK_BRIGHTNESS, MASK_BRIGHTNESS);
if (whichMask < 0.3333333)
mask.x = 1.0;
else if (whichMask < 0.6666666)
mask.y = 1.0;
else
mask.z = 1.0;
#endif
gl_FragColor = vec4(colour * mask, 1.0);
#endif
}
}
#endif

234
data/shaders/crtpi_256.glsl Normal file
View File

@@ -0,0 +1,234 @@
/*
crt-pi - A Raspberry Pi friendly CRT shader.
Copyright (C) 2015-2016 davej
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your option)
any later version.
Notes:
This shader is designed to work well on Raspberry Pi GPUs (i.e. 1080P @ 60Hz on a game with a 4:3 aspect ratio). It pushes the Pi's GPU hard and enabling some features will slow it down so that it is no longer able to match 1080P @ 60Hz. You will need to overclock your Pi to the fastest setting in raspi-config to get the best results from this shader: 'Pi2' for Pi2 and 'Turbo' for original Pi and Pi Zero. Note: Pi2s are slower at running the shader than other Pis, this seems to be down to Pi2s lower maximum memory speed. Pi2s don't quite manage 1080P @ 60Hz - they drop about 1 in 1000 frames. You probably won't notice this, but if you do, try enabling FAKE_GAMMA.
SCANLINES enables scanlines. You'll almost certainly want to use it with MULTISAMPLE to reduce moire effects. SCANLINE_WEIGHT defines how wide scanlines are (it is an inverse value so a higher number = thinner lines). SCANLINE_GAP_BRIGHTNESS defines how dark the gaps between the scan lines are. Darker gaps between scan lines make moire effects more likely.
GAMMA enables gamma correction using the values in INPUT_GAMMA and OUTPUT_GAMMA. FAKE_GAMMA causes it to ignore the values in INPUT_GAMMA and OUTPUT_GAMMA and approximate gamma correction in a way which is faster than true gamma whilst still looking better than having none. You must have GAMMA defined to enable FAKE_GAMMA.
CURVATURE distorts the screen by CURVATURE_X and CURVATURE_Y. Curvature slows things down a lot.
By default the shader uses linear blending horizontally. If you find this too blury, enable SHARPER.
BLOOM_FACTOR controls the increase in width for bright scanlines.
MASK_TYPE defines what, if any, shadow mask to use. MASK_BRIGHTNESS defines how much the mask type darkens the screen.
*/
#pragma parameter CURVATURE_X "Screen curvature - horizontal" 0.10 0.0 1.0 0.01
#pragma parameter CURVATURE_Y "Screen curvature - vertical" 0.15 0.0 1.0 0.01
#pragma parameter MASK_BRIGHTNESS "Mask brightness" 0.70 0.0 1.0 0.01
#pragma parameter SCANLINE_WEIGHT "Scanline weight" 6.0 0.0 15.0 0.1
#pragma parameter SCANLINE_GAP_BRIGHTNESS "Scanline gap brightness" 0.12 0.0 1.0 0.01
#pragma parameter BLOOM_FACTOR "Bloom factor" 1.5 0.0 5.0 0.01
#pragma parameter INPUT_GAMMA "Input gamma" 2.4 0.0 5.0 0.01
#pragma parameter OUTPUT_GAMMA "Output gamma" 2.2 0.0 5.0 0.01
// Haven't put these as parameters as it would slow the code down.
#define SCANLINES
#define MULTISAMPLE
#define GAMMA
//#define FAKE_GAMMA
//#define CURVATURE
//#define SHARPER
// MASK_TYPE: 0 = none, 1 = green/magenta, 2 = trinitron(ish)
#define MASK_TYPE 2
#ifdef GL_ES
#define COMPAT_PRECISION mediump
precision mediump float;
#else
#define COMPAT_PRECISION
#endif
#ifdef PARAMETER_UNIFORM
uniform COMPAT_PRECISION float CURVATURE_X;
uniform COMPAT_PRECISION float CURVATURE_Y;
uniform COMPAT_PRECISION float MASK_BRIGHTNESS;
uniform COMPAT_PRECISION float SCANLINE_WEIGHT;
uniform COMPAT_PRECISION float SCANLINE_GAP_BRIGHTNESS;
uniform COMPAT_PRECISION float BLOOM_FACTOR;
uniform COMPAT_PRECISION float INPUT_GAMMA;
uniform COMPAT_PRECISION float OUTPUT_GAMMA;
#else
#define CURVATURE_X 0.05
#define CURVATURE_Y 0.1
#define MASK_BRIGHTNESS 0.80
#define SCANLINE_WEIGHT 6.0
#define SCANLINE_GAP_BRIGHTNESS 0.12
#define BLOOM_FACTOR 3.5
#define INPUT_GAMMA 2.4
#define OUTPUT_GAMMA 2.2
#endif
/* COMPATIBILITY
- GLSL compilers
*/
//uniform vec2 TextureSize;
#if defined(CURVATURE)
varying vec2 screenScale;
#endif
varying vec2 TEX0;
varying float filterWidth;
#if defined(VERTEX)
//uniform mat4 MVPMatrix;
//attribute vec4 VertexCoord;
//attribute vec2 TexCoord;
//uniform vec2 InputSize;
//uniform vec2 OutputSize;
void main()
{
#if defined(CURVATURE)
screenScale = vec2(1.0, 1.0); //TextureSize / InputSize;
#endif
filterWidth = (768.0 / 256.0) / 3.0;
TEX0 = vec2(gl_MultiTexCoord0.x, 1.0-gl_MultiTexCoord0.y)*1.0001;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#elif defined(FRAGMENT)
uniform sampler2D Texture;
#if defined(CURVATURE)
vec2 Distort(vec2 coord)
{
vec2 CURVATURE_DISTORTION = vec2(CURVATURE_X, CURVATURE_Y);
// Barrel distortion shrinks the display area a bit, this will allow us to counteract that.
vec2 barrelScale = 1.0 - (0.23 * CURVATURE_DISTORTION);
coord *= screenScale;
coord -= vec2(0.5);
float rsq = coord.x * coord.x + coord.y * coord.y;
coord += coord * (CURVATURE_DISTORTION * rsq);
coord *= barrelScale;
if (abs(coord.x) >= 0.5 || abs(coord.y) >= 0.5)
coord = vec2(-1.0); // If out of bounds, return an invalid value.
else
{
coord += vec2(0.5);
coord /= screenScale;
}
return coord;
}
#endif
float CalcScanLineWeight(float dist)
{
return max(1.0-dist*dist*SCANLINE_WEIGHT, SCANLINE_GAP_BRIGHTNESS);
}
float CalcScanLine(float dy)
{
float scanLineWeight = CalcScanLineWeight(dy);
#if defined(MULTISAMPLE)
scanLineWeight += CalcScanLineWeight(dy-filterWidth);
scanLineWeight += CalcScanLineWeight(dy+filterWidth);
scanLineWeight *= 0.3333333;
#endif
return scanLineWeight;
}
void main()
{
vec2 TextureSize = vec2(320.0, 256.0);
#if defined(CURVATURE)
vec2 texcoord = Distort(TEX0);
if (texcoord.x < 0.0)
gl_FragColor = vec4(0.0);
else
#else
vec2 texcoord = TEX0;
#endif
{
vec2 texcoordInPixels = texcoord * TextureSize;
#if defined(SHARPER)
vec2 tempCoord = floor(texcoordInPixels) + 0.5;
vec2 coord = tempCoord / TextureSize;
vec2 deltas = texcoordInPixels - tempCoord;
float scanLineWeight = CalcScanLine(deltas.y);
vec2 signs = sign(deltas);
deltas.x *= 2.0;
deltas = deltas * deltas;
deltas.y = deltas.y * deltas.y;
deltas.x *= 0.5;
deltas.y *= 8.0;
deltas /= TextureSize;
deltas *= signs;
vec2 tc = coord + deltas;
#else
float tempY = floor(texcoordInPixels.y) + 0.5;
float yCoord = tempY / TextureSize.y;
float dy = texcoordInPixels.y - tempY;
float scanLineWeight = CalcScanLine(dy);
float signY = sign(dy);
dy = dy * dy;
dy = dy * dy;
dy *= 8.0;
dy /= TextureSize.y;
dy *= signY;
vec2 tc = vec2(texcoord.x, yCoord + dy);
#endif
vec3 colour = texture2D(Texture, tc).rgb;
#if defined(SCANLINES)
#if defined(GAMMA)
#if defined(FAKE_GAMMA)
colour = colour * colour;
#else
colour = pow(colour, vec3(INPUT_GAMMA));
#endif
#endif
scanLineWeight *= BLOOM_FACTOR;
colour *= scanLineWeight;
#if defined(GAMMA)
#if defined(FAKE_GAMMA)
colour = sqrt(colour);
#else
colour = pow(colour, vec3(1.0/OUTPUT_GAMMA));
#endif
#endif
#endif
#if MASK_TYPE == 0
gl_FragColor = vec4(colour, 1.0);
#else
#if MASK_TYPE == 1
float whichMask = fract((gl_FragCoord.x*1.0001) * 0.5);
vec3 mask;
if (whichMask < 0.5)
mask = vec3(MASK_BRIGHTNESS, 1.0, MASK_BRIGHTNESS);
else
mask = vec3(1.0, MASK_BRIGHTNESS, 1.0);
#elif MASK_TYPE == 2
float whichMask = fract((gl_FragCoord.x*1.0001) * 0.3333333);
vec3 mask = vec3(MASK_BRIGHTNESS, MASK_BRIGHTNESS, MASK_BRIGHTNESS);
if (whichMask < 0.3333333)
mask.x = 1.0;
else if (whichMask < 0.6666666)
mask.y = 1.0;
else
mask.z = 1.0;
#endif
gl_FragColor = vec4(colour * mask, 1.0);
#endif
}
}
#endif

View File

@@ -1,157 +0,0 @@
#version 330 core
// Configuración
#define SCANLINES
#define MULTISAMPLE
#define GAMMA
//#define FAKE_GAMMA
//#define CURVATURE
//#define SHARPER
#define MASK_TYPE 2
#define CURVATURE_X 0.05
#define CURVATURE_Y 0.1
#define MASK_BRIGHTNESS 0.80
#define SCANLINE_WEIGHT 6.0
#define SCANLINE_GAP_BRIGHTNESS 0.12
#define BLOOM_FACTOR 3.5
#define INPUT_GAMMA 2.4
#define OUTPUT_GAMMA 2.2
// Inputs desde vertex shader
in vec2 vTexCoord;
in float vFilterWidth;
#if defined(CURVATURE)
in vec2 vScreenScale;
#endif
// Output
out vec4 FragColor;
// Uniforms
uniform sampler2D Texture;
uniform vec2 TextureSize;
#if defined(CURVATURE)
vec2 Distort(vec2 coord)
{
vec2 CURVATURE_DISTORTION = vec2(CURVATURE_X, CURVATURE_Y);
vec2 barrelScale = 1.0 - (0.23 * CURVATURE_DISTORTION);
coord *= vScreenScale;
coord -= vec2(0.5);
float rsq = coord.x * coord.x + coord.y * coord.y;
coord += coord * (CURVATURE_DISTORTION * rsq);
coord *= barrelScale;
if (abs(coord.x) >= 0.5 || abs(coord.y) >= 0.5)
coord = vec2(-1.0);
else
{
coord += vec2(0.5);
coord /= vScreenScale;
}
return coord;
}
#endif
float CalcScanLineWeight(float dist)
{
return max(1.0 - dist * dist * SCANLINE_WEIGHT, SCANLINE_GAP_BRIGHTNESS);
}
float CalcScanLine(float dy)
{
float scanLineWeight = CalcScanLineWeight(dy);
#if defined(MULTISAMPLE)
scanLineWeight += CalcScanLineWeight(dy - vFilterWidth);
scanLineWeight += CalcScanLineWeight(dy + vFilterWidth);
scanLineWeight *= 0.3333333;
#endif
return scanLineWeight;
}
void main()
{
#if defined(CURVATURE)
vec2 texcoord = Distort(vTexCoord);
if (texcoord.x < 0.0) {
FragColor = vec4(0.0);
return;
}
#else
vec2 texcoord = vTexCoord;
#endif
vec2 texcoordInPixels = texcoord * TextureSize;
#if defined(SHARPER)
vec2 tempCoord = floor(texcoordInPixels) + 0.5;
vec2 coord = tempCoord / TextureSize;
vec2 deltas = texcoordInPixels - tempCoord;
float scanLineWeight = CalcScanLine(deltas.y);
vec2 signs = sign(deltas);
deltas.x *= 2.0;
deltas = deltas * deltas;
deltas.y = deltas.y * deltas.y;
deltas.x *= 0.5;
deltas.y *= 8.0;
deltas /= TextureSize;
deltas *= signs;
vec2 tc = coord + deltas;
#else
float tempY = floor(texcoordInPixels.y) + 0.5;
float yCoord = tempY / TextureSize.y;
float dy = texcoordInPixels.y - tempY;
float scanLineWeight = CalcScanLine(dy);
float signY = sign(dy);
dy = dy * dy;
dy = dy * dy;
dy *= 8.0;
dy /= TextureSize.y;
dy *= signY;
vec2 tc = vec2(texcoord.x, yCoord + dy);
#endif
vec3 colour = texture(Texture, tc).rgb;
#if defined(SCANLINES)
#if defined(GAMMA)
#if defined(FAKE_GAMMA)
colour = colour * colour;
#else
colour = pow(colour, vec3(INPUT_GAMMA));
#endif
#endif
scanLineWeight *= BLOOM_FACTOR;
colour *= scanLineWeight;
#if defined(GAMMA)
#if defined(FAKE_GAMMA)
colour = sqrt(colour);
#else
colour = pow(colour, vec3(1.0 / OUTPUT_GAMMA));
#endif
#endif
#endif
#if MASK_TYPE == 0
FragColor = vec4(colour, 1.0);
#elif MASK_TYPE == 1
float whichMask = fract(gl_FragCoord.x * 0.5);
vec3 mask;
if (whichMask < 0.5)
mask = vec3(MASK_BRIGHTNESS, 1.0, MASK_BRIGHTNESS);
else
mask = vec3(1.0, MASK_BRIGHTNESS, 1.0);
FragColor = vec4(colour * mask, 1.0);
#elif MASK_TYPE == 2
float whichMask = fract(gl_FragCoord.x * 0.3333333);
vec3 mask = vec3(MASK_BRIGHTNESS, MASK_BRIGHTNESS, MASK_BRIGHTNESS);
if (whichMask < 0.3333333)
mask.x = 1.0;
else if (whichMask < 0.6666666)
mask.y = 1.0;
else
mask.z = 1.0;
FragColor = vec4(colour * mask, 1.0);
#endif
}

View File

@@ -1,152 +0,0 @@
//
// CRT-Pi Fragment Shader - Metal Shading Language
// Portado desde GLSL a MSL para macOS
//
#include <metal_stdlib>
using namespace metal;
// Configuración (equivalente a los #define en GLSL)
constant bool SCANLINES = true;
constant bool MULTISAMPLE = true;
constant bool GAMMA = true;
constant bool FAKE_GAMMA = false;
constant bool CURVATURE = false;
constant bool SHARPER = false;
constant int MASK_TYPE = 2; // 0=none, 1=green/magenta, 2=trinitron
constant float CURVATURE_X = 0.05;
constant float CURVATURE_Y = 0.1;
constant float MASK_BRIGHTNESS = 0.80;
constant float SCANLINE_WEIGHT = 6.0;
constant float SCANLINE_GAP_BRIGHTNESS = 0.12;
constant float BLOOM_FACTOR = 3.5;
constant float INPUT_GAMMA = 2.4;
constant float OUTPUT_GAMMA = 2.2;
// Estructura de entrada (salida del vertex shader)
struct VertexOut {
float4 position [[position]];
float2 texCoord;
float filterWidth;
// float2 screenScale; // Solo si CURVATURE está activo
};
// Uniforms
struct Uniforms {
float2 textureSize;
};
// Función para calcular el peso de la scanline
float CalcScanLineWeight(float dist) {
return max(1.0 - dist * dist * SCANLINE_WEIGHT, SCANLINE_GAP_BRIGHTNESS);
}
// Función para calcular scanline con multisampling
float CalcScanLine(float dy, float filterWidth) {
float scanLineWeight = CalcScanLineWeight(dy);
if (MULTISAMPLE) {
scanLineWeight += CalcScanLineWeight(dy - filterWidth);
scanLineWeight += CalcScanLineWeight(dy + filterWidth);
scanLineWeight *= 0.3333333;
}
return scanLineWeight;
}
// Entry point del fragment shader
fragment float4 fragment_main(VertexOut in [[stage_in]],
texture2d<float> colorTexture [[texture(0)]],
constant Uniforms& uniforms [[buffer(1)]],
sampler textureSampler [[sampler(0)]]) {
float2 texcoord = in.texCoord;
// Si CURVATURE estuviera activo, aquí iría la función Distort()
float2 texcoordInPixels = texcoord * uniforms.textureSize;
float2 tc;
float scanLineWeight;
if (!SHARPER) {
// Modo normal (no SHARPER)
float tempY = floor(texcoordInPixels.y) + 0.5;
float yCoord = tempY / uniforms.textureSize.y;
float dy = texcoordInPixels.y - tempY;
scanLineWeight = CalcScanLine(dy, in.filterWidth);
float signY = sign(dy);
dy = dy * dy;
dy = dy * dy;
dy *= 8.0;
dy /= uniforms.textureSize.y;
dy *= signY;
tc = float2(texcoord.x, yCoord + dy);
} else {
// Modo SHARPER
float2 tempCoord = floor(texcoordInPixels) + 0.5;
float2 coord = tempCoord / uniforms.textureSize;
float2 deltas = texcoordInPixels - tempCoord;
scanLineWeight = CalcScanLine(deltas.y, in.filterWidth);
float2 signs = sign(deltas);
deltas.x *= 2.0;
deltas = deltas * deltas;
deltas.y = deltas.y * deltas.y;
deltas.x *= 0.5;
deltas.y *= 8.0;
deltas /= uniforms.textureSize;
deltas *= signs;
tc = coord + deltas;
}
// Muestrear textura (texture() en GLSL = sample() en MSL)
float3 colour = colorTexture.sample(textureSampler, tc).rgb;
if (SCANLINES) {
if (GAMMA) {
if (FAKE_GAMMA) {
colour = colour * colour;
} else {
colour = pow(colour, float3(INPUT_GAMMA));
}
}
scanLineWeight *= BLOOM_FACTOR;
colour *= scanLineWeight;
if (GAMMA) {
if (FAKE_GAMMA) {
colour = sqrt(colour);
} else {
colour = pow(colour, float3(1.0 / OUTPUT_GAMMA));
}
}
}
// Aplicar máscara CRT
if (MASK_TYPE == 0) {
return float4(colour, 1.0);
} else if (MASK_TYPE == 1) {
// Máscara verde/magenta
float whichMask = fract(in.position.x * 0.5);
float3 mask;
if (whichMask < 0.5) {
mask = float3(MASK_BRIGHTNESS, 1.0, MASK_BRIGHTNESS);
} else {
mask = float3(1.0, MASK_BRIGHTNESS, 1.0);
}
return float4(colour * mask, 1.0);
} else if (MASK_TYPE == 2) {
// Máscara trinitron
float whichMask = fract(in.position.x * 0.3333333);
float3 mask = float3(MASK_BRIGHTNESS, MASK_BRIGHTNESS, MASK_BRIGHTNESS);
if (whichMask < 0.3333333) {
mask.x = 1.0;
} else if (whichMask < 0.6666666) {
mask.y = 1.0;
} else {
mask.z = 1.0;
}
return float4(colour * mask, 1.0);
}
return float4(colour, 1.0);
}

View File

@@ -1,48 +0,0 @@
#version 330 core
// Configuración
#define SCANLINES
#define MULTISAMPLE
#define GAMMA
//#define FAKE_GAMMA
//#define CURVATURE
//#define SHARPER
#define MASK_TYPE 2
#define CURVATURE_X 0.05
#define CURVATURE_Y 0.1
#define MASK_BRIGHTNESS 0.80
#define SCANLINE_WEIGHT 6.0
#define SCANLINE_GAP_BRIGHTNESS 0.12
#define BLOOM_FACTOR 3.5
#define INPUT_GAMMA 2.4
#define OUTPUT_GAMMA 2.2
// Inputs (desde VAO)
layout(location = 0) in vec2 aPosition;
layout(location = 1) in vec2 aTexCoord;
// Outputs al fragment shader
out vec2 vTexCoord;
out float vFilterWidth;
#if defined(CURVATURE)
out vec2 vScreenScale;
#endif
// Uniforms
uniform vec2 TextureSize;
void main()
{
#if defined(CURVATURE)
vScreenScale = vec2(1.0, 1.0);
#endif
// Calcula filterWidth dinámicamente basándose en la altura de la textura
vFilterWidth = (768.0 / TextureSize.y) / 3.0;
// Pasar coordenadas de textura (invertir Y para SDL)
vTexCoord = vec2(aTexCoord.x, 1.0 - aTexCoord.y) * 1.0001;
// Posición del vértice (ya en espacio de clip [-1, 1])
gl_Position = vec4(aPosition, 0.0, 1.0);
}

View File

@@ -1,46 +0,0 @@
//
// CRT-Pi Vertex Shader - Metal Shading Language
// Portado desde GLSL a MSL para macOS
//
#include <metal_stdlib>
using namespace metal;
// Estructura de entrada del vertex shader (desde el buffer de vértices)
struct VertexIn {
float2 position [[attribute(0)]]; // Posición del vértice
float2 texCoord [[attribute(1)]]; // Coordenadas de textura
};
// Estructura de salida del vertex shader (entrada al fragment shader)
struct VertexOut {
float4 position [[position]]; // Posición en clip space
float2 texCoord; // Coordenadas de textura
float filterWidth; // Ancho del filtro calculado
// float2 screenScale; // Solo si CURVATURE está activo
};
// Uniforms (constantes del shader)
struct Uniforms {
float2 textureSize; // Tamaño de la textura (width, height)
};
// Entry point del vertex shader
vertex VertexOut vertex_main(VertexIn in [[stage_in]],
constant Uniforms& uniforms [[buffer(1)]]) {
VertexOut out;
// Posición del vértice (ya está en espacio de clip [-1, 1])
out.position = float4(in.position, 0.0, 1.0);
// Pasar coordenadas de textura (invertir Y para SDL, igual que en GLSL)
out.texCoord = float2(in.texCoord.x, 1.0 - in.texCoord.y) * 1.0001;
// Calcular filterWidth dinámicamente basándose en la altura de la textura
out.filterWidth = (768.0 / uniforms.textureSize.y) / 3.0;
// Si CURVATURE estuviera activo:
// out.screenScale = float2(1.0, 1.0);
return out;
}

Binary file not shown.

BIN
data/sound/click-2.wav Normal file

Binary file not shown.

BIN
data/sound/click-3.wav Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,43 +0,0 @@
# Directrices de Desarrollo - Coffee Crisis Arcade Edition
## Directrices Principales Confirmadas
### 1. **Sistema Temporal**
- **TODO migrado de frame based a time based**
- **Delta time en segundos (float)**
- **Unidades de tiempo: SOLO segundos** (no frames, no milisegundos)
### 2. **Contadores y Timers**
- **CRECIENTES**: para sistemas con múltiples eventos temporales (timeline)
- Patrón: `elapsed_time += deltaTime; if (elapsed_time >= EVENT_TIME) { /* acción */ }`
- **DECRECIENTES**: para contadores con diferentes valores de inicialización
- Patrón: `timer -= deltaTime; if (timer <= 0.0f) { /* acción */ timer = DURATION; }`
### 3. **Números Mágicos**
- **Definidos en constantes**
- **Preferencia**: cabecera de la clase
- **Excepción**: si es algo local a un método específico
## Problemas Pendientes de Reparación (game.cpp)
### ❌ PENDIENTES
1. **param.fade.post_duration_ms verification** (líneas 89, 1671)
2. **setRotateSpeed verification** (línea 797)
3. **TOTAL_DEMO_DATA - 200 magic number** (línea 1669)
4. **Comprehensive magic number search** - Buscar 100, 150, 200, 250, 300, 400, 500, 1000
### 4. **Velocidades y Aceleraciones**
- **Velocidades**: pixels/segundo
- **Aceleraciones**: pixels/segundo²
### 5. **Documentación de Conversiones**
- **Comentarios explicativos** en cambios críticos de timing
- Documentar conversiones frame→tiempo en el código
### 6. **Patrón de Constantes**
- Crear constantes para valores repetidos (evitar duplicación)
- Nombres descriptivos para constantes de tiempo
---
**Estado**: Directrices completas confirmadas

View File

@@ -43,10 +43,6 @@ auto loadAnimationsFromFile(const std::string& file_path) -> AnimationsFileBuffe
std::vector<std::string> buffer; std::vector<std::string> buffer;
std::string line; std::string line;
while (std::getline(input_stream, line)) { while (std::getline(input_stream, line)) {
// Eliminar caracteres de retorno de carro (\r) al final de la línea
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
if (!line.empty()) { if (!line.empty()) {
buffer.push_back(line); buffer.push_back(line);
} }
@@ -86,33 +82,33 @@ auto AnimatedSprite::getAnimationIndex(const std::string& name) -> int {
return -1; return -1;
} }
// Calcula el frame correspondiente a la animación (time-based) // Calcula el frame correspondiente a la animación
void AnimatedSprite::animate(float deltaTime) { void AnimatedSprite::animate() {
if (animations_[current_animation_].speed == 0 || animations_[current_animation_].paused) { if (animations_[current_animation_].speed == 0 || animations_[current_animation_].paused) {
return; return;
} }
// Acumular tiempo transcurrido // Calcula el frame actual a partir del contador
animations_[current_animation_].time_accumulator += deltaTime; animations_[current_animation_].current_frame = animations_[current_animation_].counter / animations_[current_animation_].speed;
// Verificar si es momento de cambiar frame // Si alcanza el final de la animación, reinicia el contador de la animación
if (animations_[current_animation_].time_accumulator >= animations_[current_animation_].speed) { // en función de la variable loop y coloca el nuevo frame
animations_[current_animation_].time_accumulator -= animations_[current_animation_].speed;
animations_[current_animation_].current_frame++;
// Si alcanza el final de la animación
if (animations_[current_animation_].current_frame >= animations_[current_animation_].frames.size()) { if (animations_[current_animation_].current_frame >= animations_[current_animation_].frames.size()) {
if (animations_[current_animation_].loop == -1) { // Si no hay loop, deja el último frame if (animations_[current_animation_].loop == -1) { // Si no hay loop, deja el último frame
animations_[current_animation_].current_frame = animations_[current_animation_].frames.size() - 1; animations_[current_animation_].current_frame = animations_[current_animation_].frames.size();
animations_[current_animation_].completed = true; animations_[current_animation_].completed = true;
} else { // Si hay loop, vuelve al frame indicado } else { // Si hay loop, vuelve al frame indicado
animations_[current_animation_].time_accumulator = 0.0f; animations_[current_animation_].counter = 0;
animations_[current_animation_].current_frame = animations_[current_animation_].loop; animations_[current_animation_].current_frame = animations_[current_animation_].loop;
} }
} }
// En caso contrario
// Actualizar el sprite clip else {
// Escoge el frame correspondiente de la animación
updateSpriteClip(); updateSpriteClip();
// Incrementa el contador de la animacion
animations_[current_animation_].counter++;
} }
} }
@@ -129,11 +125,11 @@ void AnimatedSprite::setCurrentAnimation(const std::string& name, bool reset) {
current_animation_ = NEW_ANIMATION; current_animation_ = NEW_ANIMATION;
if (reset) { if (reset) {
animations_[current_animation_].current_frame = 0; animations_[current_animation_].current_frame = 0;
animations_[current_animation_].time_accumulator = 0.0f; animations_[current_animation_].counter = 0;
animations_[current_animation_].completed = false; animations_[current_animation_].completed = false;
} else { } else {
animations_[current_animation_].current_frame = std::min(animations_[OLD_ANIMATION].current_frame, animations_[current_animation_].frames.size() - 1); animations_[current_animation_].current_frame = std::min(animations_[OLD_ANIMATION].current_frame, animations_[current_animation_].frames.size() - 1);
animations_[current_animation_].time_accumulator = animations_[OLD_ANIMATION].time_accumulator; animations_[current_animation_].counter = animations_[OLD_ANIMATION].counter;
animations_[current_animation_].completed = animations_[OLD_ANIMATION].completed; animations_[current_animation_].completed = animations_[OLD_ANIMATION].completed;
} }
updateSpriteClip(); updateSpriteClip();
@@ -148,27 +144,27 @@ void AnimatedSprite::setCurrentAnimation(int index, bool reset) {
current_animation_ = NEW_ANIMATION; current_animation_ = NEW_ANIMATION;
if (reset) { if (reset) {
animations_[current_animation_].current_frame = 0; animations_[current_animation_].current_frame = 0;
animations_[current_animation_].time_accumulator = 0.0f; animations_[current_animation_].counter = 0;
animations_[current_animation_].completed = false; animations_[current_animation_].completed = false;
} else { } else {
animations_[current_animation_].current_frame = std::min(animations_[OLD_ANIMATION].current_frame, animations_[current_animation_].frames.size()); animations_[current_animation_].current_frame = std::min(animations_[OLD_ANIMATION].current_frame, animations_[current_animation_].frames.size());
animations_[current_animation_].time_accumulator = animations_[OLD_ANIMATION].time_accumulator; animations_[current_animation_].counter = animations_[OLD_ANIMATION].counter;
animations_[current_animation_].completed = animations_[OLD_ANIMATION].completed; animations_[current_animation_].completed = animations_[OLD_ANIMATION].completed;
} }
updateSpriteClip(); updateSpriteClip();
} }
} }
// Actualiza las variables del objeto (time-based) // Actualiza las variables del objeto
void AnimatedSprite::update(float deltaTime) { void AnimatedSprite::update() {
animate(deltaTime); animate();
MovingSprite::update(deltaTime); MovingSprite::update();
} }
// Reinicia la animación // Reinicia la animación
void AnimatedSprite::resetAnimation() { void AnimatedSprite::resetAnimation() {
animations_[current_animation_].current_frame = 0; animations_[current_animation_].current_frame = 0;
animations_[current_animation_].time_accumulator = 0.0f; animations_[current_animation_].counter = 0;
animations_[current_animation_].completed = false; animations_[current_animation_].completed = false;
animations_[current_animation_].paused = false; animations_[current_animation_].paused = false;
updateSpriteClip(); updateSpriteClip();
@@ -194,12 +190,6 @@ void AnimatedSprite::loadFromAnimationsFileBuffer(const AnimationsFileBuffer& so
// Pone un valor por defecto // Pone un valor por defecto
setWidth(config.frame_width); setWidth(config.frame_width);
setHeight(config.frame_height); setHeight(config.frame_height);
// Establece el primer frame inmediatamente si hay animaciones
if (!animations_.empty()) {
current_animation_ = 0;
updateSpriteClip();
}
} }
// Procesa una línea de configuración // Procesa una línea de configuración
@@ -269,7 +259,7 @@ void AnimatedSprite::processAnimationParameter(const std::string& line, Animatio
if (key == "name") { if (key == "name") {
animation.name = value; animation.name = value;
} else if (key == "speed") { } else if (key == "speed") {
animation.speed = std::stof(value); animation.speed = std::stoi(value);
} else if (key == "loop") { } else if (key == "loop") {
animation.loop = std::stoi(value); animation.loop = std::stoi(value);
} else if (key == "frames") { } else if (key == "frames") {
@@ -296,7 +286,7 @@ void AnimatedSprite::parseFramesParameter(const std::string& frames_str, Animati
} }
// Establece la velocidad de la animación // Establece la velocidad de la animación
void AnimatedSprite::setAnimationSpeed(float value) { void AnimatedSprite::setAnimationSpeed(size_t value) {
animations_[current_animation_].speed = value; animations_[current_animation_].speed = value;
} }

View File

@@ -17,15 +17,15 @@ class Texture;
// --- Estructuras --- // --- Estructuras ---
struct Animation { struct Animation {
static constexpr float DEFAULT_SPEED = 80.0F; static constexpr int DEFAULT_SPEED = 5;
std::string name; // Nombre de la animación std::string name; // Nombre de la animación
std::vector<SDL_FRect> frames; // Frames que componen la animación std::vector<SDL_FRect> frames; // Frames que componen la animación
float speed{DEFAULT_SPEED}; // Velocidad de reproducción (ms entre frames) int speed{DEFAULT_SPEED}; // Velocidad de reproducción
int loop{0}; // Frame de vuelta al terminar (-1 para no repetir) int loop{0}; // Frame de vuelta al terminar (-1 para no repetir)
bool completed{false}; // Indica si la animación ha finalizado bool completed{false}; // Indica si la animación ha finalizado
size_t current_frame{0}; // Frame actual en reproducción size_t current_frame{0}; // Frame actual en reproducción
float time_accumulator{0.0f}; // Acumulador de tiempo para animaciones time-based int counter{0}; // Contador para la animación
bool paused{false}; // La animación no avanza bool paused{false}; // La animación no avanza
Animation() = default; Animation() = default;
@@ -55,14 +55,14 @@ class AnimatedSprite : public MovingSprite {
~AnimatedSprite() override = default; ~AnimatedSprite() override = default;
// --- Métodos principales --- // --- Métodos principales ---
void update(float deltaTime) override; // Actualiza la animación (time-based) void update() override; // Actualiza la animación
// --- Control de animaciones --- // --- Control de animaciones ---
void setCurrentAnimation(const std::string& name = "default", bool reset = true); // Establece la animación por nombre void setCurrentAnimation(const std::string& name = "default", bool reset = true); // Establece la animación por nombre
void setCurrentAnimation(int index = 0, bool reset = true); // Establece la animación por índice void setCurrentAnimation(int index = 0, bool reset = true); // Establece la animación por índice
void resetAnimation(); // Reinicia la animación actual void resetAnimation(); // Reinicia la animación actual
void setAnimationSpeed(float value); // Establece la velocidad de la animación void setAnimationSpeed(size_t value); // Establece la velocidad de la animación
auto getAnimationSpeed() const -> float { return animations_[current_animation_].speed; } // Obtiene la velocidad de la animación actual auto getAnimationSpeed() const -> size_t { return animations_[current_animation_].speed; } // Obtiene la velocidad de la animación actual
void animtionPause() { animations_[current_animation_].paused = true; } // Detiene la animación void animtionPause() { animations_[current_animation_].paused = true; } // Detiene la animación
void animationResume() { animations_[current_animation_].paused = false; } // Reanuda la animación void animationResume() { animations_[current_animation_].paused = false; } // Reanuda la animación
auto getCurrentAnimationFrame() const -> size_t { return animations_[current_animation_].current_frame; } // Obtiene el numero de frame de la animación actual auto getCurrentAnimationFrame() const -> size_t { return animations_[current_animation_].current_frame; } // Obtiene el numero de frame de la animación actual
@@ -78,7 +78,7 @@ class AnimatedSprite : public MovingSprite {
int current_animation_ = 0; // Índice de la animación activa int current_animation_ = 0; // Índice de la animación activa
// --- Métodos internos --- // --- Métodos internos ---
void animate(float deltaTime); // Calcula el frame correspondiente a la animación (time-based) void animate(); // Calcula el frame correspondiente a la animación
void loadFromAnimationsFileBuffer(const AnimationsFileBuffer& source); // Carga la animación desde un vector de cadenas void loadFromAnimationsFileBuffer(const AnimationsFileBuffer& source); // Carga la animación desde un vector de cadenas
void processConfigLine(const std::string& line, AnimationConfig& config); // Procesa una línea de configuración void processConfigLine(const std::string& line, AnimationConfig& config); // Procesa una línea de configuración
void updateFrameCalculations(AnimationConfig& config); // Actualiza los cálculos basados en las dimensiones del frame void updateFrameCalculations(AnimationConfig& config); // Actualiza los cálculos basados en las dimensiones del frame

View File

@@ -2,10 +2,8 @@
#include <SDL3/SDL.h> // Para SDL_LogCategory, SDL_LogInfo, SDL_LogError, SDL_LogWarn #include <SDL3/SDL.h> // Para SDL_LogCategory, SDL_LogInfo, SDL_LogError, SDL_LogWarn
#include <algorithm> // Para std::sort
#include <cstddef> // Para size_t #include <cstddef> // Para size_t
#include <exception> // Para exception #include <exception> // Para exception
#include <filesystem> // Para std::filesystem
#include <fstream> // Para basic_istream, basic_ifstream, ifstream, istringstream #include <fstream> // Para basic_istream, basic_ifstream, ifstream, istringstream
#include <sstream> // Para basic_istringstream #include <sstream> // Para basic_istringstream
#include <stdexcept> // Para runtime_error #include <stdexcept> // Para runtime_error
@@ -207,28 +205,25 @@ auto Asset::check() const -> bool {
} }
// Comprueba que existe un fichero // Comprueba que existe un fichero
auto Asset::checkFile(const std::string &path) const -> bool { auto Asset::checkFile(const std::string &path) -> bool {
// Construir ruta del pack usando executable_path_ // Intentar primero con ResourceHelper
std::string pack_path = executable_path_ + "resources.pack";
bool pack_exists = std::filesystem::exists(pack_path);
if (pack_exists) {
// MODO PACK: Usar ResourceHelper (igual que la carga real)
auto data = ResourceHelper::loadFile(path); auto data = ResourceHelper::loadFile(path);
return !data.empty(); bool success = !data.empty();
} else {
// MODO FILESYSTEM: Verificación directa (modo desarrollo) // Si no se encuentra en el pack, intentar con filesystem directo
if (!success) {
std::ifstream file(path); std::ifstream file(path);
bool success = file.good(); success = file.good();
file.close(); file.close();
}
if (!success) { if (!success) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error: Could not open file: %s", path.c_str()); "Checking file: %s [ ERROR ]",
getFileName(path).c_str());
} }
return success; return success;
}
} }
// Parsea string a Type // Parsea string a Type
@@ -300,9 +295,6 @@ auto Asset::getListByType(Type type) const -> std::vector<std::string> {
} }
} }
// Ordenar alfabéticamente para garantizar orden consistente
std::sort(list.begin(), list.end());
return list; return list;
} }

View File

@@ -57,7 +57,7 @@ class Asset {
std::string executable_path_; // Ruta del ejecutable std::string executable_path_; // Ruta del ejecutable
// --- Métodos internos --- // --- Métodos internos ---
[[nodiscard]] auto checkFile(const std::string &path) const -> bool; // Verifica si un archivo existe [[nodiscard]] static auto checkFile(const std::string &path) -> bool; // Verifica si un archivo existe
[[nodiscard]] static auto getTypeName(Type type) -> std::string; // Obtiene el nombre del tipo [[nodiscard]] static auto getTypeName(Type type) -> std::string; // Obtiene el nombre del tipo
[[nodiscard]] static auto parseAssetType(const std::string &type_str) -> Type; // Convierte string a tipo [[nodiscard]] static auto parseAssetType(const std::string &type_str) -> Type; // Convierte string a tipo
void addToMap(const std::string &file_path, Type type, bool required, bool absolute); // Añade archivo al mapa void addToMap(const std::string &file_path, Type type, bool required, bool absolute); // Añade archivo al mapa

View File

@@ -4,7 +4,9 @@
#include <algorithm> // Para clamp #include <algorithm> // Para clamp
#ifndef NO_AUDIO
#include "external/jail_audio.h" // Para JA_FadeOutMusic, JA_Init, JA_PauseM... #include "external/jail_audio.h" // Para JA_FadeOutMusic, JA_Init, JA_PauseM...
#endif
#include "options.h" // Para AudioOptions, audio, MusicOptions #include "options.h" // Para AudioOptions, audio, MusicOptions
#include "resource.h" // Para Resource #include "resource.h" // Para Resource
@@ -25,7 +27,9 @@ Audio::Audio() { initSDLAudio(); }
// Destructor // Destructor
Audio::~Audio() { Audio::~Audio() {
#ifndef NO_AUDIO
JA_Quit(); JA_Quit();
#endif
} }
// Método principal // Método principal
@@ -39,7 +43,9 @@ void Audio::playMusic(const std::string &name, const int loop) {
music_.loop = (loop != 0); music_.loop = (loop != 0);
if (music_enabled_ && music_.state != MusicState::PLAYING) { if (music_enabled_ && music_.state != MusicState::PLAYING) {
#ifndef NO_AUDIO
JA_PlayMusic(Resource::get()->getMusic(name), loop); JA_PlayMusic(Resource::get()->getMusic(name), loop);
#endif
music_.state = MusicState::PLAYING; music_.state = MusicState::PLAYING;
} }
} }
@@ -47,7 +53,9 @@ void Audio::playMusic(const std::string &name, const int loop) {
// Pausa la música // Pausa la música
void Audio::pauseMusic() { void Audio::pauseMusic() {
if (music_enabled_ && music_.state == MusicState::PLAYING) { if (music_enabled_ && music_.state == MusicState::PLAYING) {
#ifndef NO_AUDIO
JA_PauseMusic(); JA_PauseMusic();
#endif
music_.state = MusicState::PAUSED; music_.state = MusicState::PAUSED;
} }
} }
@@ -55,7 +63,9 @@ void Audio::pauseMusic() {
// Continua la música pausada // Continua la música pausada
void Audio::resumeMusic() { void Audio::resumeMusic() {
if (music_enabled_ && music_.state == MusicState::PAUSED) { if (music_enabled_ && music_.state == MusicState::PAUSED) {
#ifndef NO_AUDIO
JA_ResumeMusic(); JA_ResumeMusic();
#endif
music_.state = MusicState::PLAYING; music_.state = MusicState::PLAYING;
} }
} }
@@ -63,7 +73,9 @@ void Audio::resumeMusic() {
// Detiene la música // Detiene la música
void Audio::stopMusic() { void Audio::stopMusic() {
if (music_enabled_) { if (music_enabled_) {
#ifndef NO_AUDIO
JA_StopMusic(); JA_StopMusic();
#endif
music_.state = MusicState::STOPPED; music_.state = MusicState::STOPPED;
} }
} }
@@ -71,37 +83,27 @@ void Audio::stopMusic() {
// Reproduce un sonido // Reproduce un sonido
void Audio::playSound(const std::string &name, Group group) const { void Audio::playSound(const std::string &name, Group group) const {
if (sound_enabled_) { if (sound_enabled_) {
#ifndef NO_AUDIO
JA_PlaySound(Resource::get()->getSound(name), 0, static_cast<int>(group)); JA_PlaySound(Resource::get()->getSound(name), 0, static_cast<int>(group));
#endif
} }
} }
// Detiene todos los sonidos // Detiene todos los sonidos
void Audio::stopAllSounds() const { void Audio::stopAllSounds() const {
if (sound_enabled_) { if (sound_enabled_) {
#ifndef NO_AUDIO
JA_StopChannel(-1); JA_StopChannel(-1);
#endif
} }
} }
// Realiza un fundido de salida de la música // Realiza un fundido de salida de la música
void Audio::fadeOutMusic(int milliseconds) const { void Audio::fadeOutMusic(int milliseconds) const {
if (music_enabled_ && getRealMusicState() == MusicState::PLAYING) { if (music_enabled_ && music_.state == MusicState::PLAYING) {
#ifndef NO_AUDIO
JA_FadeOutMusic(milliseconds); JA_FadeOutMusic(milliseconds);
} #endif
}
// Consulta directamente el estado real de la música en jailaudio
auto Audio::getRealMusicState() const -> MusicState {
JA_Music_state ja_state = JA_GetMusicState();
switch (ja_state) {
case JA_MUSIC_PLAYING:
return MusicState::PLAYING;
case JA_MUSIC_PAUSED:
return MusicState::PAUSED;
case JA_MUSIC_STOPPED:
case JA_MUSIC_INVALID:
case JA_MUSIC_DISABLED:
default:
return MusicState::STOPPED;
} }
} }
@@ -109,8 +111,10 @@ auto Audio::getRealMusicState() const -> MusicState {
void Audio::setSoundVolume(int sound_volume, Group group) const { void Audio::setSoundVolume(int sound_volume, Group group) const {
if (sound_enabled_) { if (sound_enabled_) {
sound_volume = std::clamp(sound_volume, MIN_VOLUME, MAX_VOLUME); sound_volume = std::clamp(sound_volume, MIN_VOLUME, MAX_VOLUME);
#ifndef NO_AUDIO
const float CONVERTED_VOLUME = (sound_volume / 100.0F) * (Options::audio.volume / 100.0F); const float CONVERTED_VOLUME = (sound_volume / 100.0F) * (Options::audio.volume / 100.0F);
JA_SetSoundVolume(CONVERTED_VOLUME, static_cast<int>(group)); JA_SetSoundVolume(CONVERTED_VOLUME, static_cast<int>(group));
#endif
} }
} }
@@ -118,8 +122,10 @@ void Audio::setSoundVolume(int sound_volume, Group group) const {
void Audio::setMusicVolume(int music_volume) const { void Audio::setMusicVolume(int music_volume) const {
if (music_enabled_) { if (music_enabled_) {
music_volume = std::clamp(music_volume, MIN_VOLUME, MAX_VOLUME); music_volume = std::clamp(music_volume, MIN_VOLUME, MAX_VOLUME);
#ifndef NO_AUDIO
const float CONVERTED_VOLUME = (music_volume / 100.0F) * (Options::audio.volume / 100.0F); const float CONVERTED_VOLUME = (music_volume / 100.0F) * (Options::audio.volume / 100.0F);
JA_SetMusicVolume(CONVERTED_VOLUME); JA_SetMusicVolume(CONVERTED_VOLUME);
#endif
} }
} }
@@ -138,6 +144,7 @@ void Audio::enable(bool value) {
// Inicializa SDL Audio // Inicializa SDL Audio
void Audio::initSDLAudio() { void Audio::initSDLAudio() {
#ifndef NO_AUDIO
if (!SDL_Init(SDL_INIT_AUDIO)) { if (!SDL_Init(SDL_INIT_AUDIO)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_AUDIO could not initialize! SDL Error: %s", SDL_GetError()); SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_AUDIO could not initialize! SDL Error: %s", SDL_GetError());
} else { } else {
@@ -146,4 +153,7 @@ void Audio::initSDLAudio() {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "** Audio system initialized successfully"); SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "** Audio system initialized successfully");
} }
#else
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "** Audio system disabled");
#endif
} }

View File

@@ -13,12 +13,6 @@ class Audio {
INTERFACE = 1 // Sonidos de la interfaz INTERFACE = 1 // Sonidos de la interfaz
}; };
enum class MusicState {
PLAYING, // Reproduciendo música
PAUSED, // Música pausada
STOPPED, // Música detenida
};
// --- Constantes --- // --- Constantes ---
static constexpr int MAX_VOLUME = 100; // Volumen máximo static constexpr int MAX_VOLUME = 100; // Volumen máximo
static constexpr int MIN_VOLUME = 0; // Volumen mínimo static constexpr int MIN_VOLUME = 0; // Volumen mínimo
@@ -66,15 +60,14 @@ class Audio {
void setSoundVolume(int volume, Group group = Group::ALL) const; // Ajustar volumen de efectos void setSoundVolume(int volume, Group group = Group::ALL) const; // Ajustar volumen de efectos
void setMusicVolume(int volume) const; // Ajustar volumen de música void setMusicVolume(int volume) const; // Ajustar volumen de música
// --- Getters para debug ---
bool isEnabled() const { return enabled_; }
bool isSoundEnabled() const { return sound_enabled_; }
bool isMusicEnabled() const { return music_enabled_; }
MusicState getMusicState() const { return music_.state; }
MusicState getRealMusicState() const; // Consulta directamente a jailaudio
const std::string& getCurrentMusicName() const { return music_.name; }
private: private:
// --- Enums privados ---
enum class MusicState {
PLAYING, // Reproduciendo música
PAUSED, // Música pausada
STOPPED, // Música detenida
};
// --- Estructuras privadas --- // --- Estructuras privadas ---
struct Music { struct Music {
MusicState state; // Estado actual de la música (reproduciendo, detenido, en pausa) MusicState state; // Estado actual de la música (reproduciendo, detenido, en pausa)

View File

@@ -13,7 +13,6 @@
#include "screen.h" // Para Screen #include "screen.h" // Para Screen
#include "sprite.h" // Para Sprite #include "sprite.h" // Para Sprite
#include "texture.h" // Para Texture #include "texture.h" // Para Texture
#include "utils.h" // Para funciones de easing
// Constructor // Constructor
Background::Background(float total_progress_to_complete) Background::Background(float total_progress_to_complete)
@@ -30,7 +29,6 @@ Background::Background(float total_progress_to_complete)
total_progress_to_complete_(total_progress_to_complete), total_progress_to_complete_(total_progress_to_complete),
progress_per_stage_(total_progress_to_complete_ / STAGES), progress_per_stage_(total_progress_to_complete_ / STAGES),
sun_completion_progress_(total_progress_to_complete_ * SUN_COMPLETION_FACTOR), sun_completion_progress_(total_progress_to_complete_ * SUN_COMPLETION_FACTOR),
minimum_completed_progress_(total_progress_to_complete_ * MINIMUM_COMPLETED_PROGRESS_PERCENTAGE),
rect_(SDL_FRect{0, 0, static_cast<float>(gradients_texture_->getWidth() / 2), static_cast<float>(gradients_texture_->getHeight() / 2)}), rect_(SDL_FRect{0, 0, static_cast<float>(gradients_texture_->getWidth() / 2), static_cast<float>(gradients_texture_->getHeight() / 2)}),
src_rect_({.x = 0, .y = 0, .w = 320, .h = 240}), src_rect_({.x = 0, .y = 0, .w = 320, .h = 240}),
@@ -95,21 +93,20 @@ void Background::initializeSprites() {
// Configura las propiedades iniciales de los sprites // Configura las propiedades iniciales de los sprites
void Background::initializeSpriteProperties() { void Background::initializeSpriteProperties() {
// Velocidades iniciales que coinciden con updateCloudsSpeed() cuando progress=0 constexpr float TOP_CLOUDS_SPEED = 0.1F;
constexpr float INITIAL_TOP_CLOUDS_SPEED_PX_PER_S = 0.05F * 60.0F; // 3.0 píxeles/segundo (coincide con CLOUDS_INITIAL_SPEED) constexpr float BOTTOM_CLOUDS_SPEED = 0.05F;
constexpr float INITIAL_BOTTOM_CLOUDS_SPEED_PX_PER_S = 0.05F * 60.0F / 2.0F; // 1.5 píxeles/segundo (mitad de velocidad)
top_clouds_sprite_a_->setSpriteClip(0, 0, top_clouds_texture_->getWidth(), top_clouds_texture_->getHeight()); top_clouds_sprite_a_->setSpriteClip(0, 0, top_clouds_texture_->getWidth(), top_clouds_texture_->getHeight());
top_clouds_sprite_a_->setVelX(-INITIAL_TOP_CLOUDS_SPEED_PX_PER_S); top_clouds_sprite_a_->setVelX(-TOP_CLOUDS_SPEED);
top_clouds_sprite_b_->setSpriteClip(0, 0, top_clouds_texture_->getWidth(), top_clouds_texture_->getHeight()); top_clouds_sprite_b_->setSpriteClip(0, 0, top_clouds_texture_->getWidth(), top_clouds_texture_->getHeight());
top_clouds_sprite_b_->setVelX(-INITIAL_TOP_CLOUDS_SPEED_PX_PER_S); top_clouds_sprite_b_->setVelX(-TOP_CLOUDS_SPEED);
bottom_clouds_sprite_a_->setSpriteClip(0, 0, bottom_clouds_texture_->getWidth(), bottom_clouds_texture_->getHeight()); bottom_clouds_sprite_a_->setSpriteClip(0, 0, bottom_clouds_texture_->getWidth(), bottom_clouds_texture_->getHeight());
bottom_clouds_sprite_a_->setVelX(-INITIAL_BOTTOM_CLOUDS_SPEED_PX_PER_S); bottom_clouds_sprite_a_->setVelX(-BOTTOM_CLOUDS_SPEED);
bottom_clouds_sprite_b_->setSpriteClip(0, 0, bottom_clouds_texture_->getWidth(), bottom_clouds_texture_->getHeight()); bottom_clouds_sprite_b_->setSpriteClip(0, 0, bottom_clouds_texture_->getWidth(), bottom_clouds_texture_->getHeight());
bottom_clouds_sprite_b_->setVelX(-INITIAL_BOTTOM_CLOUDS_SPEED_PX_PER_S); bottom_clouds_sprite_b_->setVelX(-BOTTOM_CLOUDS_SPEED);
buildings_sprite_->setY(base_ - buildings_sprite_->getHeight()); buildings_sprite_->setY(base_ - buildings_sprite_->getHeight());
grass_sprite_->setY(base_ - grass_sprite_->getHeight()); grass_sprite_->setY(base_ - grass_sprite_->getHeight());
@@ -129,24 +126,20 @@ void Background::initializeTextures() {
} }
// Actualiza la lógica del objeto // Actualiza la lógica del objeto
void Background::update(float delta_time) { void Background::update() {
// Actualiza la progresión y calcula transiciones // Actualiza la progresión y calcula transiciones
if (!manual_mode_) { if (!manual_mode_) {
updateProgression(delta_time); updateProgression();
} }
// Actualiza el valor de alpha // Actualiza el valor de alpha
updateAlphaColorTexture(); updateAlphaColorTexture();
// Actualiza las nubes // Actualiza las nubes
updateClouds(delta_time); updateClouds();
// Actualiza timer de hierba // Calcula el frame de la hierba
grass_timer_ += delta_time; grass_sprite_->setSpriteClip(0, (10 * (counter_ / 20 % 2)), 320, 10);
// Calcula el frame de la hierba (alterna cada GRASS_FRAME_DURATION ms)
int grass_frame = static_cast<int>(grass_timer_ / GRASS_FRAME_DURATION) % 2;
grass_sprite_->setSpriteClip(0, (10 * grass_frame), 320, 10);
// Calcula el valor de alpha // Calcula el valor de alpha
alpha_ = std::max((255 - (int)(255 * transition_)), 0); alpha_ = std::max((255 - (int)(255 * transition_)), 0);
@@ -155,6 +148,9 @@ void Background::update(float delta_time) {
sun_sprite_->setPosition(sun_path_.at(sun_index_)); sun_sprite_->setPosition(sun_path_.at(sun_index_));
moon_sprite_->setPosition(moon_path_.at(moon_index_)); moon_sprite_->setPosition(moon_path_.at(moon_index_));
// Incrementa el contador
++counter_;
// Compone todos los elementos del fondo en la textura // Compone todos los elementos del fondo en la textura
fillCanvas(); fillCanvas();
} }
@@ -186,12 +182,6 @@ void Background::setProgress(float absolute_progress) {
// Cambia el estado del fondo // Cambia el estado del fondo
void Background::setState(State new_state) { void Background::setState(State new_state) {
// Si entra en estado completado, inicializar variables de transición
if (new_state == State::COMPLETED && state_ != State::COMPLETED) {
completion_initial_progress_ = progress_;
completion_transition_timer_ = 0.0f;
}
state_ = new_state; state_ = new_state;
} }
@@ -206,10 +196,6 @@ void Background::reset() {
sun_index_ = 0; sun_index_ = 0;
moon_index_ = 0; moon_index_ = 0;
// Resetear variables de transición de completado
completion_transition_timer_ = 0.0f;
completion_initial_progress_ = 0.0f;
// Notifica el cambio si hay callback // Notifica el cambio si hay callback
if (progress_callback_ && progress_ != old_progress) { if (progress_callback_ && progress_ != old_progress) {
progress_callback_(progress_); progress_callback_(progress_);
@@ -266,24 +252,13 @@ void Background::setMoonProgression(float progress) {
} }
// Actualiza la progresión y calcula las transiciones // Actualiza la progresión y calcula las transiciones
void Background::updateProgression(float delta_time) { void Background::updateProgression() {
// Si el juego está completado, hacer transición suave con easing // Si el juego está completado, reduce la progresión gradualmente
if (state_ == State::COMPLETED) { if (state_ == State::COMPLETED) {
completion_transition_timer_ += delta_time; if (progress_ > MINIMUM_COMPLETED_PROGRESS) {
progress_ -= COMPLETED_REDUCTION_RATE;
// Calcular progreso normalizado de la transición (0.0 a 1.0)
float t = std::min(completion_transition_timer_ / COMPLETION_TRANSITION_DURATION_S, 1.0f);
if (t < 1.0f) {
// Usar easeOutCubic para transición suave (rápido al inicio, lento al final)
float eased_t = easeOutCubic(static_cast<double>(t));
// Interpolación desde progreso inicial hasta mínimo
float progress_range = completion_initial_progress_ - minimum_completed_progress_;
progress_ = completion_initial_progress_ - (progress_range * eased_t);
} else { } else {
// Transición completada, fijar al valor mínimo progress_ = MINIMUM_COMPLETED_PROGRESS;
progress_ = minimum_completed_progress_;
} }
} }
@@ -308,19 +283,18 @@ void Background::updateProgression(float delta_time) {
// Actualiza la velocidad de las nubes según el estado y progresión // Actualiza la velocidad de las nubes según el estado y progresión
void Background::updateCloudsSpeed() { void Background::updateCloudsSpeed() {
// Cálculo de velocidad según progreso (convertido de frame-based a time-based) // Cálculo de velocidad según progreso
constexpr float CLOUDS_INITIAL_SPEED_PX_PER_S = 0.05F * 60.0F; // 3.0 píxeles/segundo (era 0.05 px/frame @ 60fps) constexpr float CLOUDS_INITIAL_SPEED = 0.05F;
constexpr float CLOUDS_TOTAL_SPEED_PX_PER_S = 2.00F * 60.0F; // 120.0 píxeles/segundo (era 2.00 px/frame @ 60fps) constexpr float CLOUDS_FINAL_SPEED = 2.00F - CLOUDS_INITIAL_SPEED;
constexpr float CLOUDS_FINAL_SPEED_RANGE_PX_PER_S = CLOUDS_TOTAL_SPEED_PX_PER_S - CLOUDS_INITIAL_SPEED_PX_PER_S; // 117.0 píxeles/segundo
// Velocidad base según progreso (de -3.0 a -120.0 píxeles/segundo, igual que la versión original) // Velocidad base según progreso (de -0.05 a -2.00)
float base_clouds_speed = (-CLOUDS_INITIAL_SPEED_PX_PER_S) + float base_clouds_speed = (-CLOUDS_INITIAL_SPEED) +
(-CLOUDS_FINAL_SPEED_RANGE_PX_PER_S * (progress_ / total_progress_to_complete_)); (-CLOUDS_FINAL_SPEED * (progress_ / total_progress_to_complete_));
// En estado completado, las nubes se ralentizan gradualmente // En estado completado, las nubes se ralentizan gradualmente
if (state_ == State::COMPLETED) { if (state_ == State::COMPLETED) {
float completion_factor = (progress_ - minimum_completed_progress_) / float completion_factor = (progress_ - MINIMUM_COMPLETED_PROGRESS) /
(total_progress_to_complete_ - minimum_completed_progress_); (total_progress_to_complete_ - MINIMUM_COMPLETED_PROGRESS);
completion_factor = std::max(0.1F, completion_factor); completion_factor = std::max(0.1F, completion_factor);
base_clouds_speed *= completion_factor; base_clouds_speed *= completion_factor;
} }
@@ -340,12 +314,12 @@ void Background::updateCloudsSpeed() {
} }
// Actualiza las nubes // Actualiza las nubes
void Background::updateClouds(float deltaTime) { void Background::updateClouds() {
// Mueve las nubes // Mueve las nubes
top_clouds_sprite_a_->update(deltaTime); top_clouds_sprite_a_->update();
top_clouds_sprite_b_->update(deltaTime); top_clouds_sprite_b_->update();
bottom_clouds_sprite_a_->update(deltaTime); bottom_clouds_sprite_a_->update();
bottom_clouds_sprite_b_->update(deltaTime); bottom_clouds_sprite_b_->update();
// Calcula el offset de las nubes // Calcula el offset de las nubes
if (top_clouds_sprite_a_->getPosX() < -top_clouds_sprite_a_->getWidth()) { if (top_clouds_sprite_a_->getPosX() < -top_clouds_sprite_a_->getWidth()) {

View File

@@ -31,7 +31,7 @@ class Background {
~Background(); // Destructor ~Background(); // Destructor
// --- Métodos principales --- // --- Métodos principales ---
void update(float delta_time); // Actualiza la lógica del objeto void update(); // Actualiza la lógica del objeto
void render(); // Dibuja el objeto void render(); // Dibuja el objeto
void reset(); // Reinicia la progresión void reset(); // Reinicia la progresión
@@ -61,9 +61,9 @@ class Background {
private: private:
// --- Constantes --- // --- Constantes ---
static constexpr size_t STAGES = 4; // Número de etapas static constexpr size_t STAGES = 4; // Número de etapas
static constexpr float MINIMUM_COMPLETED_PROGRESS_PERCENTAGE = 0.05F; // Porcentaje mínimo completado (10%) static constexpr float COMPLETED_REDUCTION_RATE = 25.0F; // Tasa de reducción completada
static constexpr float MINIMUM_COMPLETED_PROGRESS = 200.0F; // Progreso mínimo completado
static constexpr float SUN_COMPLETION_FACTOR = 0.5F; // Factor de completado del sol static constexpr float SUN_COMPLETION_FACTOR = 0.5F; // Factor de completado del sol
static constexpr float COMPLETION_TRANSITION_DURATION_S = 3.0F; // Duración de la transición de completado en segundos
// --- Objetos y punteros --- // --- Objetos y punteros ---
SDL_Renderer *renderer_; // Renderizador de la ventana SDL_Renderer *renderer_; // Renderizador de la ventana
@@ -90,7 +90,6 @@ class Background {
const float total_progress_to_complete_; // Progreso total para completar const float total_progress_to_complete_; // Progreso total para completar
const float progress_per_stage_; // Progreso por etapa const float progress_per_stage_; // Progreso por etapa
const float sun_completion_progress_; // Progreso de completado del sol const float sun_completion_progress_; // Progreso de completado del sol
const float minimum_completed_progress_; // Progreso mínimo calculado dinámicamente
ProgressCallback progress_callback_; // Callback para notificar cambios de progreso ProgressCallback progress_callback_; // Callback para notificar cambios de progreso
// --- Variables de estado --- // --- Variables de estado ---
@@ -108,8 +107,7 @@ class Background {
float clouds_speed_ = 0; // Velocidad de las nubes float clouds_speed_ = 0; // Velocidad de las nubes
float transition_ = 0; // Porcentaje de transición float transition_ = 0; // Porcentaje de transición
size_t gradient_number_ = 0; // Índice de fondo degradado size_t gradient_number_ = 0; // Índice de fondo degradado
float grass_timer_ = 0.0f; // Timer para animación de hierba (ms) size_t counter_ = 0; // Contador interno
static constexpr float GRASS_FRAME_DURATION = 333.34f; // Duración por frame de hierba (20 frames * 16.67ms)
size_t alpha_color_texture_ = 0; // Transparencia de atenuación size_t alpha_color_texture_ = 0; // Transparencia de atenuación
size_t previous_alpha_color_texture_ = 0; // Transparencia anterior size_t previous_alpha_color_texture_ = 0; // Transparencia anterior
size_t sun_index_ = 0; // Índice del recorrido del sol size_t sun_index_ = 0; // Índice del recorrido del sol
@@ -118,24 +116,20 @@ class Background {
Uint8 alpha_ = 0; // Transparencia entre fases Uint8 alpha_ = 0; // Transparencia entre fases
bool manual_mode_ = false; // Si está en modo manual bool manual_mode_ = false; // Si está en modo manual
// --- Variables para transición suave de completado ---
float completion_transition_timer_ = 0.0f; // Timer para la transición de completado
float completion_initial_progress_ = 0.0f; // Progreso inicial al entrar en estado completado
// --- Métodos internos --- // --- Métodos internos ---
void initializePaths(); // Inicializa las rutas del sol y la luna void initializePaths(); // Inicializa las rutas del sol y la luna
void initializeRects(); // Inicializa los rectángulos de gradientes y nubes void initializeRects(); // Inicializa los rectángulos de gradientes y nubes
void initializeSprites(); // Crea los sprites void initializeSprites(); // Crea los sprites
void initializeSpriteProperties(); // Configura las propiedades iniciales de los sprites void initializeSpriteProperties(); // Configura las propiedades iniciales de los sprites
void initializeTextures(); // Inicializa las texturas de renderizado void initializeTextures(); // Inicializa las texturas de renderizado
void updateProgression(float delta_time); // Actualiza la progresión y calcula transiciones void updateProgression(); // Actualiza la progresión y calcula transiciones
void updateCloudsSpeed(); // Actualiza la velocidad de las nubes según el estado void updateCloudsSpeed(); // Actualiza la velocidad de las nubes según el estado
void renderGradient(); // Dibuja el gradiente de fondo void renderGradient(); // Dibuja el gradiente de fondo
void renderTopClouds(); // Dibuja las nubes superiores void renderTopClouds(); // Dibuja las nubes superiores
void renderBottomClouds(); // Dibuja las nubes inferiores void renderBottomClouds(); // Dibuja las nubes inferiores
void fillCanvas(); // Compone todos los elementos en la textura void fillCanvas(); // Compone todos los elementos en la textura
void updateAlphaColorTexture(); // Actualiza el alpha de la textura de atenuación void updateAlphaColorTexture(); // Actualiza el alpha de la textura de atenuación
void updateClouds(float deltaTime); // Actualiza el movimiento de las nubes (time-based) void updateClouds(); // Actualiza el movimiento de las nubes
void createSunPath(); // Precalcula el recorrido del sol void createSunPath(); // Precalcula el recorrido del sol
void createMoonPath(); // Precalcula el recorrido de la luna void createMoonPath(); // Precalcula el recorrido de la luna
}; };

View File

@@ -23,13 +23,13 @@ Balloon::Balloon(const Config& config)
creation_counter_ini_(config.creation_counter), creation_counter_ini_(config.creation_counter),
type_(config.type), type_(config.type),
size_(config.size), size_(config.size),
game_tempo_(config.game_tempo), speed_(config.speed),
play_area_(config.play_area), play_area_(config.play_area),
sound_(config.sound) { sound_(config.sound) {
switch (type_) { switch (type_) {
case Type::BALLOON: { case Type::BALLOON: {
vy_ = 0; vy_ = 0;
max_vy_ = 3.0F * 60.0F; // Convert from frames to seconds (180 pixels/s) max_vy_ = 3.0F;
const int INDEX = static_cast<int>(size_); const int INDEX = static_cast<int>(size_);
gravity_ = param.balloon.settings.at(INDEX).grav; gravity_ = param.balloon.settings.at(INDEX).grav;
@@ -65,12 +65,12 @@ Balloon::Balloon(const Config& config)
power_ = score_ = menace_ = 0; power_ = score_ = menace_ = 0;
vy_ = 0; vy_ = 0;
max_vy_ = 3.0F * 60.0F; // Convert from frames to seconds (180 pixels/s) max_vy_ = 3.0F;
gravity_ = param.balloon.settings.at(INDEX).grav; gravity_ = param.balloon.settings.at(INDEX).grav;
default_vy_ = param.balloon.settings.at(INDEX).vel; default_vy_ = param.balloon.settings.at(INDEX).vel;
sprite_->setRotate(config.creation_counter <= 0); sprite_->setRotate(config.creation_counter <= 0);
sprite_->setRotateAmount(vx_ > 0.0F ? 120.0 : -120.0); // Convert from 2 degrees/frame to 120 degrees/second sprite_->setRotateAmount(vx_ > 0.0F ? 2.0 : -2.0);
break; break;
} }
@@ -89,12 +89,6 @@ Balloon::Balloon(const Config& config)
// Establece la animación a usar // Establece la animación a usar
setAnimation(); setAnimation();
// Si no se está creando (creation_counter = 0), asegurar estado activo
if (!being_created_) {
start();
setInvulnerable(false);
}
} }
// Centra el globo en la posición X // Centra el globo en la posición X
@@ -141,20 +135,19 @@ void Balloon::render() {
} }
} }
// Actualiza la posición y estados del globo (time-based) // Actualiza la posición y estados del globo
void Balloon::move(float deltaTime) { void Balloon::move() {
if (isStopped()) { if (isStopped()) {
return; return;
} }
handleHorizontalMovement(deltaTime); handleHorizontalMovement();
handleVerticalMovement(deltaTime); handleVerticalMovement();
applyGravity(deltaTime); applyGravity();
} }
void Balloon::handleHorizontalMovement(float deltaTime) { void Balloon::handleHorizontalMovement() {
// DeltaTime en segundos: velocidad (pixels/s) * tempo * tiempo (s) x_ += vx_ * speed_;
x_ += vx_ * game_tempo_ * deltaTime;
const int CLIP = 2; const int CLIP = 2;
const float MIN_X = play_area_.x - CLIP; const float MIN_X = play_area_.x - CLIP;
@@ -165,9 +158,8 @@ void Balloon::handleHorizontalMovement(float deltaTime) {
} }
} }
void Balloon::handleVerticalMovement(float deltaTime) { void Balloon::handleVerticalMovement() {
// DeltaTime en segundos: velocidad (pixels/s) * tempo * tiempo (s) y_ += vy_ * speed_;
y_ += vy_ * game_tempo_ * deltaTime;
if (shouldCheckTopCollision()) { if (shouldCheckTopCollision()) {
handleTopCollision(); handleTopCollision();
@@ -222,9 +214,20 @@ void Balloon::handleBottomCollision() {
} }
} }
void Balloon::applyGravity(float deltaTime) { void Balloon::applyGravity() {
// DeltaTime en segundos: aceleración (pixels/s²) * tempo * tiempo (s) /*
vy_ += gravity_ * game_tempo_ * deltaTime; Para aplicar la gravedad, el diseño original la aplicaba en cada iteración del bucle
Al añadir el modificador de velocidad se reduce la distancia que recorre el objeto y por
tanto recibe mas gravedad. Para solucionarlo se va a aplicar la gravedad cuando se haya
recorrido una distancia igual a la velocidad en Y, que era el cálculo inicial
*/
travel_y_ += speed_;
if (travel_y_ >= 1.0F) {
travel_y_ -= 1.0F;
vy_ += gravity_;
}
} }
void Balloon::playBouncingSound() { void Balloon::playBouncingSound() {
@@ -239,20 +242,19 @@ void Balloon::playPoppingSound() {
} }
} }
// Actualiza al globo a su posicion, animación y controla los contadores (time-based) // Actualiza al globo a su posicion, animación y controla los contadores
void Balloon::update(float deltaTime) { void Balloon::update() {
move(deltaTime); move();
updateState(deltaTime); updateState();
updateBounceEffect(); updateBounceEffect();
shiftSprite(); shiftSprite();
shiftColliders(); shiftColliders();
sprite_->update(deltaTime); sprite_->update();
// Contador interno con deltaTime en segundos ++counter_;
counter_ += deltaTime;
} }
// Actualiza los estados del globo (time-based) // Actualiza los estados del globo
void Balloon::updateState(float deltaTime) { void Balloon::updateState() {
// Si se está creando // Si se está creando
if (isBeingCreated()) { if (isBeingCreated()) {
// Actualiza el valor de las variables // Actualiza el valor de las variables
@@ -261,14 +263,9 @@ void Balloon::updateState(float deltaTime) {
if (creation_counter_ > 0) { if (creation_counter_ > 0) {
// Desplaza lentamente el globo hacia abajo y hacia un lado // Desplaza lentamente el globo hacia abajo y hacia un lado
// Cada 10/60 segundos (equivalente a 10 frames a 60fps) if (creation_counter_ % 10 == 0) {
movement_accumulator_ += deltaTime;
constexpr float MOVEMENT_INTERVAL_S = 10.0f / 60.0f; // 10 frames = ~0.167s
if (movement_accumulator_ >= MOVEMENT_INTERVAL_S) {
movement_accumulator_ -= MOVEMENT_INTERVAL_S;
y_++; y_++;
x_ += vx_ / 60.0f; // Convierte de pixels/segundo a pixels/frame para movimiento discreto x_ += vx_;
// Comprueba no se salga por los laterales // Comprueba no se salga por los laterales
const int MIN_X = play_area_.x; const int MIN_X = play_area_.x;
@@ -276,12 +273,11 @@ void Balloon::updateState(float deltaTime) {
if (x_ < MIN_X || x_ > MAX_X) { if (x_ < MIN_X || x_ > MAX_X) {
// Corrige y cambia el sentido de la velocidad // Corrige y cambia el sentido de la velocidad
x_ -= vx_ / 60.0f; x_ -= vx_;
vx_ = -vx_; vx_ = -vx_;
} }
} }
creation_counter_ -= deltaTime; --creation_counter_;
if (creation_counter_ < 0) creation_counter_ = 0;
} }
else { else {
@@ -315,14 +311,11 @@ void Balloon::setAnimation() {
} }
// Establece el frame de animación // Establece el frame de animación
std::string chosen_animation;
if (use_reversed_colors_) { if (use_reversed_colors_) {
chosen_animation = creating_animation; sprite_->setCurrentAnimation(creating_animation);
} else { } else {
chosen_animation = isBeingCreated() ? creating_animation : normal_animation; sprite_->setCurrentAnimation(isBeingCreated() ? creating_animation : normal_animation);
} }
sprite_->setCurrentAnimation(chosen_animation);
} }
// Detiene el globo // Detiene el globo

View File

@@ -36,12 +36,10 @@ class Balloon {
"balloon_pop2.wav", "balloon_pop2.wav",
"balloon_pop3.wav"}; "balloon_pop3.wav"};
// Velocidades horizontales en pixels/segundo (convertidas desde 0.7 pixels/frame a 60fps) static constexpr float VELX_POSITIVE = 0.7F;
static constexpr float VELX_POSITIVE = 0.7F * 60.0F; // 42 pixels/segundo static constexpr float VELX_NEGATIVE = -0.7F;
static constexpr float VELX_NEGATIVE = -0.7F * 60.0F; // -42 pixels/segundo
// Multiplicadores de tempo del juego (sin cambios, son puros multiplicadores) static constexpr std::array<float, 5> SPEED = {0.60F, 0.70F, 0.80F, 0.90F, 1.00F};
static constexpr std::array<float, 5> GAME_TEMPO = {0.60F, 0.70F, 0.80F, 0.90F, 1.00F};
static constexpr int POWERBALL_SCREENPOWER_MINIMUM = 10; static constexpr int POWERBALL_SCREENPOWER_MINIMUM = 10;
static constexpr int POWERBALL_COUNTER = 8; static constexpr int POWERBALL_COUNTER = 8;
@@ -76,8 +74,8 @@ class Balloon {
Type type = Type::BALLOON; Type type = Type::BALLOON;
Size size = Size::EXTRALARGE; Size size = Size::EXTRALARGE;
float vel_x = VELX_POSITIVE; float vel_x = VELX_POSITIVE;
float game_tempo = GAME_TEMPO.at(0); float speed = SPEED.at(0);
float creation_counter = 0.0f; Uint16 creation_counter = 0;
SDL_FRect play_area = {.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F}; SDL_FRect play_area = {.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F};
std::shared_ptr<Texture> texture = nullptr; std::shared_ptr<Texture> texture = nullptr;
std::vector<std::string> animation; std::vector<std::string> animation;
@@ -91,8 +89,8 @@ class Balloon {
// --- Métodos principales --- // --- Métodos principales ---
void alignTo(int x); // Centra el globo en la posición X void alignTo(int x); // Centra el globo en la posición X
void render(); // Pinta el globo en la pantalla void render(); // Pinta el globo en la pantalla
void move(float deltaTime); // Actualiza la posición y estados del globo (time-based) void move(); // Actualiza la posición y estados del globo
void update(float deltaTime); // Actualiza el globo (posición, animación, contadores) (time-based) void update(); // Actualiza el globo (posición, animación, contadores)
void stop(); // Detiene el globo void stop(); // Detiene el globo
void start(); // Pone el globo en movimiento void start(); // Pone el globo en movimiento
void pop(bool should_sound = false); // Explota el globo void pop(bool should_sound = false); // Explota el globo
@@ -122,9 +120,7 @@ class Balloon {
// --- Setters --- // --- Setters ---
void setVelY(float vel_y) { vy_ = vel_y; } void setVelY(float vel_y) { vy_ = vel_y; }
void setVelX(float vel_x) { vx_ = vel_x; } void setSpeed(float speed) { speed_ = speed; }
void alterVelX(float percent) {vx_ *= percent; }
void setGameTempo(float tempo) { game_tempo_ = tempo; }
void setInvulnerable(bool value) { invulnerable_ = value; } void setInvulnerable(bool value) { invulnerable_ = value; }
void setBouncingSound(bool value) { sound_.bouncing_enabled = value; } void setBouncingSound(bool value) { sound_.bouncing_enabled = value; }
void setPoppingSound(bool value) { sound_.poping_enabled = value; } void setPoppingSound(bool value) { sound_.poping_enabled = value; }
@@ -260,15 +256,15 @@ class Balloon {
bool stopped_; // Si el globo está parado bool stopped_; // Si el globo está parado
bool use_reversed_colors_ = false; // Si se usa el color alternativo bool use_reversed_colors_ = false; // Si se usa el color alternativo
Circle collider_; // Círculo de colisión Circle collider_; // Círculo de colisión
float creation_counter_; // Temporizador de creación Uint16 creation_counter_; // Temporizador de creación
float creation_counter_ini_; // Valor inicial del temporizador de creación Uint16 creation_counter_ini_; // Valor inicial del temporizador de creación
Uint16 score_; // Puntos al destruir el globo Uint16 score_; // Puntos al destruir el globo
Type type_; // Tipo de globo Type type_; // Tipo de globo
Size size_; // Tamaño de globo Size size_; // Tamaño de globo
Uint8 menace_; // Amenaza que genera el globo Uint8 menace_; // Amenaza que genera el globo
Uint32 counter_ = 0; // Contador interno Uint32 counter_ = 0; // Contador interno
float game_tempo_; // Multiplicador de tempo del juego float travel_y_ = 1.0F; // Distancia a recorrer en Y antes de aplicar gravedad
float movement_accumulator_ = 0.0f; // Acumulador para movimiento durante creación (deltaTime) float speed_; // Velocidad del globo
Uint8 power_; // Poder que alberga el globo Uint8 power_; // Poder que alberga el globo
SDL_FRect play_area_; // Zona de movimiento del globo SDL_FRect play_area_; // Zona de movimiento del globo
Sound sound_; // Configuración de sonido del globo Sound sound_; // Configuración de sonido del globo
@@ -284,9 +280,9 @@ class Balloon {
void playPoppingSound(); // Reproduce el sonido de reventar void playPoppingSound(); // Reproduce el sonido de reventar
// --- Movimiento y física --- // --- Movimiento y física ---
void handleHorizontalMovement(float deltaTime); // Maneja el movimiento horizontal (time-based) void handleHorizontalMovement(); // Maneja el movimiento horizontal
void handleVerticalMovement(float deltaTime); // Maneja el movimiento vertical (time-based) void handleVerticalMovement(); // Maneja el movimiento vertical
void applyGravity(float deltaTime); // Aplica la gravedad al objeto (time-based) void applyGravity(); // Aplica la gravedad al objeto
// --- Rebote --- // --- Rebote ---
void enableBounceEffect(); // Activa el efecto de rebote void enableBounceEffect(); // Activa el efecto de rebote
@@ -301,5 +297,5 @@ class Balloon {
void handleBottomCollision(); // Maneja la colisión inferior void handleBottomCollision(); // Maneja la colisión inferior
// --- Lógica de estado --- // --- Lógica de estado ---
void updateState(float deltaTime); // Actualiza los estados del globo (time-based) void updateState(); // Actualiza los estados del globo
}; };

View File

@@ -155,7 +155,7 @@ auto BalloonFormations::parseBalloonLine(const std::string& line, const std::map
return std::nullopt; return std::nullopt;
} }
float creation_time = CREATION_TIME + evaluateExpression(tokens.at(6), variables); // Base time + offset from formations.txt int creation_time = DEFAULT_CREATION_TIME + evaluateExpression(tokens.at(6), variables);
return SpawnParams(x + offset, y, vel_x, type, size, creation_time); return SpawnParams(x + offset, y, vel_x, type, size, creation_time);
} catch (const std::exception&) { } catch (const std::exception&) {
@@ -168,7 +168,7 @@ auto BalloonFormations::evaluateExpression(const std::string& expr, const std::m
// Si es un número directo // Si es un número directo
if ((std::isdigit(trimmed_expr.at(0)) != 0) || (trimmed_expr.at(0) == '-' && trimmed_expr.length() > 1)) { if ((std::isdigit(trimmed_expr.at(0)) != 0) || (trimmed_expr.at(0) == '-' && trimmed_expr.length() > 1)) {
return std::stof(trimmed_expr); return std::stoi(trimmed_expr);
} }
// Si es una variable simple // Si es una variable simple
@@ -205,7 +205,7 @@ auto BalloonFormations::evaluateSimpleExpression(const std::string& expr, const
} }
// Si no se encuentra operador, intentar como variable o número // Si no se encuentra operador, intentar como variable o número
return variables.find(expr) != variables.end() ? variables.at(expr) : std::stof(expr); return variables.find(expr) != variables.end() ? variables.at(expr) : std::stoi(expr);
} }
auto BalloonFormations::trim(const std::string& str) -> std::string { auto BalloonFormations::trim(const std::string& str) -> std::string {
@@ -235,10 +235,10 @@ void BalloonFormations::createFloaterVariants() {
#ifdef _DEBUG #ifdef _DEBUG
void BalloonFormations::addTestFormation() { void BalloonFormations::addTestFormation() {
std::vector<SpawnParams> test_params = { std::vector<SpawnParams> test_params = {
{10, -BLOCK, 0, Balloon::Type::FLOATER, Balloon::Size::SMALL, 3.334f}, // 200 frames ÷ 60fps = 3.334s {10, -BLOCK, 0, Balloon::Type::FLOATER, Balloon::Size::SMALL, 200},
{50, -BLOCK, 0, Balloon::Type::FLOATER, Balloon::Size::MEDIUM, 3.334f}, // 200 frames ÷ 60fps = 3.334s {50, -BLOCK, 0, Balloon::Type::FLOATER, Balloon::Size::MEDIUM, 200},
{90, -BLOCK, 0, Balloon::Type::FLOATER, Balloon::Size::LARGE, 3.334f}, // 200 frames ÷ 60fps = 3.334s {90, -BLOCK, 0, Balloon::Type::FLOATER, Balloon::Size::LARGE, 200},
{140, -BLOCK, 0, Balloon::Type::FLOATER, Balloon::Size::EXTRALARGE, 3.334f}}; // 200 frames ÷ 60fps = 3.334s {140, -BLOCK, 0, Balloon::Type::FLOATER, Balloon::Size::EXTRALARGE, 200}};
formations_.at(99) = Formation(test_params); formations_.at(99) = Formation(test_params);
} }

View File

@@ -20,13 +20,13 @@ class BalloonFormations {
float vel_x = 0.0F; // Velocidad inicial en el eje X float vel_x = 0.0F; // Velocidad inicial en el eje X
Balloon::Type type = Balloon::Type::BALLOON; // Tipo de globo Balloon::Type type = Balloon::Type::BALLOON; // Tipo de globo
Balloon::Size size = Balloon::Size::SMALL; // Tamaño de globo Balloon::Size size = Balloon::Size::SMALL; // Tamaño de globo
float creation_counter = 0.0f; // Temporizador para la creación del globo Uint16 creation_counter = 0; // Temporizador para la creación del globo
// Constructor por defecto // Constructor por defecto
SpawnParams() = default; SpawnParams() = default;
// Constructor con parámetros // Constructor con parámetros
SpawnParams(float x, float y, float vel_x, Balloon::Type type, Balloon::Size size, float creation_counter) SpawnParams(float x, float y, float vel_x, Balloon::Type type, Balloon::Size size, Uint16 creation_counter)
: x(x), : x(x),
y(y), y(y),
vel_x(vel_x), vel_x(vel_x),
@@ -82,8 +82,7 @@ class BalloonFormations {
private: private:
// --- Constantes --- // --- Constantes ---
static constexpr int BALLOON_SPAWN_HEIGHT = 208; // Altura desde el suelo en la que aparecen los globos static constexpr int BALLOON_SPAWN_HEIGHT = 208; // Altura desde el suelo en la que aparecen los globos
static constexpr float CREATION_TIME = 5.0f; // Tiempo base de creación de los globos en segundos (300 frames ÷ 60fps = 5.0s) static constexpr int DEFAULT_CREATION_TIME = 200; // Tiempo base de creación de los globos para las formaciones
static constexpr float DEFAULT_CREATION_TIME = 3.334f; // Tiempo base de creación de los globos en segundos (200 frames ÷ 60fps = 3.334s)
// --- Variables --- // --- Variables ---
std::vector<Formation> formations_; // Vector con todas las formaciones disponibles std::vector<Formation> formations_; // Vector con todas las formaciones disponibles

View File

@@ -16,7 +16,7 @@
#include "utils.h" #include "utils.h"
// Constructor // Constructor
BalloonManager::BalloonManager(IStageInfo* stage_info) BalloonManager::BalloonManager(IStageInfo *stage_info)
: explosions_(std::make_unique<Explosions>()), : explosions_(std::make_unique<Explosions>()),
balloon_formations_(std::make_unique<BalloonFormations>()), balloon_formations_(std::make_unique<BalloonFormations>()),
stage_info_(stage_info) { init(); } stage_info_(stage_info) { init(); }
@@ -62,18 +62,18 @@ void BalloonManager::init() {
explosions_->addTexture(3, explosions_textures_.at(3), explosions_animations_.at(3)); explosions_->addTexture(3, explosions_textures_.at(3), explosions_animations_.at(3));
} }
// Actualiza (time-based) // Actualiza
void BalloonManager::update(float deltaTime) { void BalloonManager::update() {
for (const auto& balloon : balloons_) { for (const auto &balloon : balloons_) {
balloon->update(deltaTime); balloon->update();
} }
updateBalloonDeployCounter(deltaTime); updateBalloonDeployCounter();
explosions_->update(deltaTime); explosions_->update();
} }
// Renderiza los objetos // Renderiza los objetos
void BalloonManager::render() { void BalloonManager::render() {
for (auto& balloon : balloons_) { for (auto &balloon : balloons_) {
balloon->render(); balloon->render();
} }
explosions_->render(); explosions_->render();
@@ -81,12 +81,12 @@ void BalloonManager::render() {
// Crea una formación de globos // Crea una formación de globos
void BalloonManager::deployRandomFormation(int stage) { void BalloonManager::deployRandomFormation(int stage) {
// Solo despliega una formación enemiga si el timer ha llegado a cero // Solo despliega una formación enemiga si ha pasado cierto tiempo desde la última
if (balloon_deploy_counter_ <= 0.0f) { if (balloon_deploy_counter_ == 0) {
// En este punto se decide entre crear una powerball o una formación enemiga // En este punto se decide entre crear una powerball o una formación enemiga
if ((rand() % 100 < 15) && (canPowerBallBeCreated())) { if ((rand() % 100 < 15) && (canPowerBallBeCreated())) {
createPowerBall(); // Crea una powerball createPowerBall(); // Crea una powerball
balloon_deploy_counter_ = POWERBALL_DEPLOY_DELAY; // Resetea con pequeño retraso balloon_deploy_counter_ = 10; // Da un poco de margen para que se creen mas globos
} else { } else {
// Decrementa el contador de despliegues de globos necesarios para la siguiente PowerBall // Decrementa el contador de despliegues de globos necesarios para la siguiente PowerBall
if (power_ball_counter_ > 0) { if (power_ball_counter_ > 0) {
@@ -113,13 +113,13 @@ void BalloonManager::deployRandomFormation(int stage) {
.type = balloon.type, .type = balloon.type,
.size = balloon.size, .size = balloon.size,
.vel_x = balloon.vel_x, .vel_x = balloon.vel_x,
.game_tempo = balloon_speed_, .speed = balloon_speed_,
.creation_counter = creation_time_enabled_ ? balloon.creation_counter : 0.0f}; .creation_counter = static_cast<Uint16>(creation_time_enabled_ ? balloon.creation_counter : 0)};
createBalloon(config); createBalloon(config);
} }
// Reinicia el timer para el próximo despliegue // Reinicia el contador para el próximo despliegue
balloon_deploy_counter_ = DEFAULT_BALLOON_DEPLOY_DELAY; balloon_deploy_counter_ = DEFAULT_BALLOON_DEPLOY_COUNTER;
} }
} }
} }
@@ -134,7 +134,7 @@ void BalloonManager::deployFormation(int formation_id) {
.type = balloon.type, .type = balloon.type,
.size = balloon.size, .size = balloon.size,
.vel_x = balloon.vel_x, .vel_x = balloon.vel_x,
.game_tempo = balloon_speed_, .speed = balloon_speed_,
.creation_counter = balloon.creation_counter}; .creation_counter = balloon.creation_counter};
createBalloon(config); createBalloon(config);
} }
@@ -150,7 +150,7 @@ void BalloonManager::deployFormation(int formation_id, float y) {
.type = balloon.type, .type = balloon.type,
.size = balloon.size, .size = balloon.size,
.vel_x = balloon.vel_x, .vel_x = balloon.vel_x,
.game_tempo = balloon_speed_, .speed = balloon_speed_,
.creation_counter = balloon.creation_counter}; .creation_counter = balloon.creation_counter};
createBalloon(config); createBalloon(config);
} }
@@ -158,14 +158,15 @@ void BalloonManager::deployFormation(int formation_id, float y) {
// Vacia del vector de globos los globos que ya no sirven // Vacia del vector de globos los globos que ya no sirven
void BalloonManager::freeBalloons() { void BalloonManager::freeBalloons() {
auto result = std::ranges::remove_if(balloons_, [](const auto& balloon) { return !balloon->isEnabled(); }); auto result = std::ranges::remove_if(balloons_, [](const auto &balloon) { return !balloon->isEnabled(); });
balloons_.erase(result.begin(), balloons_.end()); balloons_.erase(result.begin(), balloons_.end());
} }
// Actualiza el timer de despliegue de globos (time-based) // Actualiza la variable enemyDeployCounter
void BalloonManager::updateBalloonDeployCounter(float deltaTime) { void BalloonManager::updateBalloonDeployCounter() {
// DeltaTime en segundos - timer decrementa hasta llegar a cero if (balloon_deploy_counter_ > 0) {
balloon_deploy_counter_ -= deltaTime; --balloon_deploy_counter_;
}
} }
// Indica si se puede crear una powerball // Indica si se puede crear una powerball
@@ -173,7 +174,7 @@ auto BalloonManager::canPowerBallBeCreated() -> bool { return (!power_ball_enabl
// Calcula el poder actual de los globos en pantalla // Calcula el poder actual de los globos en pantalla
auto BalloonManager::calculateScreenPower() -> int { auto BalloonManager::calculateScreenPower() -> int {
return std::accumulate(balloons_.begin(), balloons_.end(), 0, [](int sum, const auto& balloon) { return sum + (balloon->isEnabled() ? balloon->getPower() : 0); }); return std::accumulate(balloons_.begin(), balloons_.end(), 0, [](int sum, const auto &balloon) { return sum + (balloon->isEnabled() ? balloon->getPower() : 0); });
} }
// Crea un globo nuevo en el vector de globos // Crea un globo nuevo en el vector de globos
@@ -194,7 +195,7 @@ auto BalloonManager::createBalloon(Balloon::Config config) -> std::shared_ptr<Ba
} }
// Crea un globo a partir de otro globo // Crea un globo a partir de otro globo
void BalloonManager::createChildBalloon(const std::shared_ptr<Balloon>& balloon, const std::string& direction) { void BalloonManager::createChildBalloon(const std::shared_ptr<Balloon> &balloon, const std::string &direction) {
if (can_deploy_balloons_) { if (can_deploy_balloons_) {
// Calcula parametros // Calcula parametros
const int PARENT_HEIGHT = balloon->getHeight(); const int PARENT_HEIGHT = balloon->getHeight();
@@ -208,31 +209,16 @@ void BalloonManager::createChildBalloon(const std::shared_ptr<Balloon>& balloon,
Balloon::Config config = { Balloon::Config config = {
.x = std::clamp(X - (CHILD_WIDTH / 2), MIN_X, MAX_X), .x = std::clamp(X - (CHILD_WIDTH / 2), MIN_X, MAX_X),
.y = balloon->getPosY() + ((PARENT_HEIGHT - CHILD_HEIGHT) / 2), .y = balloon->getPosY() + ((PARENT_HEIGHT - CHILD_HEIGHT) / 2),
.type = balloon->getType(),
.size = static_cast<Balloon::Size>(static_cast<int>(balloon->getSize()) - 1), .size = static_cast<Balloon::Size>(static_cast<int>(balloon->getSize()) - 1),
.vel_x = direction == "LEFT" ? Balloon::VELX_NEGATIVE : Balloon::VELX_POSITIVE, .vel_x = direction == "LEFT" ? Balloon::VELX_NEGATIVE : Balloon::VELX_POSITIVE,
.game_tempo = balloon_speed_, .speed = balloon_speed_,
.creation_counter = 0}; .creation_counter = 0};
// Crea el globo // Crea el globo
auto b = createBalloon(config); auto b = createBalloon(config);
// Establece parametros // Establece parametros
constexpr float VEL_Y_BALLOON_PER_S = -150.0F; b->setVelY(b->getType() == Balloon::Type::BALLOON ? -2.50F : Balloon::VELX_NEGATIVE * 2.0F);
switch (b->getType()) {
case Balloon::Type::BALLOON: {
b->setVelY(VEL_Y_BALLOON_PER_S);
break;
}
case Balloon::Type::FLOATER: {
const float MODIFIER = (rand() % 2 == 0) ? 1.0F : 1.0F;
b->setVelY(Balloon::VELX_NEGATIVE * 2.0F * MODIFIER);
(rand() % 2 == 0) ? b->alterVelX(1.0F) : b->alterVelX(1.0F);
break;
}
default:
break;
}
// Herencia de estados // Herencia de estados
if (balloon->isStopped()) { b->stop(); } if (balloon->isStopped()) { b->stop(); }
@@ -259,7 +245,7 @@ void BalloonManager::createPowerBall() {
.type = Balloon::Type::POWERBALL, .type = Balloon::Type::POWERBALL,
.size = Balloon::Size::EXTRALARGE, .size = Balloon::Size::EXTRALARGE,
.vel_x = VEL_X.at(LUCK), .vel_x = VEL_X.at(LUCK),
.game_tempo = balloon_speed_, .speed = balloon_speed_,
.creation_counter = 0, .creation_counter = 0,
.play_area = play_area_, .play_area = play_area_,
.texture = balloon_textures_.at(4), .texture = balloon_textures_.at(4),
@@ -280,13 +266,13 @@ void BalloonManager::createPowerBall() {
// Establece la velocidad de los globos // Establece la velocidad de los globos
void BalloonManager::setBalloonSpeed(float speed) { void BalloonManager::setBalloonSpeed(float speed) {
balloon_speed_ = speed; balloon_speed_ = speed;
for (auto& balloon : balloons_) { for (auto &balloon : balloons_) {
balloon->setGameTempo(speed); balloon->setSpeed(speed);
} }
} }
// Explosiona un globo. Lo destruye y crea otros dos si es el caso // Explosiona un globo. Lo destruye y crea otros dos si es el caso
auto BalloonManager::popBalloon(const std::shared_ptr<Balloon>& balloon) -> int { auto BalloonManager::popBalloon(const std::shared_ptr<Balloon> &balloon) -> int {
stage_info_->addPower(1); stage_info_->addPower(1);
int score = 0; int score = 0;
@@ -294,7 +280,7 @@ auto BalloonManager::popBalloon(const std::shared_ptr<Balloon>& balloon) -> int
balloon->pop(true); balloon->pop(true);
score = destroyAllBalloons(); score = destroyAllBalloons();
power_ball_enabled_ = false; power_ball_enabled_ = false;
balloon_deploy_counter_ = BALLOON_POP_DELAY; // Resetea con retraso balloon_deploy_counter_ = 20;
} else { } else {
score = balloon->getScore(); score = balloon->getScore();
if (balloon->getSize() != Balloon::Size::SMALL) { if (balloon->getSize() != Balloon::Size::SMALL) {
@@ -311,7 +297,7 @@ auto BalloonManager::popBalloon(const std::shared_ptr<Balloon>& balloon) -> int
} }
// Explosiona un globo. Lo destruye = no crea otros globos // Explosiona un globo. Lo destruye = no crea otros globos
auto BalloonManager::destroyBalloon(std::shared_ptr<Balloon>& balloon) -> int { auto BalloonManager::destroyBalloon(std::shared_ptr<Balloon> &balloon) -> int {
int score = 0; int score = 0;
// Calcula la puntuación y el poder que generaria el globo en caso de romperlo a él y a sus hijos // Calcula la puntuación y el poder que generaria el globo en caso de romperlo a él y a sus hijos
@@ -346,12 +332,12 @@ auto BalloonManager::destroyBalloon(std::shared_ptr<Balloon>& balloon) -> int {
// Destruye todos los globos // Destruye todos los globos
auto BalloonManager::destroyAllBalloons() -> int { auto BalloonManager::destroyAllBalloons() -> int {
int score = 0; int score = 0;
for (auto& balloon : balloons_) { for (auto &balloon : balloons_) {
score += destroyBalloon(balloon); score += destroyBalloon(balloon);
} }
balloon_deploy_counter_ = DEFAULT_BALLOON_DEPLOY_DELAY; balloon_deploy_counter_ = 300;
Screen::get()->flash(Colors::FLASH, 0.05F); Screen::get()->flash(Colors::FLASH, 3);
Screen::get()->shake(); Screen::get()->shake();
return score; return score;
@@ -359,16 +345,14 @@ auto BalloonManager::destroyAllBalloons() -> int {
// Detiene todos los globos // Detiene todos los globos
void BalloonManager::stopAllBalloons() { void BalloonManager::stopAllBalloons() {
for (auto& balloon : balloons_) { for (auto &balloon : balloons_) {
if (!balloon->isBeingCreated()) {
balloon->stop(); balloon->stop();
} }
}
} }
// Pone en marcha todos los globos // Pone en marcha todos los globos
void BalloonManager::startAllBalloons() { void BalloonManager::startAllBalloons() {
for (auto& balloon : balloons_) { for (auto &balloon : balloons_) {
if (!balloon->isBeingCreated()) { if (!balloon->isBeingCreated()) {
balloon->start(); balloon->start();
} }
@@ -377,7 +361,7 @@ void BalloonManager::startAllBalloons() {
// Cambia el color de todos los globos // Cambia el color de todos los globos
void BalloonManager::reverseColorsToAllBalloons() { void BalloonManager::reverseColorsToAllBalloons() {
for (auto& balloon : balloons_) { for (auto &balloon : balloons_) {
if (balloon->isStopped()) { if (balloon->isStopped()) {
balloon->useReverseColor(); balloon->useReverseColor();
} }
@@ -386,7 +370,7 @@ void BalloonManager::reverseColorsToAllBalloons() {
// Cambia el color de todos los globos // Cambia el color de todos los globos
void BalloonManager::normalColorsToAllBalloons() { void BalloonManager::normalColorsToAllBalloons() {
for (auto& balloon : balloons_) { for (auto &balloon : balloons_) {
balloon->useNormalColor(); balloon->useNormalColor();
} }
} }
@@ -398,13 +382,13 @@ void BalloonManager::createTwoBigBalloons() {
// Obtiene el nivel de ameza actual generado por los globos // Obtiene el nivel de ameza actual generado por los globos
auto BalloonManager::getMenace() -> int { auto BalloonManager::getMenace() -> int {
return std::accumulate(balloons_.begin(), balloons_.end(), 0, [](int sum, const auto& balloon) { return sum + (balloon->isEnabled() ? balloon->getMenace() : 0); }); return std::accumulate(balloons_.begin(), balloons_.end(), 0, [](int sum, const auto &balloon) { return sum + (balloon->isEnabled() ? balloon->getMenace() : 0); });
} }
// Establece el sonido de los globos // Establece el sonido de los globos
void BalloonManager::setSounds(bool value) { void BalloonManager::setSounds(bool value) {
sound_enabled_ = value; sound_enabled_ = value;
for (auto& balloon : balloons_) { for (auto &balloon : balloons_) {
balloon->setSound(value); balloon->setSound(value);
} }
} }
@@ -412,14 +396,14 @@ void BalloonManager::setSounds(bool value) {
// Activa o desactiva los sonidos de rebote los globos // Activa o desactiva los sonidos de rebote los globos
void BalloonManager::setBouncingSounds(bool value) { void BalloonManager::setBouncingSounds(bool value) {
bouncing_sound_enabled_ = value; bouncing_sound_enabled_ = value;
for (auto& balloon : balloons_) { for (auto &balloon : balloons_) {
balloon->setBouncingSound(value); balloon->setBouncingSound(value);
} }
} }
// Activa o desactiva los sonidos de los globos al explotar // Activa o desactiva los sonidos de los globos al explotar
void BalloonManager::setPoppingSounds(bool value) { void BalloonManager::setPoppingSounds(bool value) {
poping_sound_enabled_ = value; poping_sound_enabled_ = value;
for (auto& balloon : balloons_) { for (auto &balloon : balloons_) {
balloon->setPoppingSound(value); balloon->setPoppingSound(value);
} }
} }

View File

@@ -28,7 +28,7 @@ class BalloonManager {
~BalloonManager() = default; ~BalloonManager() = default;
// --- Métodos principales --- // --- Métodos principales ---
void update(float deltaTime); // Actualiza el estado de los globos (time-based) void update(); // Actualiza el estado de los globos
void render(); // Renderiza los globos en pantalla void render(); // Renderiza los globos en pantalla
// --- Gestión de globos --- // --- Gestión de globos ---
@@ -49,7 +49,7 @@ class BalloonManager {
void setBalloonSpeed(float speed); // Ajusta la velocidad de los globos void setBalloonSpeed(float speed); // Ajusta la velocidad de los globos
void setDefaultBalloonSpeed(float speed) { default_balloon_speed_ = speed; }; // Establece la velocidad base void setDefaultBalloonSpeed(float speed) { default_balloon_speed_ = speed; }; // Establece la velocidad base
void resetBalloonSpeed() { setBalloonSpeed(default_balloon_speed_); }; // Restablece la velocidad de los globos void resetBalloonSpeed() { setBalloonSpeed(default_balloon_speed_); }; // Restablece la velocidad de los globos
void updateBalloonDeployCounter(float deltaTime); // Actualiza el contador de despliegue (time-based) void updateBalloonDeployCounter(); // Actualiza el contador de despliegue
auto canPowerBallBeCreated() -> bool; // Indica si se puede crear una PowerBall auto canPowerBallBeCreated() -> bool; // Indica si se puede crear una PowerBall
auto calculateScreenPower() -> int; // Calcula el poder de los globos en pantalla auto calculateScreenPower() -> int; // Calcula el poder de los globos en pantalla
@@ -82,9 +82,7 @@ class BalloonManager {
private: private:
// --- Constantes --- // --- Constantes ---
static constexpr float DEFAULT_BALLOON_DEPLOY_DELAY = 5.0f; // 300 frames = 5 segundos static const int DEFAULT_BALLOON_DEPLOY_COUNTER = 300;
static constexpr float POWERBALL_DEPLOY_DELAY = 0.167f; // 10 frames = 0.167 segundos
static constexpr float BALLOON_POP_DELAY = 0.333f; // 20 frames = 0.333 segundos
// --- Objetos y punteros --- // --- Objetos y punteros ---
Balloons balloons_; // Vector con los globos activos Balloons balloons_; // Vector con los globos activos
@@ -98,9 +96,9 @@ class BalloonManager {
// --- Variables de estado --- // --- Variables de estado ---
SDL_FRect play_area_ = param.game.play_area.rect; SDL_FRect play_area_ = param.game.play_area.rect;
float balloon_speed_ = Balloon::GAME_TEMPO.at(0); float balloon_speed_ = Balloon::SPEED.at(0);
float default_balloon_speed_ = Balloon::GAME_TEMPO.at(0); float default_balloon_speed_ = Balloon::SPEED.at(0);
float balloon_deploy_counter_ = 0; int balloon_deploy_counter_ = 0;
int power_ball_counter_ = 0; int power_ball_counter_ = 0;
int last_balloon_deploy_ = 0; int last_balloon_deploy_ = 0;
bool power_ball_enabled_ = false; bool power_ball_enabled_ = false;

View File

@@ -4,64 +4,46 @@
#include <string> // Para char_traits, basic_string, operator+, string #include <string> // Para char_traits, basic_string, operator+, string
#include "param.h" // Para Param, ParamGame, param #include "param.h" // Para Param, ParamGame, param
#include "player.h" // Para Player::Id
#include "resource.h" // Para Resource #include "resource.h" // Para Resource
// Constructor // Constructor
Bullet::Bullet(float x, float y, Type type, Color color, int owner) Bullet::Bullet(float x, float y, BulletType bullet_type, bool powered, Player::Id owner)
: sprite_(std::make_unique<AnimatedSprite>(Resource::get()->getTexture("bullet.png"), Resource::get()->getAnimation("bullet.ani"))), : sprite_(std::make_unique<AnimatedSprite>(Resource::get()->getTexture("bullet.png"), Resource::get()->getAnimation("bullet.ani"))),
type_(type), bullet_type_(bullet_type),
owner_(owner), owner_(owner),
pos_x_(x), pos_x_(x),
pos_y_(y) { pos_y_(y) {
vel_x_ = calculateVelocity(type_); vel_x_ = calculateVelocity(bullet_type_);
sprite_->setCurrentAnimation(buildAnimationString(type_, color)); sprite_->setCurrentAnimation(buildAnimationString(bullet_type_, powered));
collider_.r = WIDTH / 2; collider_.r = WIDTH / 2;
shiftColliders(); shiftColliders();
} }
// Calcula la velocidad horizontal de la bala basada en su tipo // Calcula la velocidad horizontal de la bala basada en su tipo
auto Bullet::calculateVelocity(Type type) -> float { auto Bullet::calculateVelocity(BulletType bullet_type) -> float {
switch (type) { switch (bullet_type) {
case Type::LEFT: case BulletType::LEFT:
return VEL_X_LEFT; return VEL_X_LEFT;
case Type::RIGHT: case BulletType::RIGHT:
return VEL_X_RIGHT; return VEL_X_RIGHT;
default: default:
return VEL_X_CENTER; return VEL_X_CENTER;
} }
} }
// Construye el string de animación basado en el tipo de bala y color específico // Construye el string de animación basado en el tipo de bala y si está potenciada
auto Bullet::buildAnimationString(Type type, Color color) -> std::string { auto Bullet::buildAnimationString(BulletType bullet_type, bool powered) -> std::string {
std::string animation_string; std::string animation_string = powered ? "powered_" : "normal_";
// Mapear color a string específico switch (bullet_type) {
switch (color) { case BulletType::UP:
case Color::YELLOW:
animation_string = "yellow_";
break;
case Color::GREEN:
animation_string = "green_";
break;
case Color::RED:
animation_string = "red_";
break;
case Color::PURPLE:
animation_string = "purple_";
break;
}
// Añadir dirección
switch (type) {
case Type::UP:
animation_string += "up"; animation_string += "up";
break; break;
case Type::LEFT: case BulletType::LEFT:
animation_string += "left"; animation_string += "left";
break; break;
case Type::RIGHT: case BulletType::RIGHT:
animation_string += "right"; animation_string += "right";
break; break;
default: default:
@@ -71,48 +53,48 @@ auto Bullet::buildAnimationString(Type type, Color color) -> std::string {
return animation_string; return animation_string;
} }
// Implementación de render // Implementación de render (llama al render del sprite_)
void Bullet::render() { void Bullet::render() {
if (type_ != Type::NONE) { if (bullet_type_ != BulletType::NONE) {
sprite_->render(); sprite_->render();
} }
} }
// Actualiza el estado del objeto // Actualiza el estado del objeto
auto Bullet::update(float deltaTime) -> MoveStatus { auto Bullet::update() -> BulletMoveStatus {
sprite_->update(deltaTime); sprite_->update();
return move(deltaTime); return move();
} }
// Implementación del movimiento usando MoveStatus // Implementación del movimiento usando BulletMoveStatus
auto Bullet::move(float deltaTime) -> MoveStatus { auto Bullet::move() -> BulletMoveStatus {
pos_x_ += vel_x_ * deltaTime; pos_x_ += vel_x_;
if (pos_x_ < param.game.play_area.rect.x - WIDTH || pos_x_ > param.game.play_area.rect.w) { if (pos_x_ < param.game.play_area.rect.x - WIDTH || pos_x_ > param.game.play_area.rect.w) {
disable(); disable();
return MoveStatus::OUT; return BulletMoveStatus::OUT;
} }
pos_y_ += VEL_Y * deltaTime; pos_y_ += VEL_Y;
if (pos_y_ < param.game.play_area.rect.y - HEIGHT) { if (pos_y_ < param.game.play_area.rect.y - HEIGHT) {
disable(); disable();
return MoveStatus::OUT; return BulletMoveStatus::OUT;
} }
shiftSprite(); shiftSprite();
shiftColliders(); shiftColliders();
return MoveStatus::OK; return BulletMoveStatus::OK;
} }
auto Bullet::isEnabled() const -> bool { auto Bullet::isEnabled() const -> bool {
return type_ != Type::NONE; return bullet_type_ != BulletType::NONE;
} }
void Bullet::disable() { void Bullet::disable() {
type_ = Type::NONE; bullet_type_ = BulletType::NONE;
} }
auto Bullet::getOwner() const -> int { auto Bullet::getOwner() const -> Player::Id {
return owner_; return owner_;
} }

View File

@@ -6,8 +6,22 @@
#include <string> // Para string #include <string> // Para string
#include "animated_sprite.h" // Para AnimatedSprite #include "animated_sprite.h" // Para AnimatedSprite
#include "player.h" // Para Player
#include "utils.h" // Para Circle #include "utils.h" // Para Circle
// --- Enums ---
enum class BulletType : Uint8 {
UP, // Bala hacia arriba
LEFT, // Bala hacia la izquierda
RIGHT, // Bala hacia la derecha
NONE // Sin bala
};
enum class BulletMoveStatus : Uint8 {
OK = 0, // Movimiento normal
OUT = 1 // Fuera de los límites
};
// --- Clase Bullet: representa una bala del jugador --- // --- Clase Bullet: representa una bala del jugador ---
class Bullet { class Bullet {
public: public:
@@ -15,45 +29,25 @@ class Bullet {
static constexpr float WIDTH = 12.0F; // Anchura de la bala static constexpr float WIDTH = 12.0F; // Anchura de la bala
static constexpr float HEIGHT = 12.0F; // Altura de la bala static constexpr float HEIGHT = 12.0F; // Altura de la bala
// --- Enums ---
enum class Type : Uint8 {
UP, // Bala hacia arriba
LEFT, // Bala hacia la izquierda
RIGHT, // Bala hacia la derecha
NONE // Sin bala
};
enum class MoveStatus : Uint8 {
OK = 0, // Movimiento normal
OUT = 1 // Fuera de los límites
};
enum class Color : Uint8 {
YELLOW,
GREEN,
RED,
PURPLE
};
// --- Constructor y destructor --- // --- Constructor y destructor ---
Bullet(float x, float y, Type type, Color color, int owner); // Constructor principal Bullet(float x, float y, BulletType bullet_type, bool powered, Player::Id owner); // Constructor principal
~Bullet() = default; // Destructor ~Bullet() = default; // Destructor
// --- Métodos principales --- // --- Métodos principales ---
void render(); // Dibuja la bala en pantalla void render(); // Dibuja la bala en pantalla
auto update(float deltaTime) -> MoveStatus; // Actualiza el estado del objeto (time-based) auto update() -> BulletMoveStatus; // Actualiza el estado del objeto
void disable(); // Desactiva la bala void disable(); // Desactiva la bala
// --- Getters --- // --- Getters ---
[[nodiscard]] auto isEnabled() const -> bool; // Comprueba si está activa [[nodiscard]] auto isEnabled() const -> bool; // Comprueba si está activa
[[nodiscard]] auto getOwner() const -> int; // Devuelve el identificador del dueño [[nodiscard]] auto getOwner() const -> Player::Id; // Devuelve el identificador del dueño
auto getCollider() -> Circle&; // Devuelve el círculo de colisión auto getCollider() -> Circle &; // Devuelve el círculo de colisión
private: private:
// --- Constantes --- // --- Constantes ---
static constexpr float VEL_Y = -180.0F; // Velocidad vertical (pixels/segundo) - era -0.18F pixels/ms static constexpr float VEL_Y = -3.0F; // Velocidad vertical
static constexpr float VEL_X_LEFT = -120.0F; // Velocidad izquierda (pixels/segundo) - era -0.12F pixels/ms static constexpr float VEL_X_LEFT = -2.0F; // Velocidad izquierda
static constexpr float VEL_X_RIGHT = 120.0F; // Velocidad derecha (pixels/segundo) - era 0.12F pixels/ms static constexpr float VEL_X_RIGHT = 2.0F; // Velocidad derecha
static constexpr float VEL_X_CENTER = 0.0F; // Velocidad central static constexpr float VEL_X_CENTER = 0.0F; // Velocidad central
// --- Objetos y punteros --- // --- Objetos y punteros ---
@@ -61,8 +55,8 @@ class Bullet {
// --- Variables de estado --- // --- Variables de estado ---
Circle collider_; // Círculo de colisión Circle collider_; // Círculo de colisión
Type type_; // Tipo de bala BulletType bullet_type_; // Tipo de bala
int owner_; // Identificador del jugador Player::Id owner_; // Identificador del dueño
float pos_x_; // Posición en el eje X float pos_x_; // Posición en el eje X
float pos_y_; // Posición en el eje Y float pos_y_; // Posición en el eje Y
float vel_x_; // Velocidad en el eje X float vel_x_; // Velocidad en el eje X
@@ -70,7 +64,7 @@ class Bullet {
// --- Métodos internos --- // --- Métodos internos ---
void shiftColliders(); // Ajusta el círculo de colisión void shiftColliders(); // Ajusta el círculo de colisión
void shiftSprite(); // Ajusta el sprite void shiftSprite(); // Ajusta el sprite
auto move(float deltaTime) -> MoveStatus; // Mueve la bala y devuelve su estado (time-based) auto move() -> BulletMoveStatus; // Mueve la bala y devuelve su estado
static auto calculateVelocity(Type type) -> float; // Calcula la velocidad horizontal de la bala static auto calculateVelocity(BulletType bullet_type) -> float; // Calcula la velocidad horizontal de la bala
static auto buildAnimationString(Type type, Color color) -> std::string; // Construye el string de animación static auto buildAnimationString(BulletType bullet_type, bool powered) -> std::string; // Construye el string de animación
}; };

View File

@@ -1,109 +0,0 @@
#include "bullet_manager.h"
#include <algorithm> // Para remove_if
#include "bullet.h" // Para Bullet
#include "param.h" // Para param
#include "player.h" // Para Player
// Constructor
BulletManager::BulletManager()
: play_area_(param.game.play_area.rect) {
}
// Actualiza el estado de todas las balas
void BulletManager::update(float deltaTime) {
for (auto& bullet : bullets_) {
if (bullet->isEnabled()) {
processBulletUpdate(bullet, deltaTime);
}
}
}
// Renderiza todas las balas activas
void BulletManager::render() {
for (auto& bullet : bullets_) {
if (bullet->isEnabled()) {
bullet->render();
}
}
}
// Crea una nueva bala
void BulletManager::createBullet(int x, int y, Bullet::Type type, Bullet::Color color, int owner) {
bullets_.emplace_back(std::make_shared<Bullet>(x, y, type, color, owner));
}
// Libera balas que ya no están habilitadas
void BulletManager::freeBullets() {
if (!bullets_.empty()) {
// Elimina las balas deshabilitadas del vector
bullets_.erase(
std::remove_if(bullets_.begin(), bullets_.end(),
[](const std::shared_ptr<Bullet>& bullet) {
return !bullet->isEnabled();
}),
bullets_.end());
}
}
// Elimina todas las balas
void BulletManager::clearAllBullets() {
bullets_.clear();
}
// Verifica colisiones de todas las balas
void BulletManager::checkCollisions() {
for (auto& bullet : bullets_) {
if (!bullet->isEnabled()) {
continue;
}
// Verifica colisión con Tabe
if (tabe_collision_callback_ && tabe_collision_callback_(bullet)) {
break; // Sale del bucle si hubo colisión
}
// Verifica colisión con globos
if (balloon_collision_callback_ && balloon_collision_callback_(bullet)) {
break; // Sale del bucle si hubo colisión
}
}
}
// Establece el callback para colisión con Tabe
void BulletManager::setTabeCollisionCallback(CollisionCallback callback) {
tabe_collision_callback_ = callback;
}
// Establece el callback para colisión con globos
void BulletManager::setBalloonCollisionCallback(CollisionCallback callback) {
balloon_collision_callback_ = callback;
}
// Establece el callback para balas fuera de límites
void BulletManager::setOutOfBoundsCallback(OutOfBoundsCallback callback) {
out_of_bounds_callback_ = callback;
}
// --- Métodos privados ---
// Procesa la actualización individual de una bala
void BulletManager::processBulletUpdate(const std::shared_ptr<Bullet>& bullet, float deltaTime) {
auto status = bullet->update(deltaTime);
// Si la bala salió de los límites, llama al callback
if (status == Bullet::MoveStatus::OUT && out_of_bounds_callback_) {
out_of_bounds_callback_(bullet);
}
}
// Verifica si la bala está fuera de los límites del área de juego
auto BulletManager::isBulletOutOfBounds(const std::shared_ptr<Bullet>& bullet) -> bool {
auto collider = bullet->getCollider();
return (collider.x < play_area_.x ||
collider.x > play_area_.x + play_area_.w ||
collider.y < play_area_.y ||
collider.y > play_area_.y + play_area_.h);
}

View File

@@ -1,79 +0,0 @@
#pragma once
#include <SDL3/SDL.h> // Para SDL_FRect
#include <functional> // Para function
#include <memory> // Para shared_ptr, unique_ptr
#include <vector> // Para vector
#include "bullet.h" // Para Bullet
#include "utils.h" // Para Circle
// --- Types ---
using Bullets = std::vector<std::shared_ptr<Bullet>>;
// --- Forward declarations ---
class Player;
// --- Clase BulletManager: gestiona todas las balas del juego ---
//
// Esta clase se encarga de la gestión completa de las balas del juego,
// incluyendo su creación, actualización, renderizado y colisiones.
//
// Funcionalidades principales:
// • Gestión del ciclo de vida: creación, actualización y destrucción de balas
// • Renderizado: dibuja todas las balas activas en pantalla
// • Detección de colisiones: mediante sistema de callbacks
// • Limpieza automática: elimina balas deshabilitadas del contenedor
// • Configuración flexible: permite ajustar parámetros de las balas
//
// La clase utiliza un sistema de callbacks para manejar las colisiones,
// permitiendo que la lógica específica del juego permanezca en Game.
class BulletManager {
public:
// --- Types para callbacks ---
using CollisionCallback = std::function<bool(const std::shared_ptr<Bullet>&)>;
using OutOfBoundsCallback = std::function<void(const std::shared_ptr<Bullet>&)>;
// --- Constructor y destructor ---
BulletManager();
~BulletManager() = default;
// --- Métodos principales ---
void update(float deltaTime); // Actualiza el estado de las balas (time-based)
void render(); // Renderiza las balas en pantalla
// --- Gestión de balas ---
void createBullet(int x, int y, Bullet::Type type, Bullet::Color color, int owner); // Crea una nueva bala
void freeBullets(); // Libera balas que ya no sirven
void clearAllBullets(); // Elimina todas las balas
// --- Detección de colisiones ---
void checkCollisions(); // Verifica colisiones de todas las balas
void setTabeCollisionCallback(CollisionCallback callback); // Establece callback para colisión con Tabe
void setBalloonCollisionCallback(CollisionCallback callback); // Establece callback para colisión con globos
void setOutOfBoundsCallback(OutOfBoundsCallback callback); // Establece callback para balas fuera de límites
// --- Configuración ---
void setPlayArea(SDL_FRect play_area) { play_area_ = play_area; }; // Define el área de juego
// --- Getters ---
auto getBullets() -> Bullets& { return bullets_; } // Obtiene referencia al vector de balas
[[nodiscard]] auto getNumBullets() const -> int { return bullets_.size(); } // Obtiene el número de balas activas
private:
// --- Objetos y punteros ---
Bullets bullets_; // Vector con las balas activas
// --- Variables de configuración ---
SDL_FRect play_area_; // Área de juego para límites
// --- Callbacks para colisiones ---
CollisionCallback tabe_collision_callback_; // Callback para colisión con Tabe
CollisionCallback balloon_collision_callback_; // Callback para colisión con globos
OutOfBoundsCallback out_of_bounds_callback_; // Callback para balas fuera de límites
// --- Métodos internos ---
void processBulletUpdate(const std::shared_ptr<Bullet>& bullet, float deltaTime); // Procesa actualización individual
auto isBulletOutOfBounds(const std::shared_ptr<Bullet>& bullet) -> bool; // Verifica si la bala está fuera de límites
};

View File

@@ -95,29 +95,6 @@ struct Color {
return Color(new_r, new_g, new_b, new_a); return Color(new_r, new_g, new_b, new_a);
} }
// Interpolación lineal hacia otro color (t=0.0: this, t=1.0: target)
[[nodiscard]] constexpr auto LERP(const Color &target, float t) const -> Color {
// Asegurar que t esté en el rango [0.0, 1.0]
t = std::clamp(t, 0.0f, 1.0f);
// Interpolación lineal para cada componente
auto lerp_component = [t](Uint8 start, Uint8 end) -> Uint8 {
return static_cast<Uint8>(start + (end - start) * t);
};
return Color(
lerp_component(r, target.r),
lerp_component(g, target.g),
lerp_component(b, target.b),
lerp_component(a, target.a)
);
}
// Sobrecarga para aceptar componentes RGBA directamente
[[nodiscard]] constexpr auto LERP(Uint8 red, Uint8 green, Uint8 blue, Uint8 alpha, float t) const -> Color {
return LERP(Color(red, green, blue, alpha), t);
}
// Convierte el color a un entero de 32 bits en formato RGBA // Convierte el color a un entero de 32 bits en formato RGBA
[[nodiscard]] constexpr auto TO_UINT32() const -> Uint32 { [[nodiscard]] constexpr auto TO_UINT32() const -> Uint32 {
return (static_cast<Uint32>(r) << 24) | return (static_cast<Uint32>(r) << 24) |

View File

@@ -6,7 +6,6 @@
#include "color.h" #include "color.h"
#include "ui/notifier.h" // Para Notifier::Position #include "ui/notifier.h" // Para Notifier::Position
#include "version.h" // Para Version::APP_NAME
// --- Namespace GameDefaults: configuración centralizada con valores por defecto del juego --- // --- Namespace GameDefaults: configuración centralizada con valores por defecto del juego ---
namespace GameDefaults { namespace GameDefaults {
@@ -15,6 +14,7 @@ namespace GameDefaults {
namespace Game { namespace Game {
constexpr float WIDTH = 320.0F; constexpr float WIDTH = 320.0F;
constexpr float HEIGHT = 256.0F; constexpr float HEIGHT = 256.0F;
constexpr float ITEM_SIZE = 20.0F;
constexpr int NAME_ENTRY_IDLE_TIME = 10; constexpr int NAME_ENTRY_IDLE_TIME = 10;
constexpr int NAME_ENTRY_TOTAL_TIME = 60; constexpr int NAME_ENTRY_TOTAL_TIME = 60;
constexpr bool HIT_STOP = false; constexpr bool HIT_STOP = false;
@@ -58,7 +58,7 @@ constexpr int SKIP_COUNTDOWN_VALUE = 8;
// --- TITLE --- // --- TITLE ---
namespace Title { namespace Title {
constexpr int PRESS_START_POSITION = 180; constexpr int PRESS_START_POSITION = 180;
constexpr float DURATION_S = 14.0F; constexpr int DURATION = 800;
constexpr int ARCADE_EDITION_POSITION = 123; constexpr int ARCADE_EDITION_POSITION = 123;
constexpr int TITLE_C_C_POSITION = 80; constexpr int TITLE_C_C_POSITION = 80;
constexpr const char* BG_COLOR = "41526F"; constexpr const char* BG_COLOR = "41526F";
@@ -80,12 +80,11 @@ struct BalloonSettings {
grav(g) {} grav(g) {}
}; };
// Valores para deltaTime en segundos: vel en pixels/s, grav en pixels/s² (aceleración)
constexpr std::array<BalloonSettings, 4> SETTINGS = {{ constexpr std::array<BalloonSettings, 4> SETTINGS = {{
BalloonSettings(165.0F, 320.0F), // Globo 0: vel=165 pixels/s, grav=320 pixels/s² BalloonSettings(2.75F, 0.09F), // Globo 0
BalloonSettings(222.0F, 360.0F), // Globo 1: vel=222 pixels/s, grav=360 pixels/s² BalloonSettings(3.70F, 0.10F), // Globo 1
BalloonSettings(282.0F, 360.0F), // Globo 2: vel=282 pixels/s, grav=360 pixels/s² BalloonSettings(4.70F, 0.10F), // Globo 2
BalloonSettings(327.0F, 360.0F) // Globo 3: vel=327 pixels/s, grav=360 pixels/s² BalloonSettings(5.45F, 0.10F) // Globo 3
}}; }};
constexpr std::array<const char*, 4> COLORS = { constexpr std::array<const char*, 4> COLORS = {
@@ -211,7 +210,7 @@ constexpr const char* PLAYER1 = "422028FF";
// --- OPTIONS --- // --- OPTIONS ---
namespace Options { namespace Options {
// Window // Window
constexpr const char* WINDOW_CAPTION = Version::APP_NAME; constexpr const char* WINDOW_CAPTION = "Coffee Crisis Arcade Edition";
constexpr int WINDOW_ZOOM = 2; constexpr int WINDOW_ZOOM = 2;
constexpr int WINDOW_MAX_ZOOM = 2; constexpr int WINDOW_MAX_ZOOM = 2;
@@ -220,7 +219,7 @@ constexpr SDL_ScaleMode VIDEO_SCALE_MODE = SDL_ScaleMode::SDL_SCALEMODE_NEAREST;
constexpr bool VIDEO_FULLSCREEN = false; constexpr bool VIDEO_FULLSCREEN = false;
constexpr bool VIDEO_VSYNC = true; constexpr bool VIDEO_VSYNC = true;
constexpr bool VIDEO_INTEGER_SCALE = true; constexpr bool VIDEO_INTEGER_SCALE = true;
constexpr bool VIDEO_SHADERS = false; constexpr bool VIDEO_SHADERS = true;
// Music // Music
constexpr bool MUSIC_ENABLED = true; constexpr bool MUSIC_ENABLED = true;

View File

@@ -39,22 +39,22 @@ void DefineButtons::render() {
} }
} }
void DefineButtons::update(float delta_time) { void DefineButtons::update() {
if (!enabled_) { if (!enabled_) {
return; return;
} }
// Actualizar la ventana siempre // Actualizar la ventana siempre
if (window_message_) { if (window_message_) {
window_message_->update(delta_time); window_message_->update();
} }
// Manejar la secuencia de cierre si ya terminamos // Manejar la secuencia de cierre si ya terminamos
if (finished_ && message_shown_) { if (finished_ && message_shown_) {
message_timer_ += delta_time; message_timer_++;
// Después del delay, iniciar animación de cierre (solo una vez) // Después del delay, iniciar animación de cierre (solo una vez)
if (message_timer_ >= MESSAGE_DISPLAY_DURATION_S && !closing_) { if (message_timer_ > MESSAGE_DISPLAY_FRAMES && !closing_) {
if (window_message_) { if (window_message_) {
window_message_->hide(); // Iniciar animación de cierre window_message_->hide(); // Iniciar animación de cierre
} }
@@ -234,7 +234,7 @@ void DefineButtons::checkEnd() {
// Solo marcar que ya mostramos el mensaje // Solo marcar que ya mostramos el mensaje
message_shown_ = true; message_shown_ = true;
message_timer_ = 0.0f; message_timer_ = 0;
} }
} }

View File

@@ -36,7 +36,7 @@ class DefineButtons {
// --- Métodos principales --- // --- Métodos principales ---
void render(); void render();
void update(float delta_time); void update();
void handleEvents(const SDL_Event &event); void handleEvents(const SDL_Event &event);
auto enable(Options::Gamepad *options_gamepad) -> bool; auto enable(Options::Gamepad *options_gamepad) -> bool;
void disable(); void disable();
@@ -48,7 +48,7 @@ class DefineButtons {
private: private:
// --- Constantes --- // --- Constantes ---
static constexpr float MESSAGE_DISPLAY_DURATION_S = 2.0f; // Cuánto tiempo mostrar el mensaje en segundos static constexpr size_t MESSAGE_DISPLAY_FRAMES = 120; // Cuánto tiempo mostrar el mensaje (en frames) ~2 segundos a 60fps
// --- Objetos y punteros --- // --- Objetos y punteros ---
Input *input_ = nullptr; // Entrada del usuario Input *input_ = nullptr; // Entrada del usuario
@@ -59,7 +59,7 @@ class DefineButtons {
std::vector<Button> buttons_; // Lista de botones std::vector<Button> buttons_; // Lista de botones
std::vector<std::string> controller_names_; // Nombres de los controladores std::vector<std::string> controller_names_; // Nombres de los controladores
size_t index_button_ = 0; // Índice del botón seleccionado size_t index_button_ = 0; // Índice del botón seleccionado
float message_timer_ = 0.0f; // Timer en segundos para el mensaje size_t message_timer_ = 0; // Contador de frames para el mensaje
bool enabled_ = false; // Flag para indicar si está activo bool enabled_ = false; // Flag para indicar si está activo
bool finished_ = false; // Flag para indicar si ha terminado bool finished_ = false; // Flag para indicar si ha terminado
bool closing_ = false; // Flag para indicar que está cerrando bool closing_ = false; // Flag para indicar que está cerrando

View File

@@ -1,70 +0,0 @@
#include "demo.h"
#include <SDL3/SDL.h> // Para SDL_IOStream, SDL_IOFromConstMem, SDL_IOFromFile, SDL_ReadIO, SDL_WriteIO, SDL_CloseIO
#include <stdexcept> // Para runtime_error
#include "resource_helper.h" // Para ResourceHelper
#include "utils.h" // Para printWithDots, getFileName
// Carga el fichero de datos para la demo
auto loadDemoDataFromFile(const std::string &file_path) -> DemoData {
DemoData dd;
SDL_IOStream *file = nullptr;
// Intentar cargar desde ResourceHelper primero
auto resource_data = ResourceHelper::loadFile(file_path);
if (!resource_data.empty()) {
file = SDL_IOFromConstMem(resource_data.data(), resource_data.size());
} else {
// Fallback a filesystem directo
file = SDL_IOFromFile(file_path.c_str(), "r+b");
}
if (file == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Fichero no encontrado %s", file_path.c_str());
throw std::runtime_error("Fichero no encontrado: " + file_path);
}
printWithDots("DemoData : ", getFileName(file_path), "[ LOADED ]");
// Lee todos los datos del fichero y los deja en el destino
for (int i = 0; i < TOTAL_DEMO_DATA; ++i) {
DemoKeys dk = DemoKeys();
SDL_ReadIO(file, &dk, sizeof(DemoKeys));
dd.push_back(dk);
}
// Cierra el fichero
SDL_CloseIO(file);
return dd;
}
#ifdef RECORDING
// Guarda el fichero de datos para la demo
bool saveDemoFile(const std::string &file_path, const DemoData &dd) {
auto success = true;
auto file = SDL_IOFromFile(file_path.c_str(), "w+b");
if (file) {
// Guarda los datos
for (const auto &data : dd) {
if (SDL_WriteIO(file, &data, sizeof(DemoKeys)) != sizeof(DemoKeys)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error al escribir el fichero %s", getFileName(file_path).c_str());
success = false;
break;
}
}
if (success) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Writing file %s", getFileName(file_path).c_str());
}
// Cierra el fichero
SDL_CloseIO(file);
} else {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Unable to save %s file! %s", getFileName(file_path).c_str(), SDL_GetError());
success = false;
}
return success;
}
#endif // RECORDING

View File

@@ -1,54 +0,0 @@
#pragma once
#include <SDL3/SDL.h> // Para Uint8
#include <string> // Para string
#include <vector> // Para vector
// --- Constantes ---
constexpr int TOTAL_DEMO_DATA = 2000;
// --- Estructuras ---
struct DemoKeys {
Uint8 left;
Uint8 right;
Uint8 no_input;
Uint8 fire;
Uint8 fire_left;
Uint8 fire_right;
explicit DemoKeys(Uint8 l = 0, Uint8 r = 0, Uint8 ni = 0, Uint8 f = 0, Uint8 fl = 0, Uint8 fr = 0)
: left(l),
right(r),
no_input(ni),
fire(f),
fire_left(fl),
fire_right(fr) {}
};
// --- Tipos ---
using DemoData = std::vector<DemoKeys>;
struct Demo {
bool enabled = false; // Indica si está activo el modo demo
bool recording = false; // Indica si está activado el modo para grabar la demo
float elapsed_s = 0.0F; // Segundos transcurridos de demo
int index = 0; // Contador para el modo demo
DemoKeys keys; // Variable con las pulsaciones de teclas del modo demo
std::vector<DemoData> data; // Vector con diferentes sets de datos con los movimientos para la demo
Demo() = default;
Demo(bool e, bool r, int c, const DemoKeys& k, const std::vector<DemoData>& d)
: enabled(e),
recording(r),
index(c),
keys(k),
data(d) {}
};
// --- Funciones ---
auto loadDemoDataFromFile(const std::string& file_path) -> DemoData;
#ifdef RECORDING
bool saveDemoFile(const std::string& file_path, const DemoData& dd);
#endif

View File

@@ -42,7 +42,7 @@ Director::Director(int argc, std::span<char *> argv) {
Section::name = Section::Name::GAME; Section::name = Section::Name::GAME;
Section::options = Section::Options::GAME_PLAY_1P; Section::options = Section::Options::GAME_PLAY_1P;
#elif _DEBUG #elif _DEBUG
Section::name = Section::Name::GAME; Section::name = Section::Name::HI_SCORE_TABLE;
Section::options = Section::Options::GAME_PLAY_1P; Section::options = Section::Options::GAME_PLAY_1P;
#else // NORMAL GAME #else // NORMAL GAME
Section::name = Section::Name::LOGO; Section::name = Section::Name::LOGO;
@@ -80,13 +80,13 @@ void Director::init() {
Asset::init(executable_path_); // Inicializa el sistema de gestión de archivos Asset::init(executable_path_); // Inicializa el sistema de gestión de archivos
#ifdef MACOS_BUNDLE #ifdef MACOS_BUNDLE
ResourceHelper::initializeResourceSystem(executable_path_ + "../Resources/resources.pack"); ResourceHelper::initializeResourceSystem(executable_path_ + "/../Resources/resources.pack");
#else #else
ResourceHelper::initializeResourceSystem(executable_path_ + "resources.pack"); ResourceHelper::initializeResourceSystem("resources.pack");
#endif #endif
loadAssets(); // Crea el índice de archivos loadAssets(); // Crea el índice de archivos
Input::init(Asset::get()->get("gamecontrollerdb.txt"), Asset::get()->get("controllers.json")); // Carga configuración de controles Input::init(Asset::get()->get("gamecontrollerdb.txt"), Asset::get()->get("controllers.json")); // Carga configuración de controles
Options::setConfigFile(Asset::get()->get("config_v2.txt")); // Establece el fichero de configuración Options::setConfigFile(Asset::get()->get("config.txt")); // Establece el fichero de configuración
Options::setControllersFile(Asset::get()->get("controllers.json")); // Establece el fichero de configuración de mandos Options::setControllersFile(Asset::get()->get("controllers.json")); // Establece el fichero de configuración de mandos
Options::loadFromFile(); // Carga el archivo de configuración Options::loadFromFile(); // Carga el archivo de configuración
loadParams(); // Carga los parámetros del programa loadParams(); // Carga los parámetros del programa
@@ -174,14 +174,8 @@ void Director::loadAssets() {
// Comprueba los parametros del programa // Comprueba los parametros del programa
void Director::checkProgramArguments(int argc, std::span<char *> argv) { void Director::checkProgramArguments(int argc, std::span<char *> argv) {
// Obtener la ruta absoluta del ejecutable // Establece la ruta del programa
std::filesystem::path exe_path = std::filesystem::absolute(argv[0]); executable_path_ = getPath(argv[0]);
executable_path_ = exe_path.parent_path().string();
// Asegurar que termine con separador de directorio
if (!executable_path_.empty() && executable_path_.back() != '/' && executable_path_.back() != '\\') {
executable_path_ += "/";
}
// Comprueba el resto de parámetros // Comprueba el resto de parámetros
for (int i = 1; i < argc; ++i) { for (int i = 1; i < argc; ++i) {

View File

@@ -1,96 +1,147 @@
#include "enter_name.h" #include "enter_name.h"
#include <array> // Para array #include <cstddef> // Para size_t
#include <cstdlib> // Para rand #include <cstdlib> // Para rand
#include <string_view> // Para basic_string_view, string_view #include <string_view> // Para basic_string_view, string_view
#include "utils.h" // Para trim
// Constructor // Constructor
EnterName::EnterName() EnterName::EnterName()
: character_list_("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789."), : character_list_(" ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789."),
selected_index_(0) {} character_index_{0} {}
// Inicializa el objeto // Inicializa el objeto
void EnterName::init(const std::string &name) { void EnterName::init(const std::string &name) {
name_ = sanitizeName(name); // No se pasa ningún nombre
selected_index_ = 0; if (name.empty()) {
name_ = "A";
position_ = 0;
position_overflow_ = false;
}
// Se pasa un nombre
else {
name_ = name;
position_ = name_.length();
position_overflow_ = position_ >= NAME_SIZE;
}
// Inicializa el vector de indices con el nombre y espacios
initCharacterIndex(name_);
} }
// Incrementa el índice del carácter seleccionado // Incrementa la posición
void EnterName::incPosition() {
if (position_overflow_) {
// Si ya estamos en overflow, no incrementamos más.
return;
}
++position_;
if (position_ >= NAME_SIZE) {
position_ = NAME_SIZE; // Mantenemos en el índice máximo válido.
position_overflow_ = true; // Activamos el flag de overflow.
} else if (position_ > 0) // No es necesario verificar position_ < MAX_NAME_LENGTH
{
// Copiamos el índice del carácter anterior si es posible.
// character_index_[position_] = character_index_[position_ - 1];
// Ponemos el caracter "espacio"
character_index_[position_] = 0;
} else {
// Si position_ es 0, inicializamos el carácter actual.
character_index_[position_] = 0;
}
updateNameFromCharacterIndex();
}
// Decrementa la posición
void EnterName::decPosition() {
if (position_overflow_) {
// Si estaba en overflow, lo desactivamos y mantenemos position_ en el máximo.
position_overflow_ = false;
position_ = NAME_SIZE - 1;
} else {
if (position_ > 0) {
--position_;
// Limpiamos el carácter siguiente si el índice es válido.
if (position_ + 1 < NAME_SIZE) {
character_index_[position_ + 1] = 0;
}
} else {
// Si position_ es 0, aseguramos que no vaya a ser negativo y limpiamos el carácter actual.
position_ = 0;
// character_index_[position_] = 0;
}
// Si position_ es menor que NAME_LENGTH, aseguramos que el overflow esté desactivado.
if (position_ < NAME_SIZE) {
position_overflow_ = false;
}
}
updateNameFromCharacterIndex();
}
// Incrementa el índice
void EnterName::incIndex() { void EnterName::incIndex() {
++selected_index_; if (position_overflow_) {
if (selected_index_ >= static_cast<int>(character_list_.size())) { return;
selected_index_ = 0;
} }
++character_index_[position_];
if (character_index_[position_] >= static_cast<int>(character_list_.size())) {
character_index_[position_] = 0;
}
updateNameFromCharacterIndex();
} }
// Decrementa el índice del carácter seleccionado // Decrementa el índice
void EnterName::decIndex() { void EnterName::decIndex() {
--selected_index_; if (position_overflow_) {
if (selected_index_ < 0) { return;
selected_index_ = character_list_.size() - 1; }
--character_index_[position_];
if (character_index_[position_] < 0) {
character_index_[position_] = character_list_.size() - 1;
}
updateNameFromCharacterIndex();
}
// Actualiza el nombre a partir de la lista de índices
void EnterName::updateNameFromCharacterIndex() {
name_.clear();
for (size_t i = 0; i < NAME_SIZE; ++i) {
name_.push_back(character_list_[character_index_[i]]);
}
name_ = trim(name_);
}
// Actualiza la variable
void EnterName::initCharacterIndex(const std::string &name) {
// Rellena de espacios
for (size_t i = 0; i < NAME_SIZE; ++i) {
character_index_[i] = 0;
}
// Coloca los índices en función de los caracteres que forman el nombre
for (size_t i = 0; i < name.substr(0, NAME_SIZE).size(); ++i) {
character_index_[i] = findIndex(name.at(i));
} }
} }
// Añade el carácter seleccionado al nombre // Encuentra el indice de un caracter en "character_list_"
void EnterName::addCharacter() { auto EnterName::findIndex(char character) const -> int {
if (name_.length() < MAX_NAME_SIZE) { for (size_t i = 0; i < character_list_.size(); ++i) {
name_.push_back(character_list_[selected_index_]); if (character == character_list_.at(i)) {
} return i;
}
// Elimina el último carácter del nombre
void EnterName::removeLastCharacter() {
if (!name_.empty()) {
name_.pop_back();
}
}
// Devuelve el carácter seleccionado con offset relativo como string
auto EnterName::getSelectedCharacter(int offset) const -> std::string {
// Calcular el índice con offset, con wrap-around circular
int size = static_cast<int>(character_list_.size());
int index = (selected_index_ + offset) % size;
// Manejar índices negativos (hacer wrap-around hacia atrás)
if (index < 0) {
index += size;
}
return std::string(1, character_list_[index]);
}
// Devuelve el carrusel completo de caracteres centrado en el seleccionado
auto EnterName::getCarousel(int size) const -> std::string {
// Asegurar que el tamaño sea impar para tener un centro claro
if (size % 2 == 0) {
++size;
}
std::string carousel;
carousel.reserve(size); // Optimización: reservar memoria de antemano
int half = size / 2;
// Construir desde -half hasta +half (inclusive)
for (int offset = -half; offset <= half; ++offset) {
carousel += getSelectedCharacter(offset);
}
return carousel;
}
// Valida y limpia el nombre: solo caracteres legales y longitud máxima
auto EnterName::sanitizeName(const std::string &name) const -> std::string {
std::string sanitized;
for (size_t i = 0; i < name.length() && sanitized.length() < MAX_NAME_SIZE; ++i) {
// Verifica si el carácter está en la lista permitida
if (character_list_.find(name[i]) != std::string::npos) {
sanitized.push_back(name[i]);
} }
} }
return 0;
return sanitized;
} }
// Devuelve un nombre al azar // Devuelve un nombre al azar
@@ -106,11 +157,12 @@ auto EnterName::getRandomName() -> std::string {
"PEPE"}; "PEPE"};
return std::string(NAMES[rand() % NAMES.size()]); return std::string(NAMES[rand() % NAMES.size()]);
} }
// Obtiene el nombre final introducido // Obtiene el nombre final introducido
auto EnterName::getFinalName() -> std::string { auto EnterName::getFinalName() -> std::string {
if (name_.empty()) { auto name = trim(name_.substr(0, position_ + 1)); // Devuelve el texto intruducido incluyendo el del selector
name_ = getRandomName(); if (name.empty()) {
name = getRandomName();
} }
name_ = name;
return name_; return name_;
} }

View File

@@ -1,38 +1,43 @@
#pragma once #pragma once
#include <array> // Para array
#include <cstddef> // Para size_t #include <cstddef> // Para size_t
#include <string> // Para allocator, string #include <string> // Para allocator, string
#include "utils.h" // Para trim
// --- Constantes ---
constexpr size_t NAME_SIZE = 5; // Tamaño máximo del nombre
// --- Clase EnterName: gestor de entrada de nombre del jugador --- // --- Clase EnterName: gestor de entrada de nombre del jugador ---
class EnterName { class EnterName {
public: public:
// --- Constantes ---
static constexpr size_t MAX_NAME_SIZE = 6; // Tamaño máximo del nombre
EnterName(); EnterName();
~EnterName() = default; ~EnterName() = default;
void init(const std::string &name = ""); // Inicializa con nombre opcional (vacío por defecto) void init(const std::string &name = ""); // Inicializa con un nombre opcional
void incIndex(); // Incrementa el índice del carácter seleccionado en la lista void incPosition(); // Incrementa la posición del carácter actual
void decIndex(); // Decrementa el índice del carácter seleccionado en la lista void decPosition(); // Decrementa la posición del carácter actual
void incIndex(); // Incrementa el índice del carácter en la lista
void decIndex(); // Decrementa el índice del carácter en la lista
void addCharacter(); // Añade el carácter seleccionado al nombre auto getFinalName() -> std::string; // Obtiene el nombre final introducido
void removeLastCharacter(); // Elimina el último carácter del nombre [[nodiscard]] auto getCurrentName() const -> std::string { return trim(name_); } // Obtiene el nombre actual en proceso
auto getFinalName() -> std::string; // Obtiene el nombre final (o aleatorio si vacío) [[nodiscard]] auto getPosition() const -> int { return position_; } // Posición actual del carácter editado
[[nodiscard]] auto getCurrentName() const -> std::string { return name_; } // Obtiene el nombre actual en proceso [[nodiscard]] auto getPositionOverflow() const -> bool { return position_overflow_; } // Indica si la posición excede el límite
[[nodiscard]] auto getSelectedCharacter(int offset = 0) const -> std::string; // Devuelve el carácter seleccionado con offset relativo
[[nodiscard]] auto getCarousel(int size) const -> std::string; // Devuelve el carrusel de caracteres (size debe ser impar)
[[nodiscard]] auto getSelectedIndex() const -> int { return selected_index_; } // Obtiene el índice del carácter seleccionado
[[nodiscard]] auto getCharacterList() const -> const std::string& { return character_list_; } // Obtiene la lista completa de caracteres
private: private:
// --- Variables de estado --- // --- Variables de estado ---
std::string character_list_; // Lista de caracteres permitidos std::string character_list_; // Lista de caracteres permitidos
std::string name_; // Nombre en proceso std::string name_; // Nombre en proceso
int selected_index_ = 0; // Índice del carácter seleccionado en "character_list_" std::array<int, NAME_SIZE> character_index_; // Índices a "character_list_"
size_t position_ = 0; // Índice del carácter que se edita
bool position_overflow_ = false; // Flag para exceder límite
[[nodiscard]] auto sanitizeName(const std::string &name) const -> std::string; // Valida y limpia el nombre void updateNameFromCharacterIndex(); // Actualiza "name_" según "character_index_"
void initCharacterIndex(const std::string &name); // Inicializa índices desde el nombre
[[nodiscard]] auto findIndex(char character) const -> int; // Busca el índice de un carácter en "character_list_"
static auto getRandomName() -> std::string; // Devuelve un nombre al azar static auto getRandomName() -> std::string; // Devuelve un nombre al azar
}; };

View File

@@ -6,10 +6,10 @@
class Texture; // lines 4-4 class Texture; // lines 4-4
// Actualiza la lógica de la clase (time-based) // Actualiza la lógica de la clase
void Explosions::update(float deltaTime) { void Explosions::update() {
for (auto &explosion : explosions_) { for (auto &explosion : explosions_) {
explosion->update(deltaTime); explosion->update();
} }
// Vacia el vector de elementos finalizados // Vacia el vector de elementos finalizados

View File

@@ -29,7 +29,7 @@ class Explosions {
~Explosions() = default; // Destructor por defecto ~Explosions() = default; // Destructor por defecto
// --- Métodos principales --- // --- Métodos principales ---
void update(float deltaTime); // Actualiza la lógica de la clase (time-based) void update(); // Actualiza la lógica de la clase
void render(); // Dibuja el objeto en pantalla void render(); // Dibuja el objeto en pantalla
// --- Configuración --- // --- Configuración ---

415
source/external/jail_shader.cpp vendored Normal file
View File

@@ -0,0 +1,415 @@
#include "jail_shader.h"
#include <SDL3/SDL.h> // Para SDL_GL_GetProcAddress, SDL_LogError
#include <stdint.h> // Para uintptr_t
#include <cstring> // Para strncmp
#include <stdexcept> // Para runtime_error
#include <vector> // Para vector
#ifdef __APPLE__
#include <OpenGL/OpenGL.h> // Para OpenGL en macOS
#include "CoreFoundation/CoreFoundation.h" // Para Core Foundation en macOS
#if ESSENTIAL_GL_PRACTICES_SUPPORT_GL3
#include <OpenGL/gl3.h> // Para OpenGL 3 en macOS
#else // NO ESSENTIAL_GL_PRACTICES_SUPPORT_GL3
#include <OpenGL/gl.h> // Para OpenGL (compatibilidad) en macOS
#endif // ESSENTIAL_GL_PRACTICES_SUPPORT_GL3
#else // SI NO ES __APPLE__
#include <SDL3/SDL_opengl.h> // Para GLuint, GLint, glTexCoord2f, glVertex2f
#endif // __APPLE__
namespace shader {
// Constantes
const GLuint INVALID_SHADER_ID = 0;
const GLuint INVALID_PROGRAM_ID = 0;
const GLuint DEFAULT_TEXTURE_ID = 1;
// Variables globales
SDL_Window *win = nullptr;
SDL_Renderer *renderer = nullptr;
GLuint programId = 0;
SDL_Texture *backBuffer = nullptr;
SDL_Point win_size = {320 * 4, 256 * 4};
SDL_FPoint tex_size = {320, 256};
bool usingOpenGL = false;
#ifndef __APPLE__
// Declaración de funciones de extensión de OpenGL (evitando GLEW)
PFNGLCREATESHADERPROC glCreateShader;
PFNGLSHADERSOURCEPROC glShaderSource;
PFNGLCOMPILESHADERPROC glCompileShader;
PFNGLGETSHADERIVPROC glGetShaderiv;
PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog;
PFNGLDELETESHADERPROC glDeleteShader;
PFNGLATTACHSHADERPROC glAttachShader;
PFNGLCREATEPROGRAMPROC glCreateProgram;
PFNGLLINKPROGRAMPROC glLinkProgram;
PFNGLVALIDATEPROGRAMPROC glValidateProgram;
PFNGLGETPROGRAMIVPROC glGetProgramiv;
PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog;
PFNGLUSEPROGRAMPROC glUseProgram;
PFNGLDELETEPROGRAMPROC glDeleteProgram;
bool initGLExtensions() {
glCreateShader = (PFNGLCREATESHADERPROC)SDL_GL_GetProcAddress("glCreateShader");
glShaderSource = (PFNGLSHADERSOURCEPROC)SDL_GL_GetProcAddress("glShaderSource");
glCompileShader = (PFNGLCOMPILESHADERPROC)SDL_GL_GetProcAddress("glCompileShader");
glGetShaderiv = (PFNGLGETSHADERIVPROC)SDL_GL_GetProcAddress("glGetShaderiv");
glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)SDL_GL_GetProcAddress("glGetShaderInfoLog");
glDeleteShader = (PFNGLDELETESHADERPROC)SDL_GL_GetProcAddress("glDeleteShader");
glAttachShader = (PFNGLATTACHSHADERPROC)SDL_GL_GetProcAddress("glAttachShader");
glCreateProgram = (PFNGLCREATEPROGRAMPROC)SDL_GL_GetProcAddress("glCreateProgram");
glLinkProgram = (PFNGLLINKPROGRAMPROC)SDL_GL_GetProcAddress("glLinkProgram");
glValidateProgram = (PFNGLVALIDATEPROGRAMPROC)SDL_GL_GetProcAddress("glValidateProgram");
glGetProgramiv = (PFNGLGETPROGRAMIVPROC)SDL_GL_GetProcAddress("glGetProgramiv");
glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)SDL_GL_GetProcAddress("glGetProgramInfoLog");
glUseProgram = (PFNGLUSEPROGRAMPROC)SDL_GL_GetProcAddress("glUseProgram");
glDeleteProgram = (PFNGLDELETEPROGRAMPROC)SDL_GL_GetProcAddress("glDeleteProgram");
return glCreateShader && glShaderSource && glCompileShader && glGetShaderiv &&
glGetShaderInfoLog && glDeleteShader && glAttachShader && glCreateProgram &&
glLinkProgram && glValidateProgram && glGetProgramiv && glGetProgramInfoLog &&
glUseProgram && glDeleteProgram;
}
#endif
// Función para verificar errores de OpenGL
void checkGLError(const char *operation) {
GLenum error = glGetError();
if (error != GL_NO_ERROR) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error OpenGL en %s: 0x%x",
operation,
error);
}
}
// Función para compilar un shader a partir de un std::string
GLuint compileShader(const std::string &source, GLuint shader_type) {
if (source.empty()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ERROR FATAL: El código fuente del shader está vacío.");
throw std::runtime_error("ERROR FATAL: El código fuente del shader está vacío.");
}
// Crear identificador del shader
GLuint shader_id = glCreateShader(shader_type);
if (shader_id == INVALID_SHADER_ID) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error al crear el shader.");
checkGLError("glCreateShader");
return INVALID_SHADER_ID;
}
// Agregar una directiva según el tipo de shader
std::string directive = (shader_type == GL_VERTEX_SHADER)
? "#define VERTEX\n"
: "#define FRAGMENT\n";
const char *sources[2] = {directive.c_str(), source.c_str()};
// Especificar el código fuente del shader
glShaderSource(shader_id, 2, sources, nullptr);
checkGLError("glShaderSource");
// Compilar el shader
glCompileShader(shader_id);
checkGLError("glCompileShader");
// Verificar si la compilación fue exitosa
GLint compiled_ok = GL_FALSE;
glGetShaderiv(shader_id, GL_COMPILE_STATUS, &compiled_ok);
if (compiled_ok != GL_TRUE) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error en la compilación del shader (%d)!", shader_id);
GLint log_length;
glGetShaderiv(shader_id, GL_INFO_LOG_LENGTH, &log_length);
if (log_length > 0) {
std::vector<GLchar> log(log_length);
glGetShaderInfoLog(shader_id, log_length, &log_length, log.data());
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Registro de compilación del shader: %s", log.data());
}
glDeleteShader(shader_id);
return INVALID_SHADER_ID;
}
return shader_id;
}
// Función para compilar un programa de shaders (vertex y fragment) a partir de std::string
GLuint compileProgram(const std::string &vertex_shader_source, const std::string &fragment_shader_source) {
GLuint program_id = glCreateProgram();
if (program_id == INVALID_PROGRAM_ID) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error al crear el programa de shaders.");
checkGLError("glCreateProgram");
return INVALID_PROGRAM_ID;
}
// Si el fragment shader está vacío, reutilizamos el código del vertex shader
GLuint vertex_shader_id = compileShader(vertex_shader_source, GL_VERTEX_SHADER);
GLuint fragment_shader_id = compileShader(fragment_shader_source.empty() ? vertex_shader_source : fragment_shader_source, GL_FRAGMENT_SHADER);
if (vertex_shader_id != INVALID_SHADER_ID && fragment_shader_id != INVALID_SHADER_ID) {
// Asociar los shaders al programa
glAttachShader(program_id, vertex_shader_id);
checkGLError("glAttachShader vertex");
glAttachShader(program_id, fragment_shader_id);
checkGLError("glAttachShader fragment");
glLinkProgram(program_id);
checkGLError("glLinkProgram");
// Verificar el estado del enlace
GLint isLinked = GL_FALSE;
glGetProgramiv(program_id, GL_LINK_STATUS, &isLinked);
if (isLinked == GL_FALSE) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error al enlazar el programa de shaders.");
GLint log_length;
glGetProgramiv(program_id, GL_INFO_LOG_LENGTH, &log_length);
if (log_length > 0) {
std::vector<char> log(log_length);
glGetProgramInfoLog(program_id, log_length, &log_length, log.data());
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Registro de enlace del programa: %s", log.data());
}
glDeleteProgram(program_id);
program_id = INVALID_PROGRAM_ID;
} else {
glValidateProgram(program_id);
checkGLError("glValidateProgram");
// Log de información del programa (solo si hay información)
GLint log_length;
glGetProgramiv(program_id, GL_INFO_LOG_LENGTH, &log_length);
if (log_length > 1) // > 1 porque algunos drivers devuelven 1 para cadena vacía
{
std::vector<char> log(log_length);
glGetProgramInfoLog(program_id, log_length, &log_length, log.data());
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Registro de información del programa:\n%s", log.data());
}
}
} else {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: No se pudieron compilar los shaders.");
glDeleteProgram(program_id);
program_id = INVALID_PROGRAM_ID;
}
// Limpiar los shaders (ya no son necesarios después del enlace)
if (vertex_shader_id != INVALID_SHADER_ID) {
glDeleteShader(vertex_shader_id);
}
if (fragment_shader_id != INVALID_SHADER_ID) {
glDeleteShader(fragment_shader_id);
}
return program_id;
}
// Función para obtener el ID de textura OpenGL desde SDL3
GLuint getTextureID(SDL_Texture *texture) {
if (!texture)
return DEFAULT_TEXTURE_ID;
// Intentar obtener el ID de textura OpenGL desde las propiedades de SDL3
SDL_PropertiesID props = SDL_GetTextureProperties(texture);
GLuint textureId = 0;
// Intentar diferentes nombres de propiedades según la versión de SDL3
textureId = (GLuint)(uintptr_t)SDL_GetPointerProperty(props, "SDL.texture.opengl.texture", nullptr);
// Si la primera no funciona, intentar con el nombre alternativo
if (textureId == 0) {
textureId = (GLuint)(uintptr_t)SDL_GetPointerProperty(props, "texture.opengl.texture", nullptr);
}
// Si aún no funciona, intentar obtener como número
if (textureId == 0) {
textureId = (GLuint)SDL_GetNumberProperty(props, "SDL.texture.opengl.texture", DEFAULT_TEXTURE_ID);
}
// Si ninguna funciona, usar el método manual de bindeo de textura
if (textureId == 0) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"No se pudo obtener el ID de textura OpenGL, usando ID por defecto (%d)",
DEFAULT_TEXTURE_ID);
textureId = DEFAULT_TEXTURE_ID;
}
return textureId;
}
bool init(SDL_Window *window, SDL_Texture *back_buffer_texture, const std::string &vertex_shader, const std::string &fragment_shader) {
shader::win = window;
shader::renderer = SDL_GetRenderer(window);
shader::backBuffer = back_buffer_texture;
if (!shader::renderer) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: No se pudo obtener el renderer de la ventana.");
return false;
}
SDL_GetWindowSize(window, &win_size.x, &win_size.y);
SDL_GetTextureSize(back_buffer_texture, &tex_size.x, &tex_size.y);
const auto render_name = SDL_GetRendererName(renderer);
if (!render_name) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: No se pudo obtener el nombre del renderer.");
return false;
}
// Verificar que el renderer sea OpenGL
if (!strncmp(render_name, "opengl", 6)) {
#ifndef __APPLE__
if (!initGLExtensions()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ERROR: No se han podido inicializar las extensiones de OpenGL.");
usingOpenGL = false;
return false;
}
#endif
// Compilar el programa de shaders utilizando std::string
programId = compileProgram(vertex_shader, fragment_shader);
if (programId == INVALID_PROGRAM_ID) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ERROR: No se pudo compilar el programa de shaders.");
usingOpenGL = false;
return false;
}
} else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "ADVERTENCIA: El driver del renderer no es OpenGL (%s).", render_name);
usingOpenGL = false;
return false;
}
usingOpenGL = true;
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "** Shader system initialized successfully");
return true;
}
void render() {
// Establece el color de fondo
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_SetRenderTarget(renderer, nullptr);
SDL_RenderClear(renderer);
if (usingOpenGL && programId != INVALID_PROGRAM_ID) {
// Guardar estados de OpenGL
GLint oldProgramId;
glGetIntegerv(GL_CURRENT_PROGRAM, &oldProgramId);
GLint oldViewport[4];
glGetIntegerv(GL_VIEWPORT, oldViewport);
GLboolean wasTextureEnabled = glIsEnabled(GL_TEXTURE_2D);
GLint oldTextureId;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &oldTextureId);
// Obtener y bindear la textura
GLuint textureId = getTextureID(backBuffer);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, textureId);
checkGLError("glBindTexture");
// Usar nuestro programa de shaders
glUseProgram(programId);
checkGLError("glUseProgram");
// Recupera el tamaño lógico configurado con SDL_RenderSetLogicalSize
int logicalW, logicalH;
SDL_RendererLogicalPresentation mode;
SDL_GetRenderLogicalPresentation(renderer, &logicalW, &logicalH, &mode);
if (logicalW == 0 || logicalH == 0) {
logicalW = win_size.x;
logicalH = win_size.y;
}
// Cálculo del viewport
int viewportX = 0, viewportY = 0, viewportW = win_size.x, viewportH = win_size.y;
const bool USE_INTEGER_SCALE = mode == SDL_LOGICAL_PRESENTATION_INTEGER_SCALE;
if (USE_INTEGER_SCALE) {
// Calcula el factor de escalado entero máximo que se puede aplicar
int scaleX = win_size.x / logicalW;
int scaleY = win_size.y / logicalH;
int scale = (scaleX < scaleY ? scaleX : scaleY);
if (scale < 1) {
scale = 1;
}
viewportW = logicalW * scale;
viewportH = logicalH * scale;
viewportX = (win_size.x - viewportW) / 2;
viewportY = (win_size.y - viewportH) / 2;
} else {
// Letterboxing: preserva la relación de aspecto usando una escala flotante
float windowAspect = static_cast<float>(win_size.x) / win_size.y;
float logicalAspect = static_cast<float>(logicalW) / logicalH;
if (windowAspect > logicalAspect) {
viewportW = static_cast<int>(logicalAspect * win_size.y);
viewportX = (win_size.x - viewportW) / 2;
} else {
viewportH = static_cast<int>(win_size.x / logicalAspect);
viewportY = (win_size.y - viewportH) / 2;
}
}
glViewport(viewportX, viewportY, viewportW, viewportH);
checkGLError("glViewport");
// Configurar la proyección ortográfica usando el espacio lógico
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
// Queremos que el origen esté en la esquina superior izquierda del espacio lógico.
glOrtho(0, static_cast<GLdouble>(logicalW), static_cast<GLdouble>(logicalH), 0, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
// Dibuja el quad con las coordenadas ajustadas.
// Se asignan las coordenadas de textura "normales" para que no quede espejado horizontalmente,
// y se mantiene el flip vertical para que la imagen no aparezca volteada.
glBegin(GL_TRIANGLE_STRIP);
// Vértice superior izquierdo
glTexCoord2f(0.0f, 1.0f);
glVertex2f(0.0f, 0.0f);
// Vértice superior derecho
glTexCoord2f(1.0f, 1.0f);
glVertex2f(static_cast<GLfloat>(logicalW), 0.0f);
// Vértice inferior izquierdo
glTexCoord2f(0.0f, 0.0f);
glVertex2f(0.0f, static_cast<GLfloat>(logicalH));
// Vértice inferior derecho
glTexCoord2f(1.0f, 0.0f);
glVertex2f(static_cast<GLfloat>(logicalW), static_cast<GLfloat>(logicalH));
glEnd();
checkGLError("render quad");
SDL_GL_SwapWindow(win);
// Restaurar estados de OpenGL
glUseProgram(oldProgramId);
glBindTexture(GL_TEXTURE_2D, oldTextureId);
if (!wasTextureEnabled) {
glDisable(GL_TEXTURE_2D);
}
glViewport(oldViewport[0], oldViewport[1], oldViewport[2], oldViewport[3]);
} else {
// Fallback a renderizado normal de SDL
SDL_RenderTexture(renderer, backBuffer, nullptr, nullptr);
SDL_RenderPresent(renderer);
}
}
void cleanup() {
if (programId != INVALID_PROGRAM_ID) {
glDeleteProgram(programId);
programId = INVALID_PROGRAM_ID;
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Programa de shaders liberado.");
}
// Reinicializar variables
win = nullptr;
renderer = nullptr;
backBuffer = nullptr;
usingOpenGL = false;
}
bool isUsingOpenGL() {
return usingOpenGL;
}
GLuint getProgramId() {
return programId;
}
} // namespace shader

23
source/external/jail_shader.h vendored Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
#include <SDL3/SDL.h> // Para SDL_Texture, SDL_Window
#include <string> // Para basic_string, string
namespace shader {
bool init(SDL_Window *ventana, SDL_Texture *texturaBackBuffer, const std::string &shaderSource, const std::string &fragmentShader = "");
void render();
void cleanup();
bool isUsingOpenGL();
#ifdef __APPLE__
namespace metal {
bool initMetal(SDL_Window* window, SDL_Texture* backBuffer, const std::string& shaderFilename);
SDL_Texture* createMetalRenderTarget(SDL_Renderer* renderer, int width, int height);
void updateMetalTexture(SDL_Texture* backBuffer);
void renderMetal();
void renderWithPostProcessing(SDL_Renderer* renderer, SDL_Texture* sourceTexture);
void cleanupMetal();
}
#endif
} // namespace shader

387
source/external/jail_shader_metal.mm vendored Normal file
View File

@@ -0,0 +1,387 @@
#include "jail_shader.h"
#ifdef __APPLE__
#include <SDL3/SDL.h>
#include <SDL3/SDL_metal.h>
#include <Metal/Metal.h>
#include <QuartzCore/CAMetalLayer.h>
#include <CoreFoundation/CoreFoundation.h>
#include <stdexcept>
#include <vector>
#include "../asset.h"
namespace shader {
namespace metal {
// Metal objects
id<MTLDevice> device = nullptr;
id<MTLRenderPipelineState> pipelineState = nullptr;
id<MTLBuffer> vertexBuffer = nullptr;
id<MTLTexture> backBufferTexture = nullptr;
id<MTLTexture> gameCanvasTexture = nullptr; // Our custom render target texture
id<MTLSamplerState> sampler = nullptr;
// SDL objects (references from main shader module)
SDL_Window* win = nullptr;
SDL_Renderer* renderer = nullptr;
SDL_Texture* backBuffer = nullptr;
// Vertex data for fullscreen quad
struct Vertex {
float position[4]; // x, y, z, w
float texcoord[2]; // u, v
};
const Vertex quadVertices[] = {
// Position (x, y, z, w) // TexCoord (u, v) - Standard OpenGL-style coordinates
{{-1.0f, -1.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}, // Bottom-left
{{ 1.0f, -1.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, // Bottom-right
{{-1.0f, 1.0f, 0.0f, 1.0f}, {0.0f, 0.0f}}, // Top-left
{{ 1.0f, 1.0f, 0.0f, 1.0f}, {1.0f, 0.0f}}, // Top-right
};
std::string loadMetalShader(const std::string& filename) {
// Try to load the .metal file from the same location as GLSL files
auto data = Asset::get()->loadData(filename);
if (!data.empty()) {
return std::string(data.begin(), data.end());
}
return "";
}
SDL_Texture* createMetalRenderTarget(SDL_Renderer* renderer, int width, int height) {
if (!renderer) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "createMetalRenderTarget: No renderer provided");
return nullptr;
}
// Crear textura Metal como render target
SDL_Texture* metalTexture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, width, height);
if (!metalTexture) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create Metal render target texture: %s", SDL_GetError());
return nullptr;
}
// Configurar filtrado nearest neighbor para look pixelado
SDL_SetTextureScaleMode(metalTexture, SDL_SCALEMODE_NEAREST);
// Try to extract and store the Metal texture directly
SDL_PropertiesID props = SDL_GetTextureProperties(metalTexture);
if (props != 0) {
const char* propertyNames[] = {
"SDL.texture.metal.texture",
"SDL.renderer.metal.texture",
"metal.texture",
"texture.metal",
"MTLTexture",
"texture"
};
for (const char* propName : propertyNames) {
gameCanvasTexture = (__bridge id<MTLTexture>)SDL_GetPointerProperty(props, propName, nullptr);
if (gameCanvasTexture) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Successfully extracted Metal texture via property '%s' (size: %lux%lu)",
propName, [gameCanvasTexture width], [gameCanvasTexture height]);
break;
}
}
}
if (!gameCanvasTexture) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Could not extract Metal texture from SDL texture - shaders may not work");
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Created Metal render target texture (%dx%d)", width, height);
return metalTexture;
}
bool initMetal(SDL_Window* window, SDL_Texture* backBufferTexture, const std::string& shaderFilename) {
// Store references
win = window;
backBuffer = backBufferTexture;
renderer = SDL_GetRenderer(window);
if (!renderer) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to get SDL renderer");
return false;
}
// Get Metal layer from SDL renderer and extract device from it
CAMetalLayer* metalLayer = (__bridge CAMetalLayer*)SDL_GetRenderMetalLayer(renderer);
if (!metalLayer) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to get Metal layer from SDL renderer");
return false;
}
device = metalLayer.device;
if (!device) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to get Metal device from layer");
return false;
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Got Metal device from SDL layer: %s", [[device name] UTF8String]);
// Note: We no longer need our own texture - we'll use the backBuffer directly
// Load and compile shaders
std::string metalShaderSource = loadMetalShader(shaderFilename);
if (metalShaderSource.empty()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load Metal shader: %s", shaderFilename.c_str());
return false;
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Loaded Metal shader %s (length: %zu)",
shaderFilename.c_str(), metalShaderSource.length());
NSString* shaderNSString = [NSString stringWithUTF8String:metalShaderSource.c_str()];
NSError* error = nil;
id<MTLLibrary> library = [device newLibraryWithSource:shaderNSString options:nil error:&error];
if (!library || error) {
if (error) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to compile Metal shader: %s",
[[error localizedDescription] UTF8String]);
}
return false;
}
id<MTLFunction> vertexFunction = [library newFunctionWithName:@"vertex_main"];
id<MTLFunction> fragmentFunction = [library newFunctionWithName:@"fragment_main"];
if (!vertexFunction || !fragmentFunction) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load Metal shader functions");
return false;
}
// Create render pipeline
MTLRenderPipelineDescriptor* pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
pipelineDescriptor.vertexFunction = vertexFunction;
pipelineDescriptor.fragmentFunction = fragmentFunction;
pipelineDescriptor.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;
// Set up vertex descriptor
MTLVertexDescriptor* vertexDescriptor = [[MTLVertexDescriptor alloc] init];
vertexDescriptor.attributes[0].format = MTLVertexFormatFloat4;
vertexDescriptor.attributes[0].offset = 0;
vertexDescriptor.attributes[0].bufferIndex = 0;
vertexDescriptor.attributes[1].format = MTLVertexFormatFloat2;
vertexDescriptor.attributes[1].offset = 4 * sizeof(float);
vertexDescriptor.attributes[1].bufferIndex = 0;
vertexDescriptor.layouts[0].stride = sizeof(Vertex);
vertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
pipelineDescriptor.vertexDescriptor = vertexDescriptor;
pipelineState = [device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:&error];
if (!pipelineState || error) {
if (error) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create Metal render pipeline: %s",
[[error localizedDescription] UTF8String]);
}
return false;
}
// Create vertex buffer
vertexBuffer = [device newBufferWithBytes:quadVertices
length:sizeof(quadVertices)
options:MTLResourceOptionCPUCacheModeDefault];
// Create sampler state for nearest neighbor filtering (pixelated look)
MTLSamplerDescriptor* samplerDescriptor = [[MTLSamplerDescriptor alloc] init];
samplerDescriptor.minFilter = MTLSamplerMinMagFilterNearest;
samplerDescriptor.magFilter = MTLSamplerMinMagFilterNearest;
samplerDescriptor.sAddressMode = MTLSamplerAddressModeClampToEdge;
samplerDescriptor.tAddressMode = MTLSamplerAddressModeClampToEdge;
sampler = [device newSamplerStateWithDescriptor:samplerDescriptor];
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "** Metal shader system initialized successfully");
return true;
}
void updateMetalTexture(SDL_Texture* backBuffer) {
if (!device || !backBuffer) return;
// Only log this occasionally to avoid spam
static int attemptCount = 0;
static bool hasLogged = false;
// Try multiple property names that SDL3 might use
SDL_PropertiesID props = SDL_GetTextureProperties(backBuffer);
if (props != 0) {
const char* propertyNames[] = {
"SDL.texture.metal.texture",
"SDL.renderer.metal.texture",
"metal.texture",
"texture.metal",
"MTLTexture",
"texture"
};
for (const char* propName : propertyNames) {
id<MTLTexture> sdlMetalTexture = (__bridge id<MTLTexture>)SDL_GetPointerProperty(props, propName, nullptr);
if (sdlMetalTexture) {
backBufferTexture = sdlMetalTexture;
if (!hasLogged) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Got Metal texture via property '%s' (size: %lux%lu)",
propName, [backBufferTexture width], [backBufferTexture height]);
hasLogged = true;
}
return;
}
}
}
// If we can't get the texture after several attempts, log once and continue
if (!hasLogged && attemptCount++ > 10) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Could not access SDL Metal texture after %d attempts - shader will be skipped", attemptCount);
hasLogged = true;
}
}
void renderMetal() {
if (!renderer || !device || !pipelineState) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Metal render failed: missing components");
return;
}
// Correct pipeline: backBuffer → Metal shaders → screen (no double rendering)
// Try to get the Metal texture directly from the SDL backBuffer texture
updateMetalTexture(backBuffer);
if (!backBufferTexture) {
static int fallbackLogCount = 0;
if (fallbackLogCount++ < 3) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Metal texture not available - falling back to normal SDL rendering (attempt %d)", fallbackLogCount);
}
// Fallback: render without shaders using normal SDL path
SDL_SetRenderTarget(renderer, nullptr);
SDL_RenderTexture(renderer, backBuffer, nullptr, nullptr);
SDL_RenderPresent(renderer);
return;
}
// Apply Metal CRT shader directly: backBuffer texture → screen
SDL_SetRenderTarget(renderer, nullptr);
// Get Metal command encoder to render directly to screen
void* encoder_ptr = SDL_GetRenderMetalCommandEncoder(renderer);
if (encoder_ptr) {
id<MTLRenderCommandEncoder> encoder = (__bridge id<MTLRenderCommandEncoder>)encoder_ptr;
static int debugCount = 0;
if (debugCount++ < 5) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Metal render attempt %d: encoder=%p, pipeline=%p, texture=%p",
debugCount, encoder, pipelineState, backBufferTexture);
}
// Apply CRT shader effect directly to backBuffer texture
[encoder setRenderPipelineState:pipelineState];
[encoder setVertexBuffer:vertexBuffer offset:0 atIndex:0];
[encoder setFragmentTexture:backBufferTexture atIndex:0];
[encoder setFragmentSamplerState:sampler atIndex:0];
// Draw fullscreen quad with CRT effect directly to screen
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
static int successCount = 0;
if (successCount++ < 5) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Applied CRT shader to backBuffer (attempt %d) - texture size: %lux%lu",
successCount, [backBufferTexture width], [backBufferTexture height]);
}
} else {
// Fallback: render normally without shaders
SDL_RenderTexture(renderer, backBuffer, nullptr, nullptr);
static int fallbackCount = 0;
if (fallbackCount++ < 3) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Failed to get Metal command encoder - fallback rendering");
}
}
SDL_RenderPresent(renderer);
}
void renderWithPostProcessing(SDL_Renderer* renderer, SDL_Texture* sourceTexture) {
if (!renderer || !sourceTexture || !device || !pipelineState) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Metal post-processing failed: missing components");
// Fallback: render normally without shaders
SDL_SetRenderTarget(renderer, nullptr);
SDL_RenderTexture(renderer, sourceTexture, nullptr, nullptr);
SDL_RenderPresent(renderer);
return;
}
// Use our stored Metal texture if available
id<MTLTexture> metalTexture = gameCanvasTexture;
if (!metalTexture) {
static int fallbackLogCount = 0;
if (fallbackLogCount++ < 3) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "gameCanvasTexture not available - falling back to normal rendering (attempt %d)", fallbackLogCount);
}
// Fallback: render normally without shaders
SDL_SetRenderTarget(renderer, nullptr);
SDL_RenderTexture(renderer, sourceTexture, nullptr, nullptr);
SDL_RenderPresent(renderer);
return;
}
// Apply Metal CRT shader post-processing: sourceTexture → screen
SDL_SetRenderTarget(renderer, nullptr);
// Get Metal command encoder to render directly to screen
void* encoder_ptr = SDL_GetRenderMetalCommandEncoder(renderer);
if (encoder_ptr) {
id<MTLRenderCommandEncoder> encoder = (__bridge id<MTLRenderCommandEncoder>)encoder_ptr;
static int debugCount = 0;
if (debugCount++ < 3) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Metal post-processing attempt %d: encoder=%p, pipeline=%p, texture=%p",
debugCount, encoder, pipelineState, metalTexture);
}
// Apply CRT shader effect to sourceTexture
[encoder setRenderPipelineState:pipelineState];
[encoder setVertexBuffer:vertexBuffer offset:0 atIndex:0];
[encoder setFragmentTexture:metalTexture atIndex:0];
[encoder setFragmentSamplerState:sampler atIndex:0];
// Draw fullscreen quad with CRT effect directly to screen
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
static int successCount = 0;
if (successCount++ < 3) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Applied CRT post-processing (attempt %d) - texture size: %lux%lu",
successCount, [metalTexture width], [metalTexture height]);
}
} else {
// Fallback: render normally without shaders
SDL_RenderTexture(renderer, sourceTexture, nullptr, nullptr);
static int fallbackCount = 0;
if (fallbackCount++ < 3) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Failed to get Metal command encoder for post-processing - fallback rendering");
}
}
SDL_RenderPresent(renderer);
}
void cleanupMetal() {
// Release Metal objects (ARC handles most of this automatically)
pipelineState = nullptr;
backBufferTexture = nullptr;
gameCanvasTexture = nullptr;
vertexBuffer = nullptr;
sampler = nullptr;
device = nullptr;
renderer = nullptr;
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Metal shader system cleaned up");
}
} // namespace metal
} // namespace shader
#endif // __APPLE__

View File

@@ -82,11 +82,6 @@ void Fade::update() {
} }
} }
// Compatibilidad delta-time (ignora el parámetro ya que usa SDL_GetTicks)
void Fade::update(float delta_time) {
update(); // Llama al método original
}
void Fade::updatePreState() { void Fade::updatePreState() {
// Sistema basado en tiempo únicamente // Sistema basado en tiempo únicamente
Uint32 elapsed_time = SDL_GetTicks() - pre_start_time_; Uint32 elapsed_time = SDL_GetTicks() - pre_start_time_;

View File

@@ -39,8 +39,7 @@ class Fade {
// --- Métodos principales --- // --- Métodos principales ---
void reset(); // Resetea variables para reutilizar el fade void reset(); // Resetea variables para reutilizar el fade
void render(); // Dibuja la transición en pantalla void render(); // Dibuja la transición en pantalla
void update(); // Actualiza el estado interno (ya usa tiempo real) void update(); // Actualiza el estado interno
void update(float delta_time); // Compatibilidad delta-time (ignora el parámetro)
void activate(); // Activa el fade void activate(); // Activa el fade
// --- Configuración --- // --- Configuración ---
@@ -48,8 +47,8 @@ class Fade {
void setColor(Color color); // Establece el color del fade void setColor(Color color); // Establece el color del fade
void setType(Type type) { type_ = type; } // Establece el tipo de fade void setType(Type type) { type_ = type; } // Establece el tipo de fade
void setMode(Mode mode) { mode_ = mode; } // Establece el modo de fade void setMode(Mode mode) { mode_ = mode; } // Establece el modo de fade
void setPostDuration(int milliseconds) { post_duration_ = milliseconds; } // Duración posterior al fade en milisegundos void setPostDuration(int value) { post_duration_ = value; } // Duración posterior al fade en milisegundos
void setPreDuration(int milliseconds) { pre_duration_ = milliseconds; } // Duración previa al fade en milisegundos void setPreDuration(int value) { pre_duration_ = value; } // Duración previa al fade en milisegundos
// --- Getters --- // --- Getters ---
[[nodiscard]] auto getValue() const -> int { return value_; } [[nodiscard]] auto getValue() const -> int { return value_; }

View File

@@ -15,9 +15,8 @@
#include "texture.h" // Para Texture #include "texture.h" // Para Texture
constexpr int ZOOM_FACTOR = 5; constexpr int ZOOM_FACTOR = 5;
constexpr float FLASH_DELAY_S = 0.05f; // 3 frames → 0.05s constexpr int FLASH_DELAY = 3;
constexpr float FLASH_DURATION_S = 0.1f; // 6 frames → 0.1s (3 + 3) constexpr int FLASH_LENGTH = FLASH_DELAY + 3;
constexpr Color FLASH_COLOR = Color(0xFF, 0xFF, 0xFF); // Color blanco para el flash
// Constructor // Constructor
GameLogo::GameLogo(int x, int y) GameLogo::GameLogo(int x, int y)
@@ -46,7 +45,6 @@ void GameLogo::init() {
arcade_edition_status_ = Status::DISABLED; arcade_edition_status_ = Status::DISABLED;
shake_.init(1, 2, 8, XP); shake_.init(1, 2, 8, XP);
zoom_ = 3.0F * ZOOM_FACTOR; zoom_ = 3.0F * ZOOM_FACTOR;
post_finished_timer_ = 0.0f;
// Inicializa el bitmap de 'Coffee' // Inicializa el bitmap de 'Coffee'
coffee_sprite_->setPosX(XP); coffee_sprite_->setPosX(XP);
@@ -54,44 +52,44 @@ void GameLogo::init() {
coffee_sprite_->setWidth(coffee_texture_->getWidth()); coffee_sprite_->setWidth(coffee_texture_->getWidth());
coffee_sprite_->setHeight(coffee_texture_->getHeight()); coffee_sprite_->setHeight(coffee_texture_->getHeight());
coffee_sprite_->setVelX(0.0F); coffee_sprite_->setVelX(0.0F);
coffee_sprite_->setVelY(COFFEE_VEL_Y); coffee_sprite_->setVelY(2.5F);
coffee_sprite_->setAccelX(0.0F); coffee_sprite_->setAccelX(0.0F);
coffee_sprite_->setAccelY(COFFEE_ACCEL_Y); coffee_sprite_->setAccelY(0.1F);
coffee_sprite_->setSpriteClip(0, 0, coffee_texture_->getWidth(), coffee_texture_->getHeight()); coffee_sprite_->setSpriteClip(0, 0, coffee_texture_->getWidth(), coffee_texture_->getHeight());
coffee_sprite_->setEnabled(true); coffee_sprite_->setEnabled(true);
coffee_sprite_->setFinishedDelay(0.0f); coffee_sprite_->setFinishedCounter(0);
coffee_sprite_->setDestX(XP); coffee_sprite_->setDestX(XP);
coffee_sprite_->setDestY(y_ - coffee_texture_->getHeight()); coffee_sprite_->setDestY(y_ - coffee_texture_->getHeight());
// Inicializa el bitmap de 'Crisis' // Inicializa el bitmap de 'Crisis'
crisis_sprite_->setPosX(XP + CRISIS_OFFSET_X); crisis_sprite_->setPosX(XP + 15);
crisis_sprite_->setPosY(y_ + DESP); crisis_sprite_->setPosY(y_ + DESP);
crisis_sprite_->setWidth(crisis_texture_->getWidth()); crisis_sprite_->setWidth(crisis_texture_->getWidth());
crisis_sprite_->setHeight(crisis_texture_->getHeight()); crisis_sprite_->setHeight(crisis_texture_->getHeight());
crisis_sprite_->setVelX(0.0F); crisis_sprite_->setVelX(0.0F);
crisis_sprite_->setVelY(CRISIS_VEL_Y); crisis_sprite_->setVelY(-2.5F);
crisis_sprite_->setAccelX(0.0F); crisis_sprite_->setAccelX(0.0F);
crisis_sprite_->setAccelY(CRISIS_ACCEL_Y); crisis_sprite_->setAccelY(-0.1F);
crisis_sprite_->setSpriteClip(0, 0, crisis_texture_->getWidth(), crisis_texture_->getHeight()); crisis_sprite_->setSpriteClip(0, 0, crisis_texture_->getWidth(), crisis_texture_->getHeight());
crisis_sprite_->setEnabled(true); crisis_sprite_->setEnabled(true);
crisis_sprite_->setFinishedDelay(0.0f); crisis_sprite_->setFinishedCounter(0);
crisis_sprite_->setDestX(XP + CRISIS_OFFSET_X); crisis_sprite_->setDestX(XP + 15);
crisis_sprite_->setDestY(y_); crisis_sprite_->setDestY(y_);
// Inicializa el bitmap de 'DustRight' // Inicializa el bitmap de 'DustRight'
dust_right_sprite_->resetAnimation(); dust_right_sprite_->resetAnimation();
dust_right_sprite_->setPosX(coffee_sprite_->getPosX() + coffee_sprite_->getWidth()); dust_right_sprite_->setPosX(coffee_sprite_->getPosX() + coffee_sprite_->getWidth());
dust_right_sprite_->setPosY(y_); dust_right_sprite_->setPosY(y_);
dust_right_sprite_->setWidth(DUST_SIZE); dust_right_sprite_->setWidth(16);
dust_right_sprite_->setHeight(DUST_SIZE); dust_right_sprite_->setHeight(16);
dust_right_sprite_->setFlip(SDL_FLIP_HORIZONTAL); dust_right_sprite_->setFlip(SDL_FLIP_HORIZONTAL);
// Inicializa el bitmap de 'DustLeft' // Inicializa el bitmap de 'DustLeft'
dust_left_sprite_->resetAnimation(); dust_left_sprite_->resetAnimation();
dust_left_sprite_->setPosX(coffee_sprite_->getPosX() - DUST_SIZE); dust_left_sprite_->setPosX(coffee_sprite_->getPosX() - 16);
dust_left_sprite_->setPosY(y_); dust_left_sprite_->setPosY(y_);
dust_left_sprite_->setWidth(DUST_SIZE); dust_left_sprite_->setWidth(16);
dust_left_sprite_->setHeight(DUST_SIZE); dust_left_sprite_->setHeight(16);
// Inicializa el bitmap de 'Arcade Edition' // Inicializa el bitmap de 'Arcade Edition'
arcade_edition_sprite_->setZoom(zoom_); arcade_edition_sprite_->setZoom(zoom_);
@@ -114,45 +112,45 @@ void GameLogo::render() {
} }
} }
// Actualiza la lógica de la clase (time-based) // Actualiza la lógica de la clase
void GameLogo::update(float deltaTime) { void GameLogo::update() {
updateCoffeeCrisis(deltaTime); updateCoffeeCrisis();
updateArcadeEdition(deltaTime); updateArcadeEdition();
updatePostFinishedCounter(deltaTime); updatePostFinishedCounter();
} }
void GameLogo::updateCoffeeCrisis(float deltaTime) { void GameLogo::updateCoffeeCrisis() {
switch (coffee_crisis_status_) { switch (coffee_crisis_status_) {
case Status::MOVING: case Status::MOVING:
handleCoffeeCrisisMoving(deltaTime); handleCoffeeCrisisMoving();
break; break;
case Status::SHAKING: case Status::SHAKING:
handleCoffeeCrisisShaking(deltaTime); handleCoffeeCrisisShaking();
break; break;
case Status::FINISHED: case Status::FINISHED:
handleCoffeeCrisisFinished(deltaTime); handleCoffeeCrisisFinished();
break; break;
default: default:
break; break;
} }
} }
void GameLogo::updateArcadeEdition(float deltaTime) { void GameLogo::updateArcadeEdition() {
switch (arcade_edition_status_) { switch (arcade_edition_status_) {
case Status::MOVING: case Status::MOVING:
handleArcadeEditionMoving(deltaTime); handleArcadeEditionMoving();
break; break;
case Status::SHAKING: case Status::SHAKING:
handleArcadeEditionShaking(deltaTime); handleArcadeEditionShaking();
break; break;
default: default:
break; break;
} }
} }
void GameLogo::handleCoffeeCrisisMoving(float deltaTime) { void GameLogo::handleCoffeeCrisisMoving() {
coffee_sprite_->update(deltaTime); coffee_sprite_->update();
crisis_sprite_->update(deltaTime); crisis_sprite_->update();
if (coffee_sprite_->hasFinished() && crisis_sprite_->hasFinished()) { if (coffee_sprite_->hasFinished() && crisis_sprite_->hasFinished()) {
coffee_crisis_status_ = Status::SHAKING; coffee_crisis_status_ = Status::SHAKING;
@@ -160,23 +158,22 @@ void GameLogo::handleCoffeeCrisisMoving(float deltaTime) {
} }
} }
void GameLogo::handleCoffeeCrisisShaking(float deltaTime) { void GameLogo::handleCoffeeCrisisShaking() {
if (shake_.remaining > 0) { if (shake_.remaining > 0) {
processShakeEffect(coffee_sprite_.get(), crisis_sprite_.get(), deltaTime); processShakeEffect(coffee_sprite_.get(), crisis_sprite_.get());
} else { } else {
finishCoffeeCrisisShaking(); finishCoffeeCrisisShaking();
} }
updateDustSprites(deltaTime); updateDustSprites();
} }
void GameLogo::handleCoffeeCrisisFinished(float deltaTime) { void GameLogo::handleCoffeeCrisisFinished() {
updateDustSprites(deltaTime); updateDustSprites();
} }
void GameLogo::handleArcadeEditionMoving(float deltaTime) { void GameLogo::handleArcadeEditionMoving() {
// DeltaTime en segundos: decremento por segundo zoom_ -= 0.1F * ZOOM_FACTOR;
zoom_ -= (ZOOM_DECREMENT_PER_S * ZOOM_FACTOR) * deltaTime;
arcade_edition_sprite_->setZoom(zoom_); arcade_edition_sprite_->setZoom(zoom_);
if (zoom_ <= 1.0F) { if (zoom_ <= 1.0F) {
@@ -184,38 +181,34 @@ void GameLogo::handleArcadeEditionMoving(float deltaTime) {
} }
} }
void GameLogo::handleArcadeEditionShaking(float deltaTime) { void GameLogo::handleArcadeEditionShaking() {
if (shake_.remaining > 0) { if (shake_.remaining > 0) {
processArcadeEditionShake(deltaTime); processArcadeEditionShake();
} else { } else {
arcade_edition_sprite_->setX(shake_.origin); arcade_edition_sprite_->setX(shake_.origin);
arcade_edition_status_ = Status::FINISHED; arcade_edition_status_ = Status::FINISHED;
} }
} }
void GameLogo::processShakeEffect(SmartSprite* primary_sprite, SmartSprite* secondary_sprite) {
void GameLogo::processShakeEffect(SmartSprite* primary_sprite, SmartSprite* secondary_sprite, float deltaTime) { if (shake_.counter > 0) {
shake_.time_accumulator += deltaTime; shake_.counter--;
} else {
if (shake_.time_accumulator >= SHAKE_DELAY_S) { shake_.counter = shake_.delay;
shake_.time_accumulator -= SHAKE_DELAY_S;
const auto DISPLACEMENT = calculateShakeDisplacement(); const auto DISPLACEMENT = calculateShakeDisplacement();
primary_sprite->setPosX(shake_.origin + DISPLACEMENT); primary_sprite->setPosX(shake_.origin + DISPLACEMENT);
if (secondary_sprite != nullptr) { if (secondary_sprite != nullptr) {
secondary_sprite->setPosX(shake_.origin + DISPLACEMENT + CRISIS_OFFSET_X); secondary_sprite->setPosX(shake_.origin + DISPLACEMENT + 15);
} }
shake_.remaining--; shake_.remaining--;
} }
} }
void GameLogo::processArcadeEditionShake(float deltaTime) { void GameLogo::processArcadeEditionShake() {
// Delay fijo en segundos (shake_.delay era frames, ahora usamos constante) if (shake_.counter > 0) {
float delayTime = SHAKE_DELAY_S; shake_.counter--;
} else {
shake_.time_accumulator += deltaTime; shake_.counter = shake_.delay;
if (shake_.time_accumulator >= delayTime) {
shake_.time_accumulator -= delayTime;
const auto DISPLACEMENT = calculateShakeDisplacement(); const auto DISPLACEMENT = calculateShakeDisplacement();
arcade_edition_sprite_->setX(shake_.origin + DISPLACEMENT); arcade_edition_sprite_->setX(shake_.origin + DISPLACEMENT);
shake_.remaining--; shake_.remaining--;
@@ -228,7 +221,7 @@ auto GameLogo::calculateShakeDisplacement() const -> int {
void GameLogo::finishCoffeeCrisisShaking() { void GameLogo::finishCoffeeCrisisShaking() {
coffee_sprite_->setPosX(shake_.origin); coffee_sprite_->setPosX(shake_.origin);
crisis_sprite_->setPosX(shake_.origin + CRISIS_OFFSET_X); crisis_sprite_->setPosX(shake_.origin + 15);
coffee_crisis_status_ = Status::FINISHED; coffee_crisis_status_ = Status::FINISHED;
arcade_edition_status_ = Status::MOVING; arcade_edition_status_ = Status::MOVING;
} }
@@ -243,20 +236,20 @@ void GameLogo::finishArcadeEditionMoving() {
void GameLogo::playTitleEffects() { void GameLogo::playTitleEffects() {
Audio::get()->playSound("title.wav"); Audio::get()->playSound("title.wav");
Screen::get()->flash(FLASH_COLOR, FLASH_DURATION_S, FLASH_DELAY_S); Screen::get()->flash(Color(0xFF, 0xFF, 0xFF), FLASH_LENGTH, FLASH_DELAY);
Screen::get()->shake(); Screen::get()->shake();
} }
void GameLogo::updateDustSprites(float deltaTime) { void GameLogo::updateDustSprites() {
dust_right_sprite_->update(deltaTime); dust_right_sprite_->update();
dust_left_sprite_->update(deltaTime); dust_left_sprite_->update();
} }
void GameLogo::updatePostFinishedCounter(float deltaTime) { void GameLogo::updatePostFinishedCounter() {
if (coffee_crisis_status_ == Status::FINISHED && if (coffee_crisis_status_ == Status::FINISHED &&
arcade_edition_status_ == Status::FINISHED) { arcade_edition_status_ == Status::FINISHED &&
post_finished_counter_ > 0) {
post_finished_timer_ += deltaTime; --post_finished_counter_;
} }
} }
@@ -268,7 +261,7 @@ void GameLogo::enable() {
// Indica si ha terminado la animación // Indica si ha terminado la animación
auto GameLogo::hasFinished() const -> bool { auto GameLogo::hasFinished() const -> bool {
return post_finished_timer_ >= post_finished_delay_s_; return post_finished_counter_ == 0;
} }
// Calcula el desplazamiento vertical inicial // Calcula el desplazamiento vertical inicial

View File

@@ -11,24 +11,13 @@ class Texture;
// --- Clase GameLogo: gestor del logo del juego --- // --- Clase GameLogo: gestor del logo del juego ---
class GameLogo { class GameLogo {
public: public:
// --- Constantes ---
static constexpr float COFFEE_VEL_Y = 0.15F * 1000.0F; // Velocidad Y de coffee sprite (pixels/s) - 0.15F * 1000 = 150 pixels/s
static constexpr float COFFEE_ACCEL_Y = 0.00036F * 1000000.0F; // Aceleración Y de coffee sprite (pixels/s²) - 0.00036F * 1000000 = 360 pixels/s²
static constexpr float CRISIS_VEL_Y = -0.15F * 1000.0F; // Velocidad Y de crisis sprite (pixels/s) - -0.15F * 1000 = -150 pixels/s
static constexpr float CRISIS_ACCEL_Y = -0.00036F * 1000000.0F; // Aceleración Y de crisis sprite (pixels/s²) - -0.00036F * 1000000 = -360 pixels/s²
static constexpr int CRISIS_OFFSET_X = 15; // Desplazamiento X de crisis sprite
static constexpr int DUST_SIZE = 16; // Tamaño de dust sprites
static constexpr float ZOOM_DECREMENT_PER_S = 0.006F * 1000.0F; // Decremento de zoom por segundo (0.006F * 1000 = 6.0F per second)
static constexpr float SHAKE_DELAY_S = 33.34F / 1000.0F; // Delay de shake en segundos (33.34ms / 1000 = 0.03334s)
static constexpr float POST_FINISHED_FRAME_TIME_S = 16.67F / 1000.0F; // Tiempo entre decrementos del counter (16.67ms / 1000 = 0.01667s)
// --- Constructores y destructor --- // --- Constructores y destructor ---
GameLogo(int x, int y); GameLogo(int x, int y);
~GameLogo() = default; ~GameLogo() = default;
// --- Métodos principales --- // --- Métodos principales ---
void render(); // Pinta la clase en pantalla void render(); // Pinta la clase en pantalla
void update(float deltaTime); // Actualiza la lógica de la clase (time-based) void update(); // Actualiza la lógica de la clase
void enable(); // Activa la clase void enable(); // Activa la clase
// --- Getters --- // --- Getters ---
@@ -46,11 +35,10 @@ class GameLogo {
// --- Estructuras privadas --- // --- Estructuras privadas ---
struct Shake { struct Shake {
int desp = 1; // Pixels de desplazamiento para agitar la pantalla en el eje x int desp = 1; // Pixels de desplazamiento para agitar la pantalla en el eje x
int delay = 2; // Retraso entre cada desplazamiento de la pantalla al agitarse (frame-based) int delay = 2; // Retraso entre cada desplazamiento de la pantalla al agitarse
int length = 8; // Cantidad de desplazamientos a realizar int length = 8; // Cantidad de desplazamientos a realizar
int remaining = length; // Cantidad de desplazamientos pendientes a realizar int remaining = length; // Cantidad de desplazamientos pendientes a realizar
int counter = delay; // Contador para el retraso (frame-based) int counter = delay; // Contador para el retraso
float time_accumulator = 0.0f; // Acumulador de tiempo para deltaTime
int origin = 0; // Valor inicial de la pantalla para dejarla igual tras el desplazamiento int origin = 0; // Valor inicial de la pantalla para dejarla igual tras el desplazamiento
Shake() = default; Shake() = default;
@@ -68,7 +56,6 @@ class GameLogo {
length = l; length = l;
remaining = l; remaining = l;
counter = de; counter = de;
time_accumulator = 0.0f;
origin = o; origin = o;
} }
}; };
@@ -92,34 +79,32 @@ class GameLogo {
float x_; // Posición X del logo float x_; // Posición X del logo
float y_; // Posición Y del logo float y_; // Posición Y del logo
float zoom_ = 1.0F; // Zoom aplicado al texto "ARCADE EDITION" float zoom_ = 1.0F; // Zoom aplicado al texto "ARCADE EDITION"
float post_finished_delay_s_ = POST_FINISHED_FRAME_TIME_S; // Retraso final tras animaciones (s) int post_finished_counter_ = 1; // Contador final tras animaciones
float post_finished_timer_ = 0.0f; // Timer acumulado para retraso final (s)
// --- Inicialización --- // --- Inicialización ---
void init(); // Inicializa las variables void init(); // Inicializa las variables
[[nodiscard]] auto getInitialVerticalDesp() const -> int; // Calcula el desplazamiento vertical inicial [[nodiscard]] auto getInitialVerticalDesp() const -> int; // Calcula el desplazamiento vertical inicial
// --- Actualización de estados específicos --- // --- Actualización de estados específicos ---
void updateCoffeeCrisis(float deltaTime); // Actualiza el estado de "Coffee Crisis" (time-based) void updateCoffeeCrisis(); // Actualiza el estado de "Coffee Crisis"
void updateArcadeEdition(float deltaTime); // Actualiza el estado de "Arcade Edition" (time-based) void updateArcadeEdition(); // Actualiza el estado de "Arcade Edition"
void updatePostFinishedCounter(float deltaTime); // Actualiza el contador tras finalizar una animación (time-based) void updatePostFinishedCounter(); // Actualiza el contador tras finalizar una animación
// --- Efectos visuales: movimiento y sacudidas --- // --- Efectos visuales: movimiento y sacudidas ---
void handleCoffeeCrisisMoving(float deltaTime); // Maneja el movimiento de "Coffee Crisis" (time-based) void handleCoffeeCrisisMoving(); // Maneja el movimiento de "Coffee Crisis"
void handleCoffeeCrisisShaking(float deltaTime); // Maneja la sacudida de "Coffee Crisis" (time-based) void handleCoffeeCrisisShaking(); // Maneja la sacudida de "Coffee Crisis"
void handleArcadeEditionMoving(float deltaTime); // Maneja el movimiento de "Arcade Edition" (time-based) void handleArcadeEditionMoving(); // Maneja el movimiento de "Arcade Edition"
void handleArcadeEditionShaking(float deltaTime); // Maneja la sacudida de "Arcade Edition" (time-based) void handleArcadeEditionShaking(); // Maneja la sacudida de "Arcade Edition"
void processShakeEffect(SmartSprite* primary_sprite, SmartSprite* secondary_sprite = nullptr); // Procesa el efecto de sacudida en sprites (frame-based) void processShakeEffect(SmartSprite* primary_sprite, SmartSprite* secondary_sprite = nullptr); // Procesa el efecto de sacudida en sprites
void processShakeEffect(SmartSprite* primary_sprite, SmartSprite* secondary_sprite, float deltaTime); // Procesa el efecto de sacudida en sprites (time-based) void processArcadeEditionShake(); // Procesa la sacudida específica de "Arcade Edition"
void processArcadeEditionShake(float deltaTime); // Procesa la sacudida específica de "Arcade Edition" (time-based)
[[nodiscard]] auto calculateShakeDisplacement() const -> int; // Calcula el desplazamiento de la sacudida [[nodiscard]] auto calculateShakeDisplacement() const -> int; // Calcula el desplazamiento de la sacudida
// --- Gestión de finalización de efectos --- // --- Gestión de finalización de efectos ---
void handleCoffeeCrisisFinished(float deltaTime); // Maneja el final de la animación "Coffee Crisis" (time-based) void handleCoffeeCrisisFinished(); // Maneja el final de la animación "Coffee Crisis"
void finishCoffeeCrisisShaking(); // Finaliza la sacudida de "Coffee Crisis" void finishCoffeeCrisisShaking(); // Finaliza la sacudida de "Coffee Crisis"
void finishArcadeEditionMoving(); // Finaliza el movimiento de "Arcade Edition" void finishArcadeEditionMoving(); // Finaliza el movimiento de "Arcade Edition"
// --- Utilidades --- // --- Utilidades ---
static void playTitleEffects(); // Reproduce efectos visuales/sonoros del título static void playTitleEffects(); // Reproduce efectos visuales/sonoros del título
void updateDustSprites(float deltaTime); // Actualiza los sprites de polvo (time-based) void updateDustSprites(); // Actualiza los sprites de polvo
}; };

View File

@@ -1,7 +1,6 @@
#include "item.h" #include "item.h"
#include <algorithm> // Para clamp #include <algorithm> // Para clamp
#include <cmath> // Para fmod
#include <cstdlib> // Para rand #include <cstdlib> // Para rand
#include "animated_sprite.h" // Para AnimatedSprite #include "animated_sprite.h" // Para AnimatedSprite
@@ -9,7 +8,7 @@
class Texture; // lines 6-6 class Texture; // lines 6-6
Item::Item(ItemType type, float x, float y, SDL_FRect& play_area, const std::shared_ptr<Texture>& texture, const std::vector<std::string>& animation) Item::Item(ItemType type, float x, float y, SDL_FRect &play_area, const std::shared_ptr<Texture> &texture, const std::vector<std::string> &animation)
: sprite_(std::make_unique<AnimatedSprite>(texture, animation)), : sprite_(std::make_unique<AnimatedSprite>(texture, animation)),
play_area_(play_area), play_area_(play_area),
type_(type) { type_(type) {
@@ -19,31 +18,29 @@ Item::Item(ItemType type, float x, float y, SDL_FRect& play_area, const std::sha
height_ = COFFEE_MACHINE_HEIGHT; height_ = COFFEE_MACHINE_HEIGHT;
pos_x_ = getCoffeeMachineSpawn(x, width_, play_area_.w); pos_x_ = getCoffeeMachineSpawn(x, width_, play_area_.w);
pos_y_ = y; pos_y_ = y;
vel_x_ = ((rand() % 3) - 1) * COFFEE_MACHINE_VEL_X_FACTOR; vel_x_ = ((rand() % 3) - 1) * 0.5F;
vel_y_ = COFFEE_MACHINE_VEL_Y; vel_y_ = -0.1F;
accel_y_ = COFFEE_MACHINE_ACCEL_Y; accel_y_ = 0.1F;
collider_.r = 10; collider_.r = 10;
break; break;
} }
default: { default: {
width_ = param.game.item_size;
height_ = param.game.item_size;
pos_x_ = x; pos_x_ = x;
pos_y_ = y; pos_y_ = y;
// 6 velocidades: 3 negativas (-1.0, -0.66, -0.33) y 3 positivas (0.33, 0.66, 1.0) // 6 velocidades: 3 negativas (-1.0, -0.66, -0.33) y 3 positivas (0.33, 0.66, 1.0)
const int direction = rand() % 6; const int direction = rand() % 6;
if (direction < 3) { if (direction < 3) {
// Velocidades negativas: -1.0, -0.66, -0.33 // Velocidades negativas: -1.0, -0.66, -0.33
vel_x_ = -ITEM_VEL_X_BASE + (direction * ITEM_VEL_X_STEP); vel_x_ = -1.0F + (direction * 0.33F);
rotate_speed_ = -720.0F;
} else { } else {
// Velocidades positivas: 0.33, 0.66, 1.0 // Velocidades positivas: 0.33, 0.66, 1.0
vel_x_ = ITEM_VEL_X_STEP + ((direction - 3) * ITEM_VEL_X_STEP); vel_x_ = 0.33F + ((direction - 3) * 0.33F);
rotate_speed_ = 720.0F;
} }
vel_y_ = ITEM_VEL_Y; vel_y_ = -4.0F;
accel_y_ = ITEM_ACCEL_Y; accel_y_ = 0.2F;
collider_.r = width_ / 2; collider_.r = width_ / 2;
sprite_->startRotate();
sprite_->setRotateAmount(rotate_speed_);
break; break;
} }
} }
@@ -69,34 +66,24 @@ void Item::alignTo(int x) {
void Item::render() { void Item::render() {
if (enabled_) { if (enabled_) {
// Muestra normalmente hasta los últimos ~3.3 segundos if (time_to_live_ > 200) {
constexpr float BLINK_START_S = LIFETIME_DURATION_S - 3.33f;
if (lifetime_timer_ < BLINK_START_S) {
sprite_->render(); sprite_->render();
} else { } else if (time_to_live_ % 20 > 10) {
// Efecto de parpadeo en los últimos segundos (cada ~0.33 segundos)
constexpr float BLINK_INTERVAL_S = 0.33f;
const float phase = fmod(lifetime_timer_, BLINK_INTERVAL_S);
const float half_interval = BLINK_INTERVAL_S / 2.0f;
if (phase < half_interval) {
sprite_->render(); sprite_->render();
} }
} }
}
} }
void Item::move(float deltaTime) { void Item::move() {
floor_collision_ = false; floor_collision_ = false;
// Calcula la nueva posición usando deltaTime (velocidad en pixels/segundo) // Calcula la nueva posición
pos_x_ += vel_x_ * deltaTime; pos_x_ += vel_x_;
pos_y_ += vel_y_ * deltaTime; pos_y_ += vel_y_;
// Aplica las aceleraciones a la velocidad usando deltaTime (aceleración en pixels/segundo²) // Aplica las aceleraciones a la velocidad
vel_x_ += accel_x_ * deltaTime; vel_x_ += accel_x_;
vel_y_ += accel_y_ * deltaTime; vel_y_ += accel_y_;
// Comprueba los laterales de la zona de juego // Comprueba los laterales de la zona de juego
const float MIN_X = param.game.play_area.rect.x; const float MIN_X = param.game.play_area.rect.x;
@@ -106,7 +93,6 @@ void Item::move(float deltaTime) {
// Si toca el borde lateral // Si toca el borde lateral
if (pos_x_ == MIN_X || pos_x_ == MAX_X) { if (pos_x_ == MIN_X || pos_x_ == MAX_X) {
vel_x_ = -vel_x_; // Invierte la velocidad horizontal vel_x_ = -vel_x_; // Invierte la velocidad horizontal
sprite_->scaleRotateAmount(-1.0F); // Invierte la rotación
} }
// Si colisiona por arriba, rebota (excepto la máquina de café) // Si colisiona por arriba, rebota (excepto la máquina de café)
@@ -120,32 +106,31 @@ void Item::move(float deltaTime) {
// Si colisiona con la parte inferior // Si colisiona con la parte inferior
if (pos_y_ > play_area_.h - height_) { if (pos_y_ > play_area_.h - height_) {
pos_y_ = play_area_.h - height_; // Corrige la posición // Corrige la posición
sprite_->scaleRotateAmount(0.5F); // Reduce la rotación pos_y_ = play_area_.h - height_;
sprite_->stopRotate(300.0F); // Detiene la rotacion
switch (type_) { switch (type_) {
case ItemType::COFFEE_MACHINE: case ItemType::COFFEE_MACHINE:
// La máquina de café es mas pesada y tiene una fisica diferente, ademas hace ruido // La máquina de café es mas pesada y tiene una fisica diferente, ademas hace ruido
floor_collision_ = true; floor_collision_ = true;
if (std::abs(vel_y_) < BOUNCE_VEL_THRESHOLD) { if (vel_y_ < 1.0F) {
// Si la velocidad vertical es baja, detiene el objeto // Si la velocidad vertical es baja, detiene el objeto
vel_y_ = vel_x_ = accel_x_ = accel_y_ = 0; vel_y_ = vel_x_ = accel_x_ = accel_y_ = 0;
} else { } else {
// Si la velocidad vertical es alta, el objeto rebota y pierde velocidad // Si la velocidad vertical es alta, el objeto rebota y pierde velocidad
vel_y_ *= COFFEE_BOUNCE_DAMPING; vel_y_ *= -0.20F;
vel_x_ *= HORIZONTAL_DAMPING; vel_x_ *= 0.75F;
} }
break; break;
default: default:
// Si no es una máquina de café // Si no es una máquina de café
if (std::abs(vel_y_) < BOUNCE_VEL_THRESHOLD) { if (vel_y_ < 1.0F) {
// Si la velocidad vertical es baja, detiene el objeto // Si la velocidad vertical es baja, detiene el objeto
vel_y_ = vel_x_ = accel_x_ = accel_y_ = 0; vel_y_ = vel_x_ = accel_x_ = accel_y_ = 0;
} else { } else {
// Si la velocidad vertical es alta, el objeto rebota y pierde velocidad // Si la velocidad vertical es alta, el objeto rebota y pierde velocidad
vel_y_ *= ITEM_BOUNCE_DAMPING; vel_y_ *= -0.5F;
vel_x_ *= HORIZONTAL_DAMPING; vel_x_ *= 0.75F;
} }
break; break;
} }
@@ -158,15 +143,16 @@ void Item::move(float deltaTime) {
void Item::disable() { enabled_ = false; } void Item::disable() { enabled_ = false; }
void Item::update(float deltaTime) { void Item::update() {
move(deltaTime); move();
sprite_->update(deltaTime); sprite_->update();
updateTimeToLive(deltaTime); updateTimeToLive();
} }
void Item::updateTimeToLive(float deltaTime) { void Item::updateTimeToLive() {
lifetime_timer_ += deltaTime; if (time_to_live_ > 0) {
if (lifetime_timer_ >= LIFETIME_DURATION_S) { time_to_live_--;
} else {
disable(); disable();
} }
} }

View File

@@ -27,38 +27,18 @@ enum class ItemType : int {
class Item { class Item {
public: public:
// --- Constantes --- // --- Constantes ---
static constexpr float WIDTH = 20.0F; // Anchura del item
static constexpr float HEIGHT = 20.0F; // ALtura del item
static constexpr int COFFEE_MACHINE_WIDTH = 30; // Anchura de la máquina de café static constexpr int COFFEE_MACHINE_WIDTH = 30; // Anchura de la máquina de café
static constexpr int COFFEE_MACHINE_HEIGHT = 39; // Altura de la máquina de café static constexpr int COFFEE_MACHINE_HEIGHT = 39; // Altura de la máquina de café
static constexpr float LIFETIME_DURATION_S = 10.0f; // Duración de vida del ítem en segundos
// Velocidades base (pixels/segundo) - Coffee Machine
static constexpr float COFFEE_MACHINE_VEL_X_FACTOR = 30.0F; // Factor para velocidad X de máquina de café (0.5*60fps)
static constexpr float COFFEE_MACHINE_VEL_Y = -6.0F; // Velocidad Y inicial de máquina de café (-0.1*60fps)
static constexpr float COFFEE_MACHINE_ACCEL_Y = 360.0F; // Aceleración Y de máquina de café (0.1*60²fps = 360 pixels/segundo²)
// Velocidades base (pixels/segundo) - Items normales
static constexpr float ITEM_VEL_X_BASE = 60.0F; // Velocidad X base para items (1.0F*60fps)
static constexpr float ITEM_VEL_X_STEP = 20.0F; // Incremento de velocidad X (0.33F*60fps)
static constexpr float ITEM_VEL_Y = -240.0F; // Velocidad Y inicial de items (-4.0F*60fps)
static constexpr float ITEM_ACCEL_Y = 720.0F; // Aceleración Y de items (0.2*60²fps = 720 pixels/segundo²)
// Constantes de física de rebote
static constexpr float BOUNCE_VEL_THRESHOLD = 60.0F; // Umbral de velocidad para parar (1.0F*60fps)
static constexpr float COFFEE_BOUNCE_DAMPING = -0.20F; // Factor de rebote Y para máquina de café
static constexpr float ITEM_BOUNCE_DAMPING = -0.5F; // Factor de rebote Y para items normales
static constexpr float HORIZONTAL_DAMPING = 0.75F; // Factor de amortiguación horizontal
// --- Constructor y destructor --- // --- Constructor y destructor ---
Item(ItemType type, float x, float y, SDL_FRect& play_area, const std::shared_ptr<Texture>& texture, const std::vector<std::string>& animation); // Constructor principal Item(ItemType type, float x, float y, SDL_FRect &play_area, const std::shared_ptr<Texture> &texture, const std::vector<std::string> &animation); // Constructor principal
~Item() = default; // Destructor ~Item() = default; // Destructor
// --- Métodos principales --- // --- Métodos principales ---
void alignTo(int x); // Centra el objeto en la posición X indicada void alignTo(int x); // Centra el objeto en la posición X indicada
void render(); // Renderiza el objeto en pantalla void render(); // Renderiza el objeto en pantalla
void disable(); // Desactiva el objeto void disable(); // Desactiva el objeto
void update(float deltaTime); // Actualiza la posición, animación y contadores (time-based) void update(); // Actualiza la posición, animación y contadores
// --- Getters --- // --- Getters ---
[[nodiscard]] auto getPosX() const -> float { return pos_x_; } // Obtiene la posición X [[nodiscard]] auto getPosX() const -> float { return pos_x_; } // Obtiene la posición X
@@ -68,7 +48,7 @@ class Item {
[[nodiscard]] auto getType() const -> ItemType { return type_; } // Obtiene el tipo [[nodiscard]] auto getType() const -> ItemType { return type_; } // Obtiene el tipo
[[nodiscard]] auto isEnabled() const -> bool { return enabled_; } // Verifica si está habilitado [[nodiscard]] auto isEnabled() const -> bool { return enabled_; } // Verifica si está habilitado
[[nodiscard]] auto isOnFloor() const -> bool { return floor_collision_; } // Verifica si está en el suelo [[nodiscard]] auto isOnFloor() const -> bool { return floor_collision_; } // Verifica si está en el suelo
auto getCollider() -> Circle& { return collider_; } // Obtiene el colisionador auto getCollider() -> Circle & { return collider_; } // Obtiene el colisionador
private: private:
// --- Objetos y punteros --- // --- Objetos y punteros ---
@@ -78,23 +58,22 @@ class Item {
SDL_FRect play_area_; // Rectángulo con la zona de juego SDL_FRect play_area_; // Rectángulo con la zona de juego
Circle collider_; // Círculo de colisión del objeto Circle collider_; // Círculo de colisión del objeto
ItemType type_; // Tipo de objeto ItemType type_; // Tipo de objeto
float pos_x_ = 0.0F; // Posición X del objeto float pos_x_; // Posición X del objeto
float pos_y_ = 0.0F; // Posición Y del objeto float pos_y_; // Posición Y del objeto
float vel_x_ = 0.0F; // Velocidad en el eje X float vel_x_; // Velocidad en el eje X
float vel_y_ = 0.0F; // Velocidad en el eje Y float vel_y_; // Velocidad en el eje Y
float accel_x_ = 0.0F; // Aceleración en el eje X float accel_x_ = 0.0F; // Aceleración en el eje X
float accel_y_ = 0.0F; // Aceleración en el eje Y float accel_y_; // Aceleración en el eje Y
float width_ = WIDTH; // Ancho del objeto int width_; // Ancho del objeto
float height_ = HEIGHT; // Alto del objeto int height_; // Alto del objeto
float rotate_speed_ = 0.0F; // Velocidad de rotacion Uint16 time_to_live_ = 600; // Tiempo que el objeto está presente
float lifetime_timer_ = 0.0f; // Acumulador de tiempo de vida del ítem (segundos)
bool floor_collision_ = false; // Indica si el objeto colisiona con el suelo bool floor_collision_ = false; // Indica si el objeto colisiona con el suelo
bool enabled_ = true; // Indica si el objeto está habilitado bool enabled_ = true; // Indica si el objeto está habilitado
// --- Métodos internos --- // --- Métodos internos ---
void shiftColliders(); // Alinea el círculo de colisión con la posición del objeto void shiftColliders(); // Alinea el círculo de colisión con la posición del objeto
void shiftSprite(); // Coloca el sprite en la posición del objeto void shiftSprite(); // Coloca el sprite en la posición del objeto
void move(float deltaTime); // Actualiza la posición y estados del objeto (time-based) void move(); // Actualiza la posición y estados del objeto
void updateTimeToLive(float deltaTime); // Actualiza el contador de tiempo de vida (time-based) void updateTimeToLive(); // Actualiza el contador de tiempo de vida
static auto getCoffeeMachineSpawn(int player_x, int item_width, int area_width, int margin = 2) -> int; // Calcula la zona de aparición de la máquina de café static auto getCoffeeMachineSpawn(int player_x, int item_width, int area_width, int margin = 2) -> int; // Calcula la zona de aparición de la máquina de café
}; };

View File

@@ -24,32 +24,6 @@ void ManageHiScoreTable::clear() {
table_.emplace_back("PACMQ", 200); table_.emplace_back("PACMQ", 200);
table_.emplace_back("PELEC", 100); table_.emplace_back("PELEC", 100);
/*
table_.emplace_back("BRY", 1000);
table_.emplace_back("USUFO", 500);
table_.emplace_back("GLUCA", 100);
table_.emplace_back("PARRA", 50);
table_.emplace_back("CAGAM", 10);
table_.emplace_back("PEPE", 5);
table_.emplace_back("ROSIT", 4);
table_.emplace_back("SAM", 3);
table_.emplace_back("PACMQ", 2);
table_.emplace_back("PELEC", 1);
*/
/*
table_.emplace_back("BRY", 5000000);
table_.emplace_back("USUFO", 5000000);
table_.emplace_back("GLUCA", 5000000);
table_.emplace_back("PARRA", 5000000);
table_.emplace_back("CAGAM", 5000000);
table_.emplace_back("PEPE", 5000000);
table_.emplace_back("ROSIT", 5000000);
table_.emplace_back("SAM", 5000000);
table_.emplace_back("PACMQ", 5000000);
table_.emplace_back("PELEC", 5000000);
*/
sort(); sort();
} }

View File

@@ -1,6 +1,5 @@
#include "moving_sprite.h" #include "moving_sprite.h"
#include <cmath> // Para std::abs
#include <utility> #include <utility>
#include "texture.h" // Para Texture #include "texture.h" // Para Texture
@@ -54,56 +53,42 @@ void MovingSprite::stop() {
flip_ = SDL_FLIP_NONE; // Establece como se ha de voltear el sprite flip_ = SDL_FLIP_NONE; // Establece como se ha de voltear el sprite
} }
// Mueve el sprite (time-based) // Mueve el sprite
void MovingSprite::move(float deltaTime) { void MovingSprite::move() {
// DeltaTime puro: velocidad (pixels/ms) * tiempo (ms) x_ += vx_;
x_ += vx_ * deltaTime; y_ += vy_;
y_ += vy_ * deltaTime;
// Aceleración (pixels/ms²) * tiempo (ms) vx_ += ax_;
vx_ += ax_ * deltaTime; vy_ += ay_;
vy_ += ay_ * deltaTime;
pos_.x = static_cast<int>(x_); pos_.x = static_cast<int>(x_);
pos_.y = static_cast<int>(y_); pos_.y = static_cast<int>(y_);
} }
// Actualiza las variables internas del objeto (time-based) // Actualiza las variables internas del objeto
void MovingSprite::update(float deltaTime) { void MovingSprite::update() {
move(deltaTime); move();
rotate(deltaTime); rotate();
} }
// Muestra el sprite por pantalla // Muestra el sprite por pantalla
void MovingSprite::render() { void MovingSprite::render() { getTexture()->render(pos_.x, pos_.y, &sprite_clip_, horizontal_zoom_, vertical_zoom_, rotate_.angle, &rotate_.center, flip_); }
getTexture()->render(pos_.x, pos_.y, &sprite_clip_, horizontal_zoom_, vertical_zoom_, rotate_.angle, &rotate_.center, flip_);
}
// Establece la rotacion (time-based) // Establece la rotacion
void MovingSprite::rotate(float deltaTime) { void MovingSprite::rotate() {
if (rotate_.enabled) { if (rotate_.enabled) {
rotate_.angle += rotate_.amount * deltaTime; ++rotate_.counter;
if (rotate_.counter % rotate_.speed == 0) {
updateAngle();
rotate_.counter = 0;
}
} }
} }
// Activa o desactiva el efecto de rotación // Activa o desactiva el efecto de rotación
void MovingSprite::setRotate(bool enable) { void MovingSprite::setRotate(bool enable) {
rotate_.enabled = enable; rotate_.enabled = enable;
} rotate_.counter = 0;
// Habilita la rotación y establece el centro en el centro del sprite
void MovingSprite::startRotate() {
rotate_.enabled = true;
rotate_.center.x = pos_.w / 2.0F;
rotate_.center.y = pos_.h / 2.0F;
}
// Detiene la rotación y resetea el ángulo a cero
void MovingSprite::stopRotate(float threshold) {
if (threshold == 0.0F || std::abs(rotate_.amount) <= threshold) {
rotate_.enabled = false;
rotate_.angle = 0.0;
}
} }
// Establece la posición y_ el tamaño del objeto // Establece la posición y_ el tamaño del objeto

View File

@@ -15,6 +15,8 @@ class MovingSprite : public Sprite {
// --- Estructuras --- // --- Estructuras ---
struct Rotate { struct Rotate {
bool enabled{false}; // Indica si ha de rotar bool enabled{false}; // Indica si ha de rotar
int counter{0}; // Contador
int speed{1}; // Velocidad de giro
double angle{0.0}; // Ángulo para dibujarlo double angle{0.0}; // Ángulo para dibujarlo
float amount{0.0F}; // Cantidad de grados a girar en cada iteración float amount{0.0F}; // Cantidad de grados a girar en cada iteración
SDL_FPoint center{.x = 0.0F, .y = 0.0F}; // Centro de rotación SDL_FPoint center{.x = 0.0F, .y = 0.0F}; // Centro de rotación
@@ -27,7 +29,7 @@ class MovingSprite : public Sprite {
~MovingSprite() override = default; ~MovingSprite() override = default;
// --- Métodos principales --- // --- Métodos principales ---
virtual void update(float deltaTime); // Actualiza las variables internas del objeto (time-based) virtual void update(); // Actualiza las variables internas del objeto
void clear() override; // Reinicia todas las variables a cero void clear() override; // Reinicia todas las variables a cero
void stop(); // Elimina el movimiento del sprite void stop(); // Elimina el movimiento del sprite
void render() override; // Muestra el sprite por pantalla void render() override; // Muestra el sprite por pantalla
@@ -46,10 +48,8 @@ class MovingSprite : public Sprite {
void setAngle(double value) { rotate_.angle = value; } // Establece el ángulo void setAngle(double value) { rotate_.angle = value; } // Establece el ángulo
void setRotatingCenter(SDL_FPoint point) { rotate_.center = point; } // Establece el centro de rotación void setRotatingCenter(SDL_FPoint point) { rotate_.center = point; } // Establece el centro de rotación
void setRotate(bool enable); // Activa o desactiva el efecto de rotación void setRotate(bool enable); // Activa o desactiva el efecto de rotación
void startRotate(); // Habilita la rotación con centro automático void setRotateSpeed(int value) { rotate_.speed = std::max(1, value); } // Establece la velocidad de rotación
void stopRotate(float threshold = 0.0F); // Detiene la rotación y resetea ángulo void setRotateAmount(double value) { rotate_.amount = value; } // Establece la cantidad de rotación
void setRotateAmount(double value) { rotate_.amount = value; } // Establece la velocidad de rotación
void scaleRotateAmount(float value) { rotate_.amount *= value; } // Modifica la velocidad de rotacion
void switchRotate() { rotate_.amount *= -1; } // Cambia el sentido de la rotación void switchRotate() { rotate_.amount *= -1; } // Cambia el sentido de la rotación
void setFlip(SDL_FlipMode flip) { flip_ = flip; } // Establece el flip void setFlip(SDL_FlipMode flip) { flip_ = flip; } // Establece el flip
void flip() { flip_ = (flip_ == SDL_FLIP_HORIZONTAL) ? SDL_FLIP_NONE : SDL_FLIP_HORIZONTAL; } // Cambia el flip void flip() { flip_ = (flip_ == SDL_FLIP_HORIZONTAL) ? SDL_FLIP_NONE : SDL_FLIP_HORIZONTAL; } // Cambia el flip
@@ -79,6 +79,6 @@ class MovingSprite : public Sprite {
// --- Métodos internos --- // --- Métodos internos ---
void updateAngle() { rotate_.angle += rotate_.amount; } // Incrementa el valor del ángulo void updateAngle() { rotate_.angle += rotate_.amount; } // Incrementa el valor del ángulo
void move(float deltaTime); // Mueve el sprite según velocidad y aceleración (time-based) void move(); // Mueve el sprite según velocidad y aceleración
void rotate(float deltaTime); // Rota el sprite según los parámetros de rotación (time-based) void rotate(); // Rota el sprite según los parámetros de rotación
}; };

View File

@@ -6,7 +6,6 @@
#include <cstddef> // Para size_t #include <cstddef> // Para size_t
#include <fstream> // Para basic_ostream, operator<<, basic_ostream::operator<<, basic_ofstream, basic_istream, basic_ifstream, ifstream, ofstream #include <fstream> // Para basic_ostream, operator<<, basic_ostream::operator<<, basic_ofstream, basic_istream, basic_ifstream, ifstream, ofstream
#include <functional> // Para function #include <functional> // Para function
#include <sstream> // Para istringstream
#include <map> // Para map, operator==, _Rb_tree_const_iterator #include <map> // Para map, operator==, _Rb_tree_const_iterator
#include <ranges> // Para std::ranges::any_of #include <ranges> // Para std::ranges::any_of
#include <stdexcept> // Para invalid_argument, out_of_range #include <stdexcept> // Para invalid_argument, out_of_range
@@ -65,27 +64,11 @@ auto loadFromFile() -> bool {
// --- CASO: EL FICHERO EXISTE --- // --- CASO: EL FICHERO EXISTE ---
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\nReading file: %s", getFileName(settings.config_file).c_str()); SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\nReading file: %s", getFileName(settings.config_file).c_str());
std::string line; std::string line;
std::string param_name;
std::string param_value;
while (std::getline(file, line)) { while (std::getline(file, line)) {
// Elimina comentarios if (line.substr(0, 1) != "#") {
auto comment_pos = line.find('#'); int pos = line.find('=');
if (comment_pos != std::string::npos) { if (!set(line.substr(0, pos), line.substr(pos + 1, line.length()))) {
line.resize(comment_pos); SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Unknown parameter: %s", line.substr(0, pos).c_str());
}
// Si la línea contiene '=', lo reemplazamos por un espacio para compatibilidad
auto equals_pos = line.find('=');
if (equals_pos != std::string::npos) {
line[equals_pos] = ' ';
}
// Usa un stream para separar palabras (elimina automáticamente espacios extra)
std::istringstream iss(line);
if (iss >> param_name >> param_value) {
if (!set(param_name, param_value)) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Unknown parameter: %s", param_name.c_str());
} }
} }
} }
@@ -117,51 +100,49 @@ auto saveToFile() -> bool {
applyPendingChanges(); applyPendingChanges();
// Versión del archivo
file << "# Coffee Crisis Arcade Edition - Configuration File\n";
file << "# Format: key value\n";
file << "config.version " << settings.config_version << "\n";
// Opciones de ventana // Opciones de ventana
file << "\n# WINDOW\n"; file << "## WINDOW\n";
file << "window.zoom " << window.zoom << "\n"; file << "window.zoom=" << window.zoom << "\n";
// Opciones de video // Opciones de video
file << "\n# VIDEO\n"; file << "\n## VIDEO\n";
file << "# video.scale_mode [" << static_cast<int>(SDL_ScaleMode::SDL_SCALEMODE_NEAREST) << ": nearest, " << static_cast<int>(SDL_ScaleMode::SDL_SCALEMODE_LINEAR) << ": linear]\n"; file << "## video.scale_mode [" << static_cast<int>(SDL_ScaleMode::SDL_SCALEMODE_NEAREST) << ": nearest, " << static_cast<int>(SDL_ScaleMode::SDL_SCALEMODE_LINEAR) << ": lineal]\n";
file << "video.fullscreen " << boolToString(video.fullscreen) << "\n";
file << "video.scale_mode " << static_cast<int>(video.scale_mode) << "\n"; file << "video.fullscreen=" << boolToString(video.fullscreen) << "\n";
file << "video.vsync " << boolToString(video.vsync) << "\n"; file << "video.scale_mode=" << static_cast<int>(video.scale_mode) << "\n";
file << "video.integer_scale " << boolToString(video.integer_scale) << "\n"; file << "video.vsync=" << boolToString(video.vsync) << "\n";
file << "video.shaders " << boolToString(video.shaders) << "\n"; file << "video.integer_scale=" << boolToString(video.integer_scale) << "\n";
file << "video.shaders=" << boolToString(video.shaders) << "\n";
// Opciones de audio // Opciones de audio
file << "\n# AUDIO\n"; file << "\n## AUDIO\n";
file << "# volume range: [0 .. 100]\n"; file << "## volume [0 .. 100]\n";
file << "audio.enabled " << boolToString(audio.enabled) << "\n";
file << "audio.volume " << audio.volume << "\n"; file << "audio.enabled=" << boolToString(audio.enabled) << "\n";
file << "audio.music.enabled " << boolToString(audio.music.enabled) << "\n"; file << "audio.volume=" << audio.volume << "\n";
file << "audio.music.volume " << audio.music.volume << "\n"; file << "audio.music.enabled=" << boolToString(audio.music.enabled) << "\n";
file << "audio.sound.enabled " << boolToString(audio.sound.enabled) << "\n"; file << "audio.music.volume=" << audio.music.volume << "\n";
file << "audio.sound.volume " << audio.sound.volume << "\n"; file << "audio.sound.enabled=" << boolToString(audio.sound.enabled) << "\n";
file << "audio.sound.volume=" << audio.sound.volume << "\n";
// Opciones del juego // Opciones del juego
file << "\n# GAME\n"; file << "\n## GAME\n";
file << "# game.language [0: spanish, 1: valencian, 2: english]\n"; file << "## game.language [0: spanish, 1: valencian, 2: english]\n";
file << "# game.difficulty [" << static_cast<int>(Difficulty::Code::EASY) << ": easy, " << static_cast<int>(Difficulty::Code::NORMAL) << ": normal, " << static_cast<int>(Difficulty::Code::HARD) << ": hard]\n"; file << "## game.difficulty [" << static_cast<int>(Difficulty::Code::EASY) << ": easy, " << static_cast<int>(Difficulty::Code::NORMAL) << ": normal, " << static_cast<int>(Difficulty::Code::HARD) << ": hard]\n";
file << "game.language " << static_cast<int>(settings.language) << "\n";
file << "game.difficulty " << static_cast<int>(settings.difficulty) << "\n"; file << "game.language=" << static_cast<int>(settings.language) << "\n";
file << "game.autofire " << boolToString(settings.autofire) << "\n"; file << "game.difficulty=" << static_cast<int>(settings.difficulty) << "\n";
file << "game.shutdown_enabled " << boolToString(settings.shutdown_enabled) << "\n"; file << "game.autofire=" << boolToString(settings.autofire) << "\n";
file << "game.params_file " << settings.params_file << "\n"; file << "game.shutdown_enabled=" << boolToString(settings.shutdown_enabled) << "\n";
file << "game.params_file=" << settings.params_file << "\n";
// Opciones de mandos // Opciones de mandos
file << "\n# CONTROLLERS\n"; file << "\n## CONTROLLERS\n";
gamepad_manager.saveToFile(file); gamepad_manager.saveToFile(file);
// Opciones de teclado // Opciones de teclado
file << "\n# KEYBOARD\n"; file << "\n## KEYBOARD\n";
file << "keyboard.player " << static_cast<int>(keyboard.player_id) << "\n"; file << "keyboard.player=" << static_cast<int>(keyboard.player_id) << "\n";
// Cierra el fichero // Cierra el fichero
file.close(); file.close();
@@ -196,8 +177,6 @@ auto set(const std::string& var, const std::string& value) -> bool {
// Un mapa estático asegura que se inicializa solo una vez // Un mapa estático asegura que se inicializa solo una vez
static const std::map<std::string, std::function<void(const std::string&)>> SETTINGS_MAP = { static const std::map<std::string, std::function<void(const std::string&)>> SETTINGS_MAP = {
// Configuración
{"config.version", [](const auto& val) { settings.config_version = std::stoi(val); }},
// Ventana // Ventana
{"window.zoom", [](const auto& val) { window.zoom = std::stoi(val); }}, {"window.zoom", [](const auto& val) { window.zoom = std::stoi(val); }},
// Vídeo // Vídeo

View File

@@ -58,7 +58,6 @@ struct Audio {
}; };
struct Settings { struct Settings {
int config_version = 2; // Versión del archivo de configuración
Difficulty::Code difficulty = Difficulty::Code::NORMAL; // Dificultad del juego Difficulty::Code difficulty = Difficulty::Code::NORMAL; // Dificultad del juego
Lang::Code language = Lang::Code::VALENCIAN; // Idioma usado en el juego Lang::Code language = Lang::Code::VALENCIAN; // Idioma usado en el juego
bool autofire = GameDefaults::Options::SETTINGS_AUTOFIRE; // Indicador de autofire bool autofire = GameDefaults::Options::SETTINGS_AUTOFIRE; // Indicador de autofire
@@ -159,12 +158,12 @@ class GamepadManager {
const auto& gamepad = gamepads_[i]; const auto& gamepad = gamepads_[i];
// Guardar el nombre solo si hay path (mando real asignado) // Guardar el nombre solo si hay path (mando real asignado)
if (!gamepad.path.empty()) { if (!gamepad.path.empty()) {
file << "controller." << i << ".name " << gamepad.name << "\n"; file << "controller." << i << ".name=" << gamepad.name << "\n";
} else { } else {
file << "controller." << i << ".name \n"; // vacío file << "controller." << i << ".name=\n"; // vacío
} }
file << "controller." << i << ".path " << gamepad.path << "\n"; file << "controller." << i << ".path=" << gamepad.path << "\n";
file << "controller." << i << ".player " << static_cast<int>(gamepad.player_id) << "\n"; file << "controller." << i << ".player=" << static_cast<int>(gamepad.player_id) << "\n";
} }
} }

View File

@@ -87,6 +87,7 @@ auto setParams(const std::string& var, const std::string& value) -> bool {
static const std::unordered_map<std::string, std::function<void(const std::string&)>> INT_PARAMS = { static const std::unordered_map<std::string, std::function<void(const std::string&)>> INT_PARAMS = {
{"game.width", [](const std::string& v) { param.game.width = std::stoi(v); }}, {"game.width", [](const std::string& v) { param.game.width = std::stoi(v); }},
{"game.height", [](const std::string& v) { param.game.height = std::stoi(v); }}, {"game.height", [](const std::string& v) { param.game.height = std::stoi(v); }},
{"game.item_size", [](const std::string& v) { param.game.item_size = std::stoi(v); }},
{"game.play_area.rect.x", [](const std::string& v) { param.game.play_area.rect.x = std::stoi(v); }}, {"game.play_area.rect.x", [](const std::string& v) { param.game.play_area.rect.x = std::stoi(v); }},
{"game.play_area.rect.y", [](const std::string& v) { param.game.play_area.rect.y = std::stoi(v); }}, {"game.play_area.rect.y", [](const std::string& v) { param.game.play_area.rect.y = std::stoi(v); }},
{"game.play_area.rect.w", [](const std::string& v) { param.game.play_area.rect.w = std::stoi(v); }}, {"game.play_area.rect.w", [](const std::string& v) { param.game.play_area.rect.w = std::stoi(v); }},
@@ -105,6 +106,7 @@ auto setParams(const std::string& var, const std::string& value) -> bool {
{"scoreboard.rect.h", [](const std::string& v) { param.scoreboard.rect.h = std::stoi(v); }}, {"scoreboard.rect.h", [](const std::string& v) { param.scoreboard.rect.h = std::stoi(v); }},
{"scoreboard.skip_countdown_value", [](const std::string& v) { param.scoreboard.skip_countdown_value = std::stoi(v); }}, {"scoreboard.skip_countdown_value", [](const std::string& v) { param.scoreboard.skip_countdown_value = std::stoi(v); }},
{"title.press_start_position", [](const std::string& v) { param.title.press_start_position = std::stoi(v); }}, {"title.press_start_position", [](const std::string& v) { param.title.press_start_position = std::stoi(v); }},
{"title.title_duration", [](const std::string& v) { param.title.title_duration = std::stoi(v); }},
{"title.arcade_edition_position", [](const std::string& v) { param.title.arcade_edition_position = std::stoi(v); }}, {"title.arcade_edition_position", [](const std::string& v) { param.title.arcade_edition_position = std::stoi(v); }},
{"title.title_c_c_position", [](const std::string& v) { param.title.title_c_c_position = std::stoi(v); }}, {"title.title_c_c_position", [](const std::string& v) { param.title.title_c_c_position = std::stoi(v); }},
{"intro.text_distance_from_bottom", [](const std::string& v) { param.intro.text_distance_from_bottom = std::stoi(v); }}}; {"intro.text_distance_from_bottom", [](const std::string& v) { param.intro.text_distance_from_bottom = std::stoi(v); }}};
@@ -180,7 +182,6 @@ auto setParams(const std::string& var, const std::string& value) -> bool {
{"balloon.settings[3].grav", [](const std::string& v) { param.balloon.settings.at(3).grav = std::stof(v); }}, {"balloon.settings[3].grav", [](const std::string& v) { param.balloon.settings.at(3).grav = std::stof(v); }},
{"tabe.min_spawn_time", [](const std::string& v) { param.tabe.min_spawn_time = std::stof(v); }}, {"tabe.min_spawn_time", [](const std::string& v) { param.tabe.min_spawn_time = std::stof(v); }},
{"tabe.max_spawn_time", [](const std::string& v) { param.tabe.max_spawn_time = std::stof(v); }}, {"tabe.max_spawn_time", [](const std::string& v) { param.tabe.max_spawn_time = std::stof(v); }},
{"title.title_duration", [](const std::string& v) { param.title.title_duration = std::stof(v); }},
{"service_menu.window_message.padding", [](const std::string& v) { param.service_menu.window_message.padding = std::stof(v); }}, {"service_menu.window_message.padding", [](const std::string& v) { param.service_menu.window_message.padding = std::stof(v); }},
{"service_menu.window_message.line_spacing", [](const std::string& v) { param.service_menu.window_message.line_spacing = std::stof(v); }}, {"service_menu.window_message.line_spacing", [](const std::string& v) { param.service_menu.window_message.line_spacing = std::stof(v); }},
{"service_menu.window_message.title_separator_spacing", [](const std::string& v) { param.service_menu.window_message.title_separator_spacing = std::stof(v); }}, {"service_menu.window_message.title_separator_spacing", [](const std::string& v) { param.service_menu.window_message.title_separator_spacing = std::stof(v); }},

View File

@@ -14,6 +14,7 @@
struct ParamGame { struct ParamGame {
float width = GameDefaults::Game::WIDTH; float width = GameDefaults::Game::WIDTH;
float height = GameDefaults::Game::HEIGHT; float height = GameDefaults::Game::HEIGHT;
float item_size = GameDefaults::Game::ITEM_SIZE;
Zone play_area{}; // Se inicializa en el constructor de Param Zone play_area{}; // Se inicializa en el constructor de Param
Zone game_area{}; // Se inicializa en el constructor de Param Zone game_area{}; // Se inicializa en el constructor de Param
int name_entry_idle_time = GameDefaults::Game::NAME_ENTRY_IDLE_TIME; int name_entry_idle_time = GameDefaults::Game::NAME_ENTRY_IDLE_TIME;
@@ -37,7 +38,7 @@ struct ParamFade {
// --- Parámetros de la pantalla de título --- // --- Parámetros de la pantalla de título ---
struct ParamTitle { struct ParamTitle {
int press_start_position = GameDefaults::Title::PRESS_START_POSITION; int press_start_position = GameDefaults::Title::PRESS_START_POSITION;
float title_duration = GameDefaults::Title::DURATION_S; int title_duration = GameDefaults::Title::DURATION;
int arcade_edition_position = GameDefaults::Title::ARCADE_EDITION_POSITION; int arcade_edition_position = GameDefaults::Title::ARCADE_EDITION_POSITION;
int title_c_c_position = GameDefaults::Title::TITLE_C_C_POSITION; int title_c_c_position = GameDefaults::Title::TITLE_C_C_POSITION;
Color bg_color = Color::fromHex(GameDefaults::Title::BG_COLOR); Color bg_color = Color::fromHex(GameDefaults::Title::BG_COLOR);

View File

@@ -4,12 +4,6 @@
#include <functional> // Para function #include <functional> // Para function
#include <utility> // Para move #include <utility> // Para move
// Constructor para paths por puntos (convertido a segundos)
Path::Path(const std::vector<SDL_FPoint> &spots_init, float waiting_time_s_init)
: spots(spots_init), is_point_path(true) {
waiting_time_s = waiting_time_s_init;
}
// Devuelve un vector con los puntos que conforman la ruta // Devuelve un vector con los puntos que conforman la ruta
auto createPath(float start, float end, PathType type, float fixed_pos, int steps, const std::function<double(double)> &easing_function) -> std::vector<SDL_FPoint> { auto createPath(float start, float end, PathType type, float fixed_pos, int steps, const std::function<double(double)> &easing_function) -> std::vector<SDL_FPoint> {
std::vector<SDL_FPoint> v; std::vector<SDL_FPoint> v;
@@ -39,9 +33,9 @@ auto createPath(float start, float end, PathType type, float fixed_pos, int step
} }
// Actualiza la posición y comprueba si ha llegado a su destino // Actualiza la posición y comprueba si ha llegado a su destino
void PathSprite::update(float delta_time) { void PathSprite::update() {
if (enabled_ && !has_finished_) { if (enabled_ && !has_finished_) {
moveThroughCurrentPath(delta_time); moveThroughCurrentPath();
goToNextPathOrDie(); goToNextPathOrDie();
} }
} }
@@ -83,14 +77,14 @@ void PathSprite::addPath(Path path, bool centered) {
} }
} }
// Añade un recorrido generado (en segundos) // Añade un recorrido
void PathSprite::addPath(float start, float end, PathType type, float fixed_pos, float duration_s, const std::function<double(double)> &easing_function, float waiting_time_s) { void PathSprite::addPath(int start, int end, PathType type, int fixed_pos, int steps, const std::function<double(double)> &easing_function, int waiting_counter) {
paths_.emplace_back(start, end, type, fixed_pos, duration_s, waiting_time_s, easing_function); paths_.emplace_back(createPath(start, end, type, fixed_pos, steps, easing_function), waiting_counter);
} }
// Añade un recorrido por puntos (en segundos) // Añade un recorrido
void PathSprite::addPath(const std::vector<SDL_FPoint> &spots, float waiting_time_s) { void PathSprite::addPath(const std::vector<SDL_FPoint> &spots, int waiting_counter) {
paths_.emplace_back(spots, waiting_time_s); paths_.emplace_back(spots, waiting_counter);
} }
// Habilita el objeto // Habilita el objeto
@@ -101,32 +95,21 @@ void PathSprite::enable() {
enabled_ = true; enabled_ = true;
// Establece la posición inicial // Establece la posición
auto &path = paths_.at(current_path_); auto &path = paths_.at(current_path_);
if (path.is_point_path) {
const auto &p = path.spots.at(path.counter); const auto &p = path.spots.at(path.counter);
setPosition(p); setPosition(p);
} else {
// Para paths generados, establecer posición inicial
SDL_FPoint initial_pos;
if (path.type == PathType::HORIZONTAL) {
initial_pos = {path.start_pos, path.fixed_pos};
} else {
initial_pos = {path.fixed_pos, path.start_pos};
}
setPosition(initial_pos);
}
} }
// Coloca el sprite en los diferentes puntos del recorrido // Coloca el sprite en los diferentes puntos del recorrido
void PathSprite::moveThroughCurrentPath(float delta_time) { void PathSprite::moveThroughCurrentPath() {
auto &path = paths_.at(current_path_); auto &path = paths_.at(current_path_);
if (path.is_point_path) { // Establece la posición
// Lógica para paths por puntos (compatibilidad)
const auto &p = path.spots.at(path.counter); const auto &p = path.spots.at(path.counter);
setPosition(p); setPosition(p);
// Comprobar si ha terminado el recorrido
if (!path.on_destination) { if (!path.on_destination) {
++path.counter; ++path.counter;
if (path.counter >= static_cast<int>(path.spots.size())) { if (path.counter >= static_cast<int>(path.spots.size())) {
@@ -135,44 +118,12 @@ void PathSprite::moveThroughCurrentPath(float delta_time) {
} }
} }
// Comprobar si ha terminado la espera
if (path.on_destination) { if (path.on_destination) {
path.waiting_elapsed += delta_time; if (path.waiting_counter == 0) {
if (path.waiting_elapsed >= path.waiting_time_s) {
path.finished = true; path.finished = true;
}
}
} else { } else {
// Lógica para paths generados en tiempo real --path.waiting_counter;
if (!path.on_destination) {
path.elapsed_time += delta_time;
// Calcular progreso (0.0 a 1.0)
float progress = path.elapsed_time / path.duration_s;
if (progress >= 1.0f) {
progress = 1.0f;
path.on_destination = true;
}
// Aplicar función de easing
double eased_progress = path.easing_function(progress);
// Calcular posición actual
float current_pos = path.start_pos + (path.end_pos - path.start_pos) * static_cast<float>(eased_progress);
// Establecer posición según el tipo
SDL_FPoint position;
if (path.type == PathType::HORIZONTAL) {
position = {current_pos, path.fixed_pos};
} else {
position = {path.fixed_pos, current_pos};
}
setPosition(position);
} else {
// Esperar en destino
path.waiting_elapsed += delta_time;
if (path.waiting_elapsed >= path.waiting_time_s) {
path.finished = true;
}
} }
} }
} }

View File

@@ -25,30 +25,16 @@ enum class PathCentered { // Centrado del recorrido
// --- Estructuras --- // --- Estructuras ---
struct Path { // Define un recorrido para el sprite struct Path { // Define un recorrido para el sprite
float start_pos; // Posición inicial std::vector<SDL_FPoint> spots; // Puntos por los que se desplazará el sprite
float end_pos; // Posición final int waiting_counter; // Tiempo de espera una vez en el destino
PathType type; // Tipo de movimiento (horizontal/vertical)
float fixed_pos; // Posición fija en el eje contrario
float duration_s; // Duración de la animación en segundos
float waiting_time_s; // Tiempo de espera una vez en el destino
std::function<double(double)> easing_function; // Función de easing
float elapsed_time = 0.0f; // Tiempo transcurrido
float waiting_elapsed = 0.0f; // Tiempo de espera transcurrido
bool on_destination = false; // Indica si ha llegado al destino bool on_destination = false; // Indica si ha llegado al destino
bool finished = false; // Indica si ha terminado de esperarse bool finished = false; // Indica si ha terminado de esperarse
int counter = 0; // Contador interno
// Constructor para paths generados // Constructor
Path(float start, float end, PathType path_type, float fixed, float duration, float waiting, std::function<double(double)> easing) Path(const std::vector<SDL_FPoint> &spots_init, int waiting_counter_init)
: start_pos(start), end_pos(end), type(path_type), fixed_pos(fixed), : spots(spots_init),
duration_s(duration), waiting_time_s(waiting), easing_function(std::move(easing)) {} waiting_counter(waiting_counter_init) {}
// Constructor para paths por puntos (convertido a segundos)
Path(const std::vector<SDL_FPoint> &spots_init, float waiting_time_s_init);
// Variables para paths por puntos
std::vector<SDL_FPoint> spots; // Solo para paths por puntos
int counter = 0; // Solo para paths por puntos
bool is_point_path = false; // Indica si es un path por puntos
}; };
// --- Funciones --- // --- Funciones ---
@@ -63,13 +49,13 @@ class PathSprite : public Sprite {
~PathSprite() override = default; ~PathSprite() override = default;
// --- Métodos principales --- // --- Métodos principales ---
void update(float delta_time); // Actualiza la posición del sprite según el recorrido (delta_time en segundos) void update(); // Actualiza la posición del sprite según el recorrido
void render() override; // Muestra el sprite por pantalla void render() override; // Muestra el sprite por pantalla
// --- Gestión de recorridos --- // --- Gestión de recorridos ---
void addPath(Path path, bool centered = false); // Añade un recorrido (Path) void addPath(Path path, bool centered = false); // Añade un recorrido (Path)
void addPath(const std::vector<SDL_FPoint> &spots, float waiting_time_s = 0.0f); // Añade un recorrido a partir de puntos void addPath(const std::vector<SDL_FPoint> &spots, int waiting_counter = 0); // Añade un recorrido a partir de puntos
void addPath(float start, float end, PathType type, float fixed_pos, float duration_s, const std::function<double(double)> &easing_function, float waiting_time_s = 0.0f); // Añade un recorrido generado void addPath(int start, int end, PathType type, int fixed_pos, int steps, const std::function<double(double)> &easing_function, int waiting_counter = 0); // Añade un recorrido generado
// --- Estado y control --- // --- Estado y control ---
void enable(); // Habilita el objeto void enable(); // Habilita el objeto
@@ -86,6 +72,6 @@ class PathSprite : public Sprite {
std::vector<Path> paths_; // Caminos a recorrer por el sprite std::vector<Path> paths_; // Caminos a recorrer por el sprite
// --- Métodos internos --- // --- Métodos internos ---
void moveThroughCurrentPath(float delta_time); // Coloca el sprite en los diferentes puntos del recorrido void moveThroughCurrentPath(); // Coloca el sprite en los diferentes puntos del recorrido
void goToNextPathOrDie(); // Cambia de recorrido o finaliza void goToNextPathOrDie(); // Cambia de recorrido o finaliza
}; };

View File

@@ -3,7 +3,6 @@
#include <SDL3/SDL.h> // Para SDL_GetTicks, SDL_FlipMode #include <SDL3/SDL.h> // Para SDL_GetTicks, SDL_FlipMode
#include <algorithm> // Para clamp, max, min #include <algorithm> // Para clamp, max, min
#include <cmath> // Para fmod
#include <cstdlib> // Para rand #include <cstdlib> // Para rand
#include "animated_sprite.h" // Para AnimatedSprite #include "animated_sprite.h" // Para AnimatedSprite
@@ -22,7 +21,7 @@
#endif #endif
// Constructor // Constructor
Player::Player(const Config& config) Player::Player(const Config &config)
: player_sprite_(std::make_unique<AnimatedSprite>(config.texture.at(0), config.animations.at(0))), : player_sprite_(std::make_unique<AnimatedSprite>(config.texture.at(0), config.animations.at(0))),
power_sprite_(std::make_unique<AnimatedSprite>(config.texture.at(4), config.animations.at(1))), power_sprite_(std::make_unique<AnimatedSprite>(config.texture.at(4), config.animations.at(1))),
enter_name_(std::make_unique<EnterName>()), enter_name_(std::make_unique<EnterName>()),
@@ -60,14 +59,17 @@ void Player::init() {
power_up_counter_ = POWERUP_COUNTER; power_up_counter_ = POWERUP_COUNTER;
extra_hit_ = false; extra_hit_ = false;
coffees_ = 0; coffees_ = 0;
continue_ticks_ = 0;
continue_counter_ = 10; continue_counter_ = 10;
name_entry_idle_time_accumulator_ = 0.0f; name_entry_ticks_ = 0;
name_entry_total_time_accumulator_ = 0.0f; name_entry_idle_counter_ = 0;
name_entry_total_counter_ = 0;
shiftColliders(); shiftColliders();
vel_x_ = 0; vel_x_ = 0;
vel_y_ = 0; vel_y_ = 0;
score_ = 0; score_ = 0;
score_multiplier_ = 1.0F; score_multiplier_ = 1.0F;
cant_fire_counter_ = 10;
enter_name_->init(last_enter_name_); enter_name_->init(last_enter_name_);
// Establece la posición del sprite // Establece la posición del sprite
@@ -128,16 +130,16 @@ void Player::setInputPlaying(Input::Action action) {
// Procesa inputs para cuando está introduciendo el nombre // Procesa inputs para cuando está introduciendo el nombre
void Player::setInputEnteringName(Input::Action action) { void Player::setInputEnteringName(Input::Action action) {
switch (action) { switch (action) {
case Input::Action::FIRE_LEFT: case Input::Action::LEFT:
enter_name_->addCharacter(); enter_name_->decPosition();
break;
case Input::Action::FIRE_CENTER:
enter_name_->removeLastCharacter();
break; break;
case Input::Action::RIGHT: case Input::Action::RIGHT:
enter_name_->incPosition();
break;
case Input::Action::UP:
enter_name_->incIndex(); enter_name_->incIndex();
break; break;
case Input::Action::LEFT: case Input::Action::DOWN:
enter_name_->decIndex(); enter_name_->decIndex();
break; break;
case Input::Action::START: case Input::Action::START:
@@ -146,37 +148,35 @@ void Player::setInputEnteringName(Input::Action action) {
default: default:
break; break;
} }
name_entry_idle_time_accumulator_ = 0.0f; name_entry_idle_counter_ = 0;
} }
// Sistema de movimiento // Mueve el jugador a la posición y animación que le corresponde
void Player::move(float deltaTime) { void Player::move() {
switch (playing_state_) { switch (playing_state_) {
case State::PLAYING: case State::PLAYING:
handlePlayingMovement(deltaTime); handlePlayingMovement();
break; break;
case State::ROLLING: case State::ROLLING:
handleRollingMovement(); handleRollingMovement();
break; break;
case State::TITLE_ANIMATION: case State::TITLE_ANIMATION:
handleTitleAnimation(deltaTime); handleTitleAnimation();
break; break;
case State::CONTINUE_TIME_OUT: case State::CONTINUE_TIME_OUT:
handleContinueTimeOut(); handleContinueTimeOut();
break; break;
case State::LEAVING_SCREEN: case State::LEAVING_SCREEN:
updateStepCounter(deltaTime); handleLeavingScreen();
handleLeavingScreen(deltaTime);
break; break;
case State::ENTERING_SCREEN: case State::ENTERING_SCREEN:
updateStepCounter(deltaTime); handleEnteringScreen();
handleEnteringScreen(deltaTime);
break; break;
case State::CREDITS: case State::CREDITS:
handleCreditsMovement(deltaTime); handleCreditsMovement();
break; break;
case State::WAITING: case State::WAITING:
handleWaitingMovement(deltaTime); handleWaitingMovement();
break; break;
case State::RECOVER: case State::RECOVER:
handleRecoverMovement(); handleRecoverMovement();
@@ -186,10 +186,9 @@ void Player::move(float deltaTime) {
} }
} }
// Movimiento time-based durante el juego void Player::handlePlayingMovement() {
void Player::handlePlayingMovement(float deltaTime) { // Mueve el jugador a derecha o izquierda
// Mueve el jugador a derecha o izquierda (time-based en segundos) pos_x_ += vel_x_;
pos_x_ += vel_x_ * deltaTime;
// Si el jugador abandona el area de juego por los laterales, restaura su posición // Si el jugador abandona el area de juego por los laterales, restaura su posición
const float MIN_X = play_area_.x - 5; const float MIN_X = play_area_.x - 5;
@@ -226,7 +225,7 @@ void Player::handleRollingGroundCollision() {
return; return;
} }
if (player_sprite_->getVelY() < 120.0F) { // 2.0F * 60fps = 120.0F pixels/segundo if (player_sprite_->getVelY() < 2.0F) {
handleRollingStop(); handleRollingStop();
} else { } else {
handleRollingBounce(); handleRollingBounce();
@@ -253,10 +252,10 @@ void Player::handleRollingBounce() {
playSound("jump.wav"); playSound("jump.wav");
} }
void Player::handleTitleAnimation(float deltaTime) { void Player::handleTitleAnimation() {
setInputBasedOnPlayerId(); setInputBasedOnPlayerId();
pos_x_ += (vel_x_ * 2.0F) * deltaTime; pos_x_ += vel_x_ * 2.0F;
const float MIN_X = -WIDTH; const float MIN_X = -WIDTH;
const float MAX_X = play_area_.w; const float MAX_X = play_area_.w;
pos_x_ = std::clamp(pos_x_, MIN_X, MAX_X); pos_x_ = std::clamp(pos_x_, MIN_X, MAX_X);
@@ -275,11 +274,11 @@ void Player::handleContinueTimeOut() {
} }
} }
void Player::handleLeavingScreen(float deltaTime) { void Player::handleLeavingScreen() {
// updateStepCounter se llama desde move() con deltaTime updateStepCounter();
setInputBasedOnPlayerId(); setInputBasedOnPlayerId();
pos_x_ += vel_x_ * deltaTime; pos_x_ += vel_x_;
const float MIN_X = -WIDTH; const float MIN_X = -WIDTH;
const float MAX_X = play_area_.w; const float MAX_X = play_area_.w;
pos_x_ = std::clamp(pos_x_, MIN_X, MAX_X); pos_x_ = std::clamp(pos_x_, MIN_X, MAX_X);
@@ -290,15 +289,15 @@ void Player::handleLeavingScreen(float deltaTime) {
} }
} }
void Player::handleEnteringScreen(float deltaTime) { void Player::handleEnteringScreen() {
// updateStepCounter se llama desde move() con deltaTime updateStepCounter();
switch (id_) { switch (id_) {
case Id::PLAYER1: case Id::PLAYER1:
handlePlayer1Entering(deltaTime); handlePlayer1Entering();
break; break;
case Id::PLAYER2: case Id::PLAYER2:
handlePlayer2Entering(deltaTime); handlePlayer2Entering();
break; break;
default: default:
break; break;
@@ -307,27 +306,26 @@ void Player::handleEnteringScreen(float deltaTime) {
shiftSprite(); shiftSprite();
} }
void Player::handlePlayer1Entering(float deltaTime) { void Player::handlePlayer1Entering() {
setInputPlaying(Input::Action::RIGHT); setInputPlaying(Input::Action::RIGHT);
pos_x_ += vel_x_ * deltaTime; pos_x_ += vel_x_;
if (pos_x_ > default_pos_x_) { if (pos_x_ > default_pos_x_) {
pos_x_ = default_pos_x_; pos_x_ = default_pos_x_;
setPlayingState(State::PLAYING); setPlayingState(State::PLAYING);
} }
} }
void Player::handlePlayer2Entering(float deltaTime) { void Player::handlePlayer2Entering() {
setInputPlaying(Input::Action::LEFT); setInputPlaying(Input::Action::LEFT);
pos_x_ += vel_x_ * deltaTime; pos_x_ += vel_x_;
if (pos_x_ < default_pos_x_) { if (pos_x_ < default_pos_x_) {
pos_x_ = default_pos_x_; pos_x_ = default_pos_x_;
setPlayingState(State::PLAYING); setPlayingState(State::PLAYING);
} }
} }
// Movimiento general en la pantalla de créditos (time-based) void Player::handleCreditsMovement() {
void Player::handleCreditsMovement(float deltaTime) { pos_x_ += vel_x_ / 2.0F;
pos_x_ += (vel_x_ / 2.0F) * deltaTime;
if (vel_x_ > 0) { if (vel_x_ > 0) {
handleCreditsRightMovement(); handleCreditsRightMovement();
@@ -353,12 +351,10 @@ void Player::handleCreditsLeftMovement() {
} }
} }
// Controla la animación del jugador saludando (time-based) void Player::handleWaitingMovement() {
void Player::handleWaitingMovement(float deltaTime) { ++waiting_counter_;
waiting_time_accumulator_ += deltaTime; if (waiting_counter_ == WAITING_COUNTER) {
const float WAITING_DURATION_S = static_cast<float>(WAITING_COUNTER) / 60.0f; // Convert frames to seconds waiting_counter_ = 0;
if (waiting_time_accumulator_ >= WAITING_DURATION_S) {
waiting_time_accumulator_ = 0.0f;
player_sprite_->resetAnimation(); player_sprite_->resetAnimation();
} }
} }
@@ -384,21 +380,20 @@ void Player::setInputBasedOnPlayerId() {
} }
} }
// Incrementa o ajusta el contador de pasos (time-based) void Player::updateStepCounter() {
void Player::updateStepCounter(float deltaTime) { ++step_counter_;
step_time_accumulator_ += deltaTime; if (step_counter_ % 10 == 0) {
const float STEP_INTERVAL_S = 10.0f / 60.0f; // 10 frames converted to seconds
if (step_time_accumulator_ >= STEP_INTERVAL_S) {
step_time_accumulator_ = 0.0f;
playSound("walk.wav"); playSound("walk.wav");
} }
} }
// Pinta el jugador en pantalla // Pinta el jugador en pantalla
void Player::render() { void Player::render() {
if (power_sprite_visible_ && isPlaying()) { if (power_up_ && isPlaying()) {
if (power_up_counter_ > (POWERUP_COUNTER / 4) || power_up_counter_ % 20 > 4) {
power_sprite_->render(); power_sprite_->render();
} }
}
if (isRenderable()) { if (isRenderable()) {
player_sprite_->render(); player_sprite_->render();
@@ -460,9 +455,10 @@ auto Player::computeAnimation() const -> std::pair<std::string, SDL_FlipMode> {
} }
// Establece la animación correspondiente al estado // Establece la animación correspondiente al estado
void Player::setAnimation(float deltaTime) { void Player::setAnimation() {
switch (playing_state_) { switch (playing_state_) {
case State::PLAYING: case State::PLAYING:
case State::ENTERING_NAME_GAME_COMPLETED:
case State::ENTERING_SCREEN: case State::ENTERING_SCREEN:
case State::LEAVING_SCREEN: case State::LEAVING_SCREEN:
case State::TITLE_ANIMATION: case State::TITLE_ANIMATION:
@@ -488,7 +484,6 @@ void Player::setAnimation(float deltaTime) {
case State::CONTINUE: case State::CONTINUE:
player_sprite_->setCurrentAnimation("dizzy"); player_sprite_->setCurrentAnimation("dizzy");
break; break;
case State::ENTERING_NAME_GAME_COMPLETED:
case State::CELEBRATING: case State::CELEBRATING:
player_sprite_->setCurrentAnimation("celebration"); player_sprite_->setCurrentAnimation("celebration");
break; break;
@@ -496,23 +491,108 @@ void Player::setAnimation(float deltaTime) {
break; break;
} }
// La diferencia clave: usa deltaTime para las animaciones player_sprite_->update();
player_sprite_->update(deltaTime); power_sprite_->update();
power_sprite_->update(deltaTime);
} }
// Actualiza al jugador con deltaTime (time-based) // Actualiza el valor de la variable
void Player::update(float deltaTime) { void Player::updateCooldown() {
move(deltaTime); // Sistema de movimiento time-based if (playing_state_ != State::PLAYING) {
setAnimation(deltaTime); // Animaciones time-based return;
shiftColliders(); // Sin cambios (posicional) }
updateFireSystem(deltaTime); // Sistema de disparo de dos líneas
updatePowerUp(deltaTime); // Sistema de power-up time-based if (cant_fire_counter_ > 0) {
updateInvulnerable(deltaTime); // Sistema de invulnerabilidad time-based handleFiringCooldown();
updateScoreboard(); // Sin cambios (no temporal) } else {
updateContinueCounter(deltaTime); // Sistema de continue time-based handleRecoilAndCooling();
updateEnterNameCounter(deltaTime); // Sistema de name entry time-based }
updateShowingName(deltaTime); // Sistema de showing name time-based }
void Player::handleFiringCooldown() {
cooling_state_counter_ = COOLING_DURATION;
// Transition to recoiling state at halfway point
if (cant_fire_counter_ == recoiling_state_duration_ / 2) {
transitionToRecoiling();
}
--cant_fire_counter_;
if (cant_fire_counter_ == 0) {
recoiling_state_counter_ = recoiling_state_duration_;
}
}
void Player::handleRecoilAndCooling() {
if (recoiling_state_counter_ > 0) {
--recoiling_state_counter_;
return;
}
handleCoolingState();
}
void Player::handleCoolingState() {
if (cooling_state_counter_ > COOLING_COMPLETE) {
if (cooling_state_counter_ == COOLING_DURATION) {
transitionToCooling();
}
--cooling_state_counter_;
}
if (cooling_state_counter_ == COOLING_COMPLETE) {
completeCooling();
}
}
void Player::transitionToRecoiling() {
switch (firing_state_) {
case State::FIRING_LEFT:
setFiringState(State::RECOILING_LEFT);
break;
case State::FIRING_RIGHT:
setFiringState(State::RECOILING_RIGHT);
break;
case State::FIRING_UP:
setFiringState(State::RECOILING_UP);
break;
default:
break;
}
}
void Player::transitionToCooling() {
switch (firing_state_) {
case State::RECOILING_LEFT:
setFiringState(State::COOLING_LEFT);
break;
case State::RECOILING_RIGHT:
setFiringState(State::COOLING_RIGHT);
break;
case State::RECOILING_UP:
setFiringState(State::COOLING_UP);
break;
default:
break;
}
}
void Player::completeCooling() {
setFiringState(State::FIRING_NONE);
cooling_state_counter_ = -1;
}
// Actualiza al jugador a su posicion, animación y controla los contadores
void Player::update() {
move();
setAnimation();
shiftColliders();
updateCooldown();
updatePowerUp();
updateInvulnerable();
updateScoreboard();
updateContinueCounter();
updateEnterNameCounter();
updateShowingName();
} }
void Player::passShowingName() { void Player::passShowingName() {
@@ -540,9 +620,8 @@ void Player::updateScoreboard() {
} }
case State::ENTERING_NAME: case State::ENTERING_NAME:
case State::ENTERING_NAME_GAME_COMPLETED: { case State::ENTERING_NAME_GAME_COMPLETED: {
Scoreboard::get()->setEnterName(scoreboard_panel_, enter_name_->getCurrentName()); Scoreboard::get()->setRecordName(scoreboard_panel_, enter_name_->getCurrentName());
Scoreboard::get()->setCharacterSelected(scoreboard_panel_, enter_name_->getSelectedCharacter()); Scoreboard::get()->setSelectorPos(scoreboard_panel_, getRecordNamePos());
Scoreboard::get()->setCarouselAnimation(scoreboard_panel_, enter_name_->getSelectedIndex(), enter_name_.get());
break; break;
} }
default: default:
@@ -583,8 +662,8 @@ void Player::setPlayingState(State state) {
} }
case State::CONTINUE: { case State::CONTINUE: {
// Inicializa el contador de continuar // Inicializa el contador de continuar
continue_ticks_ = SDL_GetTicks();
continue_counter_ = 9; continue_counter_ = 9;
continue_time_accumulator_ = 0.0f; // Initialize time accumulator
playSound("continue_clock.wav"); playSound("continue_clock.wav");
setScoreboardMode(Scoreboard::Mode::CONTINUE); setScoreboardMode(Scoreboard::Mode::CONTINUE);
break; break;
@@ -603,7 +682,6 @@ void Player::setPlayingState(State state) {
} }
pos_y_ = default_pos_y_; pos_y_ = default_pos_y_;
waiting_counter_ = 0; waiting_counter_ = 0;
waiting_time_accumulator_ = 0.0f; // Initialize time accumulator
shiftSprite(); shiftSprite();
player_sprite_->setCurrentAnimation("hello"); player_sprite_->setCurrentAnimation("hello");
player_sprite_->animtionPause(); player_sprite_->animtionPause();
@@ -611,28 +689,28 @@ void Player::setPlayingState(State state) {
break; break;
} }
case State::ENTERING_NAME: { case State::ENTERING_NAME: {
setScoreboardMode(Scoreboard::Mode::SCORE_TO_ENTER_NAME); // Iniciar animación de transición setScoreboardMode(Scoreboard::Mode::ENTER_NAME);
break; break;
} }
case State::SHOWING_NAME: { case State::SHOWING_NAME: {
showing_name_time_accumulator_ = 0.0f; // Inicializar acumulador time-based showing_name_ticks_ = SDL_GetTicks();
setScoreboardMode(Scoreboard::Mode::ENTER_TO_SHOW_NAME); // Iniciar animación de transición setScoreboardMode(Scoreboard::Mode::SHOW_NAME);
Scoreboard::get()->setEnterName(scoreboard_panel_, last_enter_name_); Scoreboard::get()->setRecordName(scoreboard_panel_, last_enter_name_);
addScoreToScoreBoard(); addScoreToScoreBoard();
break; break;
} }
case State::ROLLING: { case State::ROLLING: {
// Activa la animación de rodar dando botes // Activa la animación de rodar dando botes
player_sprite_->setCurrentAnimation("rolling"); player_sprite_->setCurrentAnimation("rolling");
player_sprite_->setAnimationSpeed(4.0f / 60.0f); // 4 frames convertido a segundos player_sprite_->setAnimationSpeed(4);
player_sprite_->setVelY(-396.0F); // Velocidad inicial (6.6 * 60 = 396 pixels/s) player_sprite_->setVelY(-6.6F); // Velocidad inicial
player_sprite_->setAccelY(720.0F); // Gravedad (0.2 * 60² = 720 pixels/s²) player_sprite_->setAccelY(0.2F); // Gravedad
player_sprite_->setPosY(pos_y_ - 2); // Para "sacarlo" del suelo, ya que está hundido un pixel para ocultar el outline de los pies player_sprite_->setPosY(pos_y_ - 2); // Para "sacarlo" del suelo, ya que está hundido un pixel para ocultar el outline de los pies
(rand() % 2 == 0) ? player_sprite_->setVelX(198.0F) : player_sprite_->setVelX(-198.0F); // 3.3 * 60 = 198 pixels/s (rand() % 2 == 0) ? player_sprite_->setVelX(3.3F) : player_sprite_->setVelX(-3.3F);
break; break;
} }
case State::TITLE_ANIMATION: { case State::TITLE_ANIMATION: {
// Activa la animación de caminar // Activa la animación de rodar
player_sprite_->setCurrentAnimation("walk"); player_sprite_->setCurrentAnimation("walk");
playSound("voice_credit_thankyou.wav"); playSound("voice_credit_thankyou.wav");
break; break;
@@ -644,11 +722,11 @@ void Player::setPlayingState(State state) {
} }
case State::CONTINUE_TIME_OUT: { case State::CONTINUE_TIME_OUT: {
// Activa la animación de sacar al jugador de la zona de juego // Activa la animación de sacar al jugador de la zona de juego
player_sprite_->setAccelY(720.0F); // 0.2 * 60² = 720 pixels/s² player_sprite_->setAccelY(0.2F);
player_sprite_->setVelY(-240.0F); // -4.0 * 60 = -240 pixels/s player_sprite_->setVelY(-4.0F);
player_sprite_->setVelX(0.0F); player_sprite_->setVelX(0.0F);
player_sprite_->setCurrentAnimation("rolling"); player_sprite_->setCurrentAnimation("rolling");
player_sprite_->setAnimationSpeed(5.0f / 60.0f); // 5 frames convertido a segundos player_sprite_->setAnimationSpeed(5);
setScoreboardMode(Scoreboard::Mode::GAME_OVER); setScoreboardMode(Scoreboard::Mode::GAME_OVER);
playSound("voice_aw_aw_aw.wav"); playSound("voice_aw_aw_aw.wav");
playSound("jump.wav"); playSound("jump.wav");
@@ -664,21 +742,19 @@ void Player::setPlayingState(State state) {
break; break;
} }
case State::ENTERING_NAME_GAME_COMPLETED: { case State::ENTERING_NAME_GAME_COMPLETED: {
// setWalkingState(State::WALKING_STOP); setWalkingState(State::WALKING_STOP);
// setFiringState(State::FIRING_NONE); setFiringState(State::FIRING_NONE);
setScoreboardMode(Scoreboard::Mode::SCORE_TO_ENTER_NAME); // Iniciar animación de transición setScoreboardMode(Scoreboard::Mode::ENTER_NAME);
break; break;
} }
case State::LEAVING_SCREEN: { case State::LEAVING_SCREEN: {
step_counter_ = 0; step_counter_ = 0;
step_time_accumulator_ = 0.0f; // Initialize time accumulator
setScoreboardMode(Scoreboard::Mode::GAME_COMPLETED); setScoreboardMode(Scoreboard::Mode::GAME_COMPLETED);
break; break;
} }
case State::ENTERING_SCREEN: { case State::ENTERING_SCREEN: {
init(); init();
step_counter_ = 0; step_counter_ = 0;
step_time_accumulator_ = 0.0f; // Initialize time accumulator
setScoreboardMode(Scoreboard::Mode::SCORE); setScoreboardMode(Scoreboard::Mode::SCORE);
switch (id_) { switch (id_) {
case Id::PLAYER1: case Id::PLAYER1:
@@ -719,26 +795,24 @@ void Player::decScoreMultiplier() {
void Player::setInvulnerable(bool value) { void Player::setInvulnerable(bool value) {
invulnerable_ = value; invulnerable_ = value;
invulnerable_counter_ = invulnerable_ ? INVULNERABLE_COUNTER : 0; invulnerable_counter_ = invulnerable_ ? INVULNERABLE_COUNTER : 0;
invulnerable_time_accumulator_ = invulnerable_ ? static_cast<float>(INVULNERABLE_COUNTER) / 60.0f : 0.0f; // Convert frames to seconds
} }
// Monitoriza el estado (time-based) // Monitoriza el estado
void Player::updateInvulnerable(float deltaTime) { void Player::updateInvulnerable() {
if (playing_state_ == State::PLAYING && invulnerable_) { if (playing_state_ == State::PLAYING && invulnerable_) {
if (invulnerable_time_accumulator_ > 0) { if (invulnerable_counter_ > 0) {
invulnerable_time_accumulator_ -= deltaTime; --invulnerable_counter_;
// Frecuencia fija de parpadeo adaptada a deltaTime (en segundos) // Frecuencia fija de parpadeo (como el original)
constexpr float BLINK_PERIOD_S = 8.0f / 60.0f; // 8 frames convertidos a segundos constexpr int blink_speed = 8;
// Calcula proporción decreciente basada en tiempo restante // Calcula proporción decreciente: menos textura blanca hacia el final
const float TOTAL_INVULNERABLE_TIME_S = static_cast<float>(INVULNERABLE_COUNTER) / 60.0f; // Al inicio: 50-50, hacia el final: 70-30 (menos blanco)
float progress = 1.0f - (invulnerable_time_accumulator_ / TOTAL_INVULNERABLE_TIME_S); float progress = 1.0f - (static_cast<float>(invulnerable_counter_) / INVULNERABLE_COUNTER);
float white_proportion = 0.5f - progress * 0.2f; // Menos blanco hacia el final int white_frames = static_cast<int>((0.5f - progress * 0.2f) * blink_speed);
// Calcula si debe mostrar textura de invulnerabilidad basado en el ciclo temporal // Alterna entre texturas con proporción variable
float cycle_position = fmod(invulnerable_time_accumulator_, BLINK_PERIOD_S) / BLINK_PERIOD_S; bool should_show_invulnerable = (invulnerable_counter_ % blink_speed) < white_frames;
bool should_show_invulnerable = cycle_position < white_proportion;
size_t target_texture = should_show_invulnerable ? INVULNERABLE_TEXTURE : coffees_; size_t target_texture = should_show_invulnerable ? INVULNERABLE_TEXTURE : coffees_;
// Solo cambia textura si es diferente (optimización) // Solo cambia textura si es diferente (optimización)
@@ -747,7 +821,6 @@ void Player::updateInvulnerable(float deltaTime) {
} }
} else { } else {
// Fin de invulnerabilidad // Fin de invulnerabilidad
invulnerable_time_accumulator_ = 0;
setInvulnerable(false); setInvulnerable(false);
player_sprite_->setActiveTexture(coffees_); player_sprite_->setActiveTexture(coffees_);
} }
@@ -758,47 +831,14 @@ void Player::updateInvulnerable(float deltaTime) {
void Player::setPowerUp() { void Player::setPowerUp() {
power_up_ = true; power_up_ = true;
power_up_counter_ = POWERUP_COUNTER; power_up_counter_ = POWERUP_COUNTER;
power_up_time_accumulator_ = static_cast<float>(POWERUP_COUNTER) / 60.0f; // Convert frames to seconds
power_sprite_visible_ = true; // Inicialmente visible cuando se activa el power-up
in_power_up_ending_phase_ = false; // Empezar en fase normal
bullet_color_toggle_ = false; // Resetear toggle
} }
// Actualiza el valor de la variable (time-based) // Actualiza el valor de la variable
void Player::updatePowerUp(float deltaTime) { void Player::updatePowerUp() {
if (playing_state_ == State::PLAYING) { if (playing_state_ == State::PLAYING) {
if (power_up_) { if (power_up_) {
power_up_time_accumulator_ -= deltaTime; --power_up_counter_;
power_up_ = power_up_time_accumulator_ > 0; power_up_ = power_up_counter_ > 0;
if (!power_up_) {
power_up_time_accumulator_ = 0;
power_sprite_visible_ = false;
in_power_up_ending_phase_ = false;
bullet_color_toggle_ = false;
// Los colores ahora se manejan dinámicamente en getNextBulletColor()
} else {
// Calcular visibilidad del power sprite
const float TOTAL_POWERUP_TIME_S = static_cast<float>(POWERUP_COUNTER) / 60.0f;
const float QUARTER_TIME_S = TOTAL_POWERUP_TIME_S / 4.0f;
if (power_up_time_accumulator_ > QUARTER_TIME_S) {
// En los primeros 75% del tiempo, siempre visible
power_sprite_visible_ = true;
in_power_up_ending_phase_ = false;
} else {
// En el último 25%, parpadea cada 20 frames (≈0.333s)
constexpr float BLINK_PERIOD_S = 20.0f / 60.0f;
constexpr float VISIBLE_PROPORTION = 4.0f / 20.0f;
float cycle_position = fmod(power_up_time_accumulator_, BLINK_PERIOD_S) / BLINK_PERIOD_S;
power_sprite_visible_ = cycle_position >= VISIBLE_PROPORTION;
in_power_up_ending_phase_ = true; // Activar modo alternancia de colores de balas
}
}
} else {
power_sprite_visible_ = false;
in_power_up_ending_phase_ = false;
bullet_color_toggle_ = false;
} }
} }
} }
@@ -830,41 +870,36 @@ void Player::shiftColliders() {
} }
// Pone las texturas del jugador // Pone las texturas del jugador
void Player::setPlayerTextures(const std::vector<std::shared_ptr<Texture>>& texture) { void Player::setPlayerTextures(const std::vector<std::shared_ptr<Texture>> &texture) {
player_sprite_->setTexture(texture[0]); player_sprite_->setTexture(texture[0]);
power_sprite_->setTexture(texture[1]); power_sprite_->setTexture(texture[1]);
} }
// Actualiza el contador de continue (time-based) // Actualiza el contador de continue
void Player::updateContinueCounter(float deltaTime) { void Player::updateContinueCounter() {
if (playing_state_ == State::CONTINUE) { if (playing_state_ == State::CONTINUE) {
continue_time_accumulator_ += deltaTime; constexpr int TICKS_SPEED = 1000;
constexpr float CONTINUE_INTERVAL_S = 1.0f; // 1 segundo if (SDL_GetTicks() - continue_ticks_ > TICKS_SPEED) {
if (continue_time_accumulator_ >= CONTINUE_INTERVAL_S) {
continue_time_accumulator_ -= CONTINUE_INTERVAL_S;
decContinueCounter(); decContinueCounter();
} }
} }
} }
// Actualiza el contador de entrar nombre (time-based) // Actualiza el contador de entrar nombre
void Player::updateEnterNameCounter(float deltaTime) { void Player::updateEnterNameCounter() {
if (playing_state_ == State::ENTERING_NAME || playing_state_ == State::ENTERING_NAME_GAME_COMPLETED) { if (playing_state_ == State::ENTERING_NAME || playing_state_ == State::ENTERING_NAME_GAME_COMPLETED) {
name_entry_time_accumulator_ += deltaTime; constexpr int TICKS_SPEED = 1000;
constexpr float NAME_ENTRY_INTERVAL_S = 1.0f; // 1 segundo if (SDL_GetTicks() - name_entry_ticks_ > TICKS_SPEED) {
if (name_entry_time_accumulator_ >= NAME_ENTRY_INTERVAL_S) {
name_entry_time_accumulator_ -= NAME_ENTRY_INTERVAL_S;
decNameEntryCounter(); decNameEntryCounter();
} }
} }
} }
// Actualiza el estado de SHOWING_NAME (time-based) // Actualiza el estado de SHOWING_NAME
void Player::updateShowingName(float deltaTime) { void Player::updateShowingName() {
if (playing_state_ == State::SHOWING_NAME) { if (playing_state_ == State::SHOWING_NAME) {
showing_name_time_accumulator_ += deltaTime; constexpr int TICKS_SPEED = 5000;
constexpr float SHOWING_NAME_DURATION_S = 5.0f; // 5 segundos if (SDL_GetTicks() - name_entry_ticks_ > TICKS_SPEED) {
if (showing_name_time_accumulator_ >= SHOWING_NAME_DURATION_S) {
game_completed_ ? setPlayingState(State::LEAVING_SCREEN) : setPlayingState(State::CONTINUE); game_completed_ ? setPlayingState(State::LEAVING_SCREEN) : setPlayingState(State::CONTINUE);
} }
} }
@@ -872,7 +907,7 @@ void Player::updateShowingName(float deltaTime) {
// Decrementa el contador de continuar // Decrementa el contador de continuar
void Player::decContinueCounter() { void Player::decContinueCounter() {
continue_time_accumulator_ = 0.0f; // Reset time accumulator continue_ticks_ = SDL_GetTicks();
--continue_counter_; --continue_counter_;
if (continue_counter_ < 0) { if (continue_counter_ < 0) {
setPlayingState(State::CONTINUE_TIME_OUT); setPlayingState(State::CONTINUE_TIME_OUT);
@@ -883,16 +918,17 @@ void Player::decContinueCounter() {
// Decrementa el contador de entrar nombre // Decrementa el contador de entrar nombre
void Player::decNameEntryCounter() { void Player::decNameEntryCounter() {
name_entry_time_accumulator_ = 0.0f; // Reset time accumulator name_entry_ticks_ = SDL_GetTicks();
// Incrementa acumuladores de tiempo (1 segundo) // Actualiza contadores
name_entry_idle_time_accumulator_ += 1.0f; ++name_entry_idle_counter_;
name_entry_total_time_accumulator_ += 1.0f; ++name_entry_total_counter_;
if ((name_entry_total_time_accumulator_ >= param.game.name_entry_total_time) || // Comprueba los contadores
(name_entry_idle_time_accumulator_ >= param.game.name_entry_idle_time)) { if ((name_entry_total_counter_ >= param.game.name_entry_total_time) ||
name_entry_total_time_accumulator_ = 0.0f; (name_entry_idle_counter_ >= param.game.name_entry_idle_time)) {
name_entry_idle_time_accumulator_ = 0.0f; name_entry_total_counter_ = 0;
name_entry_idle_counter_ = 0;
if (playing_state_ == State::ENTERING_NAME) { if (playing_state_ == State::ENTERING_NAME) {
last_enter_name_ = getRecordName(); last_enter_name_ = getRecordName();
setPlayingState(State::SHOWING_NAME); setPlayingState(State::SHOWING_NAME);
@@ -902,6 +938,15 @@ void Player::decNameEntryCounter() {
} }
} }
// Obtiene la posición que se está editando del nombre del jugador para la tabla de mejores puntuaciones
auto Player::getRecordNamePos() const -> int {
if (enter_name_) {
return enter_name_->getPosition();
}
return 0;
}
// Recoloca los sprites // Recoloca los sprites
void Player::shiftSprite() { void Player::shiftSprite() {
player_sprite_->setPosX(pos_x_); player_sprite_->setPosX(pos_x_);
@@ -910,12 +955,12 @@ void Player::shiftSprite() {
} }
// Hace sonar un sonido // Hace sonar un sonido
void Player::playSound(const std::string& name) const { void Player::playSound(const std::string &name) const {
if (demo_) { if (demo_) {
return; return;
} }
static auto* audio_ = Audio::get(); static auto *audio_ = Audio::get();
audio_->playSound(name); audio_->playSound(name);
} }
@@ -924,33 +969,6 @@ auto Player::isRenderable() const -> bool {
return !isTitleHidden(); return !isTitleHidden();
}; };
// Devuelve el color actual de bala según el estado
auto Player::getBulletColor() const -> Bullet::Color {
return power_up_ ? bullet_colors_.powered_color : bullet_colors_.normal_color;
}
// Devuelve el color para la próxima bala (alterna si está en modo toggle)
auto Player::getNextBulletColor() -> Bullet::Color {
if (in_power_up_ending_phase_) {
// En fase final: alternar entre colores powered y normal
bullet_color_toggle_ = !bullet_color_toggle_;
return bullet_color_toggle_ ? bullet_colors_.powered_color : bullet_colors_.normal_color;
}
// Modo normal: sin power-up = normal_color, con power-up = powered_color
return power_up_ ? bullet_colors_.powered_color : bullet_colors_.normal_color;
}
// Establece los colores de bala para este jugador
void Player::setBulletColors(Bullet::Color normal, Bullet::Color powered) {
bullet_colors_.normal_color = normal;
bullet_colors_.powered_color = powered;
}
// Establece el archivo de sonido de bala para este jugador
void Player::setBulletSoundFile(const std::string& filename) {
bullet_sound_file_ = filename;
}
// Añade una puntuación a la tabla de records // Añade una puntuación a la tabla de records
void Player::addScoreToScoreBoard() const { void Player::addScoreToScoreBoard() const {
if (hi_score_table_ == nullptr) { if (hi_score_table_ == nullptr) {
@@ -971,160 +989,3 @@ void Player::addCredit() {
++credits_used_; ++credits_used_;
playSound("credit.wav"); playSound("credit.wav");
} }
// ========================================
// SISTEMA DE DISPARO DE DOS LÍNEAS
// ========================================
// Método principal del sistema de disparo
void Player::updateFireSystem(float deltaTime) {
updateFunctionalLine(deltaTime); // Línea 1: CanFire
updateVisualLine(deltaTime); // Línea 2: Animaciones
}
// LÍNEA 1: Sistema Funcional (CanFire)
void Player::updateFunctionalLine(float deltaTime) {
if (fire_cooldown_timer_ > 0) {
fire_cooldown_timer_ -= deltaTime;
can_fire_new_system_ = false;
} else {
fire_cooldown_timer_ = 0; // Evitar valores negativos
can_fire_new_system_ = true;
}
}
// LÍNEA 2: Sistema Visual (Animaciones)
void Player::updateVisualLine(float deltaTime) {
if (visual_fire_state_ == VisualFireState::NORMAL) {
return; // No hay temporizador activo en estado NORMAL
}
visual_state_timer_ -= deltaTime;
switch (visual_fire_state_) {
case VisualFireState::AIMING:
if (visual_state_timer_ <= 0) {
transitionToRecoilingNew();
}
break;
case VisualFireState::RECOILING:
if (visual_state_timer_ <= 0) {
transitionToThreatPose();
}
break;
case VisualFireState::THREAT_POSE:
if (visual_state_timer_ <= 0) {
transitionToNormalNew();
}
break;
case VisualFireState::NORMAL:
// Ya manejado arriba
break;
}
}
// Inicia un disparo en ambas líneas
void Player::startFiringSystem(int cooldown_frames) {
// LÍNEA 1: Inicia cooldown funcional
fire_cooldown_timer_ = static_cast<float>(cooldown_frames) / 60.0f; // Convertir frames a segundos
can_fire_new_system_ = false;
// LÍNEA 2: Resetea completamente el estado visual
aiming_duration_ = fire_cooldown_timer_ * AIMING_DURATION_FACTOR; // 50% del cooldown
recoiling_duration_ = aiming_duration_ * RECOILING_DURATION_MULTIPLIER; // 4 veces la duración de aiming
visual_fire_state_ = VisualFireState::AIMING;
visual_state_timer_ = aiming_duration_;
updateFiringStateFromVisual(); // Sincroniza firing_state_ para animaciones
}
// Sincroniza firing_state_ con visual_fire_state_
void Player::updateFiringStateFromVisual() {
// Mantener la dirección actual del disparo
State base_state = State::FIRING_NONE;
if (firing_state_ == State::FIRING_LEFT || firing_state_ == State::RECOILING_LEFT || firing_state_ == State::COOLING_LEFT) {
base_state = State::FIRING_LEFT;
} else if (firing_state_ == State::FIRING_RIGHT || firing_state_ == State::RECOILING_RIGHT || firing_state_ == State::COOLING_RIGHT) {
base_state = State::FIRING_RIGHT;
} else if (firing_state_ == State::FIRING_UP || firing_state_ == State::RECOILING_UP || firing_state_ == State::COOLING_UP) {
base_state = State::FIRING_UP;
}
switch (visual_fire_state_) {
case VisualFireState::NORMAL:
firing_state_ = State::FIRING_NONE;
break;
case VisualFireState::AIMING:
firing_state_ = base_state; // FIRING_LEFT/RIGHT/UP
break;
case VisualFireState::RECOILING:
switch (base_state) {
case State::FIRING_LEFT:
firing_state_ = State::RECOILING_LEFT;
break;
case State::FIRING_RIGHT:
firing_state_ = State::RECOILING_RIGHT;
break;
case State::FIRING_UP:
firing_state_ = State::RECOILING_UP;
break;
default:
firing_state_ = State::RECOILING_UP;
break;
}
break;
case VisualFireState::THREAT_POSE:
switch (base_state) {
case State::FIRING_LEFT:
firing_state_ = State::COOLING_LEFT;
break;
case State::FIRING_RIGHT:
firing_state_ = State::COOLING_RIGHT;
break;
case State::FIRING_UP:
firing_state_ = State::COOLING_UP;
break;
default:
firing_state_ = State::COOLING_UP;
break;
}
break;
}
}
// Transiciones del sistema visual
void Player::transitionToRecoilingNew() {
visual_fire_state_ = VisualFireState::RECOILING;
visual_state_timer_ = recoiling_duration_;
updateFiringStateFromVisual();
}
void Player::transitionToThreatPose() {
visual_fire_state_ = VisualFireState::THREAT_POSE;
// Calcular threat_pose_duration ajustada:
// Duración original (833ms) menos el tiempo extra que ahora dura recoiling
float original_recoiling_duration = fire_cooldown_timer_; // Era 100% del cooldown
float new_recoiling_duration = aiming_duration_ * RECOILING_DURATION_MULTIPLIER; // Ahora es más del cooldown
float extra_recoiling_time = new_recoiling_duration - original_recoiling_duration;
float adjusted_threat_duration = THREAT_POSE_DURATION - extra_recoiling_time;
// Asegurar que no sea negativo
visual_state_timer_ = std::max(adjusted_threat_duration, MIN_THREAT_POSE_DURATION);
updateFiringStateFromVisual();
}
void Player::transitionToNormalNew() {
visual_fire_state_ = VisualFireState::NORMAL;
visual_state_timer_ = 0;
updateFiringStateFromVisual();
}

View File

@@ -8,7 +8,6 @@
#include <vector> // Para vector #include <vector> // Para vector
#include "animated_sprite.h" // Para AnimatedSprite #include "animated_sprite.h" // Para AnimatedSprite
#include "bullet.h" // Para Bullet
#include "enter_name.h" // Para EnterName #include "enter_name.h" // Para EnterName
#include "input.h" // Para Input #include "input.h" // Para Input
#include "manage_hiscore_table.h" // Para Table #include "manage_hiscore_table.h" // Para Table
@@ -18,33 +17,13 @@
class Texture; class Texture;
// --- Clase Player: jugador principal del juego --- // --- Clase Player ---
//
// Esta clase gestiona todos los aspectos de un jugador durante el juego,
// incluyendo movimiento, disparos, animaciones y estados especiales.
//
// Funcionalidades principales:
// • Sistema de disparo de dos líneas: funcional (cooldown) + visual (animaciones)
// • Estados de animación: normal → aiming → recoiling → threat_pose → normal
// • Movimiento time-based: compatibilidad con deltaTime para fluidez variable
// • Power-ups e invulnerabilidad: coffee machine, extra hits, parpadeos
// • Sistema de puntuación: multipliers, high scores, entrada de nombres
// • Estados de juego: playing, rolling, continue, entering_name, etc.
//
// El sistema de disparo utiliza duraciones configurables mediante constantes
// para facilitar el ajuste del gameplay y la sensación de disparo.
class Player { class Player {
public: public:
// --- Constantes --- // --- Constantes ---
static constexpr int WIDTH = 32; // Anchura static constexpr int WIDTH = 32; // Anchura
static constexpr int HEIGHT = 32; // Altura static constexpr int HEIGHT = 32; // Altura
// --- Estructuras ---
struct BulletColorPair {
Bullet::Color normal_color; // Color de bala sin power-up
Bullet::Color powered_color; // Color de bala con power-up
};
// --- Enums --- // --- Enums ---
enum class Id : int { enum class Id : int {
NO_PLAYER = -1, // Sin jugador NO_PLAYER = -1, // Sin jugador
@@ -102,21 +81,21 @@ class Player {
float x; // Posición X inicial float x; // Posición X inicial
int y; // Posición Y inicial int y; // Posición Y inicial
bool demo; // Modo demo bool demo; // Modo demo
SDL_FRect* play_area; // Área de juego (puntero para mantener referencia) SDL_FRect *play_area; // Área de juego (puntero para mantener referencia)
std::vector<std::shared_ptr<Texture>> texture; // Texturas del jugador std::vector<std::shared_ptr<Texture>> texture; // Texturas del jugador
std::vector<std::vector<std::string>> animations; // Animaciones del jugador std::vector<std::vector<std::string>> animations; // Animaciones del jugador
Table* hi_score_table; // Tabla de puntuaciones (puntero para referencia) Table *hi_score_table; // Tabla de puntuaciones (puntero para referencia)
int* glowing_entry; // Entrada brillante (puntero para mantener referencia) int *glowing_entry; // Entrada brillante (puntero para mantener referencia)
IStageInfo* stage_info; // Gestor de pantallas (puntero) IStageInfo *stage_info; // Gestor de pantallas (puntero)
}; };
// --- Constructor y destructor --- // --- Constructor y destructor ---
Player(const Config& config); Player(const Config &config);
~Player() = default; ~Player() = default;
// --- Inicialización y ciclo de vida --- // --- Inicialización y ciclo de vida ---
void init(); // Inicializa el jugador void init(); // Inicializa el jugador
void update(float deltaTime); // Actualiza estado, animación y contadores (time-based) void update(); // Actualiza estado, animación y contadores
void render(); // Dibuja el jugador en pantalla void render(); // Dibuja el jugador en pantalla
// --- Entrada y control --- // --- Entrada y control ---
@@ -125,13 +104,16 @@ class Player {
void setInputEnteringName(Input::Action action); // Procesa entrada al introducir nombre void setInputEnteringName(Input::Action action); // Procesa entrada al introducir nombre
// --- Movimiento y animación --- // --- Movimiento y animación ---
void move(float deltaTime); // Mueve el jugador (time-based) void move(); // Mueve el jugador
void setAnimation(float deltaTime); // Establece la animación según el estado (time-based) void setAnimation(); // Establece la animación según el estado
// --- Texturas y animaciones --- // --- Texturas y animaciones ---
void setPlayerTextures(const std::vector<std::shared_ptr<Texture>>& texture); // Cambia las texturas del jugador void setPlayerTextures(const std::vector<std::shared_ptr<Texture>> &texture); // Cambia las texturas del jugador
// --- Gameplay: Puntuación y power-ups --- // --- Estados y contadores ---
void updateCooldown(); // Actualiza el cooldown de disparo
// --- Puntuación y marcador ---
void addScore(int score, int lowest_hi_score_entry); // Añade puntos void addScore(int score, int lowest_hi_score_entry); // Añade puntos
void incScoreMultiplier(); // Incrementa el multiplicador void incScoreMultiplier(); // Incrementa el multiplicador
void decScoreMultiplier(); // Decrementa el multiplicador void decScoreMultiplier(); // Decrementa el multiplicador
@@ -140,17 +122,15 @@ class Player {
void setPlayingState(State state); // Cambia el estado de juego void setPlayingState(State state); // Cambia el estado de juego
void setInvulnerable(bool value); // Establece el valor del estado de invulnerabilidad void setInvulnerable(bool value); // Establece el valor del estado de invulnerabilidad
void setPowerUp(); // Activa el modo PowerUp void setPowerUp(); // Activa el modo PowerUp
void updatePowerUp(float deltaTime); // Actualiza el valor de PowerUp void updatePowerUp(); // Actualiza el valor de PowerUp
void giveExtraHit(); // Concede un toque extra al jugador void giveExtraHit(); // Concede un toque extra al jugador
void removeExtraHit(); // Quita el toque extra al jugador void removeExtraHit(); // Quita el toque extra al jugador
void decContinueCounter(); // Decrementa el contador de continuar void decContinueCounter(); // Decrementa el contador de continuar
void setWalkingState(State state) { walking_state_ = state; } // Establece el estado de caminar
void startFiringSystem(int cooldown_frames); // Inicia el sistema de disparo
void setScoreBoardPanel(Scoreboard::Id panel) { scoreboard_panel_ = panel; } // Establece el panel del marcador
void addCredit();
void passShowingName();
// --- Estado del juego: Consultas (is* methods) --- // --- Getters y comprobaciones de estado ---
[[nodiscard]] auto getRecordNamePos() const -> int; // Obtiene la posición que se está editando del nombre del jugador para la tabla de mejores puntuaciones
// Comprobación de playing_state
[[nodiscard]] auto isLyingOnTheFloorForever() const -> bool { return playing_state_ == State::LYING_ON_THE_FLOOR_FOREVER; } [[nodiscard]] auto isLyingOnTheFloorForever() const -> bool { return playing_state_ == State::LYING_ON_THE_FLOOR_FOREVER; }
[[nodiscard]] auto isCelebrating() const -> bool { return playing_state_ == State::CELEBRATING; } [[nodiscard]] auto isCelebrating() const -> bool { return playing_state_ == State::CELEBRATING; }
[[nodiscard]] auto isContinue() const -> bool { return playing_state_ == State::CONTINUE; } [[nodiscard]] auto isContinue() const -> bool { return playing_state_ == State::CONTINUE; }
@@ -164,91 +144,72 @@ class Player {
[[nodiscard]] auto isWaiting() const -> bool { return playing_state_ == State::WAITING; } [[nodiscard]] auto isWaiting() const -> bool { return playing_state_ == State::WAITING; }
[[nodiscard]] auto isTitleHidden() const -> bool { return playing_state_ == State::TITLE_HIDDEN; } [[nodiscard]] auto isTitleHidden() const -> bool { return playing_state_ == State::TITLE_HIDDEN; }
// --- Estados específicos: Consultas adicionales --- // Getters
[[nodiscard]] auto canFire() const -> bool { return can_fire_new_system_; } // Usa nuevo sistema [[nodiscard]] auto canFire() const -> bool { return cant_fire_counter_ <= 0; }
[[nodiscard]] auto hasExtraHit() const -> bool { return extra_hit_; } [[nodiscard]] auto hasExtraHit() const -> bool { return extra_hit_; }
[[nodiscard]] auto isCooling() const -> bool { return firing_state_ == State::COOLING_LEFT || firing_state_ == State::COOLING_UP || firing_state_ == State::COOLING_RIGHT; } [[nodiscard]] auto isCooling() const -> bool { return firing_state_ == State::COOLING_LEFT || firing_state_ == State::COOLING_UP || firing_state_ == State::COOLING_RIGHT; }
[[nodiscard]] auto isRecoiling() const -> bool { return firing_state_ == State::RECOILING_LEFT || firing_state_ == State::RECOILING_UP || firing_state_ == State::RECOILING_RIGHT; } [[nodiscard]] auto isRecoiling() const -> bool { return firing_state_ == State::RECOILING_LEFT || firing_state_ == State::RECOILING_UP || firing_state_ == State::RECOILING_RIGHT; }
[[nodiscard]] auto qualifiesForHighScore() const -> bool { return qualifies_for_high_score_; } [[nodiscard]] auto qualifiesForHighScore() const -> bool { return qualifies_for_high_score_; }
[[nodiscard]] auto isInvulnerable() const -> bool { return invulnerable_; } [[nodiscard]] auto isInvulnerable() const -> bool { return invulnerable_; }
[[nodiscard]] auto isPowerUp() const -> bool { return power_up_; } [[nodiscard]] auto isPowerUp() const -> bool { return power_up_; }
[[nodiscard]] auto isInBulletColorToggleMode() const -> bool { return in_power_up_ending_phase_; } auto getCollider() -> Circle & { return collider_; }
[[nodiscard]] auto getScoreMultiplier() const -> float { return score_multiplier_; }
// --- Getters: Propiedades y valores --- [[nodiscard]] auto getCoffees() const -> int { return coffees_; }
// Posición y dimensiones [[nodiscard]] auto getContinueCounter() const -> int { return continue_counter_; }
[[nodiscard]] auto getController() const -> int { return controller_index_; }
[[nodiscard]] static auto getHeight() -> int { return HEIGHT; }
[[nodiscard]] auto getId() const -> Player::Id { return id_; }
[[nodiscard]] auto getInvulnerableCounter() const -> int { return invulnerable_counter_; }
[[nodiscard]] auto getPosX() const -> int { return static_cast<int>(pos_x_); } [[nodiscard]] auto getPosX() const -> int { return static_cast<int>(pos_x_); }
[[nodiscard]] auto getPosY() const -> int { return pos_y_; } [[nodiscard]] auto getPosY() const -> int { return pos_y_; }
[[nodiscard]] static auto getWidth() -> int { return WIDTH; }
[[nodiscard]] static auto getHeight() -> int { return HEIGHT; }
// Jugador y identificación
[[nodiscard]] auto getId() const -> Player::Id { return id_; }
[[nodiscard]] auto getName() const -> const std::string& { return name_; }
[[nodiscard]] auto getPlayingState() const -> State { return playing_state_; }
auto getCollider() -> Circle& { return collider_; }
// Puntuación y juego
[[nodiscard]] auto getScore() const -> int { return score_; }
[[nodiscard]] auto getScoreMultiplier() const -> float { return score_multiplier_; }
[[nodiscard]] auto get1CC() const -> bool { return game_completed_ && credits_used_ <= 1; }
[[nodiscard]] auto getScoreBoardPanel() const -> Scoreboard::Id { return scoreboard_panel_; }
// Power-ups y estado especial
[[nodiscard]] auto getCoffees() const -> int { return coffees_; }
[[nodiscard]] auto getPowerUpCounter() const -> int { return power_up_counter_; } [[nodiscard]] auto getPowerUpCounter() const -> int { return power_up_counter_; }
[[nodiscard]] auto getInvulnerableCounter() const -> int { return invulnerable_counter_; }
[[nodiscard]] auto getBulletColor() const -> Bullet::Color; // Devuelve el color actual de bala según el estado
auto getNextBulletColor() -> Bullet::Color; // Devuelve el color para la próxima bala (alterna si está en modo toggle)
void setBulletColors(Bullet::Color normal, Bullet::Color powered); // Establece los colores de bala para este jugador
[[nodiscard]] auto getBulletSoundFile() const -> std::string { return bullet_sound_file_; } // Devuelve el archivo de sonido de bala
void setBulletSoundFile(const std::string& filename); // Establece el archivo de sonido de bala para este jugador
// Contadores y timers
[[nodiscard]] auto getContinueCounter() const -> int { return continue_counter_; }
[[nodiscard]] auto getRecordName() const -> std::string { return enter_name_ ? enter_name_->getFinalName() : "xxx"; } [[nodiscard]] auto getRecordName() const -> std::string { return enter_name_ ? enter_name_->getFinalName() : "xxx"; }
[[nodiscard]] auto getLastEnterName() const -> std::string { return last_enter_name_; } [[nodiscard]] auto getLastEnterName() const -> std::string { return last_enter_name_; }
[[nodiscard]] auto getScore() const -> int { return score_; }
[[nodiscard]] auto getScoreBoardPanel() const -> Scoreboard::Id { return scoreboard_panel_; }
[[nodiscard]] static auto getWidth() -> int { return WIDTH; }
[[nodiscard]] auto getPlayingState() const -> State { return playing_state_; }
[[nodiscard]] auto getName() const -> const std::string & { return name_; }
[[nodiscard]] auto get1CC() const -> bool { return game_completed_ && credits_used_ <= 1; }
[[nodiscard]] auto getEnterNamePositionOverflow() const -> bool { return enter_name_ ? enter_name_->getPositionOverflow() : false; }
// --- Configuración e interfaz externa --- // Setters inline
void setName(const std::string& name) { name_ = name; } void setController(int index) { controller_index_ = index; }
void setCantFireCounter(int counter) { recoiling_state_duration_ = cant_fire_counter_ = counter; }
void setFiringState(State state) { firing_state_ = state; }
void setInvulnerableCounter(int value) { invulnerable_counter_ = value; }
void setName(const std::string &name) { name_ = name; }
void setPowerUpCounter(int value) { power_up_counter_ = value; }
void setScore(int score) { score_ = score; }
void setScoreBoardPanel(Scoreboard::Id panel) { scoreboard_panel_ = panel; }
void setScoreMultiplier(float value) { score_multiplier_ = value; }
void setWalkingState(State state) { walking_state_ = state; }
void addCredit();
void passShowingName();
void setGamepad(std::shared_ptr<Input::Gamepad> gamepad) { gamepad_ = std::move(gamepad); } void setGamepad(std::shared_ptr<Input::Gamepad> gamepad) { gamepad_ = std::move(gamepad); }
[[nodiscard]] auto getGamepad() const -> std::shared_ptr<Input::Gamepad> { return gamepad_; } [[nodiscard]] auto getGamepad() const -> std::shared_ptr<Input::Gamepad> { return gamepad_; }
void setUsesKeyboard(bool value) { uses_keyboard_ = value; } void setUsesKeyboard(bool value) { uses_keyboard_ = value; }
[[nodiscard]] auto getUsesKeyboard() const -> bool { return uses_keyboard_; } [[nodiscard]] auto getUsesKeyboard() const -> bool { return uses_keyboard_; }
[[nodiscard]] auto getController() const -> int { return controller_index_; }
// Demo file management
[[nodiscard]] auto getDemoFile() const -> size_t { return demo_file_; }
void setDemoFile(size_t demo_file) { demo_file_ = demo_file; }
private: private:
// --- Constantes de física y movimiento --- // --- Constantes ---
static constexpr float BASE_SPEED = 90.0f; // Velocidad base del jugador (pixels/segundo) static constexpr int POWERUP_COUNTER = 1500; // Duración del estado PowerUp
static constexpr int INVULNERABLE_COUNTER = 200; // Duración del estado invulnerable
// --- Constantes de power-ups y estados especiales ---
static constexpr int POWERUP_COUNTER = 1500; // Duración del estado PowerUp (frames)
static constexpr int INVULNERABLE_COUNTER = 200; // Duración del estado invulnerable (frames)
static constexpr size_t INVULNERABLE_TEXTURE = 3; // Textura usada durante invulnerabilidad static constexpr size_t INVULNERABLE_TEXTURE = 3; // Textura usada durante invulnerabilidad
static constexpr float BASE_SPEED = 1.5F; // Velocidad base del jugador
// --- Constantes del sistema de disparo (obsoletas - usar nuevo sistema) ---
static constexpr int COOLING_DURATION = 50; // Duración del enfriamiento tras disparar static constexpr int COOLING_DURATION = 50; // Duración del enfriamiento tras disparar
static constexpr int COOLING_COMPLETE = 0; // Valor que indica enfriamiento completado static constexpr int COOLING_COMPLETE = 0; // Valor que indica enfriamiento completado
// --- Constantes de estados de espera ---
static constexpr int WAITING_COUNTER = 1000; // Tiempo de espera en estado de espera static constexpr int WAITING_COUNTER = 1000; // Tiempo de espera en estado de espera
// --- Constantes del nuevo sistema de disparo de dos líneas ---
static constexpr float AIMING_DURATION_FACTOR = 0.5f; // 50% del cooldown funcional
static constexpr float RECOILING_DURATION_MULTIPLIER = 4.0f; // 4 veces la duración de aiming
static constexpr float THREAT_POSE_DURATION = 50.0f / 60.0f; // 50 frames = ~0.833s (duración base)
static constexpr float MIN_THREAT_POSE_DURATION = 6.0f / 60.0f; // 6 frames = ~0.1s (duración mínima)
// --- Objetos y punteros --- // --- Objetos y punteros ---
std::unique_ptr<AnimatedSprite> player_sprite_; // Sprite para dibujar el jugador std::unique_ptr<AnimatedSprite> player_sprite_; // Sprite para dibujar el jugador
std::unique_ptr<AnimatedSprite> power_sprite_; // Sprite para dibujar el aura del jugador con el poder a tope std::unique_ptr<AnimatedSprite> power_sprite_; // Sprite para dibujar el aura del jugador con el poder a tope
std::unique_ptr<EnterName> enter_name_; // Clase utilizada para introducir el nombre std::unique_ptr<EnterName> enter_name_; // Clase utilizada para introducir el nombre
std::shared_ptr<Input::Gamepad> gamepad_ = nullptr; // Dispositivo asociado std::shared_ptr<Input::Gamepad> gamepad_ = nullptr; // Dispositivo asociado
Table* hi_score_table_ = nullptr; // Tabla de máximas puntuaciones Table *hi_score_table_ = nullptr; // Tabla de máximas puntuaciones
int* glowing_entry_ = nullptr; // Entrada de la tabla de puntuaciones para hacerla brillar int *glowing_entry_ = nullptr; // Entrada de la tabla de puntuaciones para hacerla brillar
IStageInfo* stage_info_; // Informacion de la pantalla actual IStageInfo *stage_info_; // Informacion de la pantalla actual
// --- Variables de estado --- // --- Variables de estado ---
SDL_FRect play_area_; // Rectángulo con la zona de juego SDL_FRect play_area_; // Rectángulo con la zona de juego
@@ -260,9 +221,10 @@ class Player {
State walking_state_ = State::WALKING_STOP; // Estado del jugador al moverse State walking_state_ = State::WALKING_STOP; // Estado del jugador al moverse
State firing_state_ = State::FIRING_NONE; // Estado del jugador al disparar State firing_state_ = State::FIRING_NONE; // Estado del jugador al disparar
State playing_state_ = State::WAITING; // Estado del jugador en el juego State playing_state_ = State::WAITING; // Estado del jugador en el juego
BulletColorPair bullet_colors_ = {Bullet::Color::YELLOW, Bullet::Color::GREEN}; // Par de colores de balas para este jugador
std::string bullet_sound_file_ = "bullet1p.wav"; // Archivo de sonido de bala para este jugador
Uint32 continue_ticks_ = 0; // Variable para poder cambiar el contador de continue en función del tiempo
Uint32 name_entry_ticks_ = 0; // Variable para poder cambiar el contador de poner nombre en función del tiempo
Uint32 showing_name_ticks_ = 0; // Tiempo en el que se entra al estado SHOWING_NAME
float pos_x_ = 0.0F; // Posición en el eje X float pos_x_ = 0.0F; // Posición en el eje X
float default_pos_x_; // Posición inicial para el jugador float default_pos_x_; // Posición inicial para el jugador
float vel_x_ = 0.0F; // Cantidad de píxeles a desplazarse en el eje X float vel_x_ = 0.0F; // Cantidad de píxeles a desplazarse en el eje X
@@ -270,35 +232,10 @@ class Player {
int pos_y_ = 0; // Posición en el eje Y int pos_y_ = 0; // Posición en el eje Y
int default_pos_y_; // Posición inicial para el jugador int default_pos_y_; // Posición inicial para el jugador
int vel_y_ = 0; // Cantidad de píxeles a desplazarse en el eje Y int vel_y_ = 0; // Cantidad de píxeles a desplazarse en el eje Y
float invulnerable_time_accumulator_ = 0.0f; // Acumulador de tiempo para invulnerabilidad (time-based) int cant_fire_counter_ = 0; // Contador durante el cual no puede disparar
float power_up_time_accumulator_ = 0.0f; // Acumulador de tiempo para power-up (time-based) int recoiling_state_counter_ = 0; // Contador para la animación del estado de retroceso
float continue_time_accumulator_ = 0.0f; // Acumulador de tiempo para continue counter (time-based) int recoiling_state_duration_ = 0; // Número de frames que dura el estado de retroceso
float name_entry_time_accumulator_ = 0.0f; // Acumulador de tiempo para name entry counter (time-based) int cooling_state_counter_ = 0; // Contador para la animación del estado cooling
float showing_name_time_accumulator_ = 0.0f; // Acumulador de tiempo para showing name (time-based)
float waiting_time_accumulator_ = 0.0f; // Acumulador de tiempo para waiting movement (time-based)
float step_time_accumulator_ = 0.0f; // Acumulador de tiempo para step counter (time-based)
// ========================================
// NUEVO SISTEMA DE DISPARO DE DOS LÍNEAS
// ========================================
// LÍNEA 1: SISTEMA FUNCIONAL (CanFire)
float fire_cooldown_timer_ = 0.0f; // Tiempo restante hasta poder disparar otra vez
bool can_fire_new_system_ = true; // true si puede disparar ahora mismo
// LÍNEA 2: SISTEMA VISUAL (Animaciones)
enum class VisualFireState {
NORMAL, // Brazo en posición neutral
AIMING, // Brazo alzado (disparando)
RECOILING, // Brazo en retroceso
THREAT_POSE // Posición amenazante
};
VisualFireState visual_fire_state_ = VisualFireState::NORMAL;
float visual_state_timer_ = 0.0f; // Tiempo en el estado visual actual
float aiming_duration_ = 0.0f; // Duración del estado AIMING
float recoiling_duration_ = 0.0f; // Duración del estado RECOILING
int invulnerable_counter_ = INVULNERABLE_COUNTER; // Contador para la invulnerabilidad int invulnerable_counter_ = INVULNERABLE_COUNTER; // Contador para la invulnerabilidad
int score_ = 0; // Puntos del jugador int score_ = 0; // Puntos del jugador
int coffees_ = 0; // Indica cuántos cafés lleva acumulados int coffees_ = 0; // Indica cuántos cafés lleva acumulados
@@ -306,9 +243,8 @@ class Player {
int power_up_x_offset_ = 0; // Desplazamiento del sprite de PowerUp respecto al sprite del jugador int power_up_x_offset_ = 0; // Desplazamiento del sprite de PowerUp respecto al sprite del jugador
int continue_counter_ = 10; // Contador para poder continuar int continue_counter_ = 10; // Contador para poder continuar
int controller_index_ = 0; // Índice del array de mandos que utilizará para moverse int controller_index_ = 0; // Índice del array de mandos que utilizará para moverse
size_t demo_file_ = 0; // Indice del fichero de datos para el modo demo int name_entry_idle_counter_ = 0; // Contador para poner nombre
float name_entry_idle_time_accumulator_ = 0.0f; // Tiempo idle acumulado para poner nombre (milisegundos) int name_entry_total_counter_ = 0; // Segundos totales que lleva acumulados poniendo nombre
float name_entry_total_time_accumulator_ = 0.0f; // Tiempo total acumulado poniendo nombre (milisegundos)
int step_counter_ = 0; // Cuenta los pasos para los estados en los que camina automáticamente int step_counter_ = 0; // Cuenta los pasos para los estados en los que camina automáticamente
int credits_used_ = 0; // Indica el número de veces que ha continuado int credits_used_ = 0; // Indica el número de veces que ha continuado
int waiting_counter_ = 0; // Contador para el estado de espera int waiting_counter_ = 0; // Contador para el estado de espera
@@ -316,9 +252,6 @@ class Player {
bool invulnerable_ = true; // Indica si el jugador es invulnerable bool invulnerable_ = true; // Indica si el jugador es invulnerable
bool extra_hit_ = false; // Indica si el jugador tiene un toque extra bool extra_hit_ = false; // Indica si el jugador tiene un toque extra
bool power_up_ = false; // Indica si el jugador tiene activo el modo PowerUp bool power_up_ = false; // Indica si el jugador tiene activo el modo PowerUp
bool power_sprite_visible_ = false; // Indica si el sprite de power-up debe ser visible
bool in_power_up_ending_phase_ = false; // Indica si está en la fase final del power-up (alternando colores)
bool bullet_color_toggle_ = false; // Para alternar entre verde y amarillo en fase final
bool demo_ = false; // Para que el jugador sepa si está en el modo demostración bool demo_ = false; // Para que el jugador sepa si está en el modo demostración
bool game_completed_ = false; // Indica si ha completado el juego bool game_completed_ = false; // Indica si ha completado el juego
bool uses_keyboard_ = false; // Indica si usa el teclado como dispositivo de control bool uses_keyboard_ = false; // Indica si usa el teclado como dispositivo de control
@@ -326,66 +259,41 @@ class Player {
// --- Métodos internos --- // --- Métodos internos ---
void shiftColliders(); // Actualiza el círculo de colisión a la posición del jugador void shiftColliders(); // Actualiza el círculo de colisión a la posición del jugador
void shiftSprite(); // Recoloca el sprite void shiftSprite(); // Recoloca el sprite
void updateInvulnerable(); // Monitoriza el estado de invulnerabilidad
// --- Setters internos --- void updateContinueCounter(); // Actualiza el contador de continue
void setController(int index) { controller_index_ = index; } void updateEnterNameCounter(); // Actualiza el contador de entrar nombre
void setFiringState(State state) { firing_state_ = state; } void updateShowingName(); // Actualiza el estado SHOWING_NAME
void setInvulnerableCounter(int value) { invulnerable_counter_ = value; }
void setPowerUpCounter(int value) { power_up_counter_ = value; }
void setScore(int score) { score_ = score; }
void setScoreMultiplier(float value) { score_multiplier_ = value; }
// --- Actualizadores de estado (time-based) ---
void updateInvulnerable(float deltaTime); // Monitoriza el estado de invulnerabilidad
void updateContinueCounter(float deltaTime); // Actualiza el contador de continue
void updateEnterNameCounter(float deltaTime); // Actualiza el contador de entrar nombre
void updateShowingName(float deltaTime); // Actualiza el estado SHOWING_NAME
void decNameEntryCounter(); // Decrementa el contador de entrar nombre void decNameEntryCounter(); // Decrementa el contador de entrar nombre
// --- Utilidades generales ---
void updateScoreboard(); // Actualiza el panel del marcador void updateScoreboard(); // Actualiza el panel del marcador
void setScoreboardMode(Scoreboard::Mode mode) const; // Cambia el modo del marcador void setScoreboardMode(Scoreboard::Mode mode) const; // Cambia el modo del marcador
void playSound(const std::string& name) const; // Hace sonar un sonido void playSound(const std::string &name) const; // Hace sonar un sonido
[[nodiscard]] auto isRenderable() const -> bool; // Indica si se puede dibujar el objeto [[nodiscard]] auto isRenderable() const -> bool; // Indica si se puede dibujar el objeto
void addScoreToScoreBoard() const; // Añade una puntuación a la tabla de records void addScoreToScoreBoard() const; // Añade una puntuación a la tabla de records
void handleFiringCooldown(); // Gestiona el tiempo de espera después de disparar antes de permitir otro disparo
// --- Sistema de disparo (nuevo - dos líneas) --- void handleRecoilAndCooling(); // Procesa simultáneamente el retroceso del arma y la transición al estado de enfriamiento si aplica
void updateFireSystem(float deltaTime); // Método principal del nuevo sistema de disparo void handleCoolingState(); // Actualiza la lógica interna mientras el sistema está en estado de enfriamiento
void updateFunctionalLine(float deltaTime); // Actualiza la línea funcional (CanFire) void transitionToRecoiling(); // Cambia el estado actual al de retroceso después de disparar
void updateVisualLine(float deltaTime); // Actualiza la línea visual (Animaciones) void transitionToCooling(); // Cambia el estado actual al de enfriamiento (por ejemplo, tras una ráfaga o sobrecalentamiento)
void updateFiringStateFromVisual(); // Sincroniza firing_state_ con visual_fire_state_ void completeCooling(); // Finaliza el proceso de enfriamiento y restablece el estado listo para disparar
void transitionToRecoilingNew(); // Transición AIMING → RECOILING void handlePlayingMovement(); // Gestiona el movimiento del personaje u objeto durante el estado de juego activo
void transitionToThreatPose(); // Transición RECOILING → THREAT_POSE void handleRecoverMovement(); // Comprueba si ha acabado la animación
void transitionToNormalNew(); // Transición THREAT_POSE → NORMAL void handleRollingMovement(); // Actualiza la lógica de movimiento de "rodar" (posiblemente tras impacto o acción especial)
void handleRollingBoundaryCollision(); // Detecta y maneja colisiones del objeto rodante con los límites de la pantalla
// --- Manejadores de movimiento --- void handleRollingGroundCollision(); // Gestiona la interacción del objeto rodante con el suelo (rebotes, frenado, etc.)
void handlePlayingMovement(float deltaTime); // Gestiona el movimiento durante el juego void handleRollingStop(); // Detiene el movimiento del objeto rodante cuando se cumplen las condiciones necesarias
void handleRecoverMovement(); // Comprueba si ha acabado la animación de recuperación void handleRollingBounce(); // Aplica una lógica de rebote al colisionar con superficies durante el rodamiento
void updateStepCounter(float deltaTime); // Incrementa o ajusta el contador de pasos void handleTitleAnimation(); // Ejecuta la animación del título en pantalla (ej. entrada, parpadeo o desplazamiento)
void setInputBasedOnPlayerId(); // Asocia las entradas de control según el jugador void handleContinueTimeOut(); // Gestiona el tiempo de espera en la pantalla de "Continuar" y decide si pasar a otro estado
void handleLeavingScreen(); // Lógica para salir de la pantalla actual (transición visual o cambio de escena)
// --- Manejadores de estados especiales --- void handleEnteringScreen(); // Lógica para entrar en una nueva pantalla, posiblemente con animación o retraso
void handleRollingMovement(); // Actualiza la lógica de movimiento de "rodar" void handlePlayer1Entering(); // Controla la animación o posición de entrada del Jugador 1 en pantalla
void handleRollingBoundaryCollision(); // Detecta colisiones con límites durante rodamiento void handlePlayer2Entering(); // Controla la animación o posición de entrada del Jugador 2 en pantalla
void handleRollingGroundCollision(); // Gestiona interacción con el suelo durante rodamiento void handleCreditsMovement(); // Movimiento general en la pantalla de créditos (desplazamiento vertical u horizontal)
void handleRollingStop(); // Detiene el movimiento del objeto rodante void handleCreditsRightMovement(); // Lógica específica para mover los créditos hacia la derecha
void handleRollingBounce(); // Aplica lógica de rebote durante rodamiento void handleCreditsLeftMovement(); // Lógica específica para mover los créditos hacia la izquierda
void handleContinueTimeOut(); // Gestiona tiempo de espera en pantalla "Continuar" void handleWaitingMovement(); // Controla la animación del jugador saludando
void updateWalkingStateForCredits(); // Actualiza el estado de caminata de algún personaje u elemento animado en los créditos
// --- Manejadores de transiciones de pantalla --- void setInputBasedOnPlayerId(); // Asocia las entradas de control en función del identificador del jugador (teclas, mando, etc.)
void handleTitleAnimation(float deltaTime); // Ejecuta animación del título void updateStepCounter(); // Incrementa o ajusta el contador de pasos para animaciones o mecánicas relacionadas con movimiento
void handleLeavingScreen(float deltaTime); // Lógica para salir de pantalla [[nodiscard]] auto computeAnimation() const -> std::pair<std::string, SDL_FlipMode>; // Calcula la animacion de moverse y disparar del jugador
void handleEnteringScreen(float deltaTime); // Lógica para entrar en pantalla
void handlePlayer1Entering(float deltaTime); // Entrada del Jugador 1
void handlePlayer2Entering(float deltaTime); // Entrada del Jugador 2
// --- Manejadores de pantallas especiales ---
void handleCreditsMovement(float deltaTime); // Movimiento en pantalla de créditos
void handleCreditsRightMovement(); // Movimiento hacia la derecha en créditos
void handleCreditsLeftMovement(); // Movimiento hacia la izquierda en créditos
void handleWaitingMovement(float deltaTime); // Animación del jugador saludando
void updateWalkingStateForCredits(); // Actualiza estado de caminata en créditos
// --- Utilidades de animación ---
[[nodiscard]] auto computeAnimation() const -> std::pair<std::string, SDL_FlipMode>; // Calcula animación de movimiento y disparo
}; };

View File

@@ -1,166 +0,0 @@
# Metal Shader Backend - Notas de Implementación
## Estado Actual
**Completado:**
- Shaders MSL (Metal Shading Language) portados desde GLSL
- Estructura básica de `MetalShader` class
- Inicialización de Metal device y command queue
- Compilación de shaders en runtime
- Creación de pipeline state
- Buffers de vértices, índices y uniforms
**Pendiente:**
- **Render loop completo** (la parte más crítica)
- Obtener textura Metal desde SDL_Texture
- Gestión de drawables y presentation
## Diferencias GLSL vs MSL
| Concepto | GLSL (OpenGL) | MSL (Metal) |
|----------|---------------|-------------|
| Entrada vertex | `layout(location = 0) in vec2` | `[[attribute(0)]]` |
| Salida vertex | `out vec2` | Struct con `[[position]]` |
| Uniforms | `uniform vec2` | `constant` struct en `[[buffer(N)]]` |
| Sampling | `texture(sampler2D, vec2)` | `texture.sample(sampler, float2)` |
| Entry point | `void main()` | `vertex/fragment function_name()` |
| Vector types | `vec2, vec3, vec4` | `float2, float3, float4` |
## Pasos para Completar el Render Loop
El método `MetalShader::render()` necesita:
```objc
1. Obtener drawable del CAMetalLayer:
id<CAMetalDrawable> drawable = [metal_layer_ nextDrawable];
2. Crear command buffer:
id<MTLCommandBuffer> command_buffer = [command_queue_ commandBuffer];
3. Crear render pass descriptor:
MTLRenderPassDescriptor* pass_descriptor = [MTLRenderPassDescriptor renderPassDescriptor];
pass_descriptor.colorAttachments[0].texture = drawable.texture;
pass_descriptor.colorAttachments[0].loadAction = MTLLoadActionClear;
pass_descriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
pass_descriptor.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 1);
4. Crear render command encoder:
id<MTLRenderCommandEncoder> encoder =
[command_buffer renderCommandEncoderWithDescriptor:pass_descriptor];
5. Configurar pipeline y buffers:
[encoder setRenderPipelineState:pipeline_state_];
[encoder setVertexBuffer:vertex_buffer_ offset:0 atIndex:0];
[encoder setVertexBuffer:uniforms_buffer_ offset:0 atIndex:1];
[encoder setFragmentBuffer:uniforms_buffer_ offset:0 atIndex:1];
[encoder setFragmentTexture:game_texture offset:0 atIndex:0];
[encoder setFragmentSamplerState:sampler_state_ atIndex:0];
6. Dibujar:
[encoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle
indexCount:6
indexType:MTLIndexTypeUInt16
indexBuffer:index_buffer_
indexBufferOffset:0];
7. Finalizar:
[encoder endEncoding];
[command_buffer presentDrawable:drawable];
[command_buffer commit];
```
## Problema Crítico: Obtener MTLTexture desde SDL_Texture
SDL3 renderiza el juego a `back_buffer_` (SDL_Texture). Necesitamos obtener
la textura Metal subyacente para pasarla al fragment shader.
**Opciones:**
1. **SDL_GetProperty()** - Usar SDL3 properties system:
```cpp
id<MTLTexture> metal_texture = (__bridge id<MTLTexture>)SDL_GetProperty(
SDL_GetTextureProperties(back_buffer_),
"SDL.texture.metal.texture",
nullptr
);
```
2. **Render to Metal texture directamente** - En lugar de usar SDL_Texture,
crear una MTLTexture directamente y renderizar el juego ahí. Más trabajo
pero más control.
3. **Copiar SDL texture a Metal texture** - Menos eficiente pero más simple.
## Sampler State
Falta crear el sampler state (equivalente a glTexParameteri en OpenGL):
```objc
MTLSamplerDescriptor* sampler_descriptor = [[MTLSamplerDescriptor alloc] init];
sampler_descriptor.minFilter = MTLSamplerMinMagFilterLinear;
sampler_descriptor.magFilter = MTLSamplerMinMagFilterLinear;
sampler_descriptor.sAddressMode = MTLSamplerAddressModeClampToEdge;
sampler_descriptor.tAddressMode = MTLSamplerAddressModeClampToEdge;
id<MTLSamplerState> sampler_state = [device_ newSamplerStateWithDescriptor:sampler_descriptor];
```
## Build Configuration
Para compilar en Xcode/CMake, necesitarás:
1. **CMakeLists.txt** - Añadir metal_shader.mm:
```cmake
if(APPLE)
set(RENDERING_SOURCES
${RENDERING_SOURCES}
source/rendering/metal/metal_shader.mm
)
target_link_libraries(${PROJECT_NAME}
"-framework Metal"
"-framework QuartzCore"
)
endif()
```
2. **Compilar shaders .metal** - Opcionalmente pre-compilar:
```bash
xcrun -sdk macosx metal -c crtpi_vertex.metal -o crtpi_vertex.air
xcrun -sdk macosx metal -c crtpi_fragment.metal -o crtpi_fragment.air
xcrun -sdk macosx metallib crtpi_*.air -o crtpi.metallib
```
3. **Cargar .metallib** en código:
```objc
NSString* path = [[NSBundle mainBundle] pathForResource:@"crtpi" ofType:@"metallib"];
id<MTLLibrary> library = [device_ newLibraryWithFile:path error:&error];
```
## Testing en macOS
Cuando pruebes en macOS:
1. Verifica que `SDL_WINDOW_METAL` está activo en screen.cpp
2. Compila con `-DCMAKE_BUILD_TYPE=Debug` para ver logs
3. Usa Xcode Instruments (Metal Debugger) para inspeccionar frames
4. Compara rendimiento con/sin shaders
## Referencias Útiles
- [Metal Programming Guide](https://developer.apple.com/metal/)
- [Metal Shading Language Specification](https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf)
- [SDL3 Metal Integration](https://github.com/libsdl-org/SDL/blob/main/docs/README-metal.md)
## Próximos Pasos
1. Implementar `render()` completo
2. Resolver obtención de textura desde SDL
3. Crear sampler state
4. Testear en macOS real
5. Optimizar si es necesario (probablemente ya será rápido)
---
**Nota importante**: Metal es significativamente más verboso que OpenGL pero
también más eficiente. Una vez que funcione el render loop, el rendimiento
debería ser excelente en macOS.

Some files were not shown because too many files have changed in this diff Show More