HGamer3D

De Wikihaskell
Saltar a: navegación, buscar
HGamer3D
HGamer3D
Lenguaje Haskell
Biblioteca HGamer3D
Autores Alberto Cejas Sánchez
Pablo García Ojeda
Manuel Rafael Campos Roldán

Contenido

¿Qué es HGamer3D?

HGamer3D es un motor de juegos para desarrollar principalmente juegos 3D en el lenguaje de programación Haskell. Se sirve de librerías libres para ofrecer funcionalidad de gráficos 3D, sonido, manejo de entrada de usuario e interfaz.

No es un motor gráfico comercial, y por tanto, no todo está implementado de serie. Esto significa que es necesario guiar el desarrollo y la codificación de tal forma que hay que manejar los recursos, entender la complejidad, conocer el API que se ofrece, etc.

En esta wiki encontrarás un enfoque práctico aplicado a las explicaciones de los distintos subsistemas que compone esta librería.

Instalación

Instalación en Windows

La instalación está solo disponible para Windows y se debe seguir el siguiente proceso:

1. Si no tienes haskell instalado, instalar Haskell Platform.

Haskell Platform

2. Instalar el Runtime Installer.

Runtime installer for version 0.2.1

3. Ejecutar sobre la terminal (Símbolo de sistema):

  1.       cabal update
  2.       cabal install HGamer3D

Probar instalación

Para comprobar si nuestra instalación es exitosa podemos descargar los ejemplos que el autor de la librería adjunta aquí.

Una vez descargados, descomprimimos el archivo y nos adentramos en cualquiera de las carpetas ya que cada una contiene un ejemplo para una funcionalidad. Dentro de la carpeta ejecutamos el archivo startme.bat que se encarga de compilar el ejemplo actual. Una vez finalizada, hacemos doble click sobre el ejecutable hg3dexample.exe que se ha generado.

Contenido

Todos los módulos que comprenden HGamer3D recoge lo necesario para poder realizar juegos en 3D usando Haskell. El motor gráfico del juego usa las librerías de las que dispone para manejar el sistema de sonido, vídeo, gráficos 3D, I/O, GUI, físicas, etc, para darle al programador Haskell una buena API de lo mejor que tiene ahora mismo para tener libertad a la hora de crear y programar un juego. En esta wiki dividiremos el contenido por cada uno de los subsistemas que componen esta librería:

Motor

El flujo de ejecución propuesto es el siguiente:

Flujo.png

A partir de la aplicación más básica que se puede crear, se explicará el uso de este motor siguiendo el flujo de ejecución propuesto.

Lo primero que tenemos que hacer es definir el tipo que usaremos para definir el mundo de nuestro juego.

  1.       type Gameworld = ()

En este caso el mundo será de tipo nulo para simplificar la escena al máximo. A continuación, tal y como se muestra en el diagrama definimos el Main:

  1.       main = do
  2.             -- Inicializamos HGamer3D
  3.             hg <- initHGamer3D "HGamer3D - Aplicación Mínima" 
  4.             -- ejecuta las rutinas HGamer3D y comienza a ejectuar el juego
  5.             (l, hg) <- runMHGamer3D hg juego 
  6.             return ()

El juego realiza una serie de tareas antes de llamar a renderLoop, la función encargada del gameloop.

1. Inicializar subsistemas. Audio, I/O, GUI...

2. Carga de recursos. Identificar las carpetas que contienen los archivos media(imágenes, audio, etc).

3. Crear escena. Crear cámaras, luces, escenario, actores...

  1. 	juego = do
  2. 		-- comienza el bucle de renderizado
  3. 		renderLoop 60 () renderStep
  4. 		return ()

Como podemos observar llamamos a renderLoop con tres argumentos:

  • FPS. El número de veces por segundo que se ejecuta el game loop.
  • Mundo. El mundo que compone la escena, más adelante explicaremos esto en mayor profundidad.
  • RenderStep. Función que será ejecutada en cada iteración del gameloop. Se encargará de actualizar la escena e interactuar con el usuario.
  1.       renderStep :: TimeMS -> Gameworld -> MHGamer3D (Bool, Gameworld)
  2.       renderStep (TimeMS time) mundo = do
  3.             -- Código aquí
  4.             return (True, mundo)

Este ejemplo básico se puede descargar desde aquí:

Ejemplo.png Ejemplo de escena básica
Pequeña aplicación que inicia HGamer3D

