¿Cuáles son los desafíos y beneficios de escribir juegos con un lenguaje funcional?

81

Si bien sé que los lenguajes funcionales no son los más utilizados para escribir juegos, hay muchos beneficios asociados con ellos que parecen ser interesantes en cualquier contexto de programación. Especialmente la facilidad de paralelización, creo que podría ser muy útil ya que el enfoque se está moviendo hacia más y más procesadores.

Además, con F # como nuevo miembro de la familia .NET, se puede usar directamente con XNA, por ejemplo, lo que reduce el umbral bastante, en lugar de ir con LISP, Haskell, Erlang, etc.

Si alguien tiene experiencia escribiendo juegos con código funcional, ¿cuáles han sido los aspectos positivos y negativos? ¿Para qué era adecuado, qué no?

Editar: resulta difícil decidir que hay una buena respuesta única para esto, por lo que probablemente sea más adecuado como una publicación wiki de la comunidad.

revs McMuttons
fuente
3
con uno de mis juegos favoritos (Rogue) escrito en LISP, esta pregunta (y su potencial de respuesta) es muy interesante para mí.
Justin L.
No sabía que Rogue estaba escrito en LISP, pero lo encuentro interesante. Juego clasico. :)
McMuttons
2
No estoy seguro de lo que estás hablando. Rogue fue escrito en C. roguebasin.roguelikedevelopment.org/index.php?title=Rogue
Mason Wheeler el
2
Estoy seguro de que tienes razón acerca de que el Rogue original está en C, ya que fue escrito en Unix, pero ciertamente hay varios clones de Rogue en Lisp: cl-user.net/asp/root-dir (no el enlace directo del Juego , tenía tantos caracteres especiales que parece haber roto el enlace).
Cyclops

Respuestas:

57

Actualmente estoy trabajando en un juego en Haskell. No puedo hablar de programación funcional en general, pero específicamente de Haskell:

El bueno

Estas son las cosas increíbles que hacen que Haskell realmente se destaque.

  • La pureza significa que razonar sobre su código es mucho más fácil. No tiene que preocuparse por el valor "actual" de una variable o si el uso de una función determinada entrará en conflicto con su estado global de alguna manera. También significa que el paralelismo y la concurrencia son mucho más fáciles de trabajar (y en muchos casos, triviales). Estas cosas pueden ser una bendición para los desarrolladores de juegos.
  • Haskell hace que sea muy fácil escribir a un nivel muy alto usando abstracciones de tu propia creación. A menudo es más fácil escribir código muy genérico que el código especializado en Haskell, y los beneficios de esto se manifiestan en menos tiempo de desarrollo y una comprensión de código más fácil. Es más cierto en Haskell que en cualquier otro idioma que conozca que usar una interfaz es como usar un idioma completamente nuevo (sin dejar de mantener los beneficios del resto del ecosistema de Haskell). Lo que la mayoría de los idiomas llaman funciones lingüísticas, Haskell llama a una biblioteca, y no se siente forzado. Si alguna vez te encuentras razonando sobre tu juego en psuedocode, probablemente puedas escribir una interfaz simple y convertirla en código real, y generalmente vale la pena hacerlo.
  • Esto podría entrar en conflicto con otras respuestas, pero Haskell es mejor para manejar el código imperativo que cualquier otro idioma que conozca. Las restricciones de pureza significan que Haskell tiene una separación entre los conceptos de "evaluación" (reducción de expresiones) y "ejecución" (realización de efectos secundarios). Puede controlar cuándo y cómo se realizan los efectos secundarios con una facilidad incomparable con los llamados lenguajes "imperativos". A pesar de lo que dije anteriormente sobre la pureza, algunos enlaces C siguen siendo muy imperativos (OpenGL, te estoy mirando), por lo que este control detallado sobre el código imperativo es muy bienvenido. Incluso los programadores más experimentados de C probablemente lo apreciarán una vez que se acostumbren.
  • GHC genera binarios bastante rápidos. No son tan rápidos como los binarios generados a partir de compiladores comunes de C, pero están dentro de un orden de magnitud y son mucho más rápidos de lo que se lograría con un lenguaje interpretado. Incluso si no puedes escribir tu juego en otros lenguajes de alto nivel como Python porque sería demasiado lento, hay muchas posibilidades de que puedas hacerlo en Haskell.
  • A pesar de que no hay muchas bibliotecas relacionadas con los juegos en Haskell, como se indica en "The Bad" a continuación, la interfaz de funciones foráneas de Haskell es la más fácil que he usado. Puedes unirte a C escribiendo casi exclusivamente en Haskell.

