2011-08-20 11 views
7

Estoy jugando con un pequeño proyecto de juego y como no tengo mucha experiencia en TDD me encantaría obtener algunas opiniones de expertos sobre un par de cosas.TDD And Game Physics

Antes que nada, me di cuenta desde el principio que el TDD no parecía ideal para el desarrollo de juegos. Parece que las opiniones varían bastante sobre el tema. Mi opinión inicial sin educación fue que TDD parecía que funcionaría muy bien para toda la lógica del juego. Pensé que todo lo que se ocupara de la salida de video y el sonido se resumiría en clases que se probarían visualmente.

Las cosas empezaron bien. El objetivo era crear un juego de vuelo espacial 2d (asteroides para aquellos que se preocupan). Creé una serie de pruebas unitarias para la clase Ship. Cosas como la inicialización, la rotación, se pueden probar fácilmente en una serie como: GetRotation(), TurnRotateRightOn(), Update (1), GetRotation(), Expect_NE (rotation1, rotation2). Luego llegué al primer problema.

Mi comprensión de TDD es que debes escribir la prueba sobre cómo crees que deberías usar la clase. Quiero que la nave se pueda mover, así que escribí una clase que básicamente decía. GetCoordinates(), ThrustOn(), Update (1), GetCoordinates(). Eso estuvo bien para asegurarse de que el barco se movió a algún lugar. Sin embargo, rápidamente me di cuenta de que tenía que asegurarme de que la nave se movía en la dirección correcta y con la velocidad correcta. Lo que siguió fue una prueba de unidad de 75 líneas donde básicamente tuve que inicializar la rotación, verificar las coordenadas, inicializar el empuje, actualizar la nave, obtener las nuevas coordenadas, verificar la nueva rotación. Además, no veo la necesidad de obtener la velocidad del barco en el juego (solo las coordenadas, el barco debería actualizarse). Debido a esto, no tenía forma directa de obtener la velocidad. Así que la prueba básicamente tuvo que volver a calcular cuál debería haber sido la velocidad para poder asegurarme de que coincida con las coordenadas que obtenía después de la actualización. En general, esto fue muy complicado y llevó mucho tiempo, pero funcionó. La prueba falló, hizo pasar la prueba, etc.

Esto estuvo bien hasta más tarde cuando me di cuenta de que quería refactorizar el código de actualización del barco en una clase abstracta "Actor". Me di cuenta de que si bien cada subclase de Actor necesitaría poder calcular correctamente una nueva posición, no todas las subclases necesariamente actualizarían su velocidad de la misma manera (algunas colisionan, otras no, algunas tienen velocidades estáticas). Ahora, básicamente me enfrento a la posibilidad de duplicar y alterar ese enorme y enorme código de prueba y no puedo evitar pensar que debería haber una mejor manera.

¿Alguien tiene alguna experiencia en el manejo de pruebas unitarias de este tipo de funcionamiento de caja negra compleja? Parece que básicamente tengo que escribir exactamente el mismo código de física en la prueba para saber cuál es el resultado. Parece realmente contraproducente, y estoy seguro de que me estoy perdiendo el sentido de todo esto en algún punto del camino. Agradecería mucho cualquier ayuda o consejo que cualquiera pueda ofrecer.

+0

¿Esta pregunta no pertenece al gamedev SE? Probablemente podrían responder mejor allí. – Spoike

+0

¡No estaba al tanto del Gamedev SE! Haré preguntas similares allí en el futuro. ¡Gracias! – Landon

Respuesta

5

que sugieren que se inicia mediante la creación de un componente que calcula la posición y orientación dada una secuencia de entradas de control. Ese componente entonces constituye una "unidad" para el propósito de la prueba. Los casos de prueba para ese componente ejercitarían todos los escenarios que se puedan imaginar: aceleración cero, aceleración constante distinta de cero, comandos de aceleración pulsada, etc. Si la aplicación no necesita velocidad, el componente no expondrá ninguna funcionalidad relacionado con la velocidad.

Al generar los resultados esperados para la inclusión en los ensayos, es importante tener una alta confianza en que los resultados esperados son correctos. Por esta razón, es necesario minimizar la cantidad de código requerido para generar los resultados esperados. En particular, si se encuentra escribiendo andamios de prueba que son casi tan complejos como el componente bajo prueba, entonces la posibilidad de que los errores aparezcan por sí mismos se convierte en una seria preocupación.