Video

Gestión de Recursos

En un videojuego se necesita cargar imágenes externas para darle riqueza visual, para ello basta con indicar el directorio donde se encuentran:

  1. 	-- Añadimos el directorio donde están los materiales
  2. 	addResourceLocationMedia "media\\materials"
  3. 	addResourceLocationMedia "media\\meshes"
  4. 	finalizeResourceLocations

Cámara

El punto de vista desde el cual se ve la escena viene determinado por la posición y la dirección de la cámara. El proceso para configurarla es el siguiente:

En primer lugar obtenemos la cámara:

  1. 	cam <- getCamera

Posicionamos la cámara en una posición elevada y separada del punto origen (0,0,0)

  1. 	let pos = Vec3 0.0 (-80.0) 30.0
  2. 	positionTo3D cam pos

Ponemos a la cámara mirando a la posición origen(0,0,0)

  1. 	let at = Vec3 0.0 0.0 0.0
  2. 	cameraLookAt cam at

Ambiente

La escena gana visualmente si se le aplican factores que intervienen en la vida real como las luces o el color del cielo. Para establecer un color de cielo celeste utilizamos el siguiente trozo de código con el sistema RGB(0-1 Red, 0-1 Green, 0-1 Blue):

  1. 	let azul = Colour 0.72 0.82 0.93 1.0
  2. 	setBackgroundColour azul

Existen diferentes tipos de luces:

  • Luz de ambiente.
  • Punto de Luz.
  • Punto de Luz Dirigido.
  • Luz Direccional.

Un ejemplo de su utilización con luz de ambiente blanca y punto de luz blanco, es el siguiente:

  1. 	let white = Colour 1.0 1.0 1.0 1.0
  2. 	setAmbientLight white
  3. 	light <- createPointlight white (Vec3 10.0 10.0 20.0)

Creación de modelos

HGamer3D permite crear modelos directamente en código. Para ello hace uso de las siguientes funciones:

  • createSphere. Crea una esfera
  • createCube. Crea un cubo
  • createPlane. Crea un plano
  • createLine. Crea una línea con un punto de comienzo y fin
  • createColouredLine. Crea una línea de las anteriores pero con color
  • createColouredCube. Crea un cubo con color
  • createRainbowCube. Crea un cubo multicolor

Podemos crear por ejemplo un plano que funcione como suelo de nuestra escena:

  1. 	plano <- createPlane


Carga de modelos

La manera más habitual de insertar elementos en un juego con HGamer3D es realizar modelos con un software de modelado como Blender o Maya y exportarlos para ser cargados en la escena. Para ello únicamente hacemos lo siguiente:

  1. 	personaje <- loadMesh "Personaje.mesh"

Materiales

Sin embargo, como podemos apreciar los modelos aparecen sin color, o mejor dicho de color blanco, ya que no tienen ningún material aplicado, es por eso que debemos crear un material que haga uso de las texturas o mapas de normales que posean los modelos. Un ejemplo muy básico de material que hace uso de la textura de césped y de un mapa de normales, puede ser:

  1. material Plane/Grass
  2. {
  3. 	technique
  4. 	{
  5. 		pass
  6. 		{
  7. 			texture_unit
  8. 			{
  9. 				texture normal_map.png
  10. 				colour_op replace
  11. 			}
  12. 		}
  13. 		pass
  14. 		{
  15. 			texture_unit
  16. 			{
  17. 				texture grass.png
  18. 			}
  19. 		}
  20. 	}
  21. }

Una vez tenemos el material lo aplicamos al plano:

  1. 	setObjectMaterial plano (NamedMaterial "Plane/Grass")

Lo mismo sería aplicable a un modelo cargado en lugar del plano creado directamente en código.

Manipulación de modelos

Los modelos pueden ser trasladados, rotados o escalados.

La traslación se realiza de la siguiente manera:

  1. 	positionTo3D plano (Vec3 0.0 0.0 0.0)

Posicionando así el plano en la posición (0,0,0)

El escalado es análogo:

  1. 	scaleTo3D plano (Vec3 1.5 1.5 1.5)

Cambiando el tamaño en cada uno de sus ejes a un 150% del tamaño actual.

Por último para la rotación, existe una función concreta para rotar en cada uno de los ejes:

  1. 	yaw3D   jugador (Deg 90.0)
  2. 	roll3D  jugador (Deg 90.0)
  3. 	pitch3D jugador (Deg 90.0)

