desarrollo de vi con softwa desarrollo de

Anuncio
http://cristiandlr.blogspot.com
Cristian de Léon Ronquillo
DESARROLLO DE VIDEOJUEGOS 3D
CON SOFTWARE LIBRE
FACULTAD DE INGENIERÍA DE SISTEMAS,
INFORMÁTICA Y CIENCIAS DE LA
COMPUTACIÓN
QUETZALTENANGO, NOVIEMBRE 2008
i
Este proyecto de carrera fue elaborado por el estudiante Cristian Rafael de León
Ronquillo como requisito establecido por la Facultad de Ingeniería de la Universidad
Mesoamericana de Quetzaltenango, previo a obtener el título de Ingeniero de Sistemas,
Informática y Ciencias de la Computación.
Quetzaltenango, noviembre de 2008.
ii
iii
ÍNDICE
Introducción
Propuesta de proyecto
Objetivo general
Objetivos específicos
Capítulo I – Álgebra lineal
1.1
Antecedentes
1.2
Introducción
1.3
Vectores
1.4
Transformaciones lineales
Capítulo II – Irrlicht 3D Engine
2.1
Introducción
2.2
Características
2.3
Configurando el IDE (vs2005) y un ejemplo básico
2.4
Cargar mapas de Quake 3 y agregar una cámara FPS
2.5
Manejo de eventos y movimiento
2.6
Graphical User Inrface (GUI)
2.7
Detección de colisiones
2.8
Audio 2D y 3D con irrKlang
2.9
irrXML
Capítulo III – RakNet
3.1
Introducción
3.2
Características
3.3
Incluir RakNet en un proyecto
3.4
Comunicación básica: envío de cadenas
3.5
Irrlicht y RakNet, envío de estructuras
Capítulo IV – Aplicación: irrArena
4.1
Introducción
4.2
Características del juego
4.3
Instalación del juego
4.4
Diseño del juego
Conclusiones
Glosario
Bibliografía
Apéndice A – Licencia de Irrlicht Engine
Apéndice B – Contribuciones y recursos
Apéndice C – Contenido del CD
iv
1
3
5
5
7
9
10
10
17
25
26
26
27
33
37
42
47
53
59
61
63
63
64
65
71
81
83
83
84
91
95
97
101
103
105
107
v
INTRODUCCIÓN
Los videojuegos representan en la actualidad una de las industrias más
grandes del mundo y su popularidad ha venido creciendo de forma paralela a los
avances informáticos y tecnológicos con los que se cuentan hoy en día.
El presente proyecto de investigación consta de dos partes: Una
introducción teórica a los videojuegos 3D y el desarrollo de un videojuego de
primera persona. Se inicia desde las bases matemáticas necesarias para trabajar
con espacios tridimensionales hasta el uso del Engine de juegos Irrlicht 3D, la API
de sonido 3D IrrKlang y la librería de funciones de red RakNet.
Con este proyecto de investigación se pretende aportar conocimiento a los
estudiantes de ingeniería de sistemas o desarrolladores de software interesados
en introducirse al mundo de la programación de videojuegos 3D con software libre.
La metodología utilizada para llevar a cabo el presente proyecto es la
investigación. Antes de elaborarlo se consultaron diversas fuentes de información
relacionadas al tema; se evaluaron características de diversos Engines de
videojuegos, tales como Ogre 3D, Blitz3D e Irrlicht 3D; finalmente se tomó la
decisión de utilizar Irrlicht 3D por las características que incorpora, la buena
estructuración de la API y la amplia documentación con la que cuenta.
El presente documento consta de cuatro capítulos, los primeros tres
corresponden a la parte teórica del proyecto y cubren los temas de espacios
tridimensionales y àlgebra lineal, introducción al Engine Irrlicht 3D, utilización de la
API de audio 2D y 3D Irrklang, acceso a archivos de configuración XML por medio
de la librería irrXML y utilización de elementos de red por medio de RakNet. La
parte demostrativa se detalla en el capítulo cuatro, corresponde al desarrollo de un
videojuego de primera persona en red: “irrArena” se abarca no sólo el diseño del
mismo, sino también su implementación, utilizando toda la teoría de la primera
parte de la investigación.
Se espera que este proyecto sea de utilidad a los lectores y que sirva como
material de apoyo para futuras investigaciones o proyectos de videojuegos.
1
2
PROPUESTA DE PROYECTO
El presente proyecto presenta una investigación sobre algunas tecnologías
libres existentes que permiten desarrollar videojuegos 3D, además incluye temas
generales acerca de los fundamentos matemáticos de los espacios
tridimensionales (Álgebra linal), Irrlicht3D Engine, Manejo de Red con Raknet,
acceso a archivos XML por medio de IrrXML, Audio 2D y 3D con IrrKlang y del
desarrollo del videojuego de primera persona “irrArena”, aplicando el software
antes mencionado.
Se compone de cuatro capítulos que explican de forma general y secuencial
los fundamentos utilizados para crear un videojuego 3D. Se asume que el lector
tiene conocimientos de álgebra y programación, específicamente del lenguaje
C++. Los primeros tres capítulos aportan la base teórica y el cuarto capítulo
describe el juego irrArena que se incluye en el CD que acompaña a este
documento.
El capítulo I presenta una introducción a las Matemáticas de los
videojuegos. Se tratan temas como sistemas lineales, vectores, interpretación
geométrica de las operaciones vectoriales, proyecciones y transformaciones
lineales.
El capítulo II incluye documentación y ejemplos del Engine Irrlicht 3D,
cubriendo temas que van desde el manejador de video, el entorno GUI, manejo de
escena, carga de mapas de Quake 3, intercepción de eventos del teclado y del
ratón (movimiento), colisiones, iluminación de escena, sonido 2D y 3D con irrKlang
y carga de archivos XML con irrXML.
En el capítulo III se presenta una introducción a la librería de funciones de
Red RakNet, la cual ha sido diseñada específicamente para videojuegos. Se
incluyen algunos ejemplos de comunicación de red, tales como el envío de
cadenas y una pequeña aplicación que ilustra cómo envíar estructuras de datos
genéricos y cómo combinar esta librería con Irrlicht.
El capítulo IV es la parte demostrativa del texto. Se desarrolla el videojuego
“irrArena”, de primera persona, multijugador, para ello se utiliza Irrlicht3D y Raknet,
presentando la documentación correspondiente y el diseño del mismo.
3
4
OBJETIVOS
General
Presentar una investigación que sirva como un aporte de conocimiento y
documentación a los estudiantes de ingeniería y desarrolladores de software
interesados en incursionar en mundo de los videojuegos 3D, particularmente con
software libre, planteando el presente proyecto de forma demostrativa y práctica.
Específicos
1. Investigar las características y el funcionamiento básico de un Engine de
gráficos 3D de tiempo real y aplicar este conocimiento para desarrollar un
juego de video.
2. Presentar una introducción a la librería de funciones de red RakNet para
programar juegos y aplicaciones de red en general.
3. Diseñar y desarrollar el juego de acción “irrArena” para aplicar y exponer el
conocimiento adquirido durante la elaboración de este proyecto.
5
6
CAPÍTULO I
ÁLGEBRA LINEAL
El álgebra lineal es la rama de las matemáticas que
estudia conceptos, tales como vectores, matrices, sistemas de
ecuaciones lineales y - en un enfoque más formal - espacios
vectoriales y transformaciones lineales. En ésta figura se
muestra un cubo al que se aplicaron diversas
diversas transformaciones
de rotación.
7
8
1.1 Antecedentes
Un videojuego es un programa informático, creado para el entretenimiento,
basado en la interacción entre una o varias personas y un aparato electrónico (ya
sea un ordenador, un sistema arcade, una videoconsola o un dispositivo
handheld), el cual lo ejecuta. En muchos casos, los videojuegos recrean entornos
y situaciones virtuales en los cuales el jugador puede controlar a uno o varios
personajes (o cualquier otro elemento), para conseguir ciertos objetivos por medio
de reglas determinadas.
La popularidad de los videojuegos ha venido aumentando con gran rapidez
en las últimas décadas, tanto que su industria representó un valor de 41.9 billones
de dólares mundialmente durante el año 2007. Se estima que para 2012 generará
unos 68.3 billones de Dólares1, superando así a la industria cinematográfica.
En el año de 1961 fue desarrollado Spacewar! – considerado el primer
videojuego para computadora – y a partir de esa fecha, la tecnología y la
informática han evolucionado para ofrecer nuevas alternativas que mejoran la
experiencia del jugador, mostrando entornos más reales. La introducción de
Mortal Kombat – un producto que utilizaba Sprites – presentó un avance
considerable en la calidad gráfica de los videojuegos. Al operar en dos
dimensiones, éste permite manejar un mayor detalle en las animaciones, así como
añadir efectos visuales que simulan profundidad, debido a que están basados en
mapas de bits dibujados en la pantalla del ordenador. Más adelante, con el
videojuego Quake, se introdujeron modelos tridimensionales para los personajes
en lugar de utilizar Sprites. En este caso la animación es realizada por medio de
bones (huesos virtuales).
En la década de 1990 se introdujo el término “Game Engine”, el cual hace
referencia a una plataforma que permite manejar los aspectos que son comunes a
todos los videojuegos (código reutilizable). Éstos pueden ser: código reutilizable,
carga de objetos, visualización y animación de modelos, detección de colisiones
entre objetos, física, dispositivos de control como: el teclado, mouse, joystick, etc.,
interfaces gráficas de usuario e incluso parte de la inteligencia artificial del juego.
1
Study: Video Games on Winning Streak, PC Magazine.
http://www.pcmag.com/article2/0,1759,2320607,00.asp
9
1.2 Introducción
La Física es una disciplina muy extensa, abarca desde el movimiento
simple de un cuerpo, el comportamiento de las ondas mecánicas como el sonido,
el comportamiento de la luz y demás ondas electromagnéticas, hasta temas más
avanzados como Relatividad y Física Cuántica.
Algunos temas de Física son útiles en el desarrollo de videojuegos, por
ejemplo, se podría utilizar la óptica para simular la forma en la que la luz viaja e
incide sobre los objetos para generar gráficas de alta calidad (Ray-tracing), pero
cuando se habla de la Física de los videojuegos se refiere a la mecánica clásica,
que consiste en describir el movimiento de sistemas de partículas físicas, de
sistemas macroscópicos y a velocidades pequeñas comparadas con la velocidad
de la luz. En un videojuego, la mecánica clásica se utiliza para dar a los objetos la
sensación de ser sólidos, tener masa, inercia, rebote y cierto grado de capacidad
para flotar.
Toda la parte física es controlada por el Engine o motor de gráficos 3D, por
tanto en este capítulo se hará referencia a conceptos fundamentales del Álgebra
lineal, como una introducción a los sistemas lineales, vectores y tranformaciones
en el espacio.
1.3 Vectores
1.3.1 SISTEMA LINEAL
Muchos problemas pueden modelarse y resolverse por medio de un
sistema lineal. Éste consiste en un conjunto de m ecuaciones con n variables (o
incógnitas), es decir, un conjunto de m ecuaciones lineales.
Las leyes de la mecánica se modelan mediante sistemas lineales.
Particularmente interesan los sistemas de tres variables (véase la figura 1.1),
donde se puede representar, por ejemplo, la posición en el espacio relativa a un
origen de una partícula, mediante las distancias x, y y z trazadas ortogonalmente
(a 90o) entre sí.
10
Figura 1.1. Imagen ilustrativa de la posición de una partícula en el espacio respecto al
origen. Las flechas que están sobre los ejes representan variables de posición de un sistema
lineal de 3 variables (x, y y z) que combinadas denotan la posición de dicha partícula.
1.3.2 VECTORES
Algunas medidas físicas como la temperatura, el área, la masa, el volumen y la
energía pueden ser representadas simplemente como una magnitud, un número real
conocido en álgebra escalar. Sin embargo, cuando se necesita indicar la dirección
(además de la magnitud), se utiliza un vector, el cual se define como una magnitud física
con dirección y sentido.
Por medio de vectores se puede modelar una fuerza, un desplazamiento, una
velocidad, una aceleración, etc. Los vectores son objetos algebraicos y geométricos.
Este tipo de dualidad permite estudiar la geometría con métodos algebraicos.
Un vector es una matriz columna de n x 1 o n-vector, por ejemplo:
1 
u =  ,
 2
 2
v =  4  ,
 6 
 −0.5
 −2 

w=
 3 


 −1 
Los anteriores son vectores 2, 3 y 4 (en R 2 , R 3 y R 4 ) respectivamente. Los
elementos de un vector se llaman componentes.
1.3.3 MÓDULO DE UN VECTOR
El módulo de un vector es su magnitud y se define de la siguiente manera:
 x1 
x 
2
2
2
v = x1 + x2 + ... + xn , donde v =  2  .
 ... 
 
 xn 
11
En la figura 1.2 se aprecia el significado geométrico de la magnitud de un vector.
Figura 1.2. Esta imagen muestra la interpretación geométrica del modulo
de un vector, esto es su magnitud desde el origen, denotada por v .
1.3.4 DIRECCIÓN DE UN VECTOR
En R 2 la dirección de un vector (véase la figura 1.3) está representada por el
ángulo entre el eje x positivo y el vector mismo, mientras que en R 3 la dirección se
representa mediante un vector unitario u llamado dirección de v.
Figura 1.3. El ángulo θ entre el vector v y el eje x
2
representa la dirección del vector v en R .
El vector unitario u se define de la siguiente manera:
uv =
v
v
Éste tiene la propiedad de tener magnitud 1 y la misma dirección de v. Existen tres
3
vectores unitarios que se usan como base para generar todo el espacio R , estos son:
i, j y k
y se definen de la siguiente manera:
12
1 
0
0




i =  0  , j = 1  y k = 0
 0 
 0 
1 
De manera que se puede escribir cualquier otro vector en R 3 como una
combinación lineal de estos vectores, por ejemplo:
3
v =  −2  = 3i − 2 j;
 0 
