Desarrollo de videojuegos para PSP con C++ y SDL/Texto completo

Esta es la versión para imprimir de Desarrollo de videojuegos para PSP con C++ y SDL.
  • Si imprimes esta página, o eliges la opción de Vista preliminar de impresión de tu navegador, verás que desaparecen este cuadro y los elementos de navegación de arriba y de la izquierda, pues no son útiles en una versión impresa.
  • Pulsando antes en Refrescar esta página te asegurarás de obtener los últimos cambios del libro antes de imprimirlo.
  • Para más información, puedes ver Wikilibros:Versión para imprimir.
Desarrollo de videojuegos para PSP con C++ y SDL
por es.wikibooks.org


Contenido


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.

Introducción Instalación →


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.


← Introducción Instalación El makefile →


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ú.


← Instalación El makefile La pantalla →


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.


← El makefile La pantalla Leyendo la entrada de PSP →


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;
}


← La pantalla Leyendo la entrada de PSP Conclusiones →


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).

← Leyendo la entrada de PSP Conclusiones