En este caso, me gustaría generar los datos de prueba directamente a partir de las ecuaciones de movimiento. Utilizo Mathematica para este propósito, ya que puedo ingresar las ecuaciones directamente, resolverlas y luego generar gráficos y tablas de los resultados. Los gráficos me permiten visualizar los resultados y, por lo tanto, tengo la confianza de que son creíbles. Excel/OpenOffice/Google Apps podría ser utilizado para el mismo propósito, así como las alternativas de código abierto para Mathematica como Sage. Independientemente de lo que uno elija, la principal preocupación es poder resolver las ecuaciones de movimiento sin tener que escribir un código no trivial.

Una vez que tenemos un buen conjunto de casos de prueba junto con los resultados esperados, se puede codificar hasta la prueba unitaria. Tenga en cuenta que el código de prueba es muy simple y no realiza ningún cálculo en sí mismo. Simplemente compara la salida del componente con los resultados codificados que obtuvimos anteriormente. Con los casos de prueba implementados, podemos escribir el componente en sí mismo, agregando código hasta que todas las pruebas pasen. Por supuesto, en el TDD estricto estas acciones ocurren exactamente en este orden. Confieso que no me apego personalmente a la cascada y tiendo a rebotar entre crear datos de prueba, escribir pruebas y escribir código de componente.

Los documentos de Mathematica o Excel mismos tienen una vida útil más allá de la creación inicial de las pruebas. Se pueden usar nuevamente cuando se agrega una nueva funcionalidad o (cielo no lo permita) si se encuentran errores más adelante. Yo recomendaría tratar los documentos como el código fuente.

Al final de este ejercicio, el resultado es un componente "a prueba de bombas" que nos hemos convencido calculará la posición correcta y la orientación de un objeto bajo un determinado conjunto de entradas de control. Desde esta base, podemos construir más componentes que usen esa funcionalidad, como barcos, asteroides, platillos y disparos. Para evitar una explosión combinatoria de casos de prueba para cada componente, me apartaría de un enfoque estricto de prueba de caja negra. Entonces, por ejemplo, si estuviéramos probando un componente de "envío", escribiríamos pruebas sabiendo que usa el componente de posición/orientación. Usando este conocimiento de caja blanca, podemos evitar volver a probar todos los casos de esquina relacionados con el movimiento.Las pruebas de unidad de barco pueden realizar "pruebas de humo" que verifican que los barcos sí responden a las entradas de control, pero el objetivo principal sería probar cualquier funcionalidad exclusiva del componente del barco.

tanto, para resumir:

  • maquillaje cálculo de la posición/orientación de la responsabilidad de un solo componente
  • generar los datos para un amplio conjunto de casos de prueba utilizando una herramienta externa que da mucha confianza de que los datos son correctos
  • evitar lógica de cálculo complejo en las propias pruebas
  • esquivar los explosión combinatoria de casos de prueba en una jerarquía de componentes mediante el uso de caja blanca conocimiento
+0

¡Esto es justo lo que estaba buscando! Buen consejo. ¡Gracias! – Landon

1

Puede simplificar un poco sus pruebas. En lugar de buscar el vector correcto y la aceleración, simplemente puedes verificar que el objeto bajo prueba se mueva. En la mayoría de los juegos, introducirías una pequeña cantidad de aleatoriedad en el modelo de física de todos modos para mantener las cosas interesantes.

1

Creo que podría ser útil si tomas un enfoque más "episódico". En lugar de seguir las coordenadas y la rotación a lo largo de un continuo, que parece que está haciendo, simplemente podría especificar dónde debería estar su nave espacial y en qué rotación, después de 30 pasos de juego. Si eso resulta correcto, entonces tu nave probablemente hizo todas las cosas correctas en el medio también. Escribirás mucho menos código de prueba de esa manera.

La misma idea funciona centrándose en el "episodio" de una colisión. Si una bola de billar tiene la intención de rebotar en dos paredes y golpear otra bola, usted podría simplemente provocar un evento cuando ocurra la colisión final y verificar el "ángulo de incidencia" de la colisión.Una vez más, no estás verificando todos los pasos intermedios. Si el ángulo de incidencia es correcto, entonces la pelota probablemente rebotó en las dos paredes correctamente antes de golpear la bola final.

Por supuesto, debe estar preparado para el caso donde nunca se produce una colisión. Su prueba puede dar cuenta de los clics del juego por unidad de tiempo para llegar a la colisión final. Haces que la prueba se ejecute para la cantidad necesaria de clics del juego para lograr la colisión. Si la colisión no se ha producido dentro del número de clics prescritos, la prueba puede fallar correctamente.

Todo esto se hace en clics del juego en vez de en tiempo real, de modo que la prueba puede suceder casi instantáneamente, en lugar de esperar el resultado esperado (como lo haría normalmente si realmente estuviera jugando el juego).