Otra forma de representar la dirección de un vector en R 3 es mediante sus
ángulos directores, tal como se aprecia en la figura 1.4.
Figura 1.4. Los ángulos directores son los que van
desde un eje hasta el vector v y definen la dirección del
mismo.
1.3.5 OPERACIONES VECTORIALES Y SU INTERPRETACIÓN GEOMÉTRICA
1.3.5.a Suma vectorial
La suma de vectores consiste en sumar las componentes de los mismos, es decir,
la suma de las componentes x será la componente x del vector resultante, y de esta
misma manera las componentes y, z, …, se suman para formar las componentes y, z, …
del vector resultante, por ejemplo:
Sea u = 3i – j, y v = –i +2j – 2k. Hallar u + v
u + v = (3 –1)i + (–1 +2)j + (–2)k = 2i + j – 2k;
Gráficamente esto es equivalente a colocar, sin importar el orden, (propiedad
conmutativa de la suma) el origen de cada vector al final de otro vector, el vector
resultante es aquél que se dirige desde el origen del primer vector hasta el final del último,
como se aprecia en la figura 1.5.
13
Figura 1.5.. (a) Tres vectores en el espacio (u, v y w). (b) El vector R
representa la suma de los vectores u, v y w.
1.3.5.b Resta vectorial
Para restar vectores algebraicamente se cambian los signos del vector a restar
(sustraendo) y luego se suman ambos vectores.
Sea u = 2i – 3j + k, y v = 2i +j – k. Hallar u – v
u – v = 2i – 3j + k – (2i +j – k) = –4j;
Al restar dos vectores se obtiene un tercer vector dirigido desde el extremo del
vector sustraendo hasta el extremo del vector minuendo, como se ve en la figura 1.6.
Figura 1.6
6. Interpretación geométrica de la resta de vectores. El
vector v – u se dirige desde el extremo de u hasta el extremo de v.
1.3.5.c Producto de un escalar por un vector
Para obtener el producto de un vector por un escalar, simplemente se multiplica el
escalar por cada uno de los componentes del vector. Esto provoca un aumento (o
disminución) proporcional al escalar en la magnitud del vector. Véase
V
la figura 1.7.
14
Figura 1.7.
1. (a) Un vector u en el espacio. (b) El vector u
multiplicado por el escalar c.
Cuando ambos factores en el producto son vectores, se pueden realizar dos tipos
de producto: uno que genere un escalar,
escalar llamado producto escalar (también
(
llamado
producto interior o producto punto) o bien un producto que genere un nuevo vector,
llamado producto vectorial (también llamado producto exterior o producto cruz).
1.3.5.d Producto escalar
 a1 
 b1 
a 
b 
2
2

Sean a =
y b =   dos vectores, entonces
ntonces el producto escalar de a y b ,
 ... 
 ... 
 
 
 an 
bn 
denotado por a ⋅ b está dado por:
a ⋅ b = a1b1 + a2b2 + ... + an bn
Ambos vectores deben tener el mismo número de elementos.
Geométricamente se puede interpretar el producto escalar de la siguiente manera:
El producto (escalar) de dos vectores no nulos es igual al módulo de uno de ellos por la
proyección del otro sobre él.
véase la figura
f
1.8.) de un vector u sobre otro vector v (diferentes
La proyección (véase
de cero) se define de la siguiente manera:
manera
proy v u =
15
u ⋅v
v
2
v
Figura 1.8. (a) proy v u cuando el ángulo θ entre ambos vectores (u
y v) es menor que
π
2
. (b) proy v u cuando el ángulo θ entre ambos
vectores (u y v) es mayor que
π
2
.
1.3.5.e Producto vectorial
 a1 
 b1 


 
Sean u y v dos vectores tales que, u = a2 y v = b2 el producto cruz entre
 
 
 a3 
b3 
j
i

ambos está definido por la determinante de la matriz a1 a2

 b1 b2
i
j
u × v = a1
b1
a2
b2
k
a3  , de forma que:
b3 
k
a3 = (a2b3 − a3b2 )i − (a1b3 − a3b1 ) j + (a1b2 − a2b1 )k;
b3
Donde i , j y k son los vectores unitarios (bases de R3 ).
El producto cruz está definido únicamente para R 3 y gráficamente u × v es un
vector ortogonal, tanto a u como a v (vector normal n ). Hay dos vectores que son
ortogonales, tanto a u como a v : u × v y v × u ; la dirección del vector u × v se determina
por medio de la regla de la mano derecha2. Si se coloca la mano derecha de manera
que el índice apunte en la dirección de u y el dedo medio en la dirección de v , entonces
el pulgar apuntará en la dirección de u × v . Véase la figura 1.9.
2
Algebra lineal. Stanley I. Grossman. El producto cruz de dos vectores, pág. 263. Editorial McGraw-Hill, 1996.
16
Figura 1.9. La dirección de
u × v se puede
determinar usando regla de la mano derecha.
Algunas propiedades importantes del producto cruz se enuncian a continuación:
Sean
•
•
•
•
•
•
u , v y w tres vectores en R3 y sea a un escalar cualquiera, entonces:
u × 0 = 0 × u = 0.
u × v = −(v × u ). (Propiedad anticonmutativa)
( au ) × v = a (u × v).
u × (v + w) = (u × v) + (u × w). (Propiedad distributiva)
(u × v) ⋅ w = u ⋅ (v × w). (Triple producto escalar)
u ⋅ (u × v) = v ⋅ (u × v) = 0. ( u × v es ortogonal a u y a v )
1.4 Transformaciones lineales
En los gráficos computacionales, las transformaciones lineales sirven para dar la
sensación de movimiento en tiempo real a un determinado objeto en el espacio, se puede
por ejemplo, cambiar la posición del objeto (traslación), rotar el objeto en torno a un eje,
aumentar o disminuir su tamaño (escalar el objeto), sesgar el objeto o bien realizar
cualquier combinación de lo anterior, simultáneamente.
Figura 1.10. Transformación de una imagen.
Una transformación3 T (también llamada mapeo o función) de un conjunto A a
un conjunto B , representada por T : A → B , es una regla que asocia a cada elemento de
A un elemento b , único de B , llamado imagen de a bajo T . Se escribe T (a) = b y se
dice que a se mapea a T ( a ) . A se llama dominio de T . B es codominio de T . El
3
Definición de transformación: Algebra lineal con aplicaciones. Transformaciones lineales, pág. 307. George
Nakos y David Joyner. Editorial Thomson, 1999.
17
subconjunto de B formado por todas las imágenes de los elementos de A se llama
rango o contradominio de T y se representa por R (T ) .
Una transformación se llama lineal cuando cumple con las siguientes
características:
Sean
•
•
u y v dos vectores y a un escalar, entonces
T (u + v) = T (u ) + T (v).
T (cu ) = cT (u ).
Se puede transformar cualquier punto (un vector en R 3 ) en otro, usando una
matriz de transformación (de 4×4 para R 3 , esto es necesario por la transformación de
traslación que verá más adelante). Se usan matrices porque permiten modificar cada
componente en un vector respecto de los tres ejes del espacio.
En el siguiente ejemplo, una matriz reinterpreta el punto (x, y, z), produciendo el
nuevo punto (x', y', z'):
Simplemente se multiplica el vector (punto a transformar)
v = [x
y
z 1]
por la matriz de transformación
 M 11
M
M =  21
 M 31

 M 41
M 12
M 13
M 22
M 32
M 42
M 23
M 33
M 43
M 14 
M 24 
M 34 

M 44 
así:
[x '
y ' z ' 1] = [ x
y
 M 11
M
z 1]  21
 M 31

 M 41
M 12
M 22
M 32
M 13
M 23
M 33
M 42
M 43
M 14 
M 24 
M 34 

M 44 
De forma que:
x ' = xM 11 + yM 21 + zM 31 + 1M 41 ,
y ' = xM 12 + yM 22 + zM 32 + 1M 42 ,
x ' = xM 13 + yM 23 + zM 33 + 1M 43 .
Se pueden combinar las matrices de transformación en una matriz simple para
calcular varias transformaciones en una sola operación (esta operación es conocida como
18
concatenación). Esto se consigue multiplicando las matrices individuales
transformación (Véase la definición de Skeletal Animation en el glosario).
de
1.3.1 EJEMPLOS DE TRANSFORMACIONES LINEALES COMUNES
A continuación se examinará brevemente algunas matrices de transformación.
1.3.1.a Escalado
Para cambiar el tamaño de un objeto en R 3 se aplica la siguiente transformación
(tal como se indicó anteriormente) a cada uno de los vértices que lo conforman:
 sx
0

0

0
0
sy
0
0
0
0
sz
0
0
0 
0

1
Donde s x , s y y s z son números reales que determinan las escalas de los ejes x, y
y z respectivamente.
Por ejemplo, si se desea duplicar el tamaño de un triángulo cuyos vértices son
[0
0 0] , [1 1 0] y [ 2 0 0] basta con multiplicar cada uno de estos vectores por la
2
0
matriz de transformación 
0

0
0
2
0
0
0
0
2
0
0
0
para obtener los vértices del triángulo
0

1
transformado (véase la figura 1.11).
2
0
[0 0 0 1] 
0

0
2
0
[1 1 0 1] 
0

0
2
0
[ 2 0 0 1] 
0

0
0 0 0
2 0 0
 = [0 ⋅ 2 + 0 ⋅ 0 + 0 ⋅ 0 + 1 ⋅ 0 0 ⋅ 0 + 0 ⋅ 2 + 0 ⋅ 0 + 1 ⋅ 0 0 ⋅ 0 + 0 ⋅ 0 + 0 ⋅ 2 + 1 ⋅ 0 0 ⋅ 0 + 0 ⋅ 0 + 0 ⋅ 0 + 1 ⋅1] = [ 0 0 0 1]
0 2 0

0 0 1
0 0 0
2 0 0
 = [1 ⋅ 2 + 1 ⋅ 0 + 0 ⋅ 0 + 1 ⋅ 0 1 ⋅ 0 + 1 ⋅ 2 + 0 ⋅ 0 + 1 ⋅ 0 1 ⋅ 0 + 1 ⋅ 0 + 0 ⋅ 2 + 1 ⋅ 0 1 ⋅ 0 + 1 ⋅ 0 + 0 ⋅ 0 + 1 ⋅1] = [ 2 2 0 1]
0 2 0

0 0 1
0 0 0
2 0 0
 = [2 ⋅ 2 + 0 ⋅ 0 + 0 ⋅ 0 + 1 ⋅ 0 2 ⋅ 0 + 0 ⋅ 2 + 0 ⋅ 0 + 1 ⋅ 0 2 ⋅ 0 + 0 ⋅ 0 + 0 ⋅ 2 + 1 ⋅ 0 2 ⋅ 0 + 0 ⋅ 0 + 0 ⋅ 0 + 1 ⋅1] = [ 4 0 0 1]
0 2 0

0 0 1
19
Figura 1.11.
1.1 (a) Un triángulo definido por sus vértices. (b) El
resultado de aplicar una transformación de tamaño al
triángulo del inciso (a) por un factor de 2.
1.3.1.b Rotación
Se puede rotar un punto alrededor de un eje,
eje para ello se cuenta con tres matrices
de rotación, una para cada eje, pueden combinarse (como ya se mencionó) multiplicando
las matrices entre sí.
Nota: Las matrices de rotación definidas aquí corresponden a un sistema de mano
izquierda,, como se aprecia en la ffigura 1.12.
Figura 1.12.
1.1 En un sistema de mano izquierda, la dirección
positiva del eje z es la que se aleja del observador. En un
sistema de mano derecha ocurre lo contrario, la dirección
positiva del eje z es la que se acerca al observador.
La siguiente transformación rota el punto (x, y, z)) sobre el eje x, produciendo un
nuevo punto (x', y', z'):
[x'
y ' z ' 1] = [ x
y
0
1
0 cos θ
z 1] 
0 − sin θ

0
0
20
0
sin θ
cos θ
0
0
0

0

1
La siguiente transformación rota el punto sobre el eje y:
[x '
y ' z ' 1] = [ x
cos θ
 0
z 1] 
 sin θ

 0
y
0 − sin θ
1
0
0 cos θ
0
0
0
0 
0

1
La siguiente transformación rota el punto sobre el eje z:
[x '
y ' z ' 1] = [ x
 cos θ
 − sin θ
z 1] 
 0

 0
y
sin θ
cos θ
0
0
0
0
1
0
0
0 
0

1
Donde θ simboliza el ángulo de rotación en radianes. Los ángulos se miden en el
sentido horario cuando se mira sobre el eje de rotación hacia el origen.
1.3.1.c Traslación
La traslación permite –como su nombre lo indica– trasladar todos los vértices que
conforman un objeto en el espacio a una nueva posición. El resultado de esta operación
es equivalente a sumar Tx , Ty y Tz a las coordenadas
obteniendo el vector  x + Tx y + Ty z + Tz
punto (x, y, z) a un nuevo punto (x', y', z').
[x '
y ' z ' 1] = [ x
x , y y z , respectivamente,
1 . La siguiente transformación traslada el
y
1
0
z 1] 
0

Tx
0
0
1
0
Ty
0
1
Tz
0
0 
0

1 
1.3.1.d Sesgado
Esta transformación (también conocida como corte o deslizamiento) permite
deslizar un punto a través de un eje determinado. La figura 1.10 es un ejemplo de
transformación de corte relativo al eje x en 2D. Las figuras 1.13 y 1.14 muestran un cubo
en el espacio que ha sido transformado, primero respecto al eje x (figura 1.13) y luego
respecto al eje y (figura 1.14).
21
La siguiente transformación desliza el punto (x,
( y, z)) por un factor c en el eje x
(plano xy),, produciendo un nuevo punto (x',
( y', z').
[x'
y ' z ' 1] = [ x
y
1
c
z 1] 
0

0
0
1
0
0
0
0
1
0
0
0 
= [ x + cy
0

1
y
z 1]
Figura 1.13.
1.1 La figura muestra el efecto que produce un
deslizamiento en el eje x (utilizando el plano xy).
La siguiente transformación desliza el un punto sobre el eje y (plano xy):
[x'
y ' z ' 1] = [ x
y
1
0
z 1] 
0

0
c
1
0
0
0
0
1
0
0
0 
= [ x cx + y
0

1
z 1]
Figura 1.15. La figura muestra el efecto que produce un
deslizamiento en el eje y (utilizando el plano xy).
En general, se puede realizar un deslizamiento en cualquier eje utilizando un plano
determinado y las transformaciones se pueden expresar como una función de la siguiente
manera:
Deslizamiento en x, plano xy:
xy S x ( x, y , z ) = ( x + cy, y, z ).
Deslizamiento en y, plano xy:
xy S y ( x, y, z ) = ( x, y + cx, z ).
Deslizamiento en x, plano xz:
xz S x ( x, y , z ) = ( x + cz , y, z ).
Deslizamiento en z, plano xz:
xz S z ( x, y , z ) = ( x, y , z + cx ).
22
Deslizamiento en y, plano yz: S y ( x, y, z) = ( x, y + cz, z ).
Deslizamiento en z, plano yz: S z ( x, y , z ) = ( x, y, z + cy ).
El factor c puede ser tanto positivo como negativo, esto se refleja en la dirección
del eje sobre el que se realiza el deslizamiento.
23
24
Capítulo II
Irrlicht3D Engine
Indoor Rendering es una característica integrada en el
Engine que permite utilizar mapas de luz y superficies
curvas; adicionalmente existe un detector de colisiones
y un sistema de respuesta.
25
2.1 Introducción
Irrlicht3D es un motor de gráficos 3D de alto rendimiento y de código abierto escrito
en C++ y utilizable tanto en C++ como en Lenguajes .NET. Es completamente
independiente de la plataforma, utilizando Direct X, Open GL o su propio software de
renderizado y tiene todas las características que pueden ser encontradas en otras
plataformas 3D comerciales.
2.2 Características
Irrlicht es un Engine 3D de tiempo real, multi-plataforma y de alto desempeño
escrito en lenguaje C++. Es una poderosa API4 de alto nivel para la creación de completas
aplicaciones 3D y 2D tal como juegos o aplicaciones para visualización científica. Viene
con una excelente documentación e integra todas las características actualmente
utilizadas para la representación visual como sombras dinámicas, sistemas de partículas,
animación de personajes, tecnología interior y exterior (indoor and outdoor technology) y
detección de colisiones.
Cabe mencionar que este software está actualmente en desarrollo. La información
acerca de los avances en el desarrollo del proyecto está disponible en la siguiente página
web:
http://irrlicht.sourceforge.net/development.html.
Sus principales características5 son:
• Renderizado 3D en tiempo real de alto desempeño utilizando Direct3D y OpenGL.
• Independiente de la plataforma. Compatible con Windows 95, 98, NT, 2000, XP,
Vista, GNU/Linux, y MacOS.
• Incorpora una enorme y extensible librería de materiales con soporte para Pixel
Shaders y Vertex Shaders.
• Manejo de escenas altamente personalizable para interiores y exteriores.
• Sistema de animación de modelos con esqueletos (Skeletal Animation) y animación
de vértices (Morph target animation).
• Efectos de partículas, billboards, mapas de luz, mapeo de entorno, stencil buffer
shadows y muchos otros efectos especiales.
• Bindings para .NET, lo que hace que el Engine esté disponible para cualquier
lenguaje de la plataforma .NET tal como C#, VisualBasic y Delphi.NET.
• Incluye dos rápidos renderizadores por software independientes, tanto de la
plataforma como del driver de video que tienen diferentes propiedades: Corrección de
texturas mapeadas en perspectiva, filtrado bilineal, corrección sub-píxel, z-buffer,
Gouraud shading, alpha-blending y transparencias, dibujo 2D rápido y más.
4
5
API, del inglés: Aplication Programming Interface o Interfaz de programación de aplicaciones.
Fuente: Irrlicht3D Features, http://irrlicht.sourceforge.net/features.html
26
• Sistema de interfaz gráfica de usuario (GUI) personalizable y fácil de usar. Incluye
botones de comando, listas, cajas de texto, etc.
• Funciones de dibujo en 2D: alpha blending, blitting, dibujo de fuentes y mezcla de
gráficas 2D y 3D.
• Una API bien documentada y correctamente estructurada.
• Escrito completamente en lenguaje C++ y totalmente orientado a objetos.
• Funciones para lectura directa de los formatos más comunes de objetos 3D, entre
ellos: 3D Studio meshes (.3ds), B3D files (.b3d), Alias Wavefront Maya (.obj),
Cartography shop 4 (.csm), COLLADA (.xml, .dae), DeleD (.dmf), FSRad oct (.oct),
Irrlicht scenes (.irr), Irrlicht static meshes (.irrmesh), Microsoft DirectX (.x) (binary &
text), Milkshape (.ms3d), My3DTools 3 (.my3D), OGRE meshes (.mesh), Pulsar
LMTools (.lmts), Quake 3 levels (.bsp), Quake 2 models (.md2) y STL 3D files (.stl).
• Funciones de lectura directa de formatos de textura: Adobe Photoshop (.psd), JPEG
File Interchange Format (.jpg), Portable Network Graphics (.png), Truevision Targa
(.tga), Windows Bitmap (.bmp) y Zsoft Paintbrush (.pcx).
• Rápido sistema de detección de colisiones.
• Librerías de contenedores de plantillas de funciones matemáticas 3D rápidas y
optimizadas.
•
•
•
•
Lectura directa de archivos comprimidos (.zip).
Parser (Analizador sintáctico) de archivos XML integrado (irrXML).
Soporte para Unicode.
Compatible con Microsoft VisualStudio6.0™, VisualStudio.NET 7.0-8.0™, Metrowerks
Codewarrior y Bloodshed Dev-C++ con g++3.2-4.0.
• El Engine es de código abierto y completamente libre. Puede ser depurado, corregido
e incluso modificado sin la obligación de hacer públicos dichos cambios: El Engine
está bajo los términos de la licencia zlib6.
2.3 Configurando el IDE (Visual Studio 2005) y un ejemplo básico
Como se ha descrito en la sección 1.2, Irrlicht puede ser incorporado en una amplia
variedad de Entornos de Desarrollo Integrados (IDEs), siendo los pasos para la
integración similares en los múltiples entornos de desarrollo. Aquí se describe cómo
configurarlo con uno de los entornos de desarrollo más utilizados por los desarrolladores
de software7; éste es el caso de Microsoft Visual Studio (actualmente puede obtenerse
gratuitamente la versión Express en http://www.microsoft.com/Express/).
El SDK de Irrlicht puede obtenerse en http://irrlicht.sourceforge.net/downloads.html,
la versión actual es la 1.4.2 e incluye la documentación, tutoriales, exportadores y varios
6
7
Véase el Apéndice A. Licencia del Engine Irrlicht3D.
Texto Original - Tutorial 1: HelloWorld - http://irrlicht.sourceforge.net/tut001.html
27
programas de demostración, incluyendo el código fuente y archivos necesarios para
correr todos los ejemplos que se detallan a continuación.
2.3.1 CONFIGURANDO EL IDE
Para utilizar el Engine se debe incluir el archivo de cabecera irrlicht.h. Éste archivo
se encuentra en el directorio include del SDK. Para permitir al compilador acceder a dicho
archivo, se debe configurar la ruta al mismo. En Microsoft Visual Studio se debe realizar el
siguiente procedimiento:
1. Seleccionar Opciones (Options) del menú de Herramientas (Tools)
2. Seleccionar la sección de Proyectos y Soluciones (Projects And Solutions) y
buscar la opción de Directorios de VC++ (VC++ Directories) como se vé en la
Figura 2.2.
Figura 2.2. Cuadro de opciones del menú Herramientas Visual Studio 2005.
3. Seleccionar Include Files del combo que dice Mostrar directorios para (Show
directories for) como se muestra en la Figura 2.3.
Figura 2.3. Opción “Show directories for” del cuadro de opciones de Visual Studio 2005.
. Dicho direcotrio se encuentra en la
4. Agregar el directorio include con el botón
carpeta de instalación del SDK.
5. También es necesario agregar la librería irrlicht.lib, para ello se debe seleccionar la
opción Archivos de Librería (Library files) del combo Mostrar directorios para
(Show directories for) y agregar el directorio lib/VisualStudio incluído en el SDK.
28
2.3.2 HOLA MUNDO !
El siguiente programa ejemplifica los aspectos básicos del Driver de video, el
entorno GUI, y el manejo de escena.
Lo primero que se debe hacer es incluir el archivo de cabecera irrlicht.h
#include <irrlicht.h>
En el Engine Irrlicht todo se encuentra dentro del nombre de espacio irr. Para
utilizar cualquiera de las clases del Engine se debe escribir irr:: antes del nombre de la
clase. Por ejemplo, para acceder a la clase IrrlichtDevice, se debe escribir irr::IrrlichtDevice.
Para evitar escribir irr:: antes de cada clase, se indica al compilador que se utilizará ese
nombre de espacio de ahora en adelante y no habrá que escribir irr:: nuevamente.
using namespace irr;
En el Engine Irrlicht hay cinco sub-espacios. Se puede encontrar información
detallada de ellos en la documentación:
http://irrlicht.sourceforge.net/docu/namespaces.html
De la misma forma en la que se declaró el nombre de espacio irr, se declara la
utilización de estos cinco nombres de espacio para mantener este ejemplo fácil de
entender.
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;
Para poder utilizar el archivo Irrlicht.DLL, se debe vincular la aplicación con Irrlicht.lib.
Ésta vinculación se puede establecer en las configuraciones del proyecto, pero para
facilitar las cosas, se utilizará la instrucción pragma comment de Visual Studio. En
plataformas Windows, se debe ocultar la ventana de consola, la cual salta cuando se
inicia el programa en main(). Esto se hace con la segunda instrucción pragma. También se
puede utilizar el método WinMain(), pero de esta manera la aplicación ya no es
independiente de la plataforma (sistema operativo).
#ifdef _IRR_WINDOWS_
#pragma comment(lib, "Irrlicht.lib")
#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
#endif
29
Éste es el método main(), el cual es utilizado por cualquier plataforma.
int main()
{
La función más importante del Engine es createDevice(). El Dispositivo Irrlicht es
creado al llamarla. Éste es el objeto raíz para hacer cualquier cosa con el Engine.
createDevice() tiene siete parámetros:
•
deviceType: tipo de dispositivo. Puede ser un dispositivo nulo, D3D8, D3D9,
OpenGL o bien alguno de los dos renderizadores de software incluídos en el
Engine. En este ejemplo se usa el EDT_SOFTWARE, pero esto puede ser
sustituido
por
EDT_BURNINGSVIDEO,
EDT_NULL,
EDT_DIRECT3D8,
EDT_DIRECT3D9, o EDT_OPENGL, según el tipo de dispositivo que se requiera.
•
windowSize: define el tamaño de la ventana, o bien, la resolución de la pantalla
cuando se está ejecutando en modo de pantalla completa. En este ejemplo se usa
640x480.
•
bits: define la cantidad de bits de color por píxel, 16 ó 32. El parámetro es
generalmente ignorado cuando se ejecuta en modo de ventana.
•
fullscreen: especifica si se va a ejecutar en pantalla completa o en modo de
ventana.
•
stencilbuffer: especifica si se va a utilizar stencil buffer (para dibujar sombras).
•
vsync: especifica la activación o desactivación de vsync, esto solo es útil en el
modo de pantalla completa.
•
eventReceiver: es un objeto para interceptar eventos. Por ahora no se utiliza, así
que se establecrá su valor a 0.
Siempre se debe verificar el valor de retorno para determinar controladores no
soportados, dimensiones no válidas, etc.
IrrlichtDevice *device =
#ifdef _IRR_OSX_PLATFORM_
createDevice(video::EDT_OPENGL, dimension2d<s32>(640, 480), 16, false, false, false, 0);
#else
createDevice(video::EDT_SOFTWARE, dimension2d<s32>(640, 480), 16, false, false, false, 0);
#endif
if (!device) return 1;
Ahora se establecerá el título de la ventana. Notese que hay una ‘L’ antes de la
cadena. El Engine Irrlicht utiliza cadenas de caracteres anchos para desplegar texto.
30
device->setWindowCaption(L"Hello World! - Irrlicht Engine Demo");
Ahora se crea un puntero al driver de video, el manejador de escena y el entorno
de interfaz gráfica de usuario, de esta forma no se tendrá que escribir siempre: device>getVideoDriver(), device->getSceneManager(), o device->getGUIEnvironment().
IVideoDriver* driver = device->getVideoDriver();
ISceneManager* smgr = device->getSceneManager();
IGUIEnvironment* guienv = device->getGUIEnvironment();
Se añade una etiqueta a la ventana con el texto Hello World, usando para ello el
entorno GUI. El cuadro de texto estático es posicionado en (10, 10) como la esquina
superior izquierda y (260, 22) como la esquina inferior derecha.
guienv->addStaticText(
L"Hello World! This is the Irrlicht Software renderer!", //cadena a desplegar
rect<s32>(10,10,260,22), //dimensiones de la caja de texto
true //borde de la caja de texto
);
Para mostrar algo más interesante, se cargará y desplegará en la escena un
modelo MD2. Únicamente se debe cargar la malla desde el manejador de escena con
getMesh() y agreagar un nodo de escena para desplegar la malla con
addAnimatedMeshSceneNode(). Es recomendable verificar el valor de retorno de getMesh() para
asegurar que no haya ocurrido ningún problema al abrir el archivo.
En lugar de escribir el nombre de archivo sydney.md2, es posible cargar un archivo
de objeto de Maya (.obj), un mapa completo de Quake 3 (.bsp) o cualquier otro formato de
archivo soportado por el Engine (véase el listado de características para más información
al respecto, Sección 2.2). El modelo sydney.md2 fue diseñado por Brian Collins.
IAnimatedMesh* mesh = smgr->getMesh("../../media/sydney.md2");
if (!mesh) return 1;
IAnimatedMeshSceneNode* node = smgr->addAnimatedMeshSceneNode( mesh );
Se debe cargar un material a la malla para que se vea mucho mejor. También se
desactivará la iluminación porque por ahora no se ha agregado ninguna luz dinámica, de
otra forma el modelo se vería completamente negro. Luego se establece el frame loop o
ciclo de animación al estado predefinido STAND y por último se aplica una textura a la
malla. Sin ésta última la malla sería dibujada, utilizando únicamente un color.
if (node) {
node->setMaterialFlag(EMF_LIGHTING, false); //desactivar iluminación
node->setMD2Animation(scene::EMAT_STAND); //frame loop
node->setMaterialTexture( //Aplicar la textura a la malla node
0, //capa de la textura
driver->getTexture("../../media/sydney.bmp") //archivo de la textura
31
);
}
Para hacer la malla visible en la pantalla se agrega una cámara en el espacio 3D,
en la posición (0, 30, -40). La cámara verá desde allí hasta la posición (0, 5, 0), que es
aproximadamente el lugar donde se cargó el modelo MD2.
smgr->addCameraSceneNode(0, vector3df(0,30,-40), vector3df(0,5,0));
El siguiente paso es configurar la escena y dibujar todo: Se ejecuta el dispositivo
en un ciclo while(), hasta que el dispositivo no desee continuar. Esto podría ocurrir cuando
el usuario cierra la ventana o presiona Alt + F4 (o cualquier otro comando que cierre la
ventana).
while(device->run()) {
Cualquier cosa puede ser dibujada entre una llamada a beginScene() y una a
endScene(). La llamada a beginScene() limpia la pantalla con un color determinado y el buffer
de profundidad (depth buffer) si así se requiere. Luego se deja al manejador de escena y
al entorno GUI dibujar su contenido. Con una llamada a endScene() todo es desplegado en
la pantalla.
driver->beginScene(true, true, SColor(255,100,101,140));
smgr->drawAll();
guienv->drawAll();
driver->endScene();
}
Después que se ha terminado con el ciclo de renderizado, se debe eliminar el
dispositivo Irrlicht creado previamente con createDevice(). En el Engine Irrlicht se deben
eliminar todos los objetos que se hayan creado con algún método o función que inicie con
create. El objeto será eliminado simplemente con una llamada al método ->drop(). Para más
información véase el artículo irr::IReferenceCounted::drop() en la documentación del Enigne.
device->drop();
return 0;
}
Solo hace falta compilar y correr la aplicación. A continuación una muestra de la
pantalla del programa en ejecución en la figura 2.4.
32
Figura 2.4. Una muestra de la aplicación de ejemplo ‘Hola Mundo’ ejecutándose.
2.4 Cargar mapas de Quake 3 y agregar una cámara FPS
En esta sección se explica como cargar desde el Engine un mapa del juego Quake
3, crear un nodo de escena para optimizar la velocidad de renderizado y cómo crear y
utilizar una cámara controlada por el usuario (First Person Shooter Camera o Cámera
FPS)
Se inicia como en la sección anterior, incluyendo el archivo de cabecera del
Engine y un archivo adicional, iostream, para permitir al usuario seleccionar desde la
consola el driver que se va a utilizar.
#include <irrlicht.h>
#include <iostream>
Como ya se mencionó en el ejemplo de la sección anterior, en el Engine Irrlicht
todo se encuentra dentro del nombre de espacio ‘irr’. Para evitar escribir irr:: antes del
nombre de cada clase del Engine se indica al compilador que se utilizará ese nombre de
espacio de aquí en adelante. Hay otros cinco nombres de espacio (subepacios) dentro del
nombre de espacio irr, estos son: core, scene, video, io y gui. A diferencia del ejemplo
anterior, no se declara la utilización de estos nombres de espacio para distinguir las
clases contendidas dentro de cada uno de ellos.
using namespace irr;
De nuevo, para poder utilizar el archivo Irrlicht.DLL, se debe vincular con el archivo
Irrlicht.lib. Se utiliza una instrucción pragma comment lib:
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif
33
De nuevo, se utiliza el método main() para iniciar (en lugar de WinMain()).
int main()
{
Ahora se crea el dispositivo con createDevice(). La diferencia ahora es que se
consultará al usuario qué controlador desea utilizar. El dispositivo de software podría ser
muy lento para dibujar un mapa enorme de Quake 3; para propósitos de prueba se hará
de ésta una opción del menú.
// Elegir manejador de video
video::E_DRIVER_TYPE driverType;
printf("Por favor elija un manejador de video para la aplicación:\n"\
" (a) Direct3D 9.0c\n"\
" (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
" (d) Software Renderer\n"\
" (e) Burning's Software Renderer\n"\
" (f) NullDevice\n (otherKey) exit\n\n"
);
char i;
std::cin >> i;
switch(i) {
case 'a': driverType = video::EDT_DIRECT3D9;break;
case 'b': driverType = video::EDT_DIRECT3D8;break;
case 'c': driverType = video::EDT_OPENGL; break;
case 'd': driverType = video::EDT_SOFTWARE; break;
case 'e': driverType = video::EDT_BURNINGSVIDEO;break;
case 'f': driverType = video::EDT_NULL; break;
default: return 1;
}
// Crear el dispositivo o salir si hay una falla
IrrlichtDevice *device = createDevice(driverType, core::dimension2d<s32>(640, 480));
if (device == 0) return 1; //No se pudo crear el dispositivo.
Se crea un puntero al manejador de video y otro para el manejador de escena, de
esta manera no es necesario tener que llamar siempre a irr::IrrlichtDevice::getVideoDriver() y
irr::IrrlichtDevice::getSceneManager().
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
Para desplegar el mapa de Quake 3 primero se debe cargar. Los mapas de Quake
3 están empaquetados dentro de archivos .pk3, los cuales no tienen ninguna diferencia
con los archivos .zip. Así que se agrega el archivo .pk3 al sistema de archivos
34
irr::io::IFileSystem.
Después de cargado, se puede leer cualquier archivo contenido dentro del
.pk3 como si estuviera directamente almacenado en el disco.
device->getFileSystem()->addZipFileArchive("../../media/map-20kdm2.pk3");
Ahora se puede cargar la malla llamando a irr::scene::ISceneManager::getMesh(). Dicho
método devuelve un puntero irr::scene::IAnimatedMesh. Como Ud. probablemente sabe, los
mapas de Quake 3 no son realmente animados, simplemente contienen una enorme
cantidad de geometría con algunos materiales adjuntos, de ahí que IAnimatedMesh consiste
en un único fotograma, así que se debe obtener el “primer fotograma” de la “animación”,
el cual representa el nivel de Quake y se crea un nodo de escena de tipo OctTree, usando
para ello irr::scene::ISceneManager::addOctTreeSceneNode(). El OctTree optimiza un poco la
escena, tratando de dibujar únicamente la geometría que es visible a cada momento
mientras se ejecuta la aplicación. Una alternativa a OctTree podría ser
irr::scene::IMeshSceneNode, el cual siempre dibuja la geometría completa de la malla, aún
cuando no es visible en la pantalla.
Se puede probar a utilizar irr::scene::ISceneManager::addMeshSceneNode() en lugar de
addOctTreeSceneNode() para comparar las primitivas dibujadas por el manejador de video.
Para contar el número de primitivas dibujadas se puede hacer uso del método llamado
irr::video::IVideoDriver::getPrimitiveCountDrawn(), contenido dentro de la clase irr::video::IVideoDriver.
Note que la optimización que se obtiene con OctTree solo es útil cuando se están
dibujando enormes mallas conformadas por una gran cantidad de geometría.
scene::IAnimatedMesh* mesh = smgr->getMesh("20kdm2.bsp");
scene::ISceneNode* node = 0;
if (mesh)
node = smgr->addOctTreeSceneNode(mesh->getMesh(0), //fotograma 0 de la malla
0, //Nodo padre
-1, //ID del nodo
128 //Cantidad mínima de polígonos por nodo
);
El mapa que se utiliza en este ejemplo no fue modelado alrededor del origen
(0,0,0), se debe transformar por medio de los métodos irr::scene::ISceneNode::setPosition(),
irr::scene::ISceneNode::setRotation() e irr::scene::ISceneNode::setScale().
if (node)
node->setPosition(core::vector3df(-1300,-144,-1249));
Ahora hace falta agregar una cámara que visualice el mapa. Por ahora se necesita
una cámara controlada por el usuario. Hay varias cámaras disponibles en el Engine
Irrlicht. Por ejemplo la cámara de Maya (MayaCamera) puede ser controlada como una
cámara en Maya: rota cuando el botón izquierdo del ratón está presionado, acerca o aleja
(Zoom) cuando ambos botones están presionados y traslada con el botón derecho
35
presionado. Para agregar una cámara de Maya se utiliza irr::scene::ISceneManager::
addCameraSceneNodeMaya().
Para este ejemplo se va a utilizar una cámara que se comporta como los juegos de
primera persona (FPS game o First Person Shooter game) y para ello se debe utilizar el
método irr::scene::ISceneManager::addCameraSceneNodeFPS().
smgr->addCameraSceneNodeFPS();
El puntero del ratón debería ser invisible al usuario, para ello se establece la
visibilidad a false por medio del método irr::IrrlichtDevice::ICursorControl.
device->getCursorControl()->setVisible(false);
Ahora que se tiene la escena casi lista, solo resta dibujarla. Se desplegará la
cantidad de fotogramas por segundo (fps) y la cantidad de primitivas dibujadas en la barra
de título de la ventana. La verificación de irr::IrrlichtDevice::isWindowActive() es opcional,
pero previene al Engine de seguir al puntero del mouse en la escena cuando se cambia a
otra aplicación activa mientras el programa sigue en ejecución. La llamada a
irr::IrrlichtDevice::yield() evitará que el ciclo principal del programa utilice todos los ciclos
de la CPU cuando la ventana no está activa.
int lastFPS = -1;
while(device->run()) {
if (device->isWindowActive()) { //la ventana está activa
driver->beginScene(true, true, video::SColor(255,200,200,200));
smgr->drawAll();
driver->endScene();
int fps = driver->getFPS();
if (lastFPS != fps) {
core::stringw str = L"Irrlicht Engine - Quake 3 Map example [";
str += driver->getName();
str += "] fotogramas/segundo:";
str += fps;
device->setWindowCaption(str.c_str());
lastFPS = fps;
}
} else { //la ventana no está activa
device->yield();
}
}
Por último, se elimina el dispositivo Irrlicht.
36
device->drop();
return 0;
}
Eso es todo. Luego de compilar y ejecutar la aplicación se verá como se muestra en la
figura 2.5.
Figura 2.5. Unaa pantalla del mapa de Quake 3 Return to Castle corriendo desde el Engine
Irrlicht.
2.5 Manejo de eventos
ventos y movimiento
movimient
En esta
sta sección se describe cómo mover y animar nodos
odos de escena, utilizando
para ello Animators (SceneNodeAnimators). También se realizará movimiento manual de
nodos utilizando el teclado por medio de un objeto recibidor de eventos.
Como siempre, se incluyen los archivos de cabecera, el nombre de espacio irr y un
vínculo con el archivo Irrlicht.lib
Irrlicht .
#include <irrlicht.h>
#include <iostream>
using namespace irr;
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif
Para recibir los eventos de teclado, mouse y de interfaz de usuario (por ejemplo
cuando se ha presionado un botón de comando) se necesita un objeto que se derive de la
clase irr::IEventReceiver. Hay solo un método que se debe sobrescribir:
irr::IEventReceiver::OnEvent(). Este método será llamado por el Engine cada vez que un
evento ocurra. Lo que realmente se debe saber es si una
na tecla está siendo presionada,
37
entonces se puede verificar el estado actual de cada tecla para saber de que tecla se
trata.
class MyEventReceiver : public IEventReceiver {
// Se utiliza éste arreglo para verificar el estado de cada tecla
bool KeyIsDown[KEY_KEY_CODES_COUNT];
public:
// Este es el método que se debe implementar
virtual bool OnEvent(const SEvent& event) {
// Verificar si una tecla está siendo presionada
if (event.EventType == irr::EET_KEY_INPUT_EVENT)
KeyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
return false;
}
// Ésta es utilizada para verificar si la tecla está siendo presionada
virtual bool IsKeyDown(EKEY_CODE keyCode) const {
return KeyIsDown[keyCode];
}
MyEventReceiver() {
for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
KeyIsDown[i] = false;
}
};
El manejador de eventos para almacenar el estado de las teclas está listo, la
respuesta a cada uno de estos eventos será realizada dentro del ciclo principal de la
aplicación, justo después de dibujar la escena. Ahora se debe crear el dispositivo y el
nodo de escena que responderá a los eventos de teclado, es decir, cuando se presiona
una tecla este nodo de escena se mueve en la pantalla. También se crearán otros nodos
adicionales, con el fin de demostrar que hay otras maneras de animar un nodo de escena.
int main() {
// Permitir al usuario seleccionar el manejador de video
video::E_DRIVER_TYPE driverType = video::EDT_DIRECT3D9;
printf("Por favor seleccione un manejador de video:\n"\
" (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
" (d) Software Renderer\n (e) Burning's Software Renderer\n"\
" (f) NullDevice\n (otherKey) exit\n\n");
char i;
std::cin >> i;
switch(i) {
case 'a': driverType = video::EDT_DIRECT3D9;break;
case 'b': driverType = video::EDT_DIRECT3D8;break;
case 'c': driverType = video::EDT_OPENGL; break;
case 'd': driverType = video::EDT_SOFTWARE; break;
38
case 'e': driverType = video::EDT_BURNINGSVIDEO;break;
case 'f': driverType = video::EDT_NULL; break;
default: return 0;
}
// Crear dispositivo
MyEventReceiver receiver;
IrrlichtDevice* device = createDevice(
driverType,
core::dimension2d<s32>(640, 480),
16, //bits de color
false, //fullscreen
false, //stencil buffer
false, //vsync
&receiver //manejador de eventos
);
if (device == 0) return 1; // No se pudo crear el dispositivo.
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
Ahora se crea el nodo que será movido (una esfera) cuando las teclas 'W' y 'S'
sean presionadas. La esfera forma parte de las primitivas geométricas incluidas en el
Engine. Se traslada el nodo a la posición (0, 0, 30) y para hacerlo un poco más
interesante, se le asigna una textura. También se desactivará la iluminación para cada
modelo porque no hay ninguna luz dinámica en la escena (de otra forma los modelos se
verían completamente negros).
scene::ISceneNode * node = smgr->addSphereSceneNode();
if (node) {
node->setPosition(core::vector3df(0,0,30));
node->setMaterialTexture(0, driver->getTexture("../../media/wall.bmp"));
node->setMaterialFlag(video::EMF_LIGHTING, false);
}
Ahora se creará otro nodo que se mueva por medio de un objeto animador
(animator). Los nodos de escena de animación modifican a los nodos de escena y pueden
ser atachados a cualquier nodo de escena, tal como un nodo de escena de malla,
billboards, luces e incluso cámaras. Los nodos de animación no modifican únicamente la
posición de un objeto, también puede por ejemplo, animar la textura de un nodo de
escena. Ahora se creará un cubo y a éste se atacha el animator fly circle (volar en
círculos), permitiendo a este nodo volar alrededor de la esfera.
scene::ISceneNode* n = smgr->addCubeSceneNode();
if (n) {
n->setMaterialTexture(0, driver->getTexture("../../media/t351sml.jpg"));
n->setMaterialFlag(video::EMF_LIGHTING, false);
scene::ISceneNodeAnimator* anim = smgr->createFlyCircleAnimator(
39
core::vector3df(0,0,30), //centro
20.0f //radio
);
if (anim) {
n->addAnimator(anim);
anim->drop(); //liberar memoria
}
}
El último nodo de escena se agregará para mostrar las posibilidades de los
animators sobre modelos MD2, ahora se usa el animator fly straight (volar en línea recta),
éste permite realizar la animación del modelo corriendo entre dos puntos.
scene::IAnimatedMeshSceneNode* anms = smgr->addAnimatedMeshSceneNode(
smgr->getMesh("../../media/sydney.md2")
);
if (anms) {
scene::ISceneNodeAnimator* anim = smgr->createFlyStraightAnimator(
core::vector3df(100,0,60), //origen
core::vector3df(-100,0,60), //destino
2500, //tiempo de la animación
true //loop
);
if (anim) {
anms->addAnimator(anim);
anim->drop();
}
Para ver el modelo correctamente se desactiva la iluminación, también se
establece el rango de fotogramas en el cual se realizará la animación del modelo, se rota
180 grados y se ajustan tanto la velocidad de animación como la textura. Para establecer
la animación correcta, esto es: valores correctos de fotorgramas inicial y final y velocidad
de la animación, se debe llamar a anms->setMD2Animation(scene::EMAT_RUN) para la
animación de correr en lugar de llamar a setFrameLoop y setAnimationSpeed, pero ésto solo
funciona con animaciones de modelos MD2.
anms->setMaterialFlag(video::EMF_LIGHTING, false);
anms->setFrameLoop(160, 183);
anms->setAnimationSpeed(40);
anms->setMD2Animation(scene::EMAT_RUN);
anms->setRotation(core::vector3df(0,180.0f,0));
anms->setMaterialTexture(0, driver->getTexture("../../media/sydney.bmp"));
}
Para poder ver y desplazarse en la escena, se agrega una cámara de tipo FPS y
también es recomendable ocultar el puntero del mouse.
40
scene::ICameraSceneNode * cam = smgr->addCameraSceneNodeFPS(0, 100.0f, 100.0f);
device->getCursorControl()->setVisible(false);
Agregar un logo de Irrlicht a todo color.
device->getGUIEnvironment()->addImage(
driver->getTexture("../../media/irrlichtlogoalpha2.tga"), //logo
core::position2d<s32>(10,10) //posición
);
Ahora la escena está lista, solo resta dibujar. También se desplegará la cantidad
actual de fotogramas por segundo y el nombre del manejador de video en el título de la
ventana.
int lastFPS = -1;
while(device->run()) {
Se debe verificar si alguna de las teclas W o S está siendo presionada, y si esto
ocurre la esfera se moverá hacia arriba o abajo respectivamente.
if(receiver.IsKeyDown(irr::KEY_KEY_W)) {
core::vector3df v = node->getPosition();
v.Y += 0.02f;
node->setPosition(v);
} else if(receiver.IsKeyDown(irr::KEY_KEY_S)) {
core::vector3df v = node->getPosition();
v.Y -= 0.02f;
node->setPosition(v);
}
driver->beginScene(
true, //backbuffer
true, //z-buffer
video::SColor(
255, //alfa
113, //rojo
113, //verde
133 //azul
)
);
smgr->drawAll(); // Dibujar la escena 3D
device->getGUIEnvironment()->drawAll(); // Dibujar el entorno GUI (El logo)
driver->endScene();
int fps = driver->getFPS(); //frames per second
if (lastFPS != fps) {
core::stringw tmp(L"Movement Example - Irrlicht Engine [");
tmp += driver->getName();
tmp += L"] fps: ";
41
tmp += fps;
device->setWindowCaption(tmp.c_str());
lastFPS = fps;
}
} //while
Para finalizar, se elimina el dispositivo.
device->drop();
return 0;
} //main
Compilar y probar.
2.6 Graphical User Inrface (GUI)
A continuación se aborda en detalle cómo crear interfaces de usuario con el
Engine Irrlicht. Se explica la forma en la que se crean y usan las ventanas, botones,
barras de desplazamiento, textos estáticos y listas.
Como siempre, se agregan los archivos de cabecera y se habilitan los nombres de
espacio, además se crea un putero para el dispositivo Irrlicht, un contador para cambiar la
posición de las ventanas cuando son creadas y un puntero para una lista (listbox).
#include <irrlicht.h>
#include <iostream>
using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;
#ifdef _IRR_WINDOWS_
#pragma comment(lib, "Irrlicht.lib")
#endif
IrrlichtDevice *device = 0;
s32 cnt = 0;
IGUIListBox* listbox = 0;
El manejador de eventos, además de capturar los eventos de teclado y mouse,
también maneja los eventos de la interfaz gráfica de usuario (GUI). Hay eventos definidos
para casi cualquier cosa: clic sobre botón, cambio en la selección de una lista, eventos
que indican que un elemento tiene el puntero encima, etc. Para responder a algunos
eventos se debe crear un manejador de eventos, por ahora solo se necesitará responder
42
a eventos GUI de la siguiente forma: se obtiene el ID del elemento que ha causado el
evento y un puntero al entorno GUI.
class MyEventReceiver : public IEventReceiver {
public:
virtual bool OnEvent(const SEvent& event) {
if (event.EventType == EET_GUI_EVENT) {
s32 id = event.GUIEvent.Caller->getID();
IGUIEnvironment* env = device->getGUIEnvironment();
switch(event.GUIEvent.EventType) {
Si una barra de desplazamiento ha cambiado de valor, y corresponde con el id
104, entonces se establece el valor de transparencia de todos los elementos GUI. Ésta es
una tarea fácil. Existe un objeto llamado skin en el cual están almacenadas todas las
configuraciones de color, solo es necesario cambiar el valor de alpha (transparencia).
case EGET_SCROLL_BAR_CHANGED:
if (id == 104) {
s32 pos = ((IGUIScrollBar*)event.GUIEvent.Caller)->getPos();
for (u32 i=0; i<EGDC_COUNT ; ++i) {
SColor col = env->getSkin()->getColor((EGUI_DEFAULT_COLOR)i);
col.setAlpha(pos);
env->getSkin()->setColor((EGUI_DEFAULT_COLOR)i, col);
}
}
break;
Si un botón ha sido presionado, podría ser cualquiera de los tres botones que se
añadirán a la ventana. Si es el primero, se cerrará la aplicación; si es el segundo, se
muestra una pequeña ventana con algún mensaje y se agrega una cadena a la lista
(listbox) para registrar lo que ha ocurrido. Por último, si es el tercer botón, se crea un
cuadro de diálogo para abrir un archivo y se agrega también una cadena al listbox. Eso es
todo para el manejador de eventos.
case EGET_BUTTON_CLICKED:
if (id == 101) {
device->closeDevice();
return true;
}
if (id == 102) {
listbox->addItem(L"Window created");
cnt += 30;
if (cnt > 200) cnt = 0;
IGUIWindow* window = env->addWindow(
rect<s32>(100 + cnt, 100 + cnt, 300 + cnt, 200 + cnt),
false, // modal?
L"Test window"
43
);
env->addStaticText(
L"Please close me",
rect<s32>(35,35,140,50),
true, // border?
false, // wordwrap?
window
);
return true;
}
if (id == 103) {
listbox->addItem(L"File open");
env->addFileOpenDialog(L"Please choose a file.");
return true;
}
break;
default:
break;
}
}
return false;
}
};
Ahora se debe crear el dispositivo Irrlicht. Como en los ejemplos anteriores, el
usuario determinará cuál de los controladores de video se va a utilizar.
int main() {
// Elegir driver
video::E_DRIVER_TYPE driverType;
printf("Por favor elija el controlador de video a utilizar:\n"\
" (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
" (d) Software Renderer\n (e) Burning's Software Renderer\n"\
" (f) NullDevice\n (otherKey) exit\n\n");
char i;
std::cin >> i;
switch(i) {
case 'a': driverType = video::EDT_DIRECT3D9;break;
case 'b': driverType = video::EDT_DIRECT3D8;break;
case 'c': driverType = video::EDT_OPENGL; break;
case 'd': driverType = video::EDT_SOFTWARE; break;
case 'e': driverType = video::EDT_BURNINGSVIDEO;break;
case 'f': driverType = video::EDT_NULL; break;
default: return 1;
}
// Crear el dispositivo o en su defecto terminar la ejecución.
44
device = createDevice(driverType, core::dimension2d<s32>(640, 480));
if (device == 0) return 1; // no se pudo crear el dispositivo.
Se ha creado satisfactoriamente, ahora se establece el manejador de eventos y se
crean los punteros al driver y al entorno GUI.
MyEventReceiver receiver;
device->setEventReceiver(&receiver);
device->setWindowCaption(L"Irrlicht Engine - User Interface Demo");
video::IVideoDriver* driver = device->getVideoDriver();
IGUIEnvironment* env = device->getGUIEnvironment();
Para hacer que el texto se vea un poco más interesante, se carga una fuente
externa y se establece como la fuente por defecto del skin. Para conservar la fuente por
defecto en los tool tips (globos de ayuda o información adicional que aparece al dejar el
puntero por unos instantes sobre algún elemento GUI) se establece la fuente incluída en
el Engine.
IGUISkin* skin = env->getSkin();
IGUIFont* font = env->getFont("../../media/fonthaettenschweiler.bmp");
if (font)
skin->setFont(font);
skin->setFont(env->getBuiltInFont(), EGDF_TOOLTIP);
Ahora se agregarán los botones, el primero cierra el Engine, el segundo crea una
ventana con un mensaje y el tercero abre el cuadro de diálogo de abrir archivo. El tercer
parámetro del método ->addButton es el ID del botón, éste es utilizado para identificar
fácilmente desde el manejador de eventos cuál de los botones ha sido presionado. Los
otros parámetros de dicho método están definidos en los comentarios del código
siguiente:
env->addButton(
rect<s32>(10,240,110,240 + 32), // posicionamiento
0, // padre
101, //ID
L"Terminar", //Etiqueta
L"Sale del programa" //Tooltip
);
env->addButton(
rect<s32>(10,280,110,280 + 32),
0,
102,
L"Nueva ventana",
L"Crea una nueva ventana"
);
45
env->addButton(
rect<s32>(10,320,110,320 + 32),
0,
103,
L"Abrir Archivo",
L"Abre un archivo"
);
Ahora se agregará un texto estático y una barra de desplazamiento que modifique
la opacidad de todos los elementos GUI. Se establece 255 como valor máximo, porque es
el valor más alto utilizado en colores (1 byte). Luego se crea otro texto estático y el listbox.
env->addStaticText(L"Transparent Control:", rect<s32>(150,20,350,40), true);
IGUIScrollBar* scrollbar = env->addScrollBar(
true, //horizontal
rect<s32>(150, 45, 350, 60), //dimensiones
0, //parent
104 //ID
);
scrollbar->setMax(255);
// Establecer el valor de la barra de deslizamiento igual al valor alpha de
// un elemento elegido arbitrariamente.
scrollbar->setPos(env->getSkin()->getColor(EGDC_WINDOW).getAlpha());
env->addStaticText(L"Registro de sucesos:", rect<s32>(50,110,250,130), true);
listbox = env->addListBox(rect<s32>(50, 140, 250, 210));
env->addEditBox(L"Texto editable", rect<s32>(350, 80, 550, 100));
Por último se agrega una imagen del logo del Engine en la esquina superior
izquierda.
env->addImage(
driver->getTexture("../../media/irrlichtlogo2.png"),
position2d<int>(10,10)
);
Por ahora solo resta dibujar la escena.
while(device->run() && driver)
if (device->isWindowActive()) {
driver->beginScene(true, true, SColor(0,200,200,200));
env->drawAll();
driver->endScene();
}
device->drop();
return 0;
} //main
46
Al compilar y ejecutar la aplicación se verá una ventana como la que se muestra
en la figura 2.6
Figura 2.6. Algunos elementos de la interfaz gráfica de usuario incluida en el Engine Irrlicht.
2.7 Detección de colisiones
A continuación se mostrará como detectar colisiones con el Engine Irrlicht, en esta
sección se abarcan tres métodos: (1) Detección automática de colisiones para moverse
por mundos tridimensionales con la capacidad para subir gradas y deslizarse, (2)
selección manual de triángulos y (3) selección de nodos de escena.
Para iniciar se utilizará el programa de la Sección 1.4 (Cargar mapas de Quake 3 y
agregar una cámara FPS), el cual carga un nivel de Quake 3. Se utilizará dicho nivel para
caminar en él y para tomar sus triángulos. Adicionalmente se agregan tres modelos
animados a la escena. El siguiente código inicia el Engine y carga el mapa de Quake 3.
Esto ya fue expuesto en la sección 1.4.
#include <irrlicht.h>
#include <iostream>
using namespace irr;
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif
int main() {
// let user select driver type
video::E_DRIVER_TYPE driverType;
printf("Por favor seleccione el driver a utilizar:\n"\
" (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
" (d) Software Renderer\n (e) Burning's Software Renderer\n"\
" (f) NullDevice\n (otherKey) exit\n\n");
47
char i;
std::cin >> i;
switch(i) {
case 'a': driverType = video::EDT_DIRECT3D9;break;
case 'b': driverType = video::EDT_DIRECT3D8;break;
case 'c': driverType = video::EDT_OPENGL; break;
case 'd': driverType = video::EDT_SOFTWARE; break;
case 'e': driverType = video::EDT_BURNINGSVIDEO;break;
case 'f': driverType = video::EDT_NULL; break;
default: return 0;
}
// Crear el dispositivo
IrrlichtDevice *device = createDevice(
driverType,
core::dimension2d<s32>(640, 480),
16,
False
);
if (device == 0)
return 1; // no se pudo crear el driver seleccionado.
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
device->getFileSystem()->addZipFileArchive("../../media/map-20kdm2.pk3");
scene::IAnimatedMesh* q3levelmesh = smgr->getMesh("20kdm2.bsp");
scene::ISceneNode* q3node = 0;
if (q3levelmesh)
q3node = smgr->addOctTreeSceneNode(q3levelmesh->getMesh(0));
Ahora algo diferente, crear el selector de triángulos. Un selector de triángulos o
triangle selector es una clase con la que se pueden extraer los triángulos de nodos de
escena para realizar diferentes cosas con ellos, por ejemplo: detección de colisiones. Hay
diferentes selectores, y todos pueden ser creados con la ISceneManager. En este ejemplo
se crea un OctTreeTriangleSelector, el cual optimiza un poco la salida de triángulos,
reduciéndolo como un octree8, esto es muy útil con mallas enormes como los niveles de
Quake 3. Después de que se ha creado el selector se atacha al objeto q3node. Esto no es
realmente necesario pero de esta forma, no será necesario encargarse del selector por
separado, por ejemplo para liberarlo cuando ya no se necesita más.
scene::ITriangleSelector* selector = 0;
if (q3node) {
8
Véase el término octree en el glosario.
48
q3node->setPosition(core::vector3df(-1350,-130,-1400));
selector = smgr->createOctTreeTriangleSelector(
q3levelmesh->getMesh(0),
q3node,
128
);
q3node->setTriangleSelector(selector);
}
Ahora se agrega una cámara de tipo FPS a la escena para poder moverse a través
del nivel de Quake 3. En esta ocasión, se agregará un animator especial a la cámara, que
responda a colisiones (collision response animator). Esto modifica el nodo de escena al
cual el animator ha sido atachado, de manera que el nodo de escena no puede moverse a
través de las paredes del entorno y es afectado por la gravedad. Lo único que se debe
indicar al animator es la forma que tiene todo el entorno, qué tan grande es el nodo de
escena, el valor de la gravedad, etc. Luego de que el animator de respuesta a colisiones
es atachado a la cámara, no se tendrá que hacer nada extra para la detección de
colisiones, todo es hecho automáticamente. El animator de respuesta a colisiones
también puede ser atachado a todos los otros nodos de escena, no solamente a cámaras,
incluso puede ser combinado con otros animators.
Ahora se examinan detenidamente los parámetros de createCollisionResponseAnimator().
El primer parámetro es el TriangleSelector, el cual especifica cómo “se ve el
mundo”. El segundo parámetro es el nodo de escena, éste es el objeto que será afectado
por la detección de colisiones, la cámara. El tercer parámetro define cuán grande es el
objeto por medio de un elipsoide9 (definiendo sus tres radios). Al reducir los radios del
elipsoide la cámara puede acercarse más a las paredes. El cuarto parámetro representa
la dirección y velocidad de la gravedad. Puede establecer este valor a (0,0,0) para
desactivar la gravedad. El quinto y último parámetro es sólo una traslación, sin esto la
cámara quedaría justo en el centro del elipsoide, de manera que se colocará la cámara 50
unidades arriba del centro del elipsoide; con esto se hará funcionar la detección de
colisiones.
scene::ICameraSceneNode* camera =
smgr->addCameraSceneNodeFPS(0, 100.0f, 300.0f, -1, 0, 0, true);
camera->setPosition(core::vector3df(-100,50,-150));
if (selector) {
scene::ISceneNodeAnimator* anim =
smgr->createCollisionResponseAnimator(
9
Un elipsoide en matemáticas, es una cuádrica (ver glosario) análoga a la elipse, pero con una dimensión
más. Las secciones originadas por planos que contienen a los ejes ortogonales son elípticas. La ecuación
general de un elipsoide con centro en el origen de coordenadas, es:
números reales positivos que determinan la forma del elipsoide.
49
donde a, b y c son
selector, //triangle selector
camera, //Nodo de escena
core::vector3df(30,50,30), //Radios del elipsoide
core::vector3df(0,-3,0), //Gravedad
core::vector3df(0,50,0) //Pos. Rel. del nodo al elipsoide.
);
selector->drop();
camera->addAnimator(anim);
anim->drop();
}
Ahora se agregan tres modelos animados, una luz dinámica para iluminarlos, un
billboard para mostrar la intersección entre el puntero y el selector, además de ocultar el
puntero del mouse.
// Esconder el puntero del mouse
device->getCursorControl()->setVisible(false);
// Agregar el billboard
scene::IBillboardSceneNode * bill = smgr->addBillboardSceneNode();
bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR );
bill->setMaterialTexture(0, driver->getTexture("../../media/particle.bmp"));
bill->setMaterialFlag(video::EMF_LIGHTING, false);
bill->setMaterialFlag(video::EMF_ZBUFFER, false);
bill->setSize(core::dimension2d<f32>(20.0f, 20.0f));
// Cargar tres adas animadas.
video::SMaterial material;
material.setTexture(0, driver->getTexture("../../media/faerie2.bmp"));
material.Lighting = true;
scene::IAnimatedMeshSceneNode* node = 0;
scene::IAnimatedMesh* faerie = smgr->getMesh("../../media/faerie.md2");
if (faerie) {
node = smgr->addAnimatedMeshSceneNode(faerie);
node->setPosition(core::vector3df(-70,0,-90));
node->setMD2Animation(scene::EMAT_RUN);
node->getMaterial(0) = material;
node = smgr->addAnimatedMeshSceneNode(faerie);
node->setPosition(core::vector3df(-70,0,-30));
node->setMD2Animation(scene::EMAT_SALUTE);
node->getMaterial(0) = material;
node = smgr->addAnimatedMeshSceneNode(faerie);
node->setPosition(core::vector3df(-70,0,-60));
node->setMD2Animation(scene::EMAT_JUMP);
node->getMaterial(0) = material;
}
material.setTexture(0, 0);
50
material.Lighting = false;
// Agregar un luz dinámica
smgr->addLightSceneNode(
0, //padre
core::vector3df(-60,100,400), //posición
video::SColorf(1.0f,1.0f,1.0f,1.0f), //color RGBA
600.0f //radio de iluminación
);
Para simplificar el proceso de detección de triángulos, se va a realizar la operación
dentro del ciclo de dibujo. Se crean dos punteros para almacenar el actual y el último
nodos de escena e iniciar el ciclo.
scene::ISceneNode* selectedSceneNode = 0;
scene::ISceneNode* lastSelectedSceneNode = 0;
int lastFPS = -1;
while(device->run())
if (device->isWindowActive()) {
driver->beginScene(true, true, 0);
smgr->drawAll();
Ahora se describirán las otras dos formas de hacer detección de colisiones.
Después de haber dibujado la escena completa con smgr->drawAll(), se realiza la
primera detección, se desea saber cuál de los triángulos que conforman la malla del
escenario está apuntando la cámara y el punto exacto donde se intersecta la línea de
visión con dicha malla. Para esto se crea una línea 3D (línea de visión), iniciando en la
posición de la cámara y dirigiéndola hasta el punto donde la cámara está viendo (camera>getTarget()), luego se verifica en el manejador de colisiones si esta línea toca alguno de los
triángulos del escenario guardados en el selector. Si es así, se dibuja un triángulo 3D que
corresponde al triánguno detectado en la malla y se establece la posición del billboard en
el punto de intersección.
core::line3d<f32> line;
line.start = camera->getPosition();
line.end = line.start +
(camera->getTarget() - line.start).normalize() * 1000.0f;
core::vector3df intersection;
core::triangle3df tri;
if (smgr->getSceneCollisionManager()->getCollisionPoint(
line, selector, intersection, tri))
{
bill->setPosition(intersection);
51
driver->setTransform(video::ETS_WORLD,
>setTransform(video::ETS_WORLD, core::matrix4());
driver->setMaterial(material);
rial(material);
driver->draw3DTriangle(tri,
>draw3DTriangle(tri, video::SColor(0,255,0,0));
}
Otra
a forma de detectar un nodo visible desde la cámara se basa en las cajas
delimitadoras (o bounding box)
box de los nodos. Véase la figura 2.7. Cada nodo de escena
tiene una caja delimitadora y en consecuencia resulta bastante rápido determinar qué
nodo de escena está diréctamen
diréctamente frente a la cámara. De nuevo, se verifica esto en el
manejador de colisiones, utilizando el método ->getSceneNodeFromCameraBB(camera).
CameraBB(camera).
Si dicho método devuelve un puntero a nodo de escena exceptuando los objetos billboard y
q3node, se procederá a desactivar la propiedad Lighting en su material.
Figura 2.7. La figura muestra el nodo de escena de
un pato, delimitado por su bonding box.
selectedSceneNode =
smgr->
>
getSceneCollisionManager()->
getSceneNodeFromCameraBB(camera);
if (lastSelectedSceneNode)
lastSelectedSceneNode
lastSelectedSceneNode->
setMaterialFlag(video::EMF_LIGHTING, true);
if (selectedSceneNode == q3node || selectedSceneNode == bill)
selectedSceneNode = 0;
if (selectedSceneNode)
Node)
selectedSceneNode
selectedSceneNode->setMaterialFlag(
video::EMF_LIGHTING, false
);
lastSelectedSceneNode = selectedSceneNode;
Para finalizar, se dibuja la escena.
driver->endScene();
52
int fps = driver->getFPS();
if (lastFPS != fps) {
core::stringw str = L"Colisiones - Irrlicht Engine [";
str += driver->getName();
str += "] FPS:";
str += fps;
device->setWindowCaption(str.c_str());
lastFPS = fps;
}
}
device->drop();
return 0;
}
2.8 Audio 2D y 3D con irrKlang
IrrKlang es una poderosa API de alto nivel para controlar la reproducción de
sonidos en aplicaciones 3D y 2D como juegos, programas científicos de visualización y
aplicaciones multimedia. Es un Engine de sonido 2D y 3D y librería de audio
multiplataforma y se puede utilizar con C++ y todos los lenguajes de la plataforma .NET
(C#, VisualBasic.NET, etc.). Tiene características incorporadas muy útiles como
decodificadores de formatos de audio (wav, ogg, mp3, mod, xm, it, s3m...), un sofisticado
mecanismo de streaming 10 , lectura de audio extensible, trabajo en modo simple y
multihilo, capacidad para emular audio 3D, un sistemas de plugins (complementos o
extensiones), múltiples modelos de rollof11 y más. Además es libre (open source) y puede
ser utilizado para propósitos no comerciales.
2.8.1 EJECUTANDO SONIDOS (2D)
A continuación se demuestra cómo ejecutar sonidos utilizando IrrKlang. Se
ejecutará un sonido de fondo que se repite indefinidamente y un segundo sonido cada vez
que se presiona una tecla.
El SDK se puede descargar de: http://www.ambiera.com/irrklang/downloads.html,
luego de instalado se agregan al proyecto los directorios lib\Win32-visualStudio e include,
contenidos en el directorio de instalación del SDK, es un procedimiento muy similar al
descrito en la sección 1.3 / Configurando el IDE. Los ejemplos y demás archivos
necesarios para su implementación vienen también incluidos en el SDK en el directorio
examples.
10
11
Más información sobre streaming en el glosario.
Vea el término rollof en el glosario.
53
Se procede, desde Visual Studio a crear un proyecto vacío (aplicación de consola).
Para comenzar se deben incluir los archivos de cabecera.
#include <iostream>
#include <irrKlang.h>
Adicionalmente de la forma en que se declaran los nombres de espacio con Irrlicht,
se debe indicar al compilador que se usará el nombre de espacio irrklang. Todas las
clases y funciones de irrKlang se encuentran dentro del nombre de espacio irrklang. Para
utilizar cualquier clase del Engine, se debe escribir irrklang:: antes del nombre de la clase.
Por ejemplo, para usar la clase ISoundEngine, se escribe: irrklang::ISoundEngine. Para evitar
escribir irrklang:: siempre que se usa una clase, se agregará instrucción using namespace
irrklang.
using namespace irrklang;
Para poder acceder al archivo irrKlang.dll, se necesita vincular la aplicación con el
archivo irrKlang.lib. Para ello basta con una instrucción pragma comment:
#pragma comment(lib, "irrKlang.lib") // Vincular con irrKlang.dll
Para iniciar, se demostrará una simple ejecución de audio 2D. Se inicializa el
Engine de sonido usando el método createIrrKlangDevice(). Dicho método puede requerir
varios parámetros, por ahora bastará con los parámetros por defecto.
int main(int argc, const char** argv) {
// Inicializar el Engine de sonido con los parámetros por defecto
ISoundEngine* Engine = createIrrKlangDevice();
if (!Engine)
return 0; // error al iniciar el Engine
Para ejecuar un sonido simple, se utiliza el método play2D(). El primer parámetro
hace referencia al archivo de sonido a ejecutar, el segundo parámetro de este método le
indica al Engine que el sonido se va a ejecutar indefinidamente (al finalizar la
reproducción inicia de nuevo).
// Ejecutar el stream de audio, indefinidamente.
Engine->play2D("../../media/getout.ogg", true);
El programa se ejecutará en un ciclo hasta que se presione la tecla q para salir de
la aplicación o cualquier otra tecla para ejecutar el segundo archivo de sonido.
std::cout << "\nHello World!\n";
char i = 0;
54
while(i != 'q') {
std::cout << "Presione cualquier tecla para ejecutar un"\
"sonido o 'q' para salir.\n";
// Ejecutar un sonido simple
Engine->play2D("../../media/bell.wav");
std::cin >> i; // esperar a que el usuario presione una tecla.
}
Al finalizar el ciclo, se debe eliminar el dispositivo IrrKlang que se ha creado
previamente con createIrrKlangDevice(), pare ello se utiliza el método drop(). En IrrKlang, al
igual que en Irrlicht se deben eliminar todos los objetos que se hayan creado con un
método o función que inicie con create. (play2D() y play3D() son excepciones a esta regla.
Para ampliar esta información consulte la documentación12).
Engine->drop(); // eliminar el Engine
return 0;
}
Sólo resta compilar y ejecutar la aplicación.
2.8.2 SONIDO 3D
Cuando un sonido se ejecuta en un punto en particular del espacio, el cual tiene
una distancia relativa al oyente, se denomirá sonido 3D. Esto puede lograrse gracias a los
sistemas de audio estéreo de dos o más canales (Equipos de audio con dos o más
bocinas). De modo que el sonido puede ejecutarse con un determinado volumen en cada
una de las bocinas para simular cierto grado de cercanía desde el origen del sonido hacia
el oyente. Por ejemplo el sonido puede trasladarse gradualmente de una bocina a la
próxima, simulando el movimiento del origen del mismo. Véase la figura 2.8.
Figura 2.8. En la figura se muestra un sistema de audio con cinco
bocinas, se está simulando que un objeto emisor de sonido se mueve
alrededor del oyente. Como se puede apreciar, solamente algunas de las
bocinas están activas.
12
irrKlang 1.1.0 API documentation: http://www.ambiera.com/irrklang/docu/index.html
55
A continuación se mostrará cómo ejecutar sonidos en el espacio 3D, utilizando
irrKlang. Un archivo de audio mp3 será ejecutado en el espacio 3D y se moverá alrededor
del usuario y otro sonido se ejecutará en una posición 3D aleatoria cada vez que se
presione una tecla.
Se necesita una función para dormir el programa por algunos segundos, entónces
se incluirán los archivos específicos de la plataforma para poder acceder a dicha función.
#ifdef WIN32
#include <windows.h>
#include <conio.h>
inline void sleepSomeTime() { Sleep(100); }
#endif
También se incluyen los archivos de cabecera de entrada/salida necesarios para
imprimir y obtener etradas de usuario desde la consola.
#include <iostream>
#include <stdio.h>
#include <irrKlang.h>
using namespace irrklang;
#pragma comment(lib, "irrKlang.lib") // link with irrKlang.dll
A continuación se inicializa el Engine utilizando createIrrKlangDevice() con las
opciones o parámetros por defecto.
int main(int argc, const char** argv) {
// Iniciar el enginde de sonido con los parámetros por defecto
ISoundEngine* Engine = createIrrKlangDevice();
if (!Engine)
return 0; // error starting up the Engine
Ahora se ejecutará indefinidamente el stream de sonido como música en el
espacio 3D, se establecerá el último parámetro llamado 'track' a 'true' para hacer que
irrKlang retorne un puntero al sonido ejecutado. (Este puntero también es devuelto si el
parámetro 'startPaused' está definido como true). Notese que es neceario llamar al
método ->drop del puntero retornado si ya no se necesita usar este sonido más adelante.
Esto se hace al final del programa.
// Ejecutar un sonido en el espacio
ISound* music = Engine->play3D(
"../../media/ophelia.mp3", //Archivo de sonido
vec3df(0,0,0), // Posición
true, //ejecutar indefinidamente (looped)
false, //iniciar pausado (startPaused)
56
true //retornar un puntero al sonido para poder controlarlo (track)
);
El siguiente paso no es realmente necesario, pero, para ajustar la distancia a la
cual el sonido puede ser oído se definirá una distancia mínima (el valor por defecto es 1
para objetos pequeños). La distancia mínima es simplemente la distancia a la cual el
sonido es ejecutado con su volumen máximo.
if (music)
music->setMinDistance(5.0f);
Imprimir un texto de ayuda e iniciar el ciclo:
printf("\nEjecutando sonido en 3D.");
printf("\nPresiones ESCAPE para salir, cualquier otra tecla "\
"para ejecutar otro sonido en una posición aleatoria.\n\n");
printf("+ = Posición de quien escucha\n");
printf("o = Ejecutando sonido\n");
float posOnCircle = 0;
const float radius = 5;
while(true) { // ciclo infinito, hasta que el usuacio salga
Para cada paso por el ciclo se calcula la posición de la música en el espacio, en
este ejemplo se hará que la música rote su posición sobre una circunferencia.
posOnCircle += 0.04f;
vec3df pos3d(radius * cosf(posOnCircle), // X
0, // Y
radius * sinf(posOnCircle * 0.5f) // Z
);
Después de calcular la posición de la música se debe indicar a irrKlang la posición
de quien escucha (en este caso (0,0,0), con dirección hacia adelante).
Engine->setListenerPosition(vec3df(0,0,0), //posición del oyente (listener)
vec3df(0,0,1)); //dirección (lookdir)
if (music) music->setPosition(pos3d);
Ahora se imprimirá la posición del sonido representada en una cadena y también
un medidor de avance de reproducción.
char stringForDisplay[] = "
+
";
int charpos = (int)((pos3d.X + radius) / radius * 10.0f);
if (charpos >= 0 && charpos < 20)
57
stringForDisplay[charpos] = 'o';
int playPos = music ? music->getPlayPosition() : 0;
printf("\rx:(%s) 3dpos: %.1f %.1f %.1f, playpos:%d:%.2d
stringForDisplay, pos3d.X, pos3d.Y, pos3d.Z,
playPos/60000, (playPos%60000)/1000 );
",
sleepSomeTime();
Manejo de teclado: Cada vez que el usuario presiona una tecla en la consola se
ejecuta un sonido aleatorio o sale de la aplicación si se ha presionado ESC.
if (kbhit()) { //si se ha presionado una tecla
int key = getch(); //obtener la tecla
if (key == 27) // Código para la tecla ESC
break;
else {
Ahora se procede a ejecutar el sonido en una posición aleatoria. El método play3D()
no retorna ningún puntero (devuelve 0) porque no se ha establecido a true ninguno de los
parámetros startPaused (iniciar pausado) o track (como se hizo anteriormente con la
múscia). Por esta razón no es necesario llamar a drop().
vec3df pos(fmodf((float)rand(),radius*2)-radius, 0, 0); //posición aleatoria
const char* filename;
if (rand()%2) //se elige un sonido al azar
filename = "../../media/bell.wav";
else
filename = "../../media/explosion.wav";
Engine->play3D(filename, pos); //ejecutar filename en la posición pos
printf("\nreproduciendo %s en %.1f %.1f %.1f\n",
filename, pos.X, pos.Y, pos.Z);
Por ahora solo resta liberar los recursos cuando el programa finaliza.
}
}
}
// No olvide liberar los recursos como se explicó.
if (music)
music->drop(); // release music stream.
Engine->drop(); // eliminar el Engine
return 0;
58
}
Además de permitir ejecutar sonido 2D y 3D, irrKlang incorpora una serie de
efectos especiales que pueden aplicarse a cualquier sonido. Entre ellos se incluye el
Efecto Doppler 13 , coro, compresión, distorsión, echo, flanger 14 , gargling 15 , reverb 16 y
efectos de ecualización paramétrica, entre otros.
Puede obtenerse una completa referencia de la API de IrrKlang, tutoriales y otros
recursos en http://www.ambiera.com/irrklang
2.9 irrXML
irrXML es un parser o analizador sintáctico para leer archivos XML con datos no
validados (asume que están sintácticame correctos de acuerdo al lenguaje XML), es
rápido y simple, de código abierto, escrito en C++ y compatible con lenguajes .NET. Este
parser de XML viene incluido en el Engine Irrlicht pero también puede obtenerse por
separado para usarlo en cualquier tipo de aplicación. Más información en:
http://www.ambiera.com/irrxml
Si se usa de forma independiente simplemente se debe agregar al proyecto el
directorio /src incluído en el SDK de irrXML. Para usuarios Linux / Unix se debe ir al
directorio /examples y ejecutar la instrucción ‘make’ en la consola, esto creará un proyecto
simple que incluye irrXML.
A continuación se describe un breve ejemplo que muestra como acceder a un
archivo XML: config.xml.
<?xml version="1.0"?>
<config>
<!-- Este es un archivo de configuración para el visor de mallas -->
<model file="dwarf.dea" />
<messageText caption="Visor de mallas del Engine Irrlicht">
Bienvenido al visor de mallas de "Irrlicht Engine".
</messageText>
</config>
El programa que leerá el archivo XML simplemente guarda los datos obtenidos en
las variables messageText, modelFile y Caption. Luego de obtenida la información se puede
proceder a realizar cualquier acción, por ejemplo, desplegarla en pantalla.
13
Efecto Doppler: Consiste en la variación de la longitud de onda de cualquier tipo de onda emitida o
recibida por un objeto en movimiento y que se produce a causa del mismo movimiento.
14
Efecto flanger: Consiste en mezclar la señal original de audio con una copia retardada en el tiempo, con la
particularidad que el retardo es muy breve pero varía de forma periódica.
15
Efecto gargling: Consiste variar la señal de audio de forma que suene como si se hicieran “gárgaras”.
16
Efecto reverb: Este es el efecto que se aplica al material de audio para dar al oyente la impresión que está
en otro cuarto.
59
#include <irrXML.h>
using namespace irr; // localizar irrXML.
using namespace io; // usando el nombre de espacio irr::io
17
#include <string> // Usamos cadenas STL
// en este ejemplo.
para almacenar los datos
void main() {
// Creamos el lector usando una de las funciones de creación
IrrXMLReader* xml = createIrrXMLReader("config.xml");
// Algunas cadenas para almacenar los datos que obtenemos del archivo xml
std::string modelFile;
std::string messageText;
std::string caption;
// leer el archivo hasta que se encuentre el final del mismo.
while(xml && xml->read()) {
switch(xml->getNodeType()) {
case EXN_TEXT:
// En este archivo xml, el único texto se encuentra dentro de las
// etiquetas <messageText> y </messageText>
messageText = xml->getNodeData();
break;
case EXN_ELEMENT:
if (!strcmp("model", xml->getNodeName())) //etiqueta model
modelFile = xml->getAttributeValue("file"); //atributo file
else
if (!strcmp("messageText", xml->getNodeName())) //etiqueta messageText
caption = xml->getAttributeValue("caption"); //atributo caption
break;
}
}
// Liberar la memoria
delete xml;
}
17
STL o Standard Template Library es una librería de software parcialmente incluida en la librería estándar
de C++. Provee contenedores, iteradores, algoritmos y funciones utilizados para manejar diferentes
estructuras de datos de computadoras.
60
Capítulo III
RakNet
RakNet
kNet es una librería para manejo de red basada en UDP.
Está diseñada para permitir a los programadores agregar
capacidad de respuesta en tiempo crítico a sus aplicaciones de
red.
61
62
3.1 Introducción
RakNet es una librería para manejo de red basada en UDP. Está diseñada para
permitir a los programadores agregar capacidad de respuesta en tiempo crítico a sus
aplicaciones de red. Es utilizada principalmente para videojuegos, pero es independiente
de la aplicación que se le quiera dar. RakNet está diseñada para ser veloz, fácil de usar,
independiente de la aplicación, independiente de la plataforma y flexible. RakNet está bajo
licencias diferentes, de acuerdo al tipo de servicio o aplicación que se va a desarrollar.
Para aplicaciones no comerciales aplica Creative Commons “Reconocimiento-No
comercial 2.5 Genérica 18 ”. En el caso de aplicaciones simples para desarrolladores
individuales aplica la licencia “Indy 19 ”. Para desarrolladores mayores, la licencia
“Developer” y para Engines de juegos y aplicaciones grandes aplica “Publisher license”.
Más información en: http://www.jenkinssoftware.com/purchase.html
3.2 Características
•
•
•
•
•
•
•
•
Entre las características más importantes se encuentran las siguientes:
Lobby System: Es un plugin que provee funcionalidad controlada por una base de
datos PostgreSQL para datos persistentes de juegos y matchmaking20.
Sistema de replicación de objetos: Permite de manera automática crear, destruir,
serializar21 y transmitir los objetos de la aplicación o juego en desarrollo.
Conexiones Seguras22: Soporte para SHA1, AES128, SYN Cookies, y RSA para
prevenir y detectar ataque de red.
Robusta capa de comunicación: Control automático de flujo, ordenamiento de
mensajes en múltiples canales, coalescence 23 de mensajes y segmentación y
reemsamblado de paquetes.
Autopatcher: Consiste en una clase que maneja la actualización de paquetes
perdidos o modificados entre dos sistemas.
Llamadas a procedimientos remotos: Puede llamar a procedimientos nativos de C
y C++ con listas de parámetros automáticamente serializadas.
Comunicación por voz: Incluye bindings de audio para Port Audio 24 , FMOD y
DirectSound.
NAT 25 Punchthrough: Si ambas máquinas se encuentran detrás de un NAT
(router), es imposible para ellas conectarse entre sí, a menos que ambas
conozcan la dirección pública y puerto de la otra. La clase NatPunchthrough
18
Creative Commons “Reconocimiento-No comercial 2.5 Genérica”,
http://creativecommons.org/licenses/by-nc/2.5/deed.es
19
Indy License, http://www.jenkinssoftware.com/IndyLicense-1.0.pdf
20
Matchmaking: Término inglés que se refiere al proceso de ayudar a las personas a encontrar pareja.
21
Vea serialización en el glosario.
22
Para más información de cada uno de los términos SHA1, AES128, SYN Cookies, y RSA consulte el glosario.
23
Coalescence: Es la acción de fusionar dos bloques adyacentes de memoria libre.
24
Port Audio: Es una librería para grabar y reproducir audio. Es independiente de la plataforma y de código
abierto.
25
NAT: Acrónimo de Network Address Translation. Mapea dirección IP públicas a direcciones IP locales.
63
implementa esta técnica permitiendo a ambos sistemas conectarse a través de un
tercer sistema llamado facilitador, éste no se encuentra detrás de NAT y actúa
como un intermediario en la comunicación.
3.3 Incluir RakNet en un proyecto26
Hay varias formas de incluir RakNet en un proyecto: puede utilizar el código
fuente, una librería estática o bien una DLL.
En este texto se explicará cómo incluir el código fuente de RakNet en un proyecto
de Visual Studio 2005. La ventaja de hacerlo de esta manera es que permite que en
cualquier momento se pueda acceder al código de todas las clases y sus métodos para
conocer el órden de los parámetos, los atributos de clase, etc. El código fuente de RakNet
incluye una amplia documentación interna con comentarios y descripciones detalladas.
Lo primero que se debe hacer es agregar el directorio /Source al proyecto. No todos
los archivos son estrictamente necesarios, pero los que no se usen tampoco estorbarán.
Sin embargo, si no se planea utilizar RakVoice podrían excluirse todos los archivos que
incluyan RakVoice en su nombre, de otra forma se deben incluir todos los archivos
speex27.
•
•
Si se necesita incluir RakVoice se debe agregar lo siguiente:
Los archivos fuente contenidos en el directorio DependentExtensions\speexx.x.xx\libspeex.
Agregar a los directorios include adicionales "Additional Include Directories" el lo
siguiente: ..\..\..\Source;..\..\..\speex-1.1.4\libspeex.
A continuación se debe importar ws2_32.lib o bien wsock32.lib si no se tiene
instalado Winsock 2. En VisaulStudio.NET se incluye haciendo clic derecho sobre el
proyecto, luego se selecciona configuration properties (propiedades de configuración) >
linker > input > additional dependencies y ahí se agrega el archivo "ws2_32.lib", como se
vé en la figura 3.2.
Figura 3.2. Incluir ws2_32.lib en el proyecto (o en su defecto, incluya wsock32.lib)
26
Fuente: Compiler Setup, http://www.jenkinssoftware.com/raknet/manual/compilersetup.html
El proyecto Speex tiene como objetivo crear un códec libre para voz, sin restricciones de ninguna patente
de software. Speex está sujeto a la Licencia BSD y es usado con el contenedor Ogg de la Fundación Xiph.org.
Fuente: http://es.wikipedia.org/wiki/Speex
27
64
También se debe configurar el proyecto para usar las librerías en tiempo de
ejecución multi-hilo (multi-threaded runtime libraries). En VisualStudio.NET se debe hacer
clic derecho sobre el proyecto, seleccionar las propiedades de configuración
(configuration properties) > C/C++ > Code Generation > Runtime Library, y por último
cambiar el valor a Multi-threaded (/MT), como se aprecia en la figura 3.3.
Figura 3.3. Establecer el proyecto para usar Multi-threaded en las librerías runtime.
Opcionalmente se pueden configurar las directivas de preprocesador. Más
información al respecto en:
http://www.jenkinssoftware.com/raknet/manual/preprocessordirectives.html
3.4 Comunicación básica: envío de cadenas
A continuación se ejemplificará el funcionamiento básico de RakNet, se crean dos
proyectos incluyendo RakNet (tal y como se mostró en la sección anterior), un servidor y
un cliente. Los clientes (instancias de la aplicación client) al momento de iniciar, solicitan
la dirección IP del servidor y se conectan por el puerto 60000, luego de conectados
envían cadenas de texto al servidor por medio de paquetes. Los archivos de proyecto se
incluyen en el CD que acompaña a este material.
3.4.1 SERVIDOR (MAIN. CPP)
Para comenzar se deben incluir los archivos de cabecera. El archivo stdio.h es para
entrada y salida desde la consola. RakNetworkFactory.h contiene las clases de creación de
todos los objetos de RakNet, RakPeerInterface.h es una interfaz de RakPeer que contiene
todas las funciones de usuario definidas como virtuales puras 28 . El archivo
MessageIdentifiers.h contiene todos los identificadores de mensajes utilizados por RakNet.
Un identificador de mensaje es el número contenido en el primer byte de cada mensaje.
#include <stdio.h>
#include "RakNetworkFactory.h"
#include "RakPeerInterface.h"
#include "MessageIdentifiers.h"
28
Las funciones virtuales puras definen la interfaz de una clase abstracta (que no se va a instanciar). La clase
abstracta es simplemente una interfaz para todas las clases derivadas de ésta, las cuales pueden redefinir las
funciones virtuales.
65
Ahora se define el número máximo de clientes y el puerto a utilizar para la
comunicación
#define MAX_CLIENTS 10
#define SERVER_PORT 60000
RakPeerInterface es la interfaz principal de RakNet, RakPeer contiene todas las
funciones principales de la librería. Se crea un objeto llamado peer que permitirá controlar
la comunicación.
RakPeerInterface *peer;
Ahora se define un procedimiento para imprimir los mensajes recibidos.
void PrintMessage(RPCParameters *rpcParameters) {
printf("%s\n", rpcParameters->input);
}
llama a una función de C en el sistema remoto que fue
registrado usando RegisterAsRemoteProcedureCall(). Sus parámetros son:
RakPeerInterface::RPC
•
•
•
•
•
•
•
uniqueID: Una cadena terminada en NULL que identifica la función a llamar. Es
recomendable usar la macro CLASS_MEMBER_ID para funciones de clase.
data: Los datos a enviar.
bitLength: El número de bits de los datos.
priority: Establecer el nivel de prioridad del envío. (Véase el archivo PacketPriority.h)
reliability: Fiabilidad de los datos. (Véase el archivo PacketPriority.h)
orderingChannel: Cuando se envían los mensajes en orden o secuencia, respecto
de qué canal se debe ordenar.
systemAddress: A quien envíar este mensaje o en el caso de usar broadcast a
quien no envíar el mensaje. Para especificar ninguno use
UNASSIGNED_SYSTEM_ADDRESS
•
•
•
•
broadcast: establecer a True para envíar el mensaje como broadcast.
includedTimestamp: Pase un valor para ajustar el tiempo por defecto como
ID_TIMESTAMP. 0 para no utilizar esta característica.
networkID: Para funciones estáticas pase UNASSIGNED_NETWORK_ID. Para
funciones miembro debe derivar de NetworkIDObject y pasar el valor retornado por
NetworkIDObject::GetNetworkID para ese objeto.
replyFromTarget: Si el valor es diferente de 0 se bloqueará hasta que reciba una
respuesta desde el procedimiento de destino, el cual debe estar remotamente
escrito por RPCParameters::replyToSender y copiado a replyFromTarget.
peer->RPC(
"PrintMessage",
(const char*)rpcParameters->input,
66
rpcParameters->numberOfBitsOfData,
HIGH_PRIORITY,
RELIABLE_ORDERED,
0,
rpcParameters->sender,
true,
0,
UNASSIGNED_NETWORK_ID,
0
);
}
Ahora en main(), se crea un paquete (packet) el cual se utilizará para recibir todos
los mensajes que lleguen a la aplicación. Se crea la interfaz de comunicación en peer y se
inicializa el servidor con StartUp.
int main(void) {
Packet *packet;
peer = RakNetworkFactory::GetRakPeerInterface();
peer->Startup(
MAX_CLIENTS, //Conexiones entrantes
30, Cuantos milisegundos debe esperar para cada ciclo de actualizacion
&SocketDescriptor( //Local socket
SERVER_PORT, //Puerto del servidor
0 //Dirección del host, 0 = local
),
1 //Tamaño del arreglo de SocketDescriptor.
);
printf("Starting the server.\n");
// Necesitamos permitir al servidor aceptar conexiones entrantes de los clientes.
peer->SetMaximumIncomingConnections(MAX_CLIENTS);
REGISTER_STATIC_RPC(peer, PrintMessage);
while (1) {
A continuación resta recibir los paquetes entrantes.
packet=peer->Receive();
Luego de recibido el paquere, se verifica el contenido del primer byte de datos del
paquete. El primer byte se usa para indicar el tipo de paquete que se ha recibido.
while(packet) {
switch (packet->data[0]) {
case ID_REMOTE_DISCONNECTION_NOTIFICATION:
printf("Otro cliente se ha desconectado.\n");
67
break;
case ID_REMOTE_CONNECTION_LOST:
printf("Otro cliente ha perdido la conexion.\n");
break;
case ID_REMOTE_NEW_INCOMING_CONNECTION:
printf("Otro cliente se ha conectado.\n");
break;
case ID_CONNECTION_REQUEST_ACCEPTED:
printf("Nuestra petición de conexión ”\
“ha sido aceptada.\n");
break;
case ID_NEW_INCOMING_CONNECTION:
printf("Hay una conexión entrante.\n");
break;
case ID_NO_FREE_INCOMING_CONNECTIONS:
printf("El servidor está lleno.\n");
break;
case ID_DISCONNECTION_NOTIFICATION:
printf("Un cliente se ha desconectado.\n");
break;
case ID_CONNECTION_LOST:
printf("Un cliente ha perdido la conexión.\n");
break;
default:
printf(
"El mensaje con el identificacdor "\
"%i ha llegado.\n",
packet->data[0]
);
break;
}
Liberar el paquete.
peer->DeallocatePacket(packet);
// Permanecer en el ciclo hasta que no haya mas paquetes.
packet = peer->Receive();
}
}
Liberar la conexión.
RakNetworkFactory::DestroyRakPeerInterface(peer);
return 0;
}
68
2.4.2 CLIENTE (MAIN.CPP)
Como se mencionó anteriormente, el cliente se conecta al servidor por medio del
puerto 60000, inicialmente pide la dirección IP del servidor y una vez conectado espera a
que el usuario ingrese una cadena para enviarla al servidor. Además de los archivos de
cabecera del ejemplo anterior se incluirá string.h para poder utilizar strcpy().
//Cliente
#include <stdio.h>
#include <string.h>
#include "RakNetworkFactory.h"
#include "RakPeerInterface.h"
#include "MessageIdentifiers.h"
#define SERVER_PORT 60000
RakPeerInterface *peer;
Procedimiento para imprimir los mensajes recibidos.
void PrintMessage(RPCParameters *rpcParameters) {
printf("%s\n", rpcParameters->input);
}
Se crea una cadena (str) para ingresar la dirección IP del servidor y los mensajes a
envíar.
int main(void)
{
char str[512];
Packet *packet;
peer = RakNetworkFactory::GetRakPeerInterface();
peer->Startup(1,30,&SocketDescriptor(), 1);
printf("Enter server IP or hit enter for 127.0.0.1\n");
gets(str);
if (str[0]==0){
strcpy(str, "127.0.0.1");
}
printf("Iniciando el Cliente.\n");
Aquí se realiza la conexión al servidor.
peer->Connect(
str, //Dirección
SERVER_PORT, //Puerto remoto
0, //PasswordData
0 //PasswordDataLength
69
);
Se registra la función PrintMessage para poder llamarla remotamente.
REGISTER_STATIC_RPC(peer, PrintMessage);
while (1) {
Luego se detiene la ejecución del programa en espera de que el usuario ingrese
una cadena para envíar.
printf("Ingrese una cadena para mostrar en el servidor: ");
gets(str);
Al envíar una cadena, se debe estar seguro que finaliza en NULL, por tanto, se
debe definir la longitud a strlen(str)+1. RPC toma la longitud de bits de los datos (no de
bytes), por lo que es necesario multiplicar el número de bytes por 8.
//Verificar que no sea una cadena vacía.
if (str[0])
peer->RPC(
"PrintMessage", //Nombre de la función a llamar
str, //Datos a envíar
(strlen(str)+1)*8, //Longitud de datos
HIGH_PRIORITY, //prioridad
RELIABLE_ORDERED, //fiabilidad
0, //Orden de canales
UNASSIGNED_SYSTEM_ADDRESS, //Dirección del destinatario
true, // Broadcast
0, // TimeStamp
UNASSIGNED_NETWORK_ID, //NetWorkID
0 //Respuesta desde el destino
);
Recibir y gestionar los paquetes entrantes.
packet=peer->Receive();
while(packet) {
switch (packet->data[0]) {
case ID_REMOTE_DISCONNECTION_NOTIFICATION:
printf("Otro cliente se ha desconectado.\n");
break;
case ID_REMOTE_CONNECTION_LOST:
printf("Otro cliente ha perdido la conexión.\n");
break;
case ID_REMOTE_NEW_INCOMING_CONNECTION:
printf("Otro cliente se ha conectado.\n");
break;
case ID_CONNECTION_REQUEST_ACCEPTED:
70
printf("Nuestra solicitud de conexión "\
"ha sido aceptada.\n");
break;
case ID_NEW_INCOMING_CONNECTION:
printf("Hay una conexión entrante.\n");
break;
case ID_NO_FREE_INCOMING_CONNECTIONS:
printf("El servidor está lleno.\n");
break;
case ID_DISCONNECTION_NOTIFICATION:
printf("Hemos sido desconectados.\n");
break;
case ID_CONNECTION_LOST:
printf("La conexión se ha perdido.\n");
break;
default:
printf("Ha llegado el mensaje con Id: %i.\n",
packet->data[0]);
break;
}
peer->DeallocatePacket(packet);
// Permanecer en el ciclo hasta que no haya mas paquetes.
packet = peer->Receive();
}
}
Destruir la interfaz de comunicación.
RakNetworkFactory::DestroyRakPeerInterface(peer);
return 0;
}
Sólo resta compilar ambos proyectos, ejecutar el servidor y una o varias instancias
del cliente.
3.5 Irrlicht y RakNet, envío de estructuras
En esta sección se mostrará una aplicación que envía estructuras de datos por
medio de RakNet, a diferencia de la sección anterior en la que se enviaban solo cadenas
de texto. Los archivos de proyecto se incluyen en el CD que acompaña a este
documento. Dicha aplicación consiste en dos programas, un cliente y un servidor. El
servidor contiene un cubo rotando y visible en pantalla y recibe eventos que le indican que
se debe desplazar por el plano XZ o cambiar la dirección de su rotación; estos eventos
son interceptados en el programa cliente y enviados como una estructura de datos al
programa servidor.
De nuevo es necesario hacer dos proyectos, un servidor y un cliente, en ambos
proyectos se han incluído los archivos fuente de RakNet. El servidor incluye la librería
71
para acceder a la API de Irrlicht, se crea un nodo de escena (un cubo) y se le
añade un animator de rotación. El programa cliente intercepta varias teclas: W, A, S, D y
R que tienen la función de desplazar al cubo creado en el servidor en el plano XZ (teclas
W, A, S y D) y cambiar la dirección de rotación (tecla R).
Irrlicht.lib
Además de los dos archivos main.cpp (del cliente y servidor) se crea un tercer
archivo llamado id_msg.h, el cual contiene una enumeración con los IDs de los mensajes
que se envían entre el servidor y el cliente, además de la estructura de los mensajes y
una función que convierte una serie de bytes (char *) en un objeto de la estructura de
mensajes.
3.5.1 C ABECERA COMPARTIDA ( ID_MSG.CPP)
Este archivo se debe incluír en ambos proyectos.
Assert se incluye para abortar el programa en caso de error al convertir los datos
del paquete a la estructura de mensaje.
#include <assert.h>
La siguiente enumeración se usa para definir todos los IDs de los mensajes entre
el cliente y el servidor, se debe iniciar siempre con ID_USER_PACKET_ENUM para que no se
traslapen con los IDs de los mensajes propios de RakNet.
//IDs de los eventos propios de juego
enum {
//Moviemiento de los jugadores
ID_MOVE = ID_USER_PACKET_ENUM,
//Rotación
ID_ROTATE
};
Ahora se define la estructura Message. En este caso sólo se necesita un ID para el
tipo de mensaje a enviar y dos variables de coma flotante x, z para mover el cubo por el
plano XZ. Las instrucciones #pragma pack(push,1) y #pragma pack(pop) son indispensables para
poder enviar la estructura como una secuencia de bytes; lo que hacen es forzar al
compilador a empaquetar la estructura como una sucesión de bytes alieados.
#pragma pack(push, 1)
struct Message{
unsigned char ID;
float x, z;
};
#pragma pack(pop)
72
Por último se define una función que permite convertir los datos de un paquete
(char *) a un objeto de tipo Message. En caso de que la longitud del paquete no concuerde
con el tamaño del mensaje se aborta la ejecución del programa.
Message str2message(Packet *packet) {
// Asignar los bytes de datos al tipo adecuado de estructura
Message *m = (Message *) packet->data;
assert(packet->length == sizeof(Message));
return *m;
}
3.5.2 SERVIDOR (MAIN.CPP)
Como en los ejemplos anteriores, se incluyen los archivos de cabecera de Irrlicht y
de RakNet, stdio para entrada y salida estándar y el archivo id_msg.h que está fuera de la
carpeta del poyecto (anteponer ../). También se vinclulará con Irrlicht.DLL, se oculta la
consola y se indica que se utilizará el nombre de espacio irr.
#include <stdio.h>
#include <irrlicht.h>
#include "RakNetworkFactory.h"
#include "RakPeerInterface.h"
#include "MessageIdentifiers.h"
#include "../id_msg.h"
//Vincular ocn Irrlicht.dll
#pragma comment(lib, "Irrlicht.lib")
//Ocultar la ventana de consola
#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
using namespace irr;
Luego se define el número máximo permitido de conexiones al servidor y el puerto
a utilizar.
#define MAX_CLIENTS 10
#define SERVER_PORT 60000
Se declara la interfaz de comunicación peer.
RakPeerInterface *peer;
Ahora se inicia el dispositivo Irrlicht, se establece el título para la ventana y se
crean los punteros a manejador de video y de escena.
int main(void) {
73
/*
--- Objetos de Irrlicht
*/
IrrlichtDevice *device = irr::createDevice(
video::EDT_DIRECT3D9, //Manejador de video
core::dimension2d<s32>(640,480), //Tamaño de la ventana
16, //Profundidad de color
false, //Pantalla completa
false, //stencil buffer
false, //vsync
0 //Manejador de eventos
);
device->setWindowCaption(L"Ejemplo de Irrlicht y RakNet por Cristiandlr");
//Punteros
video::IVideoDriver *driver = device->getVideoDriver();
scene::ISceneManager *smgr = device->getSceneManager();
Luego se crea un cubo, se le asigna una textura y se desactiva la iluminación a
falta de una luz dinámica en la escena. También se le agrega un animator para hacerlo
rotar respecto del eje Y.
//Agregamos un nodo de escena (un cubo) para poder mover
scene::ISceneNode *node = smgr->addCubeSceneNode();
//moverlo al origen
node->setPosition(core::vector3df(0,0,0));
//agregar una textura al cubo
node->setMaterialTexture(0,driver->getTexture("./cruz.jpg"));
//desactivar la iluminación
node->setMaterialFlag(video::EMF_LIGHTING, false);
//Agregar un animator de rotación
scene::ISceneNodeAnimator *a=0;
a = smgr->createRotationAnimator(core::vector3df(0,2.0f,0));
node->addAnimator(a);
a->drop();
Para poder visualizar la escena se necesita añadir una cámara, la cual se apuntará
al origen que es donde se colocó inicialmente el cubo y se posiciona en (0,0,-50).
//añadir una cámara y apuntarla al origen
smgr->addCameraSceneNode(
0, //parent
core::vector3df(0,0,-50), //posicion
core::vector3df(0,0,0) //ver hacia
);
74
Ahora se definen los objetos de RakNet, un puntero packet para recibir los paquetes
que lleguen desde los clientes, se inicia la interfaz de comunicación y se establece el
número máximo de conexiones entrantes.
/*
--- Objetos de RakNet
*/
Packet *packet;
peer = RakNetworkFactory::GetRakPeerInterface();
peer->Startup(MAX_CLIENTS, 30, &SocketDescriptor(SERVER_PORT,0), 1);
printf("Starting the server.\n");
// We need to let the server accept incoming connections from the clients
peer->SetMaximumIncomingConnections(MAX_CLIENTS);
Se requiere un objeto de tipo Message para “reacomodar los bytes” que llegan en
los paquetes a su estructura original. La variable booleana direction se utiliza para
determinar si el animator de rotación gira a la derecha o a la izquierda.
/*
--- Objetos propios del ejemplo
*/
Message m;
bool direction=true;
Se inicia el ciclo principal, se repite mientra el dispositivo esté en ejecución.
while (device->run()) {
driver->beginScene(
true, //backbuffer
true, //zbuffer
video::SColor(255,100,101,140) //color
);
Se captura el paquete. Si el ID del paquete (packet->data[0]) es mayor o igual a
se sabrá que es un mensaje definido para este programa y no uno
de RakNet, entonces se asigna al objeto m para acceder a la información que contiene.
ID_USER_PACKET_ENUM
packet=peer->Receive();
while(packet) {
if (packet->data[0]>= ID_USER_PACKET_ENUM)
m = str2message(packet);
switch (packet->data[0]) {
Se compara el ID del paquete para verificar el tipo de mensaje.
75
case ID_REMOTE_DISCONNECTION_NOTIFICATION:
printf("Otro cliente se ha desconectado.\n");
break;
case ID_REMOTE_CONNECTION_LOST:
printf("Otro cliente ha perdido la conexión.\n");
break;
case ID_REMOTE_NEW_INCOMING_CONNECTION:
printf("Otro cliente se ha conectado.\n");
break;
case ID_CONNECTION_REQUEST_ACCEPTED:
printf("Nuestra conexión ha sido aceptada.\n");
break;
case ID_NEW_INCOMING_CONNECTION:
printf("Hay una conexión entrante.\n");
break;
case ID_NO_FREE_INCOMING_CONNECTIONS:
printf("El Servidor está lleno.\n");
break;
case ID_DISCONNECTION_NOTIFICATION:
printf("Un cliente se ha desconectado.\n");
break;
case ID_CONNECTION_LOST:
printf("Un cliente perdió la conexión.\n");
break;
En caso de que el ID corresponda con ID_MOVE, se establece la nueva posición del
cubo en la escena.
case ID_MOVE:
node->setPosition(core::vector3df(m.x, 0, m.z));
break;
Si el ID corresponde con ID_ROTATE, se cambia la dirección de rotación del cubo.
Como este nodo ya contiene un animator de rotación, debe ser eliminado antes de
agregar el nuevo animator con la dirección adecuada.
case ID_ROTATE:
node->setPosition(core::vector3df(m.x, 0, m.z));
direction=!direction;
//quitar el animator actual
node->removeAnimators();
if(direction) {
a = smgr->createRotationAnimator(
core::vector3df(0,2.0f,0)
);
} else {
a = smgr->createRotationAnimator(
core::vector3df(0,-2.0f,0)
);
}
76
node->addAnimator(a);
a->drop();
break;
Si el ID del mensaje no está entre las opciones, simplemente se depliega un
mensaje al usuario por medio de la consola. Luego de liberar el paquete, se verifica si hay
más paquetes en espera, si los hay, continúa el el ciclo.
default:
printf("Mensaje desconocido, id=%i.\n", packet->data[0]);
break;
}
//Liberar el paquete.
peer->DeallocatePacket(packet);
// Permanecer en el ciclo hasta que no haya más paquetes.
packet = peer->Receive();
}
Sólo resta dibujar la escena.
smgr->drawAll();
driver->endScene();
}
Se libera la interfaz de comunicación antes de salir por completo.
RakNetworkFactory::DestroyRakPeerInterface(peer);
return 0;
}
3.5.3 CLIENTE (MAIN.CPP)
Para iniciar, se incluyen los archivos de cabecera para entrada y salida estándar
en la consola, string.h para manejo de cadenas, cabeceras de RakNet y por último id_msg.h.
//Cliente
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include "RakNetworkFactory.h"
#include "RakPeerInterface.h"
#include "MessageIdentifiers.h"
#include "../id_msg.h"
Ahora se define el puerto y la interfaz de comunicación, en este programa no se
incluye la librería Irrlicht porque los eventos se van a interceptar por medio de kbhit().
77
#define SERVER_PORT 60000
RakPeerInterface *peer;
int main(void) {
char str[512];
Packet *packet;
peer = RakNetworkFactory::GetRakPeerInterface();
peer->Startup(1,30,&SocketDescriptor(), 1);
Se solicita al usuario que indique la dirección IP del Servidor o que presione
ENTER para 127.0.0.1.
printf("Ingrese la dirección IP del Servidor o ENTER para 127.0.0.1\n");
gets(str);
if (str[0]==0){
strcpy(str, "127.0.0.1");
}
printf("Iniciando el Cliente.\n");
peer->Connect(str, SERVER_PORT, 0,0);
La variable key va a guardar la tecla presionada por el usuario, se crea también un
objeto de tipo Message para envíar los datos al servidor y dos variables que guardan la
posición del cubo en el servidor en el plano XZ.
unsigned char key=0;
Message m;
float X=0, Z=0;
La variable conectado indica si el Servidor ya ha aceptado o no la solicitud de
conexión. Una vez acepatada la conexión se guarda la dirección del servidor en el objeto
servidor y se interceptan los eventos de teclado.
bool conectado = false;
SystemAddress servidor;
while (1) {
if(kbhit() && conectado) {
key = getch();
if(key==27) { //salir del programa
break;
}else if(key=='w') { //Hacia adelante
m.ID = ID_MOVE;
Z+=5;
} else if (key=='s'){ //Hacia atrás
m.ID = ID_MOVE;
78
Z-=5;
} else if (key=='d'){ //Hacia la derecha
m.ID = ID_MOVE;
X+=5;
} else if (key=='a'){ //Hacia la izquierda
m.ID = ID_MOVE;
X-=5;
} else if (key=='r'){ //Cambiar la dirección de rotación
m.ID = ID_ROTATE;
printf("Cambiar rotación.\n");
}
Una vez definidos los valores de X, Z y m.ID se envía la estructura al servidor. Para
envíar cualquier estructura basta con una conversión forzada a char * para manejarla como
una secuencia de bytes: (char *)&m;
m.x = X;
m.z = Z;
peer->Send(
(char *)&m,
sizeof(m),
HIGH_PRIORITY, // PacketPriority
RELIABLE_ORDERED, // PacketReliability
0, // orderingChannel
servidor, // systemAddress
false // broadcast (true)
);
Imprimir la nueva posición del cubo.
printf("Desplazar: X=%.2f, Z=%.2f\n", X, Z);
}
Se reciben los paquetes desde el servidor (si los hay).
packet=peer->Receive();
while(packet) {
switch (packet->data[0]) {
case ID_REMOTE_DISCONNECTION_NOTIFICATION:
printf("Otro cliente se ha desconectado.\n");
break;
case ID_REMOTE_CONNECTION_LOST:
printf("Otro cliente ha perdido la conexión.\n");
break;
case ID_REMOTE_NEW_INCOMING_CONNECTION:
printf("Otro cliente se ha conectado.\n");
break;
case ID_CONNECTION_REQUEST_ACCEPTED:
printf("Nuestra conexión ha sido aceptada.\n");
conectado = true;
79
servidor = packet->systemAddress;
break;
case ID_NO_FREE_INCOMING_CONNECTIONS:
printf("El Servidor está lleno.\n");
break;
case ID_DISCONNECTION_NOTIFICATION:
printf("Hemos sido desconectados.\n");
break;
case ID_CONNECTION_LOST:
printf("Se perdió la conexión.\n");
break;
default:
printf("Nuevo mensaje con id: %i.\n", packet->data[0]);
break;
}
peer->DeallocatePacket(packet);
// Permanecemos en el ciclo hasta que no haya más paquetes.
packet = peer->Receive();
}
}
Por último se libera la interfaz de comunicación. La figura 3.4 muesta la aplicación
en ejecución.
RakNetworkFactory::DestroyRakPeerInterface(peer);
return 0;
}
Figura 3.4. Una muestra de la aplicación en ejecución. Atrás se puede ver la ventana del Servidor
con el cubo en rotación. La consola muestra los desplazamientos que han sido enviados al
servidor.
80
Capítulo IV
Aplicación: irrArena
IrrArena es un juego de primera persona que utiliza Irrlicht y RakNet. Se
pueden cargar diversos escenarios de Quake 3 y también diferentes
personajes utilizando modelos MD2. También incorpora irrKlang para
manejo de audio 2D y 3D. Esta figura muestra el logotipo del juego.
81
82
4.1 Introducción
Este capítulo presenta el diseño completo del juego de video de primera persona,
en red y utilizando Irrlicht y RakNet.
irrArena es un pequeño juego de acción de primera persona inspirado en el juego
Quake 3 Arena de Id Software. El objetivo del juego es moverse a través de todo el
escenario o campo de batalla eliminando a los demás jugadores. Cada jugador controla a
su personaje a través de una computadora conectada al Servidor Arena, el cual refleja las
acciones de cada jugador a las otras computadoras que ejecutan el programa Cliente en
la red. Cuando el nivel de vida de un jugador llega a cero éste es reubicado en el
escenario (conservando sus municiones) y el jugador que le ha eliminado suma un punto
en el conteo de eliminados. El juego termina cuando uno de los jugadores alcanza el
límite de enemigos eliminados especificado en la configuración del servidor.
Tanto el Cliente como el Servidor son configurables por medio de los archivos
config.xml que se encuentran en el mismo directorio del ejecutable. En el caso del
servidor pueden configurarse diferentes opciones, tales como: el número de enemigos
eliminados para ganar la partida, el número máximo permitido de clientes, el puerto a
utilizar y también se deben cargar los diferentes mapas de Quake 3 (archivos pk3). Para
el cliente las opciones de configuración son: Dirección IP del Servidor, puerto, volumen de
los efectos de sonido, configuración de video, lista de reproducción de audio (pueden
agregarse diferentes archivos de mp3 para escuchar mientras juega), los modelos de los
jugadores (md2) y el listado de mapas de Quake 3.
4.2 Características del Juego
•
•
•
•
•
irrArena es un juego de acción de primera persona.
Multijugador: El juego solo funciona en red y con dos o más jugadores.
Gráficos 3D: Utiliza el Engine Irrlicht para todo lo relacionado con el manejo de
gráficos 3D, cámaras, escenarios, modelos, etc.
Audio 2D y 3D: Utilizando el motor de audio irrKlang se reproducen diferentes
sonidos en 2D (tal como música de fondo) y 3D (por ejemplo, los pasos de otro
jugador que se acerca hacia (o que se aleja de) el oyente o jugador).
Configurable: El juego es configurable por medio de archivos XML. Se pueden
cambiar ciertos valores como la gravedad y velocidad de salto del jugador para
cada mapa. También se pueden añadir nuevos escenarios y modelos al juego,
elegir el manejador de video (Direct3D, OpenGL, Software…), modo de pantalla
completa o ventana, etc.
83
4.3 Instalación y puesta en marcha del juego
En el CD que acompaña a este texto se incluye tanto el código fuente de la
aplicación como los binarios.
En esta sección se describe la configuración del juego e instalación de los binarios
del juego.
En el directorio Capítulo IV/bin hay dos directorios más, cliente y servidor. Se debe
copiar la carpeta "Servidor" a la computadora de la red que se desea utilizar para servidor
y la carpeta "Cliente" a cada computadora donde se va a ejecutar un cliente o instancia
del juego. Se ejecuta "ServidorArena.exe" en el servidor, se selecciona un mapa
(utilizando el número que lo identifica) y en cada cliente se ejecuta "arena.exe" y se
selecciona un jugador (también por medio del número que lo identifica).
4.3.1 TECLAS UTILIZADAS PARA JUGAR
Las teclas utilizadas para jugar son las siguientes:
Movimiento:
W - Caminar hacia adelante
S - Caminar hacia atrás
A - Caminar hacia la izquierda
D - Caminar hacia la derecha
BARRA ESPACIADORA - Saltar
Información:
I - Información de municiones y vida del jugador
L - Listado de jugadores conectados a la partida
Reproducción de audio:
NUM * - Play / Pause
NUM / - Siguiente canción
NUM - - Bajar volumen
NUM + - Subir volumen
Acción:
CTRL IZQ - Risa
BACKSPACE - Suicidio
Clic izquierdo - Disparar
Salida:
Para salir del juego se utiliza la tecla ESC.
84
A continuación se tratará acerca de la configuración del cliente y del servidor.
El juego ya viene preconfigurado por lo que no necesita crear estos archivos
manualmente. Si Ud. quiere modificarlos, por ejemplo para jugar en pantalla completa,
entonces revise ésta documentación.
4.3.2 CONFIGURACIÓN DEL S ERVIDOR ( CONFIG.XML)
Ahora se analizará parte por parte la estructura del archivo de configuración del
Servidor irrArena: config.xml.
<?xml version="1.0" ?>
<!--Archivo de configuracion del servidor irrArena-->
<!--Cristian de Leon Ronquillo-->
Toda la configuración del juego se encuentra dentro de las etiquetas <config> y
</config>
<!--Configuracion del juego-->
<config>
Se define el número de bajas que un jugador debe efectuar sobre los demás para
ganar la partida. También el número máximo de conexiones y el puerto a utilizar.
<!--El ganador es el primero en alcanzar este numero-->
<max_muertes val="15" />
<!--maximo numero de conexiones aceptadas por el servidor-->
<max_clientes val="30" />
<!--Puerto del servidor-->
<puerto_srv val="60000" />
Solo resta agregar el listado de mapas y los puntos válidos de cada mapa. Estos
puntos se utilizan en el servidor para ubicar / reubicar a los jugadores en el mapa de
Quake 3. También se utilizan para posicionar las municiones y energía que los jugadores
pueden recoger. Es importante notar que el id del mapa debe coincidir con el id definido
en el archivo de configuración del cliente, donde se definen otros valores como
skyboxes29, gravedad, etc.
<!--Configuracion de mapas, el id debe coincidir con el id del archivo de configuracion del cliente-->
<mapa id="0" nombre="Return to Castle">
<!--Listado de puntos validos dentro del mapa para cargar a los jugadores-->
<punto id="0" x="-100" y="100" z="-100" />
<punto id="1" x="-366" y="100" z="-53" />
<punto id="2" x="200" y="245" z="830" />
29
Un skybox es un cubo de dimensiones muy grandes que se ubica alrededor del escenario y que contiene
seis texturas (una por cada lado) que simula el cielo en la escena.
85
<punto id="3" x="226" y="245" z="-290" />
<punto id="4" x="-571" y="245" z="-543" />
<punto id="5" x="-479" y="245" z="-48" />
<punto id="6" x="555" y="360" z="1061" />
<punto id="7" x="548" y="450" z="321" />
<punto id="8" x="545" y="450" z="-66" />
<punto id="9" x="-117" y="450" z="-59" />
</mapa>
<mapa id="1" nombre="ChiropteraDM">
<punto id="0" x="-1500" y="35" z="-775" />
<punto id="1" x="-700" y="75" z="-2321" />
<punto id="2" x="100" y="-157" z="-1438" />
<punto id="3" x="-1277" y="-549" z="-1047" />
<punto id="4" x="-1050" y="-325" z="-1796" />
<punto id="5" x="-2163" y="-101" z="-976" />
<punto id="6" x="-1639" y="35" z="-1471" />
<punto id="7" x="-667" y="-545" z="-660" />
</mapa>
<mapa id="2" nombre="unbalanced II">
<punto id="0" x="-1539" y="419" z="-1379" />
<punto id="1" x="-895" y="291" z="-884" />
<punto id="2" x="-1527" y="419" z="-1407" />
<punto id="3" x="-1143" y="355" z="-2335" />
<punto id="4" x="-798" y="-29" z="-2381" />
<punto id="5" x="-381" y="-93" z="-1375" />
<punto id="6" x="-917" y="99" z="-2034" />
<punto id="7" x="-682" y="99" z="-2205" />
</mapa>
<mapa id="3" nombre="JUL18">
<punto id="0" x="-806" y="163" z="-1522" />
<punto id="1" x="-1613" y="163" z="-1444" />
<punto id="2" x="-1403" y="163" z="-775" />
<punto id="3" x="-1293" y="163" z="-1400" />
</mapa>
</config>
4.3.3 CONFIGURACIÓN DEL CLIENTE ( CONFIG.XML)
Ahora se examinará la configuración del cliente. Al igual que el anterior, se coloca
toda la configuración entre las etiquetas <config> y </config>
<?xml version="1.0" ?>
<!--Archivo de configuracion del cliente irrArena-->
<!--Cristian de Leon Ronquillo-->
<!--Configuracion del juego-->
<config>
86
Lo primero es definir la dirección IP del servidor y el puerto a utilizar.
<!--IP / Hostname del servidor irrArena-->
<IP_srv val="127.0.0.1" />
<!--Puerto del servidor-->
<puerto_srv val="60000" />
Ahora se establece el volumen de los efectos de sonido del juego, tal como el
sonido de los pasos de los jugadores, los disparos, la risa del jugador, etc. El volumen
puede variar de 0.00 a 1.00.
<!--Sonidos de los jugadores y efectos especiales de audio-->
<sonidos volumen="0.8" />
•
•
•
•
•
En la configuración de video los parámetros son:
completa: 1=pantalla completa, 0=modo de ventana
anchura: Anchura en pixeles de la ventana (o modo de video si se activa pantalla
completa)
altura: Altura en pixeles de la ventana (o modo de video si se activa pantalla
completa)
profundidad: Bits de color; cantidad de colores = pow(2, profundidad)
driver: Manejador de video
1=Direct3D 9
2=Direct3D 8.1
3=Open GL 1.5
4=Software Renderer
<!--Configuracion de video-->
<video completa="0" anchura="800" altura="600" profundidad="8" driver="1" />
También es posible configurar una lista de reproducción de audio. Esta lista se
ejecutará mientras el juego esté en ejecución y es independiente en cada cliente. Los
parámetros principales son:
activa: 1=Inicialmente activa y 0=Inicialmente pausada.
volumen: Volumen de las pistas de audio. Toma valores de 0.00 a 1.00
aleatorio: 1=Reproducción aleatoria, 0=Ejecutar pistas ordenadas por su id.
<!-- Lista de reproducciOn de audio -->
<lista_reproduccion activa="0" volumen="0.45" aleatorio="0" >
Las etiquetas ítem_audio permiten añadir pistas de audio, solo basta asignar un id
numérico y la ubicación física del archivo de audio. Los formatos que se aceptan son wav,
ogg, mp3, mod, xm, it, s3m y demás formatos aceptados por irrKlang.
<item_audio id="0" archivo="./media/music/ich_will.mp3" />
87
<item_audio id="1" archivo="./media/music/drop_out.mp3" />
<item_audio id="2" archivo="./media/music/mutter.mp3" />
<item_audio id="3" archivo="./media/music/deeply_disturbed.mp3" />
<item_audio id="4" archivo="./media/music/toxicity.mp3" />
</lista_reproduccion>
La configuración de jugadores permite cargar modelos MD2 al juego. Las etiquetas
se colocan dentro de las etiquetas <jugadores> y </jugadores>. Se debe definir el
archivo de textura, la escala del modelo, los sonidos, etc. Los parámetros de la etiqueta
<jugador> son:
• id: int. Un entero que identifica al modelo
• nombre: str. Descripcion del modelo
• archivo: str. Modelo MD2
• textura: str. Textura del modelo
• escala: float. Escala del modelo
• quitar_y: float. Diferencia entre el punto de rotación del modelo y los pies del
mismo
• snd_saltar: str. Archivo de audio a ejecutar cuando el jugador salta
• snd_morir: str. Archivo de audio a ejecutar cuando el jugador sea eliminado
• snd_reir: str. Archivo de audio a ejecutar para que el jugador exprese risa
<jugador>
<!-- Configuracion de jugadores -->
<jugadores>
<jugador
id="0"
nombre="Sydney"
archivo="./media/models/sydney.md2"
textura="./media/models/sydney.bmp"
escala="1.2"
quitar_y="35.0"
snd_saltar="./media/sounds/jump_woman.wav"
snd_morir="./media/sounds/death1.wav"
snd_reir="./media/sounds/taunt_woman.wav"
/>
<jugador
id="1"
nombre="Tris"
archivo="./media/models/tris.md2"
textura="./media/models/tris1.pcx"
escala="1.0"
quitar_y="35.0"
snd_saltar="./media/sounds/jump.wav"
snd_morir="./media/sounds/death3.wav"
snd_reir="./media/sounds/taunt_man.wav"
/>
</jugadores>
88
Por último se configuran los archivos de los mapas o niveles de Quake 3. Como ya
se mencionó, el id debe coincidir con el id del archivo de configuración del servidor. Los
atributos de cada etiqueta <mapa> son los siguientes:
• nombre: (str) descripcion del mapa
• autor: (str) autor del mapa
• archivo: (str) pk3
• bsp: (str) nombre del archivo bsp contenido en el pk3
• cielo: (str) nombre de los archivos para crear un skybox (sin incluir _ft, _bk, _up,
_lf...)
• cielo_ext: (str) formato de las imagenes de skybox
• velocidad_salto: (float) velocidad inicial de salto del jugador
Todas las etiquetas <mapa> deben ir dentro de <mapas> y </mapas>.
<mapas>
<mapa id="0"
nombre="Return to Castle"
autor="Michael <|3FG20K> Cook"
archivo="./media/levels/map-20kdm2.pk3"
bsp="20kdm2.bsp"
cielo="dashsky"
cielo_ext="tga"
velocidad_salto="0.7"
gravedad="-3.0"
/>
<mapa id="1"
nombre="ChiropteraDM"
autor="Alcatraz / Nunuk / Sock"
archivo="./media/levels/chiropteraDM.pk3"
bsp="chiropteradm.bsp"
cielo="chiroptera"
cielo_ext="jpg"
velocidad_salto="0.6"
gravedad="-3.0"
/>
<mapa id="2"
nombre="unbalanced II"
autor="acid"
archivo="./media/levels/map-acid3dm7.pk3"
bsp="acid3dm7.bsp"
cielo="" <!-- No definido -->
cielo_ext="jpg"
velocidad_salto="0.8"
gravedad="-3.0"
/>
<mapa id="3"
nombre="JUL18"
autor="Julek (Julian Traciak)"
archivo="./media/levels/jul18.pk3"
89
bsp="jul18.bsp"
cielo="gwiazdy"
cielo_ext="jpg"
velocidad_salto="0.6"
gravedad="-3.0"
/>
</mapas>
Cerrar la configuración.
</config>
Es importante hacer notar que el archivo de configuración del cliente es
independiente en cada computadora en donde se ejecute una instancia de dicha
aplicación, por ejemplo, puede ejecutarse el juego en pantalla completa en una
computadora y en modo de ventana en otra o tener listas de reproducción diferentes en
cada instancia del juego.
90
4.4 Diseño del juego
Una buena forma de representar la lógica de un juego de video es por medio de un
Diagrama de Estados. Este diagrama presenta las transiciones que ocurren de un estado
a otro a causa de los eventos involucrados en la aplicación.
A continuación se muestra el diagrama de estados del Servidor en la Figura 4.2. El
servidor gestiona toda la comunicación entre los clientes, por medio del envío de eventos.
El diagrama muestra una vista general del funcionamiento de la aplicación servidor.
Figura 4.2. Diagrama de Estados del servidor IrrArena, desarrollado por el autor.
91
La aplicación del cliente es más extensa porque además de manejar la
comunicación con el servidor también controla todos los eventos de teclado y mouse,
animaciones, sonidos, etc.
En la Figura 4.3 se muestra el diagrama general del funcionamiento del servidor
del videojugo IrrArena.
Figura 4.2. Primera parte del diagrama de estados del cliente IrrArena, desarrollado por el autor.
Continúa en la siguiente página.
92
Figura 4.2. (Continuación). Segunda parte del diagrama de estados del cliente IrrArena, desarrollado
por Cristian de León.
93
94
CONCLUSIONES
1. El Engine (o Motor de gráficos) Irrlicht 3D es una librería para manejo de gráficos de
tiempo real de alto rendimiento, de código abierto e independiente de la plataforma,
escrita en C++ por Nikolaus Gebhardt. Su principal ventaja es la sencillez de uso que
presenta. Por ejemplo, con unas pocas líneas de código se puede cargar un mapa de
Quake 3, añadir luces dinámicas, interceptar eventos de teclado y ratón e incluso
hacer uso de elementos GUI. Su diseño es completamente orientado a objetos y está
muy bien estructurado y completamente documentado.
2. RakNet es una librería para manejo de red basada en UDP; presenta una amplia
gama de características entre las que se puede mencionar su sistema de replicación
de objetos (permite automáticamente crear, destruir, serializar y transmitir cualquier
objeto), implementación de algoritmos para la comunicación segura, llamadas a
procedimientos de forma remota, respuesta en tiempo crítico, etc. RakNet es
utilizada principalmente para videojuegos, pero es independiente de la aplicación que
se le quiera dar. Se puede utilizar en proyectos no-comerciales bajo la licencia
Creative Commons “Reconocimiento-No comercial 2.5 Genérica”. En general es una
librería bastante extensa y muy bien diseñada.
3. Al desarrollar un juego de video es sumamente importante realizar la planeación y
diseño del mismo. Los videojuegos son aplicaciones que generalmente tienen un alto
grado de complejidad, se debe definir desde un principio el tema y el tipo de juego, las
características que pretende implementar y los objetivos del mismo. El éxito de un
juego de video depende en gran parte de la idea que lo origina. El desarrollo de
videojuegos incluye mucho más que solo la lógica y programación, involucra a
expertos en diversas materias, por ejemplo: artistas gráficos para diseñar los
personajes, pantallas y escenarios, ingenieros de audio, programadores, diseñadores
3D para modelar los personajes, los entornos y otros. El juego presentado en este
texto “irrArena” se centra principalmente en el área de diseño y programación y utiliza
recursos de diferentes sitios de Internet como stocks de audio y mapas de Quake 3 de
diversos autores.
95
96
GLOSARIO
AES: En criptografía, Advanced Encryption Standard (AES), también conocido como
Rijndael, es un esquema de cifrado por bloques adoptado como un estándar de cifrado
por el gobierno de los Estados Unidos. Se espera que sea usado en el mundo entero y
analizado exhaustivamente, como fue el caso de su predecesor, el Data Encryption
Standard (DES). El AES fue anunciado por el Instituto Nacional de Estandares y
Tecnología (NIST) como FIPS PUB 197 de los Estados Unidos (FIPS 197) el 26 de
noviembre de 2001 después de un proceso de estandarización que duró 5 años. AES
tiene un tamaño de bloque fijo de 128 bits y tamaños de llave de 128, 192 ó 256 bits.
Fuente: http://es.wikipedia.org/wiki/AES
Alpha blending: Es la combinación del canal alpha con otras capas en una imagen con el
objetivo de mostrar objetos translúcidos. El canal alpha es un canal adicional a los
canales de color de 8 bits usado con cada píxel en una gráfica de 32 bits y puede
representar hasta 256 niveles de transparencia. El color negro (0) y el blanco (255)
representan imagenes completamente opacas y completamente translúcidas
respectivamente, mientras las tonalidades de gris (1-254) representan niveles de
transparencia.
Fuente:
http://www.pcmag.com/encyclopedia_term/0,2542,t=alpha+blending&i=37668,00.asp
Bilinear filtering: Es un método de filtrado utilizado para suavizar las texturas cuando son
desplegadas en un tamaño diferente del tamaño real de la imagen.
Blitting (Bit blit): Es una operación de gráficos de computadora en la cual varios
patrones de mapas de bits son combinados en una sola imagen utilizando una operación
raster (por ejemplo una operación AND entre matrices).
Cuádrica: (o superficie cuádrica) es una hipersuperficie D-dimensional representada por
una ecuación de segundo grado en variables (coordenadas) espaciales. La ecuación
normalizada para una cuádrica tridimensional (D = 3) centrada en el origen (0,0,0) es:
Algunas superficies cuádricas son: Elipsoide, esferoide, esfera,
paraboloide elíptico, paraboloide circular, paraboloide hiperbólico, hiperboloide de una
hoja, hiperboloide de dos hojas, cono, cilindo elíptico, cilindro circular, cilindro hiperbólico
y cilindro parabólico. Fuente: http://es.wikipedia.org/wiki/Cu%C3%A1drica
Gouraud shading: Es un método usado en gráficos de computadora para simular la
diferencia entre los efectos de color y de luz sobre la superficie de un objeto. Se llama así
por
su
inventor
el
científico
francés
Henri
Gouraud.
Fuente:
http://en.wikipedia.org/wiki/Gouraud_shading
Hash: (o función hash) Se refiere a una función o método para generar claves o llaves
que representen de manera casi unívoca a un documento, registro, archivo, etc., resumir
o identificar un dato a través de la probabilidad, utilizando una función hash o algoritmo
97
hash. Un hash es el resultado de dicha función o algoritmo. Una función de hash es una
función para resumir o identificar probabilísticamente un gran conjunto de información,
dando como resultado un conjunto imagen finito generalmente menor (un subconjunto de
los números naturales por ejemplo). Varían en los conjuntos de partida y de llegada y en
cómo afectan a la salida similitudes o patrones de la entrada. Una propiedad fundamental
del hashing es que si dos resultados de una misma función son diferentes, entonces las
dos entradas que generaron dichos resultados también lo son. Fuente:
http://es.wikipedia.org/wiki/Funci%C3%B3n_hash
Mapa de luz (Lightmap): Es una estructura de datos de luz 3D que contiene el brillo de
superficies en un juego de video. Los mapas de luz son precalculados y usado para
objetos estáticos. Quake fue el primer juego de computadora en utilizar mapas de luz para
agilizar el proceso de renderizado evitando que los pisos se vean distorsionados. Fuente:
http://en.wikipedia.org/wiki/Lightmap
Morph target animation: (también conocida como animación por vértices) Es un método
de animación computarizada 3D que algunas veces es utilizado como una alternativa a la
animación con esqueletos (skeletal animation). Estas animaciones son almacenadas
como una serie de posiciones de los vértices de un objeto o modelo. En cada fotograma
de animación los vértices se mueven a una posición diferente. Fuente:
http://en.wikipedia.org/wiki/Morph_target_animation
Pixel Shader: Es un programa de sombreado, normalmente ejecutado en la unidad de
procesamiento gráfico. Este añade efectos 3D de sombreado e iluminación por píxel en
una imagen. Un pixel shader sirve para manipular un píxel, por lo general para aplicar un
efecto sobre la imagen, por ejemplo, el realismo, bump maping, sombras, explosiones etc.
Fuete: http://es.wikipedia.org/wiki/Pixel_Shaders
Roll-off: Es la atenuación de un filtro de frecuencia a paso bajo o a paso alto, que está
definida generalmente como la frecuencia en la que la respuesta es reducida por -3 dB.
Fuente: http://www.audioc.com/library1/glossary.htm
RSA: Es el sistema criptográfico con clave pública RSA, que es un algoritmo asimétrico
cifrador de bloques, que utiliza una clave pública, la cual se distribuye (en forma
autenticada preferentemente), y otra privada, la cual es guardada en secreto por su
propietario. La clave pública se genera a partir de la clave privada. Una clave es un
número de gran tamaño, que una persona puede conceptualizar como una cadena de bits
o bytes. Cuando se quiere enviar un mensaje, el emisor busca la clave pública de cifrado
del receptor, cifra su mensaje con esa clave, y una vez que el mensaje cifrado llega al
receptor, éste se ocupa de descifrarlo usando su clave privada. Fuente:
http://es.wikipedia.org/wiki/RSA
Serialización: (o marshalling en inglés) consiste en un proceso de codificación de un
Objeto (programación orientada a objetos) en un medio de almacenamiento (como puede
ser un archivo, o un buffer de memoria) con el fin de transmitirlo a través de una conexión
en red como una serie de bytes o en un formato humanamente más legible como XML. La
98
serie de bytes o el formato pueden ser usados para crear un nuevo objeto que es idéntico
en todo al original, incluido su estado interno. La serialización es un mecanismo
ampliamente usado para transportar objetos a través de una red, para hacer persistente
un objeto en un archivo o base de datos, o para distribuir objetos idénticos a varias
aplicaciones o localizaciones. Fuente: http://es.wikipedia.org/wiki/Serializaci%C3%B3n
SHA: (Secure Hash Algorithm, Algoritmo de Hash Seguro) es un sistema de funciones
hash criptográficas relacionadas de la Agencia de Seguridad Nacional de los Estados
Unidos y publicadas por el National Institute of Standards and Technology (NIST). El
primer miembro de la familia fue publicado en 1993 es oficialmente llamado SHA. Sin
embargo, hoy día, no oficialmente se le llama SHA-0 para evitar confusiones con sus
sucesores. Dos años más tarde el primer sucesor de SHA fue publicado con el nombre de
SHA-1. Existen cuatro variantes más que se han publicado desde entonces cuyas
diferencias se basan en un diseño algo modificado y rangos de salida incrementados:
SHA-224, SHA-256, SHA-384, y SHA-512 (llamándose SHA-2 a todos ellos). Fuente:
http://es.wikipedia.org/wiki/SHA
Shader: Un Shader es un conjunto de instrucciones gráficas destinadas para el
acelerador gráfico, estas instrucciones dan el aspecto final de un objeto. Los Shaders
determinan
materiales,
efectos,
color,
luz,
sombra
y
etc.
Fuente:
http://es.wikipedia.org/wiki/Shader
Skeletal animation: Es una técnica utilizada en animación por computadora,
particularmente en la animación de vertebrados en la cual un modelo es representado en
dos partes: Una representación de la superficie utilizada para dibujar el modelo (llamada
piel) y un conjunto jerárquico de huesos utilizados únicamente para la animación
(Llamado el esqueleto). Esta técnica es usada para construir una serie de "huesos", en la
que cada uno tiene una transformación tridimensional (que incluye su posición, escala y
orientación), y opcionalmente un hueso padre. Estos huesos forman una jerarquía. La
transformación completa de un nodo hijo (hueso) es el producto de la transformación de
su nodo padre y su propia transformación. De esta forma, por ejemplo, moviendo el hueso
superior de la pierna de un modelo, la parte inferior de la pierna también se moverá.
Conforme el modelo es animado, los huesos cambian su transformación en el tiempo,
bajo
la
influencia
de
un
controlador
de
animación.
Fuente:
http://en.wikipedia.org/wiki/Skeletal_animation
Stencil buffer: Es un buffer extra, en adición al buffer de color (pixel buffer) y el buffer de
profundidad (z-buffering) que se encuentran en el hardware moderno de gráficos por
computadora. El buffer está por píxel y trabaja en valores enteros, usualmente con una
profundidad de un byte por píxel. La profundidad del buffer y el stencil buffer a menudo
comparten un área de la memoria del hardware de gráficos. En el caso más simple, el
stencil buffer es utilizado para limitar el área de renderizado (stenciling). Otros usos mas
avanzados del stencil buffer hacen uso de la amplia conexión entre el buffer de
profundidad (depth buffer) y el stencil buffer en el pipeline de renderizado (por ejemplo, los
valores del stencil o troquel (área delimitada de dibujo) pueden ser automáticamente
incrementados/decrementados por cada píxel que falló o pasó la prueba de profundidad).
99
La simple combinación de la prueba de profundidad y el modificador de stencil permiten
un gran número de efectos posibles (tal como sombras, dibujo de siluetas o alta
iluminación de intersecciones entre formas complejas) aunque éstas a menudo requieren
múltiples procesos de renderizado y por consiguiente añaden una pesada carga de
trabajo al hardware de gráficos. La aplicación más común sigue siendo la de añadir
sombras para aplicaciones 3D. Esto también es utilizado para reflejos en planos. Fuente:
http://en.wikipedia.org/wiki/Stencil_buffer
Streaming: Es un término que se refiere a ver u oír un archivo directamente desde
internet sin necesidad de descargarlo antes al ordenador. Se podría describir como "hacer
clic y obtener". En términos más complejos podría decirse que describe una estrategia
sobre demanda para la distribución de contenido multimedia a través del internet. Fuente:
http://es.wikipedia.org/wiki/Buffer%C3%A9o
SYN Cookies: Representan el elemento principal de una técnica utilizada para evitar
ataques de inundación SYN (SYN Flood). El inventor de esta técnica es Daniel J.
Bernstein. Las SYN Cookies evitan que el servidor descarte las conexiones cuando la cola
SYN está llena. En lugar de eso, el servidor se comporta como si la cosa SYN fuera
ampliada. El servidor envía de regreso la respuesta apropiada SYN+ACK a los clientes y
descarta el paquete de la cola. Fuente: http://en.wikipedia.org/wiki/SYN_cookies
SYN Flood Atack: Es un método de ataque en redes que consiste en que el atacante
envía varios paquetes sin enviar el mensaje de acuse de recibo (ACK) de regreso al
servidor. Por consiguiente, las conexiones permanecen abiertas y consumen recursos del
servidor. Otro usuario en la red trata de conectarse al servidor, el cual deniega la conexión
a causa de las otras muchas conexiones abiertas.
Fuente: http://en.wikipedia.org/wiki/SYN_flood
UDP: (User Datagram Protocol) es un protocolo del nivel de transporte basado en el
intercambio de datagramas. Permite el envío de datagramas a través de la red sin que se
haya establecido previamente una conexión, ya que el propio datagrama incorpora
suficiente información de direccionamiento en su cabecera. Tampoco tiene confirmación,
ni control de flujo, por lo que los paquetes pueden adelantarse unos a otros; y tampoco se
sabe si ha llegado correctamente, ya que no hay confirmación de entrega o de recepción.
Fuente: http://es.wikipedia.org/wiki/UDP
Vertex Shader: Es una herramienta capaz de trabajar con la estructura de vértices de los
modelos tridimensionales y con ello realizar operaciones matemáticas modificando estas
variables y así definiendo colores, texturas y incidencia de la luz. Esto da libertad a los
programadores para realizar diferentes efectos desde la deformación de un objeto hasta
la recreación de las olas del mar. En caso de representaciones gráficas de pelo se
basaría en los vértices de la malla dando un efecto más realista al resultado. Con lo que
conlleva una rápida ejecución de la imagen puesto que se utiliza el hardware especifico,
en
este
caso
el
de
las
tarjetas
gráficas.
Fuente:
http://es.wikipedia.org/wiki/Vertex_Shaders
100
BIBLIOGRAFÍA
1 Bourg , David M. 2002. Physics for Game Developers. USA: Editorial O’reilly &
Associates, Inc.
2 Cplusplus.com. Standard Template Library Reference. En línea:
http://www.cplusplus.com/reference/stl/
3 Diversos autores. Irrlicht3D Engine Wiki. En línea: http://www.irrlicht3d.org/wiki/
4 Gebhardt, Nikolaus. Irrlicht3D Tutorials. En línea:
http://irrlicht.sourceforge.net/tutorials.html
5 Gebhardt, Nikolaus.2008. Irrlicht3D Engine API Reference. En línea:
http://irrlicht.sourceforge.net/docu/index.html
6 Grossman, Stanley I. 1996. Algebra Lineal. Mèxico: Editorial McGraw-Hill.
7 Jenkins Software. Manual de programación con RakNet. En línea:
http://www.jenkinssoftware.com/raknet/manual/index.html
8 Microsoft. Matrices y Transformaciones, Microsoft DirectX 8.0 Programmer’s
Reference. Traducción en línea:
http://idam.ladei.org.ar/Tutoriales/Direct3D/Intro_MatTransf.html.
9 Millington, Ian. 2007. Game Physics Engine Development. USA: Morgan
Kaufmann Publishers.
10 Nakos, George y Joyner, David. 1999. Álgebra Lineal con Aplicaciones.
México: Editorial Thomson.
101
102
APÉNDICE A
Licencia de Irrlicht3d30
LICENSE
The license of the Irrlicht Engine is based on the zlib/libpng license, only the name
and the authors name have been replaced. Even though this license does not require you
to mention that you are using the Irrlicht Engine in your product, an acknowledgement
would be highly appreciated.
Please note that the Irrlicht Engine is based in part on the work of the Independent
JPEG Group and the zlib. This means that if you use the Irrlicht Engine in your product,
you must acknowledge somewhere in your documentation that you've used the IJG code.
It would also be nice to mention that you use the Irrlicht Engine and the zlib. See the
README files in the jpeglib and the zlib for further informations.
Irrlicht Engine License
The Irrlicht Engine License
Copyright © 2002-2005 Nikolaus Gebhardt
This software is provided 'as-is', without any express or implied warranty. In no event will
the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose, including
commercial applications, and to alter it and redistribute it freely, subject to the following
restrictions:
1. The origin of this software must not be misrepresented; you must not claim that you
wrote the original software. If you use this software in a product, an
acknowledgment in the product documentation would be appreciated but is not
required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
30
Texto original de la licencia: http://irrlicht.sourceforge.net/license.html
103
LICENCIA
La licencia31 del Engine Irrlicht está basada en la licencia de zlib/libpng, únicamente
el nombre y los autores han sido reemplazados, incluso aunque esta licencia no requiere
que usted mencione que está utilizando el Engine Irrlicht en su producto, mencionarlo
sería sumamente apreciado.
Por favor note que el Engine Irrlicht está basado en parte en el trabajo del Grupo
Independiente JPEG y zlib. Esto significa que si Ud. utiliza el Engine Irrlicht en su
producto, debe mencionar en algún lugar de su documentación que ha usado el código de
IJG (Independent JPEG Group). También sería apreciado que mencione que está usando
el Engine Irrlicht y zlib. Vea el archivo README incluido en jpeglib y zlib para mayor
información al respecto.
Irrlicht Engine License
La licencia del Engine Irrlicht
Derechos Reservados © 2002-2005 Nikolaus Gebhardt
Este software se provee 'como-está', sin ninguna garantía implícita o expresa. Por
ningún motivo los autores se harán responsables por ningún daño causado por éste
software.
Se concede permiso de usar este software para cualquier propósito, incluyendo
aplicaciones comerciales, libre modificación y redistribución del mismo, sujeto bajo las
siguientes restricciones:
1. El origen de este software no puede ser alterado; usted no puede reclamar haber
escrito el software original. Si usted utiliza este software en una aplicación, mencionarlo
en la documentación del producto sería apreciado pero no es requerido.
2. Las versiones alteradas o modificadas del código fuente de este software deben
ser expresamente marcadas como tal, y no pueden ser presentadas como el software
original.
3. Esta nota no debería ser alterada o eliminada de ninguna distribución de este
software.
31
Traducción realizada por el autor.
104
APÉNDICE B
Contribuciones y Recursos
Irrlicht 3D Engine: Motor de gráficos 3D de tiempo real.
http://irrlicht.sourceforge.net/
RakNet: Librería para manejo de red.
http://www.jenkinssoftware.com/
IrrKlang: Librería para manejo de audio 2D y 3D
http://www.ambiera.com/irrklang/
irrXML: XML parser
http://www.ambiera.com/irrxml/
Mapas de Quake 3: Varios mapas incluídos en el ejemplo fueron descargados de
lvlworld.com
http://lvlworld.com/
Sonidos y modelos MD2:
Varios sonidos del juego de ejemplo fueron extraídos del juego Quake 3 Arena y el
personaje Tris y el arma utilizada fueron extraídos del juego Quake 2. Tanto los juegos
Quake 2 como Quake 3 han sido liberados por sus autores bajo la licencia GNU GPL.
http://cristiandlr.blogspot.com
105
106
APÉNDICE C
CONTENIDO DEL CD
El CD que acompaña a este material incluye todos los ejemplos como proyectos
de Visual Studio 2005 además de los instaladores necesarios para ejecutar dichas
aplicaciones. La estructura de directorios es la siguiente:
Videojuegos3D.pdf Este documento en formato pdf
Capítulo II*
LEAME.TXT
Sección 2.3* Proyecto
Sección 2.4* Proyecto
Sección 2.5* Proyecto
Sección 2.6* Proyecto
Sección 2.7* Proyecto
Sección 2.8* Proyecto
Sección 2.9* Proyecto
Capítulo III*
LEAME.TXT
Sección 3.4* Proyecto
Sección 3.5* Proyecto
Capítulo IV*
LEAME.TXT
Arena source*
id_msg.h Cabecera compartida por ambos proyectos cli y srv
cli* Proyecto
srv* Proyecto
imagenes* Algunas imagenes hechas para el juego (jpg, Freehand y psd)
diagramas* Diseño del juego (formatos jpg y SmartDraw)
bin*
INSTALAR.TXT
Cliente* Binarios
irrKlang.dll DLL de irrKlang
ikpMP3.dll MP3
config.xml XML de configuración del Cliente
arena.exe Ejecutable del cliente
media* Recursos del juego
Servidor*
config.xml XML de configuración del Servidor
ServidorArena.exe
Los archivos LEAME.TXT situados dentro de cada directorio principal contienen
información detallada de cada uno de los proyectos incluidos.
*Directorio.
107
Descargar