El malo

Estas son cosas que no son buenas pero que pueden solucionarse sin demasiado esfuerzo.

  • El tiempo y el esfuerzo necesarios para aprender Haskell pueden ser bastante altos si está acostumbrado a trabajar con lenguajes no funcionales. El lenguaje en sí no es muy difícil, pero cuando se tienen en cuenta las extensiones de lenguaje comunes y la gran cantidad de bibliotecas (recuerde, muchas cosas que podría considerar características del lenguaje en otros idiomas son bibliotecas en Haskell), se ve mucho más siniestro. En mi opinión, vale la pena, pero otros pueden estar en desacuerdo.
  • Algunas personas se quejan mucho de la pereza. Si sabe lo que está haciendo, no causará fugas de espacio que son difíciles de rastrear (no he creado una fuga de espacio en un par de años), pero los principiantes tienen muchos más problemas con esto. Sería tonto de mi parte derribar a estas personas y afirmar que esto no es un problema, pero afirmaré que es un problema que se resuelve fácilmente con experiencia.
  • No hay muchas bibliotecas excelentes para hacer juegos en Haskell, y no muchos Haskellers escriben juegos en él, por lo que es difícil encontrar recursos y ayuda sobre este asunto.

El feo

Estas son cosas que requerirían un esfuerzo considerable para superarlas.

  • Si estás escribiendo un juego intensivo en recursos, el recolector de basura de GHC podría morderte bastante. Es un GC generacional que detiene el mundo, por lo que de vez en cuando es posible que pierda algunos fotogramas. Para algunos juegos esto está bien, pero para otros es un gran problema. A mí y a otros desarrolladores de juegos que usan Haskell nos gustaría ver un GC en tiempo real suave implementado para GHC, pero que yo sepa, todavía no se está haciendo ningún esfuerzo en eso. Actualmente, no me preocupo por solucionar esto (y todavía no he visto ningún fotograma eliminado), pero al menos otro equipo ha recurrido a poner su bucle de representación principal en C.
Jake McArthur
fuente
2
¿Qué opinas de FRP?
Wei Hu
44
@Wei Hu: Soy un gran admirador de la idea de FRP y la he investigado bastante extensamente, incluida la creación de algunas semánticas diferentes y la escritura de algunas implementaciones. Las mejores implementaciones todavía tienen graves errores semánticos y / o de implementación. Diría que son viables para el desarrollo del juego, pero no con muchas ventajas (todavía) sobre los enfoques tradicionales.
Jake McArthur el
1
Un usuario anónimo intentó editar esta respuesta borrando la parte "fea". "El recolector de basura en Haskell ahora es concurrente, paralelo y generacional a partir de 2011 ( ghc.haskell.org/trac/ghc/blog/new-gc-preview )"
Jari Komppa
1
Desafortunadamente, eso no es realmente cierto. Se encontró que el GC concurrente era demasiado complicado para justificar la pequeña mejora general, y no se ha fusionado.
Jake McArthur
1
@ Hi-Angel Hay un viejo backend de GHC que generó C, pero no es diferente del generador de código nativo o del backend LLVM. Todavía requiere el tiempo de ejecución de GHC. Además, no hay nada especial en un backend C que haga más fácil evitar un recolector de basura. Si fuera tan fácil y barato eliminar el GC, los otros backends probablemente también lo harían.
Jake McArthur
19

Pasé el último año desarrollando un motor de juego comercial en Haskell, y para nosotros, la experiencia ha sido abrumadoramente positiva. Nuestro mundo de juegos es complejo, y Haskell ha facilitado modelar el proceso de conversión de un formato de editor a un formato de motor de juego. Odiaría pensar cómo se vería ese código en un lenguaje imperativo.

