Capítulo 5: Control de Teclas
SDL es capaz de controlar muchas cosas, una de ellas es el control del
teclado. El control de teclas y botones es muy utilizado en gran medida en
los juegos. En este tutorial voy a explicar cómo mostrar diferentes imágenes
al presionar algunas teclas. NOTA: En este capítulo
reestructuré y cambié algunos aspectos del código fuente para adaptarlos al
lenguaje C++ estándar, ya que yo no soy partidario de trabajar
mezclando C++ y C en un mismo código, por lo tanto voy a tratar de explicar lo
más importante del código fuente y señalar los cambios con respecto a los
códigos anteriores. Comencemos por los archivos de encabezado:
//Inclusión de los encabezados utilizados en nuestros
programas
#include <SDL.h>
#include <iostream>
#include <string>
#include <SDL.h>
#include <iostream>
#include <string>
Como puedes ver, he quitado
#include <stdio.h>
que es un encabezado de C estándar, y lo cambié por
#include <iostream>
que es un
encabezado de C++ estándar, que también es utilizado para la
entrada y salida de cadena de textos, totalmente compatible con la
programación orientada a objeto de C++. También incluimos también el
encabezado
<stream>
que se utiliza trabajar con
cadenas de textos (stream
), para manejar los mensajes y los nombres de
los archivos de una manera más cómoda. Todo esto lo cambié porque voy a trabajar
de ahora en adelante por motivos de compatibilidad totalmente con C++.
Observe también que modifiqué el comentario. Luego de esto, hacemos una
enumeración de las superficies de las imágenes que vamos a mostrar en
pantalla.
//Enumeramos las superficies
enum ImgSuperficies{
IMG_SUPERF_PREDETERMINADO,
IMG_SUPERF_ARRIBA,
IMG_SUPERF_ABAJO,
IMG_SUPERF_IZQUIERDA,
IMG_SUPERF_DERECHA,
IMG_SUPERF_TODAS
};
enum ImgSuperficies{
IMG_SUPERF_PREDETERMINADO,
IMG_SUPERF_ARRIBA,
IMG_SUPERF_ABAJO,
IMG_SUPERF_IZQUIERDA,
IMG_SUPERF_DERECHA,
IMG_SUPERF_TODAS
};
Las enumeraciones (
Luego después de las declaraciones de las superficies tenemos una nueva sentencia:
enum
) es
una forma rápida de hacer constantes. En vez de colocar por ejemplo: const int IMG_SUPERF_PREDETERMINADO = 0; const int IMG_SUPERF_ARRIBA =
1; const int IMG_SUPERF_ABAJO = 2
, etc,
simplemente hacemos la enumeración y se asignan automáticamente un valor creciente a las
constantes que comienza desde 0. También se pueden colocar valores
explícitos. Yo hago esto porque es más fácil recordar cuando se trabajan con
varios elementos enumerados, por ejemplo, supongamos que en nuestro juego
debemos saber que el tipo de imagen arriba es 1, la imagen abajo es 2, la
imagen izquierda es 3 y la imagen derecha es 4, y el juego tiene miles de
líneas de código donde se requiera saber el tipo de imagen, tendrá menos
dolores de cabeza usando
if(img.type == IMG_LEFT)
que if(img.type ==
3)
.Luego después de las declaraciones de las superficies tenemos una nueva sentencia:
//Inicialización de la ventana, la superficie y la imagen
SDL_Window* Ventana = NULL; //La ventana donde se renderizará la imagen
SDL_Surface* Superficie = NULL; //La superficie que contendrá la ventana
SDL_Surface* ImagenMostrada = NULL; //La imagen que se mostrará en la superficie
SDL_Surface* ImagenSuperficie[IMG_SUPERF_TODAS]; //Las imagenes que corresponden con la tecla presionada
SDL_Window* Ventana = NULL; //La ventana donde se renderizará la imagen
SDL_Surface* Superficie = NULL; //La superficie que contendrá la ventana
SDL_Surface* ImagenMostrada = NULL; //La imagen que se mostrará en la superficie
SDL_Surface* ImagenSuperficie[IMG_SUPERF_TODAS]; //Las imagenes que corresponden con la tecla presionada
Después de declarar la ventana y las superficies hemos declarado un puntero a una matriz unidimensional
llamada
Como mencioné anteriormente, al cambiar el archivo de cabecera
ImagenSuperficie
de tipo SDL_Surface
donde
vamos a cargar todas las imágenes que iremos a mostrar cuando el usuario
presione ciertas teclas del teclado.Como mencioné anteriormente, al cambiar el archivo de cabecera
stdio.h
por
iostream
debemos cambiar las funciones
que dependían del archivo de cabecera anterior por funciones de la cabecera
nueva. Por ese motivo vamos a cambiar la función printf
que es una función de C por cout
que es una función de C++ como se puede observar:
//Cambiamos las funciones printf por cout
printf("ERROR: No se pudo inicializar SDL, Error SDL: %s\n", SDL_GetError());
std::cout << "ERROR: No se pudo inicializar SDL, Error SDL: " << SDL_GetError() << "\n";
printf("ERROR: No se pudo inicializar SDL, Error SDL: %s\n", SDL_GetError());
std::cout << "ERROR: No se pudo inicializar SDL, Error SDL: " << SDL_GetError() << "\n";
El prefijo
Ahora vamos a ver como cargar cada imagen en la superficie, esto lo hacemos en la función
std::
se debe a que la función cout pertenece al
espacio de nombres std. Si
desea utilizar la función cout sin el prefijo, puede "usar" el espacio de
nombres incluyendo la sentencia using namespace std;
al inicio del código, después de incluir las cabeceras.Ahora vamos a ver como cargar cada imagen en la superficie, esto lo hacemos en la función
cargarMedios
.
bool cargarMedios(){
//Bandera de carga correcta
bool correcto = true;
//Establecemos los nombres de los archivos que se van a abrir en una matriz unidimensional
std::string NombreArchivos[IMG_SUPERF_TODAS] = {"Predeterminado.bmp","Arriba.bmp","Abajo.bmp", "Izquierda.bmp","Derecha.bmp"};
//Carga cada una de las imágenes
for(int i=0; i <IMG_SUPERF_TODAS;i++){
ImagenSuperficie[i] = SDL_LoadBMP(NombreArchivos[i].c_str());
if(ImagenSuperficie[i] == NULL){
std::cout << "No se pudo cargar la imagen " << NombreArchivos[i] << " SDL Error: " << SDL_GetError() << "\n";
correcto = false;
}
}
//Salimos de la función
return correcto;
}
//Bandera de carga correcta
bool correcto = true;
//Establecemos los nombres de los archivos que se van a abrir en una matriz unidimensional
std::string NombreArchivos[IMG_SUPERF_TODAS] = {"Predeterminado.bmp","Arriba.bmp","Abajo.bmp", "Izquierda.bmp","Derecha.bmp"};
//Carga cada una de las imágenes
for(int i=0; i <IMG_SUPERF_TODAS;i++){
ImagenSuperficie[i] = SDL_LoadBMP(NombreArchivos[i].c_str());
if(ImagenSuperficie[i] == NULL){
std::cout << "No se pudo cargar la imagen " << NombreArchivos[i] << " SDL Error: " << SDL_GetError() << "\n";
correcto = false;
}
}
//Salimos de la función
return correcto;
}
En esta función, declaramos una matriz unidimensional de tipo
string
con todos
los nombres de los archivos llamada NombreArchivos.
Con una
sentencia for
, cargamos cada una de las imágenes
que vamos a utilizar en la matriz ImagenSuperficie
usando la
función SDL_LoadBMP
, donde especificamos en su parámetro la
matriz NombreArchivos
. La variable i
usada como contador en el for
especifica el índice de la matrices a medida que esta va aumentando. Esto lo
hacemos para automatizar y ahorrar líneas de código. En la función cerrar
también automatizamos la liberación de los recursos.
void cerrar(){
//Liberamos la superficie utilizada
for(int i =0; i < IMG_SUPERF_TODAS; i++){
SDL_FreeSurface(ImagenSuperficie[i]);
ImagenSuperficie[i] = NULL;
}
//Destruimos la Ventana para liberar recursos.
SDL_DestroyWindow(Ventana);
//Quitamos el subsistema de SDL.
SDL_Quit();
}
//Liberamos la superficie utilizada
for(int i =0; i < IMG_SUPERF_TODAS; i++){
SDL_FreeSurface(ImagenSuperficie[i]);
ImagenSuperficie[i] = NULL;
}
//Destruimos la Ventana para liberar recursos.
SDL_DestroyWindow(Ventana);
//Quitamos el subsistema de SDL.
SDL_Quit();
}
También en esta función usamos
Luego en la función
for
para
ir liberando una a una las superficies y de esta forma liberamos los
recursos.Luego en la función
main
antes de entrar al bucle principal
se establecerá la imagen predeterminada:
//Establece la imágen predeterminada
ImagenMostrada = ImagenSuperficie[IMG_SUPERF_PREDETERMINADO];
ImagenMostrada = ImagenSuperficie[IMG_SUPERF_PREDETERMINADO];
Esta sentencia establece la imagen de la matriz
ImagenSuperficie
especificada con el subíndice
IMG_SUPERF_PREDETERMINADO
en ImagenMostrada
. De
este modo se establece la imagen que se mostrará de forma predeterminada. Luego de esto en el
bucle principal veremos como captar los eventos para realizar una acción.
//Bucle principal
while(!quitar){
//Maneja la cola de eventos
while(SDL_PollEvent(&e) != 0){
//Si el usuario quiere salir
if(e.type == SDL_QUIT){
quitar = true;
}
//Establece la imagen cuando el usuario presiona las teclas
else if(e.type == SDL_KEYDOWN){
switch(e.key.keysym.sym){
case SDLK_UP:
ImagenMostrada = ImagenSuperficie[IMG_SUPERF_ARRIBA];
break;
case SDLK_DOWN:
ImagenMostrada = ImagenSuperficie[IMG_SUPERF_ABAJO];
break;
case SDLK_LEFT:
ImagenMostrada = ImagenSuperficie[IMG_SUPERF_IZQUIERDA];
break;
case SDLK_RIGHT:
ImagenMostrada = ImagenSuperficie[IMG_SUPERF_DERECHA];
break;
case SDLK_ESCAPE:
quitar = true;
break;
default:
ImagenMostrada = ImagenSuperficie[IMG_SUPERF_PREDETERMINADO];
break;
}
}
}
while(!quitar){
//Maneja la cola de eventos
while(SDL_PollEvent(&e) != 0){
//Si el usuario quiere salir
if(e.type == SDL_QUIT){
quitar = true;
}
//Establece la imagen cuando el usuario presiona las teclas
else if(e.type == SDL_KEYDOWN){
switch(e.key.keysym.sym){
case SDLK_UP:
ImagenMostrada = ImagenSuperficie[IMG_SUPERF_ARRIBA];
break;
case SDLK_DOWN:
ImagenMostrada = ImagenSuperficie[IMG_SUPERF_ABAJO];
break;
case SDLK_LEFT:
ImagenMostrada = ImagenSuperficie[IMG_SUPERF_IZQUIERDA];
break;
case SDLK_RIGHT:
ImagenMostrada = ImagenSuperficie[IMG_SUPERF_DERECHA];
break;
case SDLK_ESCAPE:
quitar = true;
break;
default:
ImagenMostrada = ImagenSuperficie[IMG_SUPERF_PREDETERMINADO];
break;
}
}
}
Al procesar la cola de eventos, observamos si el usuario quiere
salir con
El código fuente debe quedar de la siguiente forma:
SDL_QUIT
, sino, verificamos si fue presionado una
tecla con SDL_KEYDOWN
, si es así, veremos cual es la tecla con la sentencia
switch
comprobando a
e.key.keysym.sym
que contiene información acerca la tecla que
fue presionada y dependiendo de ello, se establecerá la imagen que
corresponda. Luego de esto, nos queda el procesamiento del resto del código,
en este caso las funciones SDL_BlitSurface
y
SDL_UpdateWindowSurface
.El código fuente debe quedar de la siguiente forma:
//Inclusión de los encabezados utilizados en nuestros
programas
#include <SDL.h>
#include <iostream>
#include <string>
//Enumeramos las superficies
enum ImgSuperficies{
IMG_SUPERF_PREDETERMINADO,
IMG_SUPERF_ARRIBA,
IMG_SUPERF_ABAJO,
IMG_SUPERF_IZQUIERDA,
IMG_SUPERF_DERECHA,
IMG_SUPERF_TODAS
};
//Declaración de la funciones
bool Inicializar(); //Inicialización de SDL y creación de Ventanas
bool cargarMedios(); //Carga los medios
void cerrar(); //Libera los medios y cierra SDL
//Inicialización de la ventana, la superficie y la imagen
SDL_Window* Ventana = NULL; //La ventana donde se renderizará la imagen
SDL_Surface* Superficie = NULL; //La superficie que contendrá la ventana
SDL_Surface* ImagenMostrada = NULL; //La imagen que se mostrará en la superficie
SDL_Surface* ImagenSuperficie[IMG_SUPERF_TODAS]; //Las imagenes que corresponden con la tecla presionada
bool Inicializar(){
//Bandera de inicialización es correcta
bool correcto = true;
//Inicializa el subsistema de Video
if(SDL_Init(SDL_INIT_VIDEO) < 0){
std::cout << "ERROR: No se pudo inicializar SDL, Error SDL: " << SDL_GetError() << "\n";
correcto = false;
}
else{
//Se crea la ventana principal
Ventana = SDL_CreateWindow("Tutorial SDL 2", 50, 50, 640, 480, SDL_WINDOW_SHOWN);
if(Ventana == NULL){
std::cout << "ERROR: No se pudo crear la ventana, SDL_Error: " << SDL_GetError() << "\n";
correcto = false;
}
else{
//Se crea la superficie para la ventana principal
Superficie = SDL_GetWindowSurface(Ventana);
}
}
//Salimos de la función
return correcto;
}
bool cargarMedios(){
//Bandera de carga correcta
bool correcto = true;
//Establecemos los nombres de los archivos que se van a abrir en una matriz unidimensional
std::string NombreArchivos[IMG_SUPERF_TODAS] = {"Predeterminado.bmp","Arriba.bmp","Abajo.bmp", "Izquierda.bmp","Derecha.bmp"};
//Carga cada una de las imágenes
for(int i=0; i <IMG_SUPERF_TODAS;i++){
ImagenSuperficie[i] = SDL_LoadBMP(NombreArchivos[i].c_str());
if(ImagenSuperficie[i] == NULL){
std::cout << "No se pudo cargar la imagen " << NombreArchivos[i] << " SDL Error: " << SDL_GetError() << "\n";
correcto = false;
}
}
//Salimos de la función
return correcto;
}
void cerrar(){
//Liberamos la superficie utilizada
for(int i =0; i < IMG_SUPERF_TODAS; i++){
SDL_FreeSurface(ImagenSuperficie[i]);
ImagenSuperficie[i] = NULL;
}
//Destruimos la Ventana para liberar recursos.
SDL_DestroyWindow(Ventana);
//Quitamos el subsistema de SDL.
SDL_Quit();
}
int main(int argc, char* args[]){
//Inicializa SDL y crea las ventanas
if(!Inicializar()){
std::cout << "No se pudo inicializar\n";
}
else{
//Carga los medios
if(!cargarMedios()){
std::cout << "No se pudo cargar los medios\n";
}
else{
//Bandera del bucle principal
bool quitar = false;
//Controlador de Eventos
SDL_Event e;
//Establece la imágen predeterminada
ImagenMostrada = ImagenSuperficie[IMG_SUPERF_PREDETERMINADO];
//Bucle principal
while(!quitar){
//Maneja la cola de eventos
while(SDL_PollEvent(&e) != 0){
//Si el usuario quiere salir
if(e.type == SDL_QUIT){
quitar = true;
}
//Establece la imagen cuando el usuario presiona las teclas
else if(e.type == SDL_KEYDOWN){
switch(e.key.keysym.sym){
case SDLK_UP:
ImagenMostrada = ImagenSuperficie[IMG_SUPERF_ARRIBA];
break;
case SDLK_DOWN:
ImagenMostrada = ImagenSuperficie[IMG_SUPERF_ABAJO];
break;
case SDLK_LEFT:
ImagenMostrada = ImagenSuperficie[IMG_SUPERF_IZQUIERDA];
break;
case SDLK_RIGHT:
ImagenMostrada = ImagenSuperficie[IMG_SUPERF_DERECHA];
break;
case SDLK_ESCAPE:
quitar = true;
break;
default:
ImagenMostrada = ImagenSuperficie[IMG_SUPERF_PREDETERMINADO];
break;
}
}
}
//Aplicamos la imagen en la superficie de la ventana
SDL_BlitSurface(ImagenMostrada, NULL, Superficie, NULL);
//Actualizamos la superficie de la ventana
SDL_UpdateWindowSurface(Ventana);
}
}
}
//Liberamos los recursos y cerramos SDL
cerrar();
//Salimos de main
return 0;
}
#include <SDL.h>
#include <iostream>
#include <string>
//Enumeramos las superficies
enum ImgSuperficies{
IMG_SUPERF_PREDETERMINADO,
IMG_SUPERF_ARRIBA,
IMG_SUPERF_ABAJO,
IMG_SUPERF_IZQUIERDA,
IMG_SUPERF_DERECHA,
IMG_SUPERF_TODAS
};
//Declaración de la funciones
bool Inicializar(); //Inicialización de SDL y creación de Ventanas
bool cargarMedios(); //Carga los medios
void cerrar(); //Libera los medios y cierra SDL
//Inicialización de la ventana, la superficie y la imagen
SDL_Window* Ventana = NULL; //La ventana donde se renderizará la imagen
SDL_Surface* Superficie = NULL; //La superficie que contendrá la ventana
SDL_Surface* ImagenMostrada = NULL; //La imagen que se mostrará en la superficie
SDL_Surface* ImagenSuperficie[IMG_SUPERF_TODAS]; //Las imagenes que corresponden con la tecla presionada
bool Inicializar(){
//Bandera de inicialización es correcta
bool correcto = true;
//Inicializa el subsistema de Video
if(SDL_Init(SDL_INIT_VIDEO) < 0){
std::cout << "ERROR: No se pudo inicializar SDL, Error SDL: " << SDL_GetError() << "\n";
correcto = false;
}
else{
//Se crea la ventana principal
Ventana = SDL_CreateWindow("Tutorial SDL 2", 50, 50, 640, 480, SDL_WINDOW_SHOWN);
if(Ventana == NULL){
std::cout << "ERROR: No se pudo crear la ventana, SDL_Error: " << SDL_GetError() << "\n";
correcto = false;
}
else{
//Se crea la superficie para la ventana principal
Superficie = SDL_GetWindowSurface(Ventana);
}
}
//Salimos de la función
return correcto;
}
bool cargarMedios(){
//Bandera de carga correcta
bool correcto = true;
//Establecemos los nombres de los archivos que se van a abrir en una matriz unidimensional
std::string NombreArchivos[IMG_SUPERF_TODAS] = {"Predeterminado.bmp","Arriba.bmp","Abajo.bmp", "Izquierda.bmp","Derecha.bmp"};
//Carga cada una de las imágenes
for(int i=0; i <IMG_SUPERF_TODAS;i++){
ImagenSuperficie[i] = SDL_LoadBMP(NombreArchivos[i].c_str());
if(ImagenSuperficie[i] == NULL){
std::cout << "No se pudo cargar la imagen " << NombreArchivos[i] << " SDL Error: " << SDL_GetError() << "\n";
correcto = false;
}
}
//Salimos de la función
return correcto;
}
void cerrar(){
//Liberamos la superficie utilizada
for(int i =0; i < IMG_SUPERF_TODAS; i++){
SDL_FreeSurface(ImagenSuperficie[i]);
ImagenSuperficie[i] = NULL;
}
//Destruimos la Ventana para liberar recursos.
SDL_DestroyWindow(Ventana);
//Quitamos el subsistema de SDL.
SDL_Quit();
}
int main(int argc, char* args[]){
//Inicializa SDL y crea las ventanas
if(!Inicializar()){
std::cout << "No se pudo inicializar\n";
}
else{
//Carga los medios
if(!cargarMedios()){
std::cout << "No se pudo cargar los medios\n";
}
else{
//Bandera del bucle principal
bool quitar = false;
//Controlador de Eventos
SDL_Event e;
//Establece la imágen predeterminada
ImagenMostrada = ImagenSuperficie[IMG_SUPERF_PREDETERMINADO];
//Bucle principal
while(!quitar){
//Maneja la cola de eventos
while(SDL_PollEvent(&e) != 0){
//Si el usuario quiere salir
if(e.type == SDL_QUIT){
quitar = true;
}
//Establece la imagen cuando el usuario presiona las teclas
else if(e.type == SDL_KEYDOWN){
switch(e.key.keysym.sym){
case SDLK_UP:
ImagenMostrada = ImagenSuperficie[IMG_SUPERF_ARRIBA];
break;
case SDLK_DOWN:
ImagenMostrada = ImagenSuperficie[IMG_SUPERF_ABAJO];
break;
case SDLK_LEFT:
ImagenMostrada = ImagenSuperficie[IMG_SUPERF_IZQUIERDA];
break;
case SDLK_RIGHT:
ImagenMostrada = ImagenSuperficie[IMG_SUPERF_DERECHA];
break;
case SDLK_ESCAPE:
quitar = true;
break;
default:
ImagenMostrada = ImagenSuperficie[IMG_SUPERF_PREDETERMINADO];
break;
}
}
}
//Aplicamos la imagen en la superficie de la ventana
SDL_BlitSurface(ImagenMostrada, NULL, Superficie, NULL);
//Actualizamos la superficie de la ventana
SDL_UpdateWindowSurface(Ventana);
}
}
}
//Liberamos los recursos y cerramos SDL
cerrar();
//Salimos de main
return 0;
}
Hasta aquí hemos
visto como saber cual tecla fue presionada con SDL 2. Puedes descargar
el código fuente completo aquí.
Muy buen tutorial, esepro que lo sigas algun día, aunque ya con estos capitulos es suficiente como para introducirse a la documentación en parte, saludos.
ResponderEliminar