La rotación se puede expresar en grados 'Deg' o en radianes 'Rad'

Demo

Siguiendo con la simplicidad de los anteriores ejemplos aquí se adjunta una escena que aúna todos los conceptos hasta ahora tocados en la sección de video:

Ejemplo.png Ejemplo de escena vídeo
Creación, carga y manipulación de modelos y gestión de escena

Audio


Introducción

Esta sección estará dedicada al manejo e incorporación de audio en un videojuego usando HGamer3D. HGamer3D hace uso de varias librerias escritas en C++ y proporciona los enlaces necesarios para poder usarlas desde Haskell. La libreria en cuestion que se encarga del audio es SFML [1], aunque también controla el manejo del ratón, teclado y joystick.

Como se ha comentado con anterioridad, HGamer3D provee de enlaces para usar dichas librerias atraves de Haskell, en el siguiente enlace podemos encontrarnos el API de HGamer3D para el manejo de audio [2].

HGamer3D permite reproducir archivos de sonido con extensión .wav o .ogg, pero no mp3, aunque la solución es facil, basta con convertir los archivos mp3 a ogg o wav. Los archivos de sonido se extraen de una ruta por defecto que es "media/sounds", así que en esa ubicación debemos de guardar nuestros archivos de sonido.

Añadiendo música de fondo

Para incluir musica de fondo al juego usaremos la función createMusic cuya especificación es:

  1.       createMusic::String->AudioSource

La función recibe una cadena de texto indicando el fichero a cargar y devuelve un dato del tipo AudioSource que permitira ser reproducido posteriormente.

Con esta función obtenemos el archivo de audio listo para ser reproducido. Para ello contamos con la función playAudioSource cuya especificación es la siguiente:

  1.       playAudioSource:: AudioSource-> MHGamer3D ()

Esta función recibe un objeto de audio y lo reproduce. Pero como siempre, hay que llevar algunas comprobaciones acabo. Si no se encontro el fichero de audio o tiene un formato que no es reconocido, se devolvera un objeto nulo, que no contiene nada. Estas comprobaciones de tipo se hacen con la funcion Just dentro de un case.

Hay varias funciones tambien encargadas de pausar y parar la reproducción. La funcion encargada de pausar la reproduccion es pauseAudioSource y la encargada de parar la reproducción es stopAudioSource. Sus especificaciones son las siguientes:

  1.       pauseAudioSource::AudioSource->MHGamer3D ()
  2.       stopAudioSource::AudioSource->MHGamer3D ()

El fragmento de código para reproducir el audio debe ir dentro de la función que se encarga del juego. El código sería el siguiente:

  1.        mu <- createMusic "New Friendly.wav"
  2. 	case mu of
  3. 		Just audio -> do
  4. 			playAudioSource audio
  5. 			return (Just audio)
  6. 		Nothing -> do
  7. 			return Nothing

Demo

En el siguiente enlace se puede encontrar el código de demostración ampliado para esta sección.

Ejemplo.png Ejemplo de escena básica con audio
Ampliación del ejemplo anterior con audio

GUI

HGamer3D incluye entre otras funcionalidades un pequeño framework para la GUI. No es tan completa como GTK+ o wxHaskell pero es lo suficientemente potente como para crear menus de juegos, dialogos o texto de salida. La instalación del framework para la GUI se hace junto a HGamer3D por lo que no lleva ningún esfuerzo adicional.

La API de la interfaz gráfica de usuario viene en dos versiones distintas: hay una version en la Base API y otra en la FRP API.

FRP

Functional Reactive Programming es un paradigma que existe desde que se aplica la programación funcional a uso de multimedia. Muchas veces es una alternativa a la orientación a objetos. Una introduccion a FRP podemos encontrarla en wiki de Haskell para FRP [3].

LA API de FRP de HGamer3D esta basada en la libreria netwire [4] realizada por Ertugrul Söylemez.

Para mas información y material sobre netwire se pueden consultar los siguientes enlaces:

  • La wiki de Haskell sobre netwire [5].
  • La documentación de hackage sobre netwire [6].
  • Información sobre los Arrows en Haskell [7].

Layout

