jueves, 2 de junio de 2011

GameEngine: Capitulo 4 . Sound Manager

Hola a todos,

Bienvenidos a la entrega de antes del fin de semana (si, el fin de semana para mi es sagrado) de como aprender programar un game engine y que tu madre te siga queriendo más o menos igual...

En este capitulo vamos ha enseñar lo que en muchos tutoriales se olvida de explicar pero que sin el cual ningún juego podría ver la luz.. el sound manager. El audio de nuestro game engine lo implementaremos mediante OpenAL y Alut (el equivalente sonoro a OpenGL y Glut). Si nunca habeis trabajado con estas librerias os recomiendo que os leais este post ya que por la red hay poca información al respecto.

Como instalar OpenAL+Alut
Con unos sencillos pasos podremos instalar las librerias en nuestro PC:
  1. Descargaros las librerias de aquí 
  2. Instaladlas como os digan las instrucciones (pueden variar de un sistema a otro). A grandes rasgos lo que os hará falta será copiar los .a, .dll ,.h en las carpetas de sistema que haga falta para que nuestro proyecto pueda leerlas.
  3. Linkar nuestro proyecto con openal. La forma más fácil de linkar las librerias es incluir estas lineas en el main.cpp justo despues de la eliminación de la consola que explicamos hace unos pocos capítulos:

    //Linking openal 
    #pragma comment(lib, "openal32.lib")
    #pragma comment(lib, "alut.lib")

Como usar OpenAL + Alut:
El funcionamiento de OpenAl es sencillo, cargamos un sonido, lo guardamos en un buffer de memoria y cuando hacemos un play de ese sonido lo asignamos a un canal. A nivel personal, esto que es tan sencillo se puede convertir en un excelente infierno. Mi recomendación, cargar todos los sonidos al principio (cargarlos en tiempo de ejecución acostumbra a acabar en desastre por liarla con los buffers) y encapsular muy bien openAl para no tener que acceder nunca directamente a ella desde nuestro juego.

Código:
El código de esta práctica es muy sencillo, como mucho habréis supuesto, la clase SoundManager es un singleton que se encarga de gestionar el sonido y solo se ha de modificar la clase core para poder unir este manager al proyecto. Miremos el código entones:

SoundManager.cpp:
/**************************************************************************************************/
// Código creado por F.Bordas (LordPakus) como ejemplo de creación de un game engine
// para el blog LordPakus (http://lordpakus.blogspot.com/).
// Prohibida la distribución fuera de este blog sin el permiso expreso del autor
/**************************************************************************************************/

/**************************************************************************************************/
// SoundManager.h : Código de la parte de audio del game engine
// Caracteristicas especiales: Esta clase implementa un singleton, es decir, solo podrá existir un objeto de esta clase en todo el proyecto
/**************************************************************************************************/
#include "SoundManager.h"
#include "MyGL.h"

#include <iostream> //Usada para imprimir por consola

using namespace std;

//Instancia única del sound manager
SoundManager SoundManager::instance;

SoundManager::SoundManager()
{
}

SoundManager::~SoundManager()
{
}

//Devolvemos el puntero al singleton
SoundManager& SoundManager::singleton()
{
return instance;
}

//Sounds functions
void SoundManager::Init(int* argc, char* argv[])
{
cout << "Inicializamos el sound manager\n";
alutInit(argc, argv) ;

//Toda esta carga de sonidos esta hardcodeada, por el momento. En breve publicaré un capitulo dedicado a los filemanager y a la eliminación de todo esto.
alGenBuffers(MAX_SOUNDS,buffer);
buffer[SOUND_BOOST] = alutCreateBufferFromFile ("Data\\Audio\\boost.wav");
buffer[SOUND_PASOS] = alutCreateBufferFromFile ("Data\\Audio\\pasos.wav");
buffer[SOUND_SALTO] = alutCreateBufferFromFile ("Data\\Audio\\salto.wav");
buffer[SOUND_COIN] = alutCreateBufferFromFile ("Data\\Audio\\coin-04.wav");
buffer[SOUND_DIE] = alutCreateBufferFromFile ("Data\\Audio\\evlaugh.wav");
alGenSources(MAX_SOUNDS, source);
}