En ocasiones, han surgido pérdidas de espacio, y aunque han causado un poco de problemas, en el esquema general ha sido una pequeña cantidad (por ejemplo, en comparación con encontrar puntos muertos en proyectos Java de tamaño similar), y una vez que se repararon , se quedaron fijos.

Estamos usando FRP similar a Yampa, y ciertamente hay una curva de aprendizaje asociada con él, pero una vez que termina, la experiencia es muy positiva. Las bibliotecas no han sido un problema para nosotros: todo lo que hemos necesitado ha estado disponible. Los retrasos en la recolección de basura fueron un problema particular ya que es para una plataforma integrada. Hemos usado algunos C ++ para administrar la animación. El rendimiento también ha sido un problema, ya que se trata de una plataforma integrada (= procesador lento). Hemos hecho algo de C y también estamos buscando tecnologías emergentes de Haskell como acelerar. El animador de C ++ fue una decisión de diseño desde el principio y los lugares donde el código es demasiado lento son solo áreas muy pequeñas. A la larga, queremos traducir toda nuestra C a Haskell.

Haskell ha hecho un trabajo difícil fácil, y todas las dificultades que acabo de mencionar han sido pequeñas en comparación con la gran cantidad de código complejo que hemos producido que es limpio, mantenible y prácticamente irrompible. El paralelismo será un problema en el desarrollo del juego muy pronto, y estamos muy bien posicionados para enfrentarlo. Es posible que parte de lo que he dicho no se aplique a proyectos pequeños, porque estamos en esto a largo plazo, por lo que los costos iniciales como las curvas de aprendizaje, el soporte de la biblioteca, etc., son mucho menos problemáticos.

Stephen Blackheath
fuente
77
Se acercan 5 años desde que publicaste esta respuesta; ¿estarías dispuesto a actualizarlo? ¿Cómo ha funcionado el motor de juego comercial? ¿Qué piezas demostraron ser valiosas? ¿Has podido aprovechar el paralelismo como esperabas?
Levi Morrison
17

Dave menciona algunos puntos excelentes, aunque debo señalar que Haskell ha resuelto sus dos problemas. La apatridia se puede omitir usando la mónada del Estado (EDITAR: no realmente, ver más abajo para más detalles) , y la secuenciación se puede manejar usando la mónada IO (EDITAR: o cualquier otra mónada, para el caso ...) .

Los desafíos que tendrá (y que he tenido que tratar de aprender tanto la programación de juegos como Haskell) son más similares. (Todos estos son específicos de Haskell, ya que todavía no he profundizado en ningún otro lenguaje FP).

  • Curva de aprendizaje de FP: FP requiere un cambio completo de mentalidad de la programación iterativa. Aprender a pensar en términos de mapas y pliegues en lugar de bucles requiere un entrenamiento mental si no estás acostumbrado.
  • Curva de aprendizaje de Mathy: Haskell es bendecido y maldecido por la sofisticación matemática de sus diseñadores, porque después de que aprende los conceptos básicos de FP, se queda atrapado con mónadas, morfismos, flechas y una gran cantidad de otras consideraciones que, aunque no son difíciles en sí mismos, son muy abstractos.
  • Mónadas: estos cachorros no son particularmente difíciles de entender, especialmente si acepta la declaración de Eric Raymond de que son "un truco para convertir la composición de funciones en una forma de forzar la secuencia de llamadas a funciones". Desafortunadamente (aunque esto está mejorando a medida que Haskell gana popularidad), muchas explicaciones de mónadas apuntan por encima de la mayoría de los programadores ("¡son monoides en la categoría de endofunctores!"), O en una analogía completamente inútil (p. Ej. , ejemplo penosamente precisa de Brent Yorgey, " mónadas son como burritos !" ¿crees que está tomando el pelo? que nadie podía llegar a una analogía tonta en un tutorial? Piense otra vez .)
  • Ecosistema: si ha logrado superar todos los otros baches en el camino, se pondrá de cara a la realidad práctica del día a día de trabajar con un lenguaje aún en gran medida experimental. Dios te ayude cuando llegues aquí. Aquí es donde comencé a criticar seriamente Scala, o F #, o algún otro idioma donde hay al menos alguna garantía de interoperabilidad con otras bibliotecas. Érase una vez, conseguí que Haskell vincule y cargue las bibliotecas OpenGL en Windows. Eso me hizo sentir bastante bien. Luego, los enlaces de OpenGL se volvieron a lanzar y rompieron todo lo que había hecho. Así es la vida en Haskell-land. Honestamente, puede ser un dolor. ¿Quieres un contenedor para el motor Bullet? Está en Hackage, como abandonwaredesde noviembre del año pasado. ¿Te gusta Ogre3d? Hackage solo tiene enlaces a un subconjunto de la biblioteca . La conclusión es que si te apegas a un lenguaje fuera de lo común para el desarrollo del juego, pasarás mucho más tiempo trabajando en fontanería básica de lo que lo harías si solo siguieras al rebaño y te quedaras con C ++.

