Cómo programar soporte de Joysticks con SFML 2.0

Estuve intentando ver que tal sería darle soporte de joysticks a mi motor. Me di cuenta que no es tan simple como parece. Por suerte la interfaz de sf::Joystick es bastante intuitiva. He probado con dos gamepads: un USB Genérico (similar al de Playstation), y el controlador de XboX 360. Lo primero que hice fue crear un std::vector para almacenar todos los joysticks que SFML detecte como conectados:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// vector para almacenar los Joysticks que hay conectados
std::vector <unsigned int> ListaJSConectados;
// Buscar si hay joysticks conectados
for (unsigned int a = 0; a < sf::Joystick::Count; ++a) {
if (sf::Joystick::isConnected (a)) {
std::cout << "Joystick num " << a << " esta conectado y tiene "
<< sf::Joystick::getButtonCount(a) << " botones" << std::endl;
ListaJSConectados.push_back (a);
} // if
} // for
std::cout << "Detectados " << ListaJSConectados.size() << " joysticks" << std::endl;

Luego de eso escribí el código para verificar si algún botón fue presionado. Para eso, hay que recorrer el vector ListaJSConectados, y luego verificar cada uno de los botones, dependiendo de cuantos botones tenga cada dispositivo. Eso se puede saber gracias al método sf::Joystick::getButtonCount.

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Revisar el estado de los JS detectados
// nj = Numero de JS, nb = Numero de boton
for (unsigned int nj = 0; nj < ListaJSConectados.size(); ++nj) {
// Revisar los botones presionados
for (unsigned int nb = 0; nb < sf::Joystick::getButtonCount (nj); ++nb) {
if (sf::Joystick::isButtonPressed (nj, nb)) {
std::cout << "Boton " << nb << " presionado en el JS num " << nj << std::endl;
}
}
}

Finalmente está el tema de tratar de mover un personaje de nuestro juego (el protagonista) usando los ejes del joystick. Esto puede llegar a ser un infierno. Cada dispositivo es libre de manejar los ejes y botones a su antojo. Entonces.. ¿Cómo puedo escribir código que sea compatible con todos los dispositivos? .. Buena pregunta..

Por lo general todos los dispositivos deberían tener un eje X y un eje Y (por suerte!). Esto se puede saber consultando el método hasAxis, el cual devuelve true si el dispositivo tiene determinado eje. El método getAxisPosition devuelve un float con la posición del eje que le demos como parámetro. SFML maneja valores entre -100 y 100. La cuestión es que algunos joysticks son más sensibles que otros.. cuando alguna palanca sea movida hacia la izquierda, podemos obtener instantáneamente el valor -100, o podemos ir obteniendo valores negativos intermedios (-23, -42, etc). Incluso puede que el valor nunca llegue exactamente a -100, puede que quede fijo en -96, -99 (como me pasó con mi joystick de X360).

Entonces, la mejor forma que encontré para saber si debo mover el personaje a la izquierda de la pantalla utilizando el eje X del joystick, es esta:

C++
1
2
3
4
5
6
float EjeXpos = sf::Joystick::getAxisPosition (0, sf::Joystick::X);
// J0 izquierda
if (EjeXpos < -50.0f) {
pos1.x -= velocidad * tiempoDelta.asSeconds();
}

Consulto la posición del eje X para el joystick 0, que en mi caso, es el primer joystick instalado en mi PC (el joystick número 0 no es necesariamente el primer joystick conectado o el primero detectado). Si la posición es menor a -50, lo tomo como que el eje fue desplazado a la izquierda, por lo tanto actualizo la posición de mi personaje (según la velocidad y el tiempo delta, como se acostumbra hacer). Este código fue probado con mis dos joysticks, y funcionó por igual con ambos.

Claro que se podría hacer algo para aprovechar la sensibilidad de algunos dispositivos como el controlador de la X360. Se podría por ejemplo tratar de aumentar la aceleración de una entidad dependiendo del valor del eje. Los botones LT y RT no son detectados como botones, sino como ejes, y devuelven valores entre -99 y 99 aprox. Tal vez un dispositivo diseñado para simuladores de vuelo tengan más ejes, más sensibilidad, etc. Pero bueno, eso quedará para más adelante…

 

Implacable Game Engine, motor 2D en crecimiento