Al contrario que en otras GUIs, en HGamer3D la disposicion de los componentes no esta determinada por lineas de código en el propio programa, esta se hace en un fichero aparte. Este fichero (layout file) es generado por un editor especial,CEGUI Layout Editor [8] y se carga en el programa con las siguientes lineas de código:

  1.     -- load and display gui graphics elements
  2.   	guiwidgets <- loadGuiLayoutFromFile "wiregui.layout" ""
  3. 	addGuiElToDisplay guiwidgets

Este código incluye todos los elementos del GUI en un arbol comenzando con la raiz que se llama "guiwidgets". Todos los elementos descendientes de la raiz se usan simplemente por su nombre y son buscados desde la raiz.

Demo

Con todo esto hemos creado un pequeño ejemplo para preparar un partido usando diversos tipos de objetos, como Button, Listbox, Combobox o Slider.

Ejemplo.png Ejemplo del menú
Menú para la preparación de un partido

Entrada de usuario


Introducción

Con esta librería podremos jugar realmente en el juego, ya teniendo el sonido y escenario construidos previamente. Así pues, podremos tomar el control del juego mediante el teclado, ratón y, si fuera necesario o diera más sensación de control, un joystick. Parte de esta librería ha sido usada en el apartado de Audio, donde se manejaba una parte para incluir sonido en el juego.

Todas las funciones que se incluyen en esta librería que maneja toda la entrada para el manejo del juego mediante 3 dispositivos (teclado, ratón y joystick) de las que se basa HGamer3D escritas en Haskell se encuentran aquí [9]

Manejo de dispositivos

Para la comprobación de teclas pulsadas en el teclado hacemos uso de la función isKeyPressed que tiene esta definición:

  1.   isKeyPressed :: EnumKey -> MHGamer3D Bool

Esta función recibe una tecla y te devuelve un booleano indicando si está siendo pulsada o no en este preciso instante. Todas las teclas del teclado se representan como KeyA, KeyB... Incluyendo también teclas para Ubuntu y Mac.

Por otro lado, contamos con las funciones homólogas para saber si algún botón del ratón está presionado isMouseButtonPressed, siendo ésta:

  1.   isMouseButtonPressed :: EnumMouseButton -> MHGamer3D Bool

Hace exactamente lo mismo que la función anterior, incorporando hasta 4 botones del ratón que están definidos para Haskell.

Ahora nos queda la función para saber dónde está el ratón en toda la pantalla con getMousePosition que es tal que así:

  1. getMousePosition :: MHGamer3D (Int, Int)

Nos devolverá la posición del píxel en el que se encuentra el ratón en cada instante.

Así, para un ejemplo de lo que mostraría el programa que muestra el teclado sería:

  1. loop = do
  2.  
  3. 	a <- isKeyPressed KeyA
  4. 	s <- isKeyPressed KeyS
  5. 	d <- isKeyPressed KeyD
  6. 	f <- isKeyPressed KeyF
  7.  
  8. 	liftIO $ putStrLn ( "Teclas pulsadas entre ASDF:" ++ (show [a,s,d,f]))
  9.  
  10. 	loop

Que hace un bucle infinito donde va mostrando las teclas pulsadas. Sólo con poner 4 es suficiente para ver el ejemplo, pero todas las teclas, como está descrito arriba, están descritas en las librerías de Haskell.

El mismo ejemplo para el ratón también lo mostramos aquí

  1. loop = do
  2.  
  3. 	l <- isMouseButtonPressed MouseButtonLeft
  4. 	r <- isMouseButtonPressed MouseButtonRight
  5. 	(x, y) <- getMousePosition 
  6.  
  7. 	liftIO $ putStrLn ( "Raton (izquierdo, derecho, posicion:" ++ (show (l,r,x,y)))
  8.  
  9. 	loop

Demo

Y todos estos ejemplos se pueden descargar desde aquí:

Ejemplo.png Ejemplo de interacción con teclado
También incluye ratón

Red

Introducción

En este apartado se tratará el subsistema de red de la librería HGamer3D. Con este subsistemas podremos enviar información entre distintos ordenadores, lo que permite crear juegos con conexiones de red. La librería usada por HGamer3D para implementar esta funcionalidad es la librería enet [10]. La principal característica de enet es que usa el protocolo UDP para la comunicación de paquetes, omitiendo por lo tanto aspectos como la autentificación o encriptación, lo que permite un uso flexible y sencillo de la comunicación de red.

Con respecto al API de HGamer3D para la conexión de red la estructura es bastante sencilla. Existen clientes y servidores. Los clientes pueden conectarse a los servidores pero no a la inversa, por lo tanto un servidor puede tener varios clientes. Ambos, clientes y servidor, son nodos y por lo tanto pueden enviar y recibir mensajes.