La otra cara de esto es que las cosas están mejorando rápidamente. Y realmente todo depende de lo que quieras de la experiencia. ¿Quieres construir un juego y ponerlo en tu sitio web para buscar fama y fortuna? Quédate con C ++ o Python. Pero si desea aprender algo nuevo que le obligue a innovar sus procesos, pruebe con un lenguaje funcional. Solo ten mucha paciencia contigo mismo mientras estás aprendiendo.

Antti Salonen tiene un blog interesante que detalla su aventura intermitente con la programación de juegos Haskell. Vale la pena leerlo.

Editar (un año después): ahora que he estudiado más la mónada estatal, me doy cuenta de que no es una solución particularmente buena para el estado que pretende persistir fuera de una función en particular. Las soluciones reales a la apatridia se encuentran en Haskell en IOVar, ST, MVar (para la seguridad de subprocesos), o a través de algo como Yampa, que usa Arrows y FRP para administrar el estado interno que, sin embargo, está oculto para el desarrollador. (Esta lista está en orden de dificultad, aunque las tres primeras no son particularmente difíciles una vez que comprenda las mónadas).

rtperson
fuente
1
Algunos buenos pensamientos allí, incluso si son bastante específicos de Haskell, creo que son pensamientos que se aplican a la mayoría de los idiomas marginales. Como mencionó brevemente, creo que aquí es donde los idiomas como F # tienen el potencial de brillar. Manejo de estado / UI con C #, por ejemplo, y luego tener módulos lógicos en F # para algoritmos como potencialmente hallazgo camino, AI, etc.
McMuttons
5

Objetivo Caml!

El uso de lenguajes funcionales puede ser una gran ventaja en la mayoría de los tipos de desarrollo de software, principalmente porque reducen considerablemente el tiempo de desarrollo. Puedo ver un gran potencial para escribir un servidor back-end para un juego, o las capas AI y lógica en el cliente, en un lenguaje funcional. Y como todos saben, LISP se ha utilizado para secuencias de comandos NPC.

Si tratara de escribir la interfaz de un juego en un lenguaje funcional, definitivamente elegiría Objective Caml , que es un lenguaje híbrido. Es un descendiente de ML, y permite mezclar estilos iterativos y funcionales, tiene un sistema objetivo con objetos con estado. (Caml es el idioma en el que se modela F #).

Los enlaces de OpenGL parecen funcionar perfectamente y la gente ha estado haciendo juegos en OCaml durante mucho tiempo. El lenguaje se puede compilar y es potencialmente muy rápido (hace mucho tiempo se ganó a C en algunos puntos de referencia, no sé cómo están las cosas ahora).

También hay toneladas de bibliotecas. Todo, desde material informático hasta enlaces y todo tipo de bibliotecas.

Felixyz
fuente
4

Realmente no es una respuesta a nada, pero siento que una discusión sobre la programación de juegos en lenguajes funcionales está incompleta sin mencionar la Programación Reactiva Funcional (FRP) - http://www.haskell.org/frp/ - y su implementación más común ( ¿Creo?), YAMPA - http://www.haskell.org/yampa/ .

usuario744
fuente
3

En cuanto a los beneficios, echa un vistazo a Clean Game Library (http://cleangl.sourceforge.net/) y mira el juego de plataformas. Una vez que entienda la sintaxis (le animo a que lo intente) verá la pura elegancia de las construcciones y qué tan cerca se asigna la fuente a los conceptos que están expresando. Como una ventaja adicional, la abstracción del estado y la necesidad de pasar explícitamente el estado hace que su código sea mucho más amigable para múltiples núcleos.

Por otro lado, requiere un cambio de paradigma importante. Mucho de lo que estás acostumbrado a hacer sin pensar en eso de repente ya no existe y estarás pensando cómo resolverlo, simplemente porque estás pensando en los patrones incorrectos.

Kaj
fuente
2

Desde mi punto de vista, los mayores desafíos serán:

  • La apatridia de los lenguajes funcionales: la idea en los juegos es que cada entidad tiene un estado y este estado se está modificando de cuadro a cuadro. Hay mecanismos para imitar ese comportamiento en algunos idiomas (por ejemplo, funciona con un "!" En el esquema plt), pero se siente de forma poco natural.
  • Orden de ejecución : en los juegos, algunas partes del código dependen de que otras partes del código se terminen antes de ejecutarse. Los lenguajes funcionales generalmente no garantizan el orden de ejecución de los argumentos de una función. Hay formas de imitar este comportamiento (por ejemplo, " (begin..." en el esquema plt).

Siéntase libre de extender esta lista (y quizás agregar algunos puntos positivos ^^).

Dave O.
fuente
1
El orden de ejecución puede variar en un lenguaje no estricto como Haskell, pero no crea problemas para las partes del código que dependen de otras partes del código. Puede pensarlo como una evaluación a pedido. No se realiza un cálculo hasta que se necesita el resultado. Creo que la única forma en que puede causarle dolor es en términos de rendimiento. Por ejemplo, puede que sea difícil obtener un buen almacenamiento en caché en algunos casos porque no tendrá control sobre el orden en la evaluación que no sea especificar las dependencias entre los cálculos.
Jason Dagit
1

Puede escribir su código en C / C ++ y usar un lenguaje incrustado como Embedded Common Lisp para sus secuencias de comandos. Aunque hacer que estos se compilen en una plataforma diferente puede ser difícil. Puede mirar Lisp In Small Pieces para aprender a escribir su propio lenguaje de secuencias de comandos. Realmente no es tan difícil, si tienes tiempo.

WarWeasle
fuente
0

He escrito juegos simples en LISP y fue divertido, pero no es algo que recomendaría. La programación funcional tiene que ver con el resultado final. Por lo tanto, es muy útil para procesar datos y tomar decisiones, pero descubrí que era difícil hacer un código limpio simple como un bucle de control básico.

Sin embargo, no he aprendido F #, por lo que puede ser mucho más fácil trabajar con él.

Jeff
fuente
Mi pensamiento inicial podría ser escribir el andamiaje y el manejo del estado con un lenguaje imperativo, y luego hacer la lógica del juego y tal con bits funcionales. Con .NET y C # / F # esto sería muy fácil, por ejemplo, ya que usan las mismas bibliotecas y pueden comunicarse entre sí sin ninguna penalización real que yo sepa.
McMuttons
-1

lo positivo sería el rendimiento y la portabilidad y lo negativo sería la gestión de la complejidad , los juegos de hoy son muy complejos y necesitan herramientas o lenguaje de programación que puedan gestionar mejor la complejidad, como la metodología orientada a objetos o la programación genérica que es difícil de implementar en la programación funcional idioma.

uray
fuente
11
¿Me estás tomando el pelo? FP es programación genérica con esteroides. Ser capaz de generalizar ambos tipos de datos y funciones le da a FP un gran poder para gestionar la complejidad.
rtperson
Estoy de acuerdo, una de las mayores fortalezas de FP es su capacidad para hacer código generalizado.
McMuttons