Con este inicio una serie de artículos telacionados con la detección de colisiones en videojuegos 3D. Esta servirá más tarde para mover objetos por este mundo 3D sin que se salgan de los límites, o se atraviesen los unos a los otros por ejemplo.
Así pues la detección de colisiones consiste básicamente en comprobar si nuestros objetos interseccionan en el espacio, pues bien, la detección de colisiones en un espacio tridimensional, se basa en XNA en los objetos BoundingBox y BoundingSphere, que no son más que cuerpos geométricos que no se renderizan pero que utilizaremos para realizar el cálculo de las colisiones. Tened en cuenta que si hiciéramos la comprobación de colisiones polígono a polígono necesitaríamos una CPU que todavía hoy no se ha fabricado para poder hacer los cálculos de forma rápida….
Como una imagen vale más que mil palabras, aquí tenemos un par de ejemplos de boundingboxes, como véis, no son más que prismas que envielven unos modelos 3D. En un juego obviamente estos prismas no se renderizan, pero puede ser útil hacerlo para debugar nuestro juego. Un boundingShpere sería exactamente lo mismo, pero obviamente tendríamos una esfera en lugar de un prisma.
Recordar aquí que siempre va a ser mucho más óptio utilizar BoundingSpheres que no BoundingBoxes. ¿Porqué? porque las esferas son fáciles de transformar… y es muy fácil de comprobar si interseccionan con otras esferas. Al fin y al cabo, matemáticamente estamos hablando de un centro en un vector 3D y un radio… así pues las operaciones con esferas son muy rápidas. Claro está que las cajas van a ser necesarias a veces… pero es recomendable usarlas solo para objetos del escenario, como es nuestro caso en estos momentos.
Tanto la clase BoundingBox como la BoundingSphere tienen como método más importante el “Intersects”. Como no quiero quitarle el pan al MSDN, la explicación detallada de estas clases y sus métodos podéis consultarla allí. Lo que si haré es destacar que el intersects nos permitirá comrpobar si el objeto bounding intersecciona con múltiples objetos muy interesantes:
- otros boundingbox
- otros boundingsphere
- rayos
- frustrums
- planos
Básicamente aquí lo utilizaré para detectar colisiones con otros objetos de tipo bounding, pero tened presentes las posibilidades. En artículos anteriores he hablado tanto de frustrums como de rayos, podéis consultarlos si tenéis ganas 🙂
En este caso concretamente desarrollaré una clase que permitirá aplicar BoundingBoxes y BoundingSpheres de forma masiva sobre un “mundo 3D”. El hecho es que si tenemos un mundo, o nivel del juego, sobre el cual queremos hacer detección de colisiones, esto se traduce en que tenemos que crear un montón de BoundingBox y BoundingSpheres. ¿Cómo los creamos y mantenemos? ¿Cómo los instanciamos en el código? A ver, lo ideal, sería tener un editor visual de niveles, y hacerlo gráficamente, y finalmente serializar el “mundo” en un XML, para cargarlo después desde el juego, deserializando ese XML.
Pues bien… como no voy a currarme aquí y ahora un editor de niveles… espero que os conformaréis con la serialización y deserialización de / a XML. Esto es algo sencillísimo para cualquier programador de .Net, pero a ser posible hay que hacerlo con un poco de gracia. Lo que he planteado es lo siguiente:
El caso es que tendremos una clase que he llamado DatosBounding que será deserializada desde un fichero XML, y con ella una lisa de objetos de tipo DatosBoundingBox y DatosBoundingSphere. Esta información posteriormente la podremos asignar a la clase que contenga la información del mundo o escenario 3D.
Para serializar la clase a un XML tendríamos el siguiente código:
1: DatosBounding listaBoundings = new DatosBounding();
2:
3: listaBoundings.listaBoundingSpheres.Add(new DatosBoundingSphere(new Vector3(1.0f,2.5f,3.2322f), 5.4f));
4: listaBoundings.listaBoundingSpheres.Add(new DatosBoundingSphere(new Vector3(4.0f, 4.5f, 2.2322f), 22.4f));
5: listaBoundings.listaBoundingBoxes.Add(new DatosBoundingBox(new Vector3(1,2,3), new Vector3(5,6,7)));
6:
7: XmlSerializer ser = new XmlSerializer(typeof(DatosBounding));
8: TextWriter escritor = new StreamWriter(@"C:test.xml");
9: ser.Serialize(escritor, listaBoundings);
10:
11: escritor.Close();
Lo cual, una vez ejecutado, generará el siguiente fichero XML:
<?xml version=»1.0″ encoding=»utf-8″?>
<DatosBounding xmlns:xsi=»http://www.w3.org/2001/XMLSchema-instance» xmlns:xsd=»http://www.w3.org/2001/XMLSchema»>
<listaBoundingSpheres>
<DatosBoundingSphere>
<Radio>0</Radio>
<Posicion>
<X>1</X>
<Y>2.5</Y>
<Z>3.2322</Z>
</Posicion>
</DatosBoundingSphere>
<DatosBoundingSphere>
<Radio>0</Radio>
<Posicion>
<X>4</X>
<Y>4.5</Y>
<Z>2.2322</Z>
</Posicion>
</DatosBoundingSphere>
</listaBoundingSpheres>
<listaBoundingBoxes>
<DatosBoundingBox>
<PosicionMin>
<X>1</X>
<Y>2</Y>
<Z>3</Z>
</PosicionMin>
<PosicionMax>
<X>5</X>
<Y>6</Y>
<Z>7</Z>
</PosicionMax>
</DatosBoundingBox>
</listaBoundingBoxes>
</DatosBounding>
Posteriormente, desde nuestro juego, necesitaremos cargar esta información (supuestamente en un juego el XML sería muchísimo mayor, la cual cosa compensaría la serialización y deserialización). El proceso de carga de un XML se llama deserialización, y el código sería este:
1: DatosBounding listaBoundings2 = new DatosBounding();
2:
3: TextReader lector = new StreamReader(@"C:test.xml");
4: listaBoundings2 = (DatosBounding)ser.Deserialize(lector);
5: lector.Close();
Observad que leo/almaceno los datos en la raíz de la C:, chicos, no hagáis esto en casa 🙂 Lo importante aquí es que después de llamar al método Deserialize ya tendríamos una lista de Boundings (listaBoundings2) cargada con los datos del XML!
En el siguiente episodio… más! Hasta entonces, si queréis podéis apalearme por escribir tanto 😛