Para establecer la comunicación en primer lugar hay que establecer el puerto por el que se realizara la comunicación entre clientes y servidor. Una vez establecido el puerto, de forma adicional puede establecerse un canal para habilitar el flujo paralelo de mensajes en diferentes canales.

Cada cliente tiene un nombre único que lo identifica. Este nombre es comunicado al servidor en el primer paquete que se envía y es necesario que el servidor conozca este nombre para realizar la comunicación. Por el contrario, el servidor no necesita tener asignado un nombre ya un cliente se comunicará exclusivamente con un servidor.

Tipo de datos

HGamer3D provee de los siguientes tipos de datos para tratar la conexiones de red:

  • NetworkClient: Representa a un cliente.
  Constructor: NetworkClient HG3DClass.
  • NetworkServer: Representa a un servidor.
  Constructor: NetworkServer HG3DClass.	 
  • NetworkPacket: Representa a un paquete de red (es una clase).
  Constructor : NetworkPacket	 
   clientname :: String
     Nombre del cliente necesario para ser respondido.
   channel :: Int
     Canal por el que es enviado el mensaje.
   message :: String
     El mensaje en sí.
  • NetworkNode: Representa a un nodo de res. Implementa las funciones para enviar y recibir datos.
   Métodos:
   sendNetworkMessage: Método para el envío de mensajes. Uso:
        :: a	
          El nodo.
        -> String	
          Nombre del cliente.
        -> Int	
          Canal
        -> String	
          Mensaje
        -> MHGamer3D ()
   receiveNetworkMessages: Método para recibir los mensajes pendientes.
        :: a	
          El nodo.
        -> TimeMS	
          Antigüedad de los mensajes que se desea recoger de la cola. Si es 0, recoge solo los mensajes pendientes.
        -> MHGamer3D [NetworkPacket]	
          Array que contiene os mensajes a recibir.

Manejo de conexiones

HGamer3D provee de las siguientes funciones para el manejo de conexiones:

createNetworkClient

Crea un nuevo cliente de red. Su prototipo es el siguiente:

createNetworkClient::MHGamer3D NetworkClient
createNetworkServer

Crea un nuevo servidor de red. Su prototipo es el siguiente:

createNetworkServer::Int-> MHGamer3D NetworkServer	 

El único parámetro que recibe es el puerto por el que escucha.

connectClientToServer

Realiza la conexion del cliente con el servidor. Su prototipo es el siguiente:

connectClientToServer::NetworkClient-> String-> Int-> MHGamer3D Bool

El primer primer parametro NetworkClient es el cliente creado con createNetworkClient. El segundo es la dirección del servidor, bien su direccion IP o nombre NS. Y por último un entero que identifica al canal por el que se realizará la transmisión.

disconnectClient
Desconecta a un cliente de su servidor. Su prototipo es el siguiente:
disconnectClient:: NetworkClient-> MHGamer3D Bool

El único parametro que recibe es el cliente de red creado con createNetworkClient.

Físicas

La capa del subsistema de físicas no está correctamente implementado en HGamer3D y no funciona con lo que resulta imposible explicarlo. El ejemplo adjunto al motor realiza la faceta de físicas directamente a mano sin usar ninguna librería externa que le facilite las cosas. En la documentación oficial se dice que esta sección está sujeta a cambios y que no es definitiva.

La limitación es debida a la falta de implementación del constructor de "Quaternion", encargado de la orientación de los objetos. Para construir un objeto físico o body es necesario pasarle un objeto de este tipo para determinar la orientación inicial. Aquí un ejemplo:

  1.        createStaticPlaneBody :: Float -> Vec3 -> Quaternion -> Vec3 -> MHGamer3D Body

que recibe los siguientes parámetros:

  • masa (Float)
  • posición (Vec3)
  • orientación (Quaternion)
  • origen (Vec3)

Lo más lógico es pasarle la orientación del objeto físico pasándole la orientación del objeto adjunto al nodo de la escena (el que es visible) a través de la función "orientation3D". Sin embargo, esta función devuelve la orientación en "Unit Quaternion" el cual si es instanciable pero no convertible a "Quaternion"

Esta información ha sido extraída del código fuente de HGamer3D ya que en su documentación oficial no aparece.

Bibliografía

Herramientas personales