Desarrollo de videojuegos para PSP con C++ y SDL/Texto completo
Contenido
- Introducción
- Instalación
- El makefile
- Callbacks de salida al SO
- La pantalla
- Leyendo la entrada de PSP
- Conclusiones
Introducción
Me he dispuesto a escribir este tutorial tras haber estado trabajando en el port a PSP de Granny's Bloodbath y encontrar algunas dificultades tanto en la instalación del Kit como en la escritura de un proyecto básico que funcione. Por internet circulan algunos tutoriales similares pero todos cuentan con algún otro error y, o yo soy muy torpe, o se hace bastante complicada la instalación.
Por otro lado, la mayoría se centraban en sistemas Windows y yo quería hacer la instalación sobre Ubuntu.
Espero que esta pequeña guía me sirva a mí como recordatorio para una próxima vez y, si alguien se ve beneficiado pues muchísimo mejor.
Instalación
Dependencias
Lo primero que debemos hacer es instalar las dependencias del kit de desarrollo de PSP. Para ello abriremos una terminal e introduciremos el siguiente comando:
sudo apt-get install build-essential autoconf automake bison flex \
libncurses5-dev libreadline-dev libusb-dev texinfo libmpfr-dev \
libgmp3-dev libtool
Variables de entorno
Ahora debemos establecer algunas variables de entorno para que el sistema sepa dónde encontrar las nuevas librerías de PSP a la hora de compilar. Editamos el fichero ~/.bashrc y añadimos al final las siguientes líneas:
export PSPDEV="/usr/local/pspdev"
export PSPSDK="$PSPDEV/psp/sdk"
export PATH="$PATH:$PSPDEV/bin:$PSPSDK/bin"
Cuando reiniciemos el equipo ~/.bashrc volverá a cargarse pero no
es necesario hacerlo, podemos ejecutar la siguiente orden:
syntaxhighlight ~/.bashrc
Instalación del SDK de PSP
El siguiente paso es descargarnos una copia del directorio trunk del repositorio de ps2dev, el cual contiene todo lo que necesitamos (y más). El repositorio tiene un tamaño considerable y, dependiendo de cómo ande el servidor, puede tardar bastante.
svn co svn://svn.ps2dev.org/psp/trunk/ pspsdk
Bueno, si habéis tenido la paciencia suficiente de llegar hasta aquí vamos
por buen camino. Ahora toca instalar el toolchain, el kit básico:
cd pspsdk
cd toolchain
sudo ./toolchain-sudo.sh
Existe un pack de bibliotecas adicionales entre las que se encuentran las
SDL llamado psplibraries. Este pack contiene: bzip2, freetype, jpeg,
libbulletml, libmad, libmikmod, libogg, libpng, libpspvram, libTremor,
libvorbis, lua, pspgl, pspirkeyb, SDL, SDL_gfx, SDL_image, SDL_mixer,
SDL_ttf, smpeg-psp, sqlite, zlib y zziplib. Muchas son dependencias de las
SDL pero algunas como sqlite (bases de datos), lua (lenguaje de scripting)
o pspgl (versión de Open GL para PSP) no tienen nada que ver aunque son muy
interesantes también. Lo instalamos de la siguiente manera:
cd ..
cd psplibraries
sudo ./libraries-sudo.sh
En teoría ya deberíamos estar listos para crear nuestros proyectos en C++
que usen las SDL para PSP, ¡pero no es así! Debe haber algún error en el
script anterior porque SDL_mixer no se instala como debería. Hemos de
compilar e instalar sus dependencias manualmente. Comenzamos cambiando
el propietario de la carpeta donde se instala el kit de desarrollo, sino
las bibliotecas no pueden instalarse (al menos yo no he conseguido hacerlo):
sudo chown -R username:group /usr/local/pspdev
sudo chown username:group /usr/local/pspdev/*
Donde group y username son los nombres de nuestro grupo y
usuario en el sistema.
Nos dirigimos a instalar libTremor manualmente, dependencia de SDL_mixer:
cd ..
cd libTremor
LDFLAGS="-L$(psp-config --pspsdk-path)/lib" LIBS="-lc -lpspuser" ./autogen.sh \
--host psp --prefix=$(psp-config --psp-prefix)
make clean
make
make install
Finalmente le toca el turno a SDL_mixer y toca seguir el siguiente proceso:
cd ..
cd SDL_mixer
./autogen.sh
LDFLAGS="-L$(psp-config --pspsdk-path)/lib" LIBS="-lc -lpspuser" \
./configure --host psp --with-sdl-prefix=$(psp-config --psp-prefix) \
--disable-music-mp3 --prefix=$(psp-config --psp-prefix) \
--disable-music-libmikmod --enable-music-mod
make clean
make
make install
Finalizamos
Con eso debería bastar, siento el rodeo, si alguien encuentra una manera mejor de instalar el kit de desarrollo para PSP con SDL en GNU/Linux, por favor, que me lo comunique. Entre todos podemos hacer una gran guía.
El makefile
El makefile para compilar proyectos para PSP tiene ciertas particularidades dignas de mención en esta humilde guía.
Jerarquía de directorios
Vamos a suponer que tenemos la siguiente jerarquía de directorios:
- Proyecto
- makefile
- main.cpp
- engine
- ficheros.cpp
- ficheros.h
El makefile
Nuestro makefile sería algo similar a lo siguiente:
#Proyecto
TARGET = Nombre del proyecto
SDL_CONFIG = $(PSPBIN)/sdl-config
# Rutas
MOTOR_DIR := engine
# Ficheros fuente del juego
SRC := main.cpp
# Ficheros fuente del motor.
SRC_MOTOR := ficheros.o del motor
# motor_dir + fuentes
SRC_DIR_MOTOR := $(foreach src, $(SRC_MOTOR),$(MOTOR_DIR)/$(src) )
# Objetos
OBJS := $(SRC:%.cpp=%.o) $(SRC_DIR_MOTOR:%.cpp=%.o)
INCDIR =
CFLAGS = $(shell $(SDL_CONFIG) --cflags) -G0 -Wall -O2 -DPSP
CXXFLAGS = $(CFLAGS) -fno-exceptions -fno-rtti -D"TIXML_USE_STL"
ASFLAGS = $(CFLAGS)
LIBDIR =
LDFLAGS =
LIBS = -lstdc++ -lsupc++ -lSDL_gfx -lSDL_image -lSDL_mixer -lSDL_ttf -lfreetype \
-lpng -ljpeg -lvorbisidec -lz -lm $(shell $(SDL_CONFIG) --libs)
EXTRA_TARGETS = EBOOT.PBP
PSP_EBOOT_TITLE = Nombre del proyecto
PSP_EBOOT_ICON= "icono.png"
PSP_EBOOT_PIC1= "fondo.png"
PSP_EBOOT_SND0= "sonido.at3"
PSPSDK = $(shell psp-config --pspsdk-path)
PSPBIN = $(shell psp-config --psp-prefix)/bin
USE_PSPSDK_LIBC=1
include $(PSPSDK)/lib/build.mak
Personalización
Podemos personalizar la apariencia de nuestro homebrew en el menú de PSP mediante las siguientes variables:
- PSP_EBOOT_ICON: icono de 144x80 que identificara al juego en la sección $Juegos$ del menú.
- PSP_EBOOT_PIC1: fondo que aparecerá en la consola cuando el juego esté seleccionado, debe tener 480x272.
- PSP_EBOOT_SND0: fichero de sonido en formato at3 que se escuchará cuando nuestro juego esté seleccionado en el menú.
Callbacks de salida al SO
Seguramente sabréis de qué hablo pero es probable que no os hayáis dado cuenta de que debe ser algo a controlar a la hora de portar la aplicación a PSP. Me refiero a la pausa del juego y a la vuelta al sistema operativo de la consola. En cualquier momento podemos pulsar el botón HOME, seleccionar "salir del juego" y volver al menú principal. Si no tenemos cuidado provocaremos un cuelgue en la consola cada vez que queramos salir.
Tendremos que definir ciertas funciones que actúen como callbacks. Un fichero main.cpp de un port génerico podría ser el siguiente:
#include <pspkernel.h>
#include "engine/application.h"
#include "engine/keyboard.h"
int exit_callback(int arg1, int arg2, void *common) {
/* Codigo para cerrar correctamente nuestro sistema */
keyboard->set_quit();
return 0;
}
int CallbackThread(SceSize args, void *argp) {
int cbid;
cbid = sceKernelCreateCallback("Exit Callback", exit_callback, NULL);
sceKernelRegisterExitCallback(cbid);
sceKernelSleepThreadCB();
return 0;
}
int SetupCallbacks(void) {
int thid = 0;
thid = sceKernelCreateThread("update_thread", CallbackThread,
0x11, 0xFA0, 0, 0);
if(thid >= 0) {
sceKernelStartThread(thid, 0, 0);
}
return thid;
}
/* Necesario para no tener problemas con C++ */
extern "C" int SDL_main (int argc, char* args[]);
int main(int argc, char *argv[])
{
/* Preparamos los callbacks */
SetupCallbacks();
/* Lanzamos nuestro sistema */
Application app("XML/configuration.xml");
app.run();
/* Volvemos al sistema operativo */
sceKernelExitGame();
return 0;
}
Es importante comprender que cuando el usuario decide salir del juego
(esperemos que no sea demasiado a menudo) se llama a la función
exit_callback por lo que debe ser ella la que se encargue de hacer
los cambios pertinentes para romper el game-loop. En mi caso una forma
podía ser llamar a la al método set_quit() de mi clase keyboard pero
en el vuestro puede ser cualquier otro.
La pantalla
Las dimensiones de la pantalla de la PSP son de 480x272 píxeles por lo que la superficie principal de SDL a crear debe ser exactamente de dichas dimensiones. Por otro lado está la profundidad de color, inicialmente pensé que sería más eficiente bajar dichos niveles de 32bpp a 16bpp o incluso 8bpp si fuera necesario por cuestiones de rendimiento. No obstante, el port de SDL para PSP parece no funcionar correctamente en los dos últimos modos. Finalmente acabé usando 32bpp aunque el rendimiento final es bastante bueno.
La llamada para inicializar SDL en PSP puede ser algo similar a esto:
SDL_Surface *screen = SDL_SetVideoMode(480, 272, 32, SDL_HWSURFACE | SDL_DOUBLEBUF);
Con las correspondientes comprobaciones posteriores.
Leyendo la entrada de PSP
El problema de SDL_Joystick
Teóricamente, como la mayoría de tutoriales apuntan, podemos usar SDL_Joystick para manejar la entrada del jugador. Se nos presenta una equivalencia entre botones de joystick y de PSP para que podamos manejarla pero lo cierto es que, o soy tremendamente torpe, o este sistema no funciona del todo bien.
Alternativa
Finalmente, para $Granny's bloodbath$ decidí cambiar la implementación de la clase que manejaba la entrada del jugador (utilizando la API de las librerías de PSP) sin cambiar su interfaz, de ese modo me adaptaba a las circunstancias de PSP sin afectar al resto del sistema.
Las clases que utilicé son las siguientes y funcionan bastante bien:
keyboard.h:
/*
This file is part of Granny's Bloodbath.
Granny's Bloodbath 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 3 of the License, or
(at your option) any later version.
Granny's Bloodbath is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Granny's Bloodbath. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _KEYBOARD_
#define _KEYBOARD_
#include <pspkernel.h>
#include <pspctrl.h>
#include <valarray>
#include <map>
//! Gestiona el teclado, consultamos teclas pulsadas
/**
@author David Saltares Marquez
@version 1.0
Clase que sigue el patron de diseno Singleton (una sola instancia accesible
desde todo el sistema).
Lleva el control de que teclas estan pulsadas en un momento determinado,
cuales se sueltan y cuales se vuelven a presionar.
Ejemplo de uso
\code
// Game loop
while(!quit){
keyboard->update() // Equivalente a Keyboard::get_instance()->update();
if(keyboard->pressed(Keyboard::KEY_UP)) // Si pulsamos arriba
...
if(keyboard->released(Keyboard::KEY_HIT)) // Si dejamos de pulsar golpear
...
if(keyboard->newpressed(Keyboard:KEY_SHOOT)) // Si antes soltamos y
// ahora pulsamos disparar
}
\endcode
*/
class Keyboard{
public:
/**
Controles del teclado utilizados en el juego
*/
enum keys{
KEY_UP,
KEY_DOWN,
KEY_RIGHT,
KEY_LEFT,
KEY_SHOOT,
KEY_HIT,
KEY_KNEEL,
KEY_EXIT,
KEY_ENTER
};
/**
@return Si es la primera vez que se usa Keyboard crea su instancia y
la devuelve. Sino simplemente la devuelve.
*/
static Keyboard* get_instance(){
/* Si es la primera vez que necesitamos Keyboard, lo creamos */
if(_instance == 0)
_instance = new Keyboard;
return _instance;
}
/**
Actualiza el estado del teclado. Debe llamarse una vez al comienzo
del game loop.
*/
void update();
/**
@param key Tecla a consultar
@return true si la tecla esta pulsada, false en caso contrario.
*/
bool pressed(keys key);
/**
@param key Tecla a consultar
@return true si la tecla estaba antes pulsada en la ultima actualizacion
y ahora no lo esta, false en caso contrario.
*/
bool released(keys key);
/**
@param key Tecla a consultar
@return true si la tecla no estaba pulsada en la ultima actualizacion y
ahora lo esta, false en caso contrario.
*/
bool newpressed(keys key);
/**
@return true si se ha producido algun evento de salida, false en caso
contrario
*/
bool quit();
void set_quit() {_quit = true;}
protected:
Keyboard();
~Keyboard();
Keyboard(const Keyboard& k);
Keyboard& operator = (const Keyboard& k);
private:
static Keyboard* _instance;
std::weeee<bool> actual_keyboard;
std::valarray<bool> old_keyboard;
bool _quit;
std::map<keys, PspCtrlButtons> configured_keys;
SceCtrlData buttonInput;
int n_keys;
};
#define keyboard Keyboard::get_instance()
#endif
keyboard.cpp:
#include <iostream>
#include <algorithm>
#include "keyboard.h"
using namespace std;
Keyboard* Keyboard::_instance = 0;
Keyboard::Keyboard()
{
/* Configuracion predeterminada */
configured_keys[KEY_UP] = PSP_CTRL_UP;
configured_keys[KEY_DOWN] = PSP_CTRL_DOWN;
configured_keys[KEY_LEFT] = PSP_CTRL_LEFT;
configured_keys[KEY_RIGHT] = PSP_CTRL_RIGHT;
configured_keys[KEY_HIT] = PSP_CTRL_CROSS;
configured_keys[KEY_SHOOT] = PSP_CTRL_CIRCLE;
configured_keys[KEY_EXIT] = PSP_CTRL_SELECT;
configured_keys[KEY_ENTER] = PSP_CTRL_START;
/* Inicializamos el estado del teclado */
n_keys = configured_keys.size();
actual_keyboard.resize(n_keys);
old_keyboard.resize(n_keys);
for(int i = 0; i < n_keys; ++i){
actual_keyboard[false];
old_keyboard[false];
}
_quit = false;
sceCtrlSetSamplingCycle(0);
sceCtrlSetSamplingMode(PSP_CTRL_MODE_ANALOG);
}
Keyboard::~Keyboard()
{
}
void Keyboard::update()
{
/* Ahora el teclado nuevo es el viejo */
old_keyboard = actual_keyboard;
/* Actualizamos el estado de la entrada PSP */
sceCtrlPeekBufferPositive(&buttonInput, 1);
/* Actualizamos el estado del teclado */
for(map<keys, PspCtrlButtons>::iterator i = configured_keys.begin();
i != configured_keys.end(); ++i)
actual_keyboard[i->first] = (buttonInput.Buttons & i->second)?
actual_keyboard[i->first] = true :
actual_keyboard[i->first] = false;
}
bool Keyboard::pressed(keys key)
{
/* Devolvemos si la tecla indicada esta pulsada */
return actual_keyboard[key];
}
bool Keyboard::released(keys key)
{
/* Comprobamos si la tecla indicada no esta pulsada y antes si */
return (!actual_keyboard[key] && old_keyboard[key]);
}
bool Keyboard::newpressed(keys key)
{
/* Comprobamos si la tecla indicada esta pulsada y antes no */
return (actual_keyboard[key] && !old_keyboard[key]);
}
bool Keyboard::quit()
{
return _quit;
}
Conclusiones
Con todos los detalles anteriormente mencionados creo que es suficiente para poder comenzar a portar los juegos que se creen en PC a PSP (siempre que la potencia, control y detalles similares lo permitan).