Poco a poco mi propio motor de juegos 2D va creciendo. Actualizado para utilizar el último development snapshot de SFML 2. Las características soportadas hasta el momento, son las siguientes:

  • Cargar/Guardar la configuración gráfica desde un archivo XML, utilizando TinyXML
  • Generación de números pseudo aleatorios, gracias a la librería RandomC
  • Administración de estados (menú, introducción, juego, etc)
  • Clase propia para la carga de mapas de tiles, en formato XML, creados con el excelente programa Tiled (el cual recomiendo)
  • Clase propia para la carga y el dibujado de spritesheets de personajes
  • Clase propia encargada de administrar diversas animaciones para cada estado de cada personaje (golpeando, caminando, saltando, etc)
  • Inteligencia Artificial: Una clase para manejar máquinas de estado finito (FSM), un administrador de entidades, y un sistema de mensajes entre entidades. Recientemente agregando el uso de Steering Behaviors para definir los comportamientos de los agentes del juego (Seek, Flee, Pursuit, Evade, etc)

Menú de ejemplo

Próximamente implementaré otros Steering Behaviors que me faltan, junto con otros métodos de IA, como Fuzzy Logic. También tengo pensado agregar una clase que se encargue de manejar diversos textos informativos en pantalla, tales como “+30 XP“, “Level Up!“, etc, con diversas formas de animación y transiciones.

Más adelante también pienso agregar algún soporte de scripting en lenguajes como Lua, tal vez algún sistema de partículas sencillo, y quizás también un sistema de manejo de luces y sombras dinámicas.

IGE screenshot

Por supuesto que en algún momento haré el port a otras plataformas, no sólo Win32.. Al menos un port para Linux sí o sí! :D

 

Programando un motor con aceleración por hardware

“No programes motores… programa juegos..”
Dijo algún sabio.. pero bueno, programar un motor propio sirve también como experiencia, no?

Hace unos días descargué una versión beta de SFML 2.0. Entre sus características más importantes se destacan la aceleración de gráficos por hardware mediante OpenGL, y el sonido 3D mediante OpenAL. Además, es portable, orientado a objetos y fácil de usar.

Empecé a escribir el “State Manager” del engine, o sea el “encargado” de manejar los distintos estados de un juego (Intro, Menú, Opciones, Jugar, Créditos, etc) , lo estuve probando y funciona bien. Luego agregué funcionalidad para cargar y salvar la configuración del engine a un archivo de datos XML mediante la librería TinyXML, la cual es simple y muy conveniente para trabajar en C++. Probado todo esto, y luego de fixear algunos bugs, el código quedó simple y efectivo.

Lo siguiente será seguir leyendo material y tratar de implementar un pequeño motor de Física y también algo de Inteligencia Artificial. Además, las cosas clásicas que debe tener un motor 2D, como Tiles, Spritesheets, etc.

También encontré un buen programa para crear los mapas de tiles: Tiled Map Editor. Es gratis, soporta XML, puede crear mapas ortogonales e isométricos, y soporta plugins para extender sus funciones.

Aquí una imagen del trabajo muy básico que hace por el momento mi motor “Implacable Game Engine“:

Implacable Game Engine screenshot 01

Jugando en 3D con Irrlicht

Existen bastantes motores para crear videojuegos 3D. Yo me decidí a probar Irrlicht. Lo que más me interesó es que es open-source y multiplataforma, algo para mí fundamental. Además, es rápido y bastante sencillo de aprender. Funciona con C++ y .NET. Corre sobre DirectX 8, 9, OpenGL, y 2 variantes de render por software.

Cuenta con una gran comunidad, y tiene bastante documentación, tutoriales, etc. Soporta varios formatos de archivo para importar y exportar escenas y meshes.

Leyendo un poco la documentación, logré importar un modelo hecho por mí en Blender, una imagen con fondo transparente, y agregar una primitiva, alguna que otra luz y una cámara. Y el resultado es este:

Aquí dejo el código:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include <irrlicht.h>
#include <iostream>
using namespace irr;
using namespace std;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;
int main() {
// Crear el dispositivo (ventana) donde mostrar los graficos
IrrlichtDevice *device =
createDevice(EDT_OPENGL, dimension2d<u32>(1250, 720), 32, false, false, false, 0);
// Si no lo pudo crear, nos vamos (enojados)
if (!device) {
cout << "Error: No se pudo crear la ventana" << endl;
return 1;
}
// Titulo de la ventana
device->setWindowCaption(L"Mate&Metal: Probando Irrlicht !!!");
// Crear el driver y el administrador de escena
IVideoDriver *driver = device->getVideoDriver();
ISceneManager *escena = device->getSceneManager();
// Ocultar el cursor del mouse
//device->getCursorControl()->setVisible(false);
// Cargar un logo
device->getGUIEnvironment()->addImage(driver->getTexture("mate3d.png"),
core::position2d<s32>(0,0));
// Cargar un modelo 3D
IAnimatedMesh *Mate = escena->getMesh("matecito.3ds");
if (!Mate) {
cout << "Error: no se puede cargar el objeto 3D !!!" << endl;
device->drop();
return 1;
}
// Crear un nodo para ese objeto 3D
IAnimatedMeshSceneNode *NodoMate = escena->addAnimatedMeshSceneNode(Mate);
// Propiedades del nodo
if (NodoMate) {
NodoMate->setMaterialFlag(EMF_LIGHTING, true);
NodoMate->setMaterialTexture(0, driver->getTexture("pasto.jpg"));
//NodoMate->setMaterialType(EMT_SOLID);
//NodoMate->getMaterial(0).SpecularColor.set(255, 255, 255, 255);
//NodoMate->getMaterial(0).Shininess = 5.0f;
NodoMate->setPosition(vector3df(12,0,0));
NodoMate->setScale(vector3df(3,3,3));
NodoMate->setRotation(vector3df(.5,0,0));
}
// Agregar un cubo
escena->addCubeSceneNode(5.5f, 0, -1, vector3df(0, -8.5, 0));
// Poner una luz ambiental
escena->setAmbientLight(SColorf(1, 1, 1, 1));
escena->addLightSceneNode();
// Agregar la cámara (desde donde mira, y hacia donde mira)
escena->addCameraSceneNode(NULL, vector3df(0,0,-20), vector3df(0,0,0));
// Loop
while(device->run()) {
driver->beginScene(true, true, SColor(255,50,130,140));
// Dibujar todo lo que es 3D
escena->drawAll();
// Dibujar por arriba lo que es 2D
device->getGUIEnvironment()->drawAll();
driver->endScene();
}
device->drop();
return 0;
}

Mi primer videojuego

Bueno este es el primer artículo que escribo en mi nuevo blog. Estoy programando mi primer videojuego 2D en lenguaje C++. Utilizo la librería SDL creada por Sam Lantinga. Aquí se puede ver un screenshot de lo que el juego es capaz de hacer por el momento.

Motor del juego funcionando

Características:

- El juego abre una ventana de resolución configurable
- Carga mapas de distintas dimensiones, con soporte de capas, parallax, scrolling
- Soporta la carga de sprites y sheets de animación
- Inicializa la posición y la vida de los enemigos basándose en números aleatorios
- Nueva lógica completamente re-escrita: Ahora la cámara sigue al jugador correctamente, y éste se mantiene centrado en pantalla
- El motor permite manejar distintos estados (en menú, en juego, en pausa, etc)
- Permite modificar la velocidad de los fotogramas por segundo
- Agregado el HUD para ver el estado del personaje en pantalla (eso que parece una barra de energía abajo de todo)
- Spawn-Timer: Un temporizador que permite crear nuevos enemigos cada cierto intervalo de tiempo
- “Inteligencia” Artificial: Escribí una simple máquina de estados para todos los enemigos. Lo que hace, no es nada de otro mundo, simplemente va persiguiendo al jugador a medida que éste se va moviendo por el mapa, buscando sus coordenadas X e Y. Cuando lo tiene cerca, se pone en estado de golpear, cuando no, vuelve a su estado de perseguir

Bugs conocidos:

- Actualmente fixeando un bug en la animación de los enemigos que hace que se repitan fotogramas rápidamente

Próximo paso:

- Tratar de implementar otros métodos de Inteligencia Artificial un poco más complejos, como el método de Bresenham, Fuzzy Logic, etc.
- Manejar todos los enemigos en una lista doblemente enlazada
- Crear los gráficos y animaciones definitivos
- Agregar soporte de sonido y joystick
- Optimizar para ganar velocidad y reducir uso de memoria

Si alguien se pregunta de donde obtuve esos gráficos, bueno, en este sitio hay muchos sprites de juegos. Yo utilicé los del juego Blood. El mapa lo diseñé yo

Para los que se interesen en este tema, luego pondré tutoriales de como empezar. Gracias por leer!