void SoundManager::DeInit()
{
cout << "Desinicializamos el sound manager\n";
alutExit();
}

void SoundManager::Play(int id)
{
static unsigned int s = 0;
alSourcei (source[s], AL_BUFFER, buffer[id]);
alSourcePlay (source[s]);

if(++s == MAX_SOUNDS)
s = 0;
}


SoundManager.h
/**************************************************************************************************/
// Código creado por F.Bordas (LordPakus) como ejemplo de creación de un game engine
// para el blog LordPakus (http://lordpakus.blogspot.com/).
// Prohibida la distribución fuera de este blog sin el permiso expreso del autor
/**************************************************************************************************/

/**************************************************************************************************/
// SoundManager.h : Código de la parte de audio del game engine
// Caracteristicas especiales: Esta clase implementa un singleton, es decir, solo podrá existir un objeto de esta clase en todo el proyecto
/**************************************************************************************************/

#include "AL/Alut.h"
#include "MyGL.h"



#ifndef __SoundManager__
#define __SoundManager__

//SOUNDS: Esto es un parche temporal... en breve pondré un capitulo referente a los filemanager que permitirá quitar todo esto.
#define MAX_SOUNDS 100
#define SOUND_BOOST 0
#define SOUND_PASOS 1
#define SOUND_SALTO 2
#define SOUND_COIN 3
#define SOUND_DIE 4

class SoundManager
{
private:
// Constructor y destructor de la clase
static SoundManager instance;
SoundManager();
~SoundManager();

public:
static SoundManager& singleton();

public:
//Funcion para inicializar el motor de sonido
void Init(int* argc, char* argv[]);

//Funcion para desinicializar el motor de sonido
void DeInit();

public:
void Play(int id);
void PlayBackground(int id);

private:
//Variables usadas para almacenar la información de audio
ALuint buffer[MAX_SOUNDS];
ALuint source[MAX_SOUNDS];
};

#endif // __GameController__

Core.cpp:
/**************************************************************************************************/
// Código creado por F.Bordas (LordPakus) como ejemplo de creación de un game engine
// para el blog LordPakus (http://lordpakus.blogspot.com/).
// Prohibida la distribución fuera de este blog sin el permiso expreso del autor
/**************************************************************************************************/

/**************************************************************************************************/
// Core.cpp : Código del core manager.
/**************************************************************************************************/


#include "Core.h"
#include "MyGL.h" //Includes standar para opengl,glut y compañia
#include "GraphicsManager.h" //Inclusión del motor gráfico
#include "SoundManager.h" //Inclusión del motor de audio

#include <iostream> //Usada para imprimir por consola
#include <cmath>
using namespace std;

//Instancia única del core manager
Core Core::instance;

//Constructor
Core::Core()
{
}

//Destructor
Core::~Core()
{
}

//Devolvemos el puntero al singleton
Core& Core::singleton()
{
return instance;
}

//Funcion para inicializar el bucle principal
void Core::Init(int* argc, char* argv[])
{
cout << "Inicializamos el Core Manager\n";
//Inicializamos el motor gráfico
GraphicsManager::singleton().Init(argc,argv);

//Inicializamos el motor de audio
SoundManager::singleton().Init(argc,argv);

//Asignamos todas las funciones de callback del glut
//a las funciones que definiremos posteriormente
glutIdleFunc(glut_idle);
}

//Funcion para ejecutar el bucle principal
void Core::Run()
{
//Pasamos el control del bucle principal al glut
//que nos gestionara los eventos y llamara a las
//funciones de callback.
glutMainLoop();
}

//Funcion para desinicializar el bucle principal
void Core::DeInit()
{
cout << "Desinicializamos el Core Manager\n";
GraphicsManager::singleton().DeInit();
SoundManager::singleton().DeInit();
}


//CALLBACKS DE GLUT
//Esta funcion se llama cuando el sistema no hace nada (por ahora gestionada por glut)
void Core::glut_idle()
{
//Nos sacamos de la manga que de vez en cuando haga sonidos aleatorios
if(!(rand()%100))
SoundManager::singleton().Play(rand()%5);

//Repintamos la escena
glutPostRedisplay();
}

Resultados:
Si todo está bien hecho (y si no, pillad el ejemplo del “repositorio” ) os tendría que salir lo mismo que el último día, pero sonando de fondo unos sonidos aleatorios (yo he puesto unos sonidos, vosotros poned los que queráis).

NOTA IMPORTANTE: Vereis que la carga de sonidos está hardcodeada y que la posición en la que se graban los sonidos se ha conseguido mediante defines..... por ahora, aceptadlo como un mal menor. Arreglarlo y hacer que funcionara como yo quisiera me habría hecho imposible publicar hoy. En un futura (y no muy lejana) entrega os pasaré el código del FileManager que nos permitirá cargar listas de sonidos desde un archivo y hacer que este código sea mucho más mantenible.

LordPakusBlog
Disfrutad de vuestro audio, buen fin de semana y nos vemos!!!

12 comentarios :

  1. Hey, que tal!

    Me gustaría felicitarte por el gran trabajo que has hecho. Yo aún no sé C++ (estoy estudiando C de manera autodidacta), y creeo que te has esforzado.

    Espero que no abandones tu proyecto, estaré checando tu blog frecuentemente :)

    Saludos!

    ResponderEliminar
  2. gracias reethok... en un par de horas(si no hay problemas de última hora) subiré el filemanager... a ver si te gusta... y bueno, mi consejo, no te dejes abrumar por la cantidad de cosas que hay en este mundo de la programación... ves poco a poco y alegrate por todo lo que vayas aprendiendo dia a dia... otro consejo, si quieres ponte como seguidor e irás recibiendo las noticias de este blog...

    ResponderEliminar
  3. Pakus, hasta el capítulo anterior he podido segirte sin problemas, pero al inluir el SoundManager, no consigo oir nada

    ResponderEliminar
  4. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  5. Yo he tenido que usar la línea "# include windows.h" en "MyGL.h", ya que si no la uso me aparecen un montón de errores en el fichero "gl.h".

    ResponderEliminar
  6. Buenas TxoniMoni.... es posible que la linea de include windows te haga falta (por eso la dejé comentada :D),por que depende un poco de como tengas el proyecto montado.En mi caso la tuve que quitar por que me entraba en conflicto...

    Lo de que los sonidos no te funcionan, has probado con los sonidos que proporciono con las prácticas en la zona de descargas?? Hay problemillas a veces con los wav, openAL no puede leer todas las frecuencias de muestreo. Es un bug conocido que aún no se como arreglarlo.

    Prueba con los sonidos que proporciono, si así tampoco te funciona dimelo y lo miraremos más a fondo.

    ResponderEliminar
  7. Muchas gracias Pako. Me he descargado tus ficheros wav y ahora ya tengo sonido.
    Por cierto, para deshabilita la consola yo tengo que usar "#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")"

    ResponderEliminar
  8. ¿Por cierto, que tipo de fichero wav se pueden usar con OpenAL?
    Yo estaba usando unos que me bajé de interrnet y no sonaban.

    ResponderEliminar
  9. Lo de los wav es un infierno( de hecho si alguien encuentra una solución que me lo diga y lo arreglo ) y la única solución que he encontrado es ir probando wav hasta que encuentro el que funciona. Mirate de los sonidos que hay en el proyecto sus frecunencias de muestreo,bitrate i similares... a ver si tu eres capaz de encontrarle un sentido :D

    ResponderEliminar
  10. Marcos:
    ¿Qué tal “Pakus”? Antes que nada quería felicitarte por tus aportes y disculparme por la fecha en que estamos en la cual comento ya que tus publicaciones del motor son del 2011. He quedado algo atrasado pero es que recién comiendo con C++. Yo soy diseñado grafico 3D y comencé a aprender a programar gracias a que los diseños, precisamente al incorporarles un esqueleto de animación, estos necesitan de pequeño códigos interpretados para poder moverlos, de ahí es que comencé a entrar mas en el asunto, el tema es que son interpretados y bajo Python. Luego comencé a entrarle mas al lenguaje y ya fuera de lo que es el diseño 3D (sin dejar este de lado ya que lo hago por que siempre he querido hacer videojuegos! jajaja) comencé a meterme con OpenGL, ventanas, eventos de teclado y Mouse y algo similar a como tu empiezas con el “engine” logre hacer (modestamente hablando), también un poco con java y OpenGL ES bajo androide. Encare primero estos lenguajes por que me eran mas amenos su forma de sintaxis y estructura respecto a c/c++ pero es algo que siempre lo he querido aprender.
    Precisamente mi consulta y discúlpame lo extenso que se me esta haciendo esto también, es que no logro compilar el este capitulo. Yo voy compilando capitulo por capitulo y no quiero saltearme ninguno, ya se que has hecho modificaciones y mejoras a este pero prefiero ir de apoco. Yo uso Visual C++ Express en XP, el primer problema que me surgió fue tratar de encontrar las librerías y cabecera de OpenAL y ALUT, tanto en el enlace que tu dejas como en otros lados están caídos (de hecho, desde la pagina oficial no entra), por lo tanto no me compilaba el error que me daba era este:

    error LNK2019: símbolo externo __imp__alutCreateBufferFromFile
    error LNK2019: símbolo externo __imp__alutInit
    error LNK2019: símbolo externo __imp__alutExit

    luego se me ocurrió descargar el paquete de Dev-C++, instalarlo y extraer las librerías y cabeceras de su carpeta de instalación y pasarlo al de vc++, vuelvo a compilar, siempre usando vc++ y el error que me da es este:

    error C3861: 'alutCreateBufferFromFile': no se encontró el identificador
    error C3861: 'alutCreateBufferFromFile': no se encontró el identificador
    error C3861: 'alutCreateBufferFromFile': no se encontró el identificador
    error C3861: 'alutCreateBufferFromFile': no se encontró el identificador
    error C3861: 'alutCreateBufferFromFile': no se encontró el identificador

    y hasta aquí llegan todas mis ideas, he buscado el error en varios sitio pero me sorprende la poca información que encuentro, incluso en sitios en ingles. Si tu puedes orientarme un poco estaré infinitamente agradecido y permítame una sugerencia, obviamente yo se que el lenguaje c++ como todos los otros lenguajes de programación, están en ingles, yo tengo manejo del ingles, pero he adoptado una forma de escribir código, no por llevarle la contra al idioma, sino por que me resulta mas facil analizarlo y ver cuales son las palabras reservadas y las funciones propias del lenguajes y que palabras son las del propio programados, entonces lo que yo hago es poner todas las variables, nombres de los archivos, carpeta, clases y métodos en castellanos, de esta manera me resulta mucho mas claro entender el código y lo que hace; es una sugerencia, no pretendo cambiar tu manera de programar, igualmente yo adapto siempre todo el código de esta forma y estoy seguro que mas de uno le será de ayuda de esta forma también o no, quizás estoy equivocado.
    Nuevamente, felicitaciones por tu espacio y por todos los aportes que haces.

    ResponderEliminar
    Respuestas
    1. Buenas,

      El tema de OpenAL es un tema recurrente. Supongo que el tema de los links es algo que volverá a la normalidad en algún momento (espero).

      OpenAL, pese a que intenta ser el openGL del audio se queda en un mero intento debido a que todo el mundo que lo usa se queja de la mismo, es imposible hacerlo funcionar como dios manda. Te recomiendo que este capitulo lo ignores e implementes el audio de otras maneras:

      Opciones?
      - FMOD: está bastante bien, aunque creo que es de pago.
      - Audiere: lo descubrí mientras hacía el PakEngine y realmente me quedé bastante contento. Te adjunto link para que te lo mires.
      - Explicación de como substituir el openAL de este capitulo por audire (muy muy sencillo): http://lordpakus.blogspot.com.es/2013/03/pakengine-capitulo-12-incluyendo-sonido.html
      - Repositorio de código donde lo tendrás ya funcionando: https://code.google.com/p/pakengine-svn/source/browse/#svn%2Ftrunk

      Espero que te sirva

      Nos vemos

      Eliminar

Entradas populares