c++ lua game-loop lua-c++-connection

c++ - Lua, estado de juego y bucle de juego.



game-loop lua-c++-connection (9)

  1. Llame al script main.lua en cada iteración del bucle del juego: ¿es un diseño bueno o malo? ¿Cómo afecta esto en el rendimiento (relativamente)?

  2. Mantener el estado del juego de un . C ++ host-programa o b . de scripts Lua o c . ¿Desde ambos y sincronizarlos?

(Pregunta anterior sobre el tema: Lua y C ++: separación de tareas )

(Voto por cada respuesta. La mejor respuesta será aceptada.)


  1. Probablemente no quieras ejecutar todo el script de Lua en cada iteración de fotograma, porque cualquier juego suficientemente complejo tendrá múltiples objetos de juego con sus propios comportamientos. En otras palabras, las ventajas de Lua se pierden a menos que tenga varios scripts pequeños que manejen una parte específica del comportamiento de un juego más grande. Puede usar la función lua_call para llamar a cualquier rutina lua apropiada en su script, no solo a todo el archivo.

  2. No hay una respuesta ideal aquí, pero la gran mayoría de su estado de juego se almacena tradicionalmente en el motor del juego (es decir, C ++). Le revela a Lua lo suficiente para que Lua tome la decisión que le asignó a Lua.

Debe considerar qué lenguaje es apropiado para qué comportamientos. Lua es útil para los controles y decisiones de alto nivel, y C ++ es útil para el código orientado al rendimiento. Lua es particularmente útil para las partes de tu juego que necesitas ajustar sin recompilar. Todas las constantes mágicas y variables podrían ir a Lua, por ejemplo. No intente calzador Lua donde no pertenece, es decir, la representación de gráficos o audio.


Acerca del rendimiento de 1: si main.lua no cambia, cárguelo una vez con lua_loadfile o loadfile , guarde una referencia a la función devuelta y luego loadfile cuando sea necesario.


En mi humilde opinión, los scripts Lua son para comportamientos específicos, definitivamente afectará el rendimiento si llama a un script Lua 60 veces por segundo.

Los scripts de Lua a menudo se relacionan con cosas como Comportamiento y eventos específicos de su lógica de Game Engine (GUI, Elementos, Diálogos, eventos de motores de juegos, etc.). Un buen uso de Lua, por ejemplo, sería cuando se desencadena una explosión (partícula FX), si el personaje del juego se desplaza hacia algún lugar, codificar la salida de ese evento en su motor sería una elección muy fea. Sin embargo, hacer que el motor active la secuencia de comandos correcta sería una mejor opción, desvincular ese comportamiento específico de su motor.

Recomendaría, para tratar de mantener su estado de juego en una parte, en lugar de elevar el nivel de complejidad de mantener los estados sincronizados en dos lugares (Lua y Engine), agregue subprocesos a eso, y terminará teniendo un lío muy feo . Mantenlo simple. (En mis diseños mantengo principalmente Game State en C ++)

Buena suerte con tu juego!


Estoy usando Lua por primera vez en un juego en el que he estado trabajando. El lado C ++ de mi aplicación en realidad contiene punteros a instancias de cada estado del juego. Algunos de los estados del juego se implementan en C ++ y otros se implementan en Lua (como el estado de "juego").

La actualización y el bucle principal de la aplicación están en el lado C ++ de las cosas. He expuesto funciones que le permiten a Lua VM agregar nuevos estados de juego a la aplicación en tiempo de ejecución.

Todavía no he tenido ningún problema con la lentitud, incluso ejecutando hardware con recursos limitados (procesador Atom con video integrado). Las funciones de Lua se llaman todos los cuadros . La operación más cara (en términos de tiempo) en mi aplicación es la prestación .

La capacidad de crear nuevos estados completamente en Lua fue una de las mejores decisiones que tomé en el proyecto, ya que me permite agregar libremente partes del juego sin volver a compilar todo.

Edición: Estoy usando Luabind , que he leído funciona más lento en general que otros marcos vinculantes y, por supuesto, la API de Lua C.


La mayor parte del rendimiento se perderá a través del enlace entre Lua y C ++. Una llamada de función en realidad tendrá que ser envuelta, y reenvasada, y así, por lo general, un par de veces. El código Lua puro o C ++ puro suele ser más rápido que el código mixto (para operaciones pequeñas).

Habiendo dicho eso, personalmente no vi ningún fuerte impacto de rendimiento al ejecutar un script Lua en cada fotograma .

Por lo general, las secuencias de comandos es bueno a alto nivel . Lua se ha utilizado en juegos famosos para los Bots ( Quake 3 ) y para la Interfaz de usuario ( World of Warcraft ). Usados ​​a alto nivel, los micro hilos de Lua son útiles: las corrutinas pueden ahorrar mucho (en comparación con los hilos reales). Por ejemplo, para ejecutar algún código Lua solo de vez en cuando.


Lo mejor de lua es que tiene una VM liviana, y luego de que los trozos se precompilan, ejecutarlos en la VM es bastante rápido, pero no tan rápido como lo sería un código C ++, y no creo que llamar a lua El marco renderizado sería una buena idea.

Pongo el estado del juego en C ++ y agrego funciones en lua que pueden alcanzar y modificar el estado. Un enfoque basado en eventos es casi mejor, donde el registro de eventos debe hacerse en lua (preferiblemente solo al comienzo del juego o en eventos específicos del juego, pero no más de unas pocas veces por minuto), pero los eventos reales deben ser activados por Código C ++. Las entradas del usuario también son eventos, y generalmente no ocurren en todos los fotogramas (excepto quizás MouseMove, pero se deben usar con cuidado debido a esto). La forma en que maneje los eventos de entrada del usuario (ya sea que maneje todo (como la tecla que se presionó, etc.) en lua, o si hay, por ejemplo, eventos separados para cada tecla en el teclado (en un caso extremo) depende del juego que elija. al intentar hacer (un juego basado en turnos puede tener solo un controlador de eventos para todos los eventos, un RTS debe tener más eventos y un FPS debe manejarse con cuidado (principalmente porque mover el mouse ocurrirá en cada cuadro)). Generalmente, más distintos tipos de eventos que tienes, menos tienes que codificar en lua (lo que aumentará el rendimiento), pero más difícil se vuelve si un "evento real" que necesitas controlar es realmente activado por "eventos de nivel de programación" más separados ( lo que en realidad podría disminuir el rendimiento, porque el código lua debe ser más complejo).

Alternativamente, si el rendimiento es realmente importante, realmente puede mejorar el VM de lua agregando nuevos códigos de operación (he visto a algunas de las compañías hacer esto, pero principalmente para hacer más difícil la descompilación de los trozos de lua compilados), que en realidad no es una cosa difícil de hacer Si tiene algo que el código lua necesita hacer muchas veces (como el registro de eventos, la ejecución de eventos o el cambio del estado del juego) es posible que desee implementarlos en la máquina virtual lua, así que en lugar de los múltiples getglobal setglobal getglobal y setglobal solo tomarían uno o dos (por ejemplo, podría hacer un código de operación SETSTATE con un parámetro 0-255 y un parámetro 0-65535, donde el primer parámetro describe qué estado modificar y el segundo describe el nuevo valor del estado. Por supuesto, esto solo funciona si tiene un máximo de 255 eventos, con un máximo de 2 ^ 16 valores, pero podría ser suficiente en algunos casos. Y el hecho de que solo requiera un código de operación significa que el código se ejecutará más rápido. Esto también haría más difícil la descompilación si intenta ocultar su código lua (aunque no mucho para alguien que conozca el funcionamiento interno de lua). Ejecutar unos pocos códigos de operación por fotograma (alrededor de 30-40 tops) no afectará tan mal su rendimiento. Pero 30-40 códigos de operación en la máquina virtual lua no lo llevarán muy lejos si necesita hacer cosas realmente complejas (un simple if-then-else puede tomar hasta 10-20 o más códigos de operación según la expresión).


Me gustaría agregar mis dos centavos porque creo firmemente que aquí se dan algunos consejos incorrectos. Para el contexto, estoy usando Lua en un juego grande que involucra tanto la representación intensiva en 3D como la simulación intensiva de la lógica del juego. Me he vuelto más familiar de lo que me hubiera gustado con Lua y el rendimiento ...

Tenga en cuenta que voy a hablar específicamente sobre LuaJIT, porque va a querer usar LuaJIT. Es plug-and-play, realmente, así que si puedes incrustar Lua puedes incrustar LuaJIT. Lo querrá, si no fuera por la velocidad adicional, luego para el módulo de interfaz de la función externa automágica (requiere ''ffi'') que le permitirá llamar a su código nativo directamente desde Lua sin tener que tocar la API de Lua C (95 % + de casos).

  1. Está perfectamente bien llamar a Lua a 60Hz (lo llamo a 90Hz en VR ..). El problema es que tendrás que tener cuidado de hacerlo correctamente. Como alguien más mencionó, es fundamental que cargues el script solo una vez . Luego puede usar la API de C para obtener acceso a las funciones que definió en ese script, o para ejecutar el script como una función. Recomiendo el primero: para un juego relativamente simple, puedes salir adelante definiendo funciones como onUpdate (dt), onRender (), onKeyPressed (key), onMouseMoved (dx, dy), etc. Puedes llamarlas en el momento adecuado de su bucle principal en C ++. De forma alternativa, puede tener todo su bucle principal en Lua, y en su lugar invocar su código C ++ para rutinas críticas para el rendimiento (hago eso). Esto es especialmente fácil de hacer con LuaJIT FFI.

  2. Esta es la pregunta realmente difícil. Dependerá de tus necesidades. ¿Puedes fácilmente dominar el estado del juego? Genial, póngalo en C ++ - lado y acceso desde LuaJIT FFI. ¿No estás seguro de cómo estará todo en el estado del juego para poder crear prototipos rápidamente? No hay nada malo con mantenerlo en Lua. Es decir, hasta que comience a hablar de un juego complejo con miles de objetos, cada uno con un estado no trivial. En este caso, híbrido es el camino a seguir, pero descubrir exactamente cómo dividir el estado entre C ++ y Lua, y cómo realizar dicho estado marcial entre los dos (especialmente en rutinas críticas) es una especie de arte. Déjeme saber si se le ocurre una técnica a prueba de balas :) Como con todo lo demás, la regla general es que los datos que atraviesan vías críticas para el rendimiento deben estar en el lado nativo. Por ejemplo, si sus entidades tienen posiciones y velocidades, actualice cada cuadro y tiene miles de dichas entidades, debe hacer esto en C ++. Sin embargo, puede salirse con la colocación de un ''inventario'' sobre estas entidades usando Lua (un inventario no necesita una actualización por marco).

Ahora, un par de notas más me gustaría descartarlas tanto como FYIs generales como en respuesta a algunas de las otras respuestas.

  1. Los enfoques basados ​​en eventos son, en general, críticos para el rendimiento de cualquier juego, pero eso se duplica en los sistemas escritos en Lua. Dije que está perfectamente bien llamar a Lua @ 60hz. Pero no está perfectamente bien estar ejecutando bucles ajustados sobre muchos objetos del juego en cada cuadro en dicho Lua. Es posible que salga impaciente llamando a update () en todo el universo en C ++ (aunque no debería), pero hacerlo en Lua comenzará a consumir esos preciosos milisegundos con demasiada rapidez. En cambio, como han mencionado otros, debe pensar que la lógica de Lua es "reactiva"; por lo general, esto significa manejar un evento. Por ejemplo, no verifique que una entidad esté dentro del alcance de otra cada cuadro en Lua (quiero decir, esto está bien para una entidad, pero en general, cuando está ampliando su juego, no debe pensar así). En su lugar, dígale a su motor de C ++ que le notifique cuando las dos entidades se encuentren a una cierta distancia entre sí. De esta manera, Lua se convierte en el controlador de alto nivel de la lógica del juego, distribuye comandos de alto nivel y responde a eventos de alto nivel, sin llevar a cabo la rutina matemática de bajo nivel de la lógica trivial del juego.

  2. Desconfíe del consejo de que el ''código mixto'' es lento. El Lua C API es ligero y rápido. Las funciones de ajuste para su uso con Lua son, en el peor de los casos, bastante fáciles (y si se toma un tiempo para entender cómo se interconecta Lua con C wrt la pila virtual, etc., notará que ha sido diseñado específicamente para minimizar la sobrecarga de llamadas), y en el mejor de los casos, trivial (no requiere envoltura) y 100% tan eficaz como las llamadas nativas (¡gracias, LuaJIT FFI!) En la mayoría de los casos, encuentro que una mezcla cuidadosa de Lua y C (a través de LJ FFI) es superior a la Lua pura. Incluso las operaciones vectoriales, que creo que los manuales de LuaJIT mencionan en alguna parte como un ejemplo donde el código debe guardarse para Lua, he encontrado que es más rápido cuando ejecuto a través de llamadas Lua-> C. En última instancia, no confíe en nadie ni en nada, excepto en sus propias (cuidadosas) mediciones de rendimiento cuando se trata de piezas de código que son sensibles al rendimiento.

  3. La mayoría de los juegos pequeños podrían salirse bien con el estado del juego, el bucle central, el manejo de las entradas, etc., realizados completamente en Lua y ejecutándose bajo LuaJIT. Si está construyendo un juego pequeño, considere el enfoque de "moverse a C ++ según sea necesario" (compilación lenta). Escriba en Lua, cuando identifique un claro cuello de botella ( y lo haya medido para asegurarse de que sea el culpable ), muévase. Ese bit a C ++ y estar en tu camino. Si está escribiendo un gran juego con miles de objetos complejos con lógica compleja, procesamiento avanzado, etc., entonces se beneficiará de un diseño más avanzado.

La mejor de las suertes :)


Mi regla básica para lua es - o cualquier lenguaje de script en un juego -

  • Cualquier cosa que pase en cada cuadro: c ++
  • eventos asíncronos - entrada de usuario - lua
  • Eventos del motor del juego síncrono - lua

Básicamente, cualquier código que se llame a> 33-100Hz (dependiendo de la velocidad de fotogramas) es C ++ Intento invocar el motor de script <10Hz.

Basado en cualquier tipo de métrica real? realmente no. pero pone una línea divisoria en el diseño, con las tareas de c ++ y lua claramente delineadas, sin la delineación frontal, las tareas de lua por cuadro crecerán hasta que se atasquen el procesamiento por cuadro, y luego no hay una guía clara sobre qué podar.


No me gusta el C ++. Pero me gustan los juegos.

Mi enfoque puede ser un poco atípico: hago todo lo que puedo en Lua, y solo el mínimo absoluto en C ++. El bucle de juego, las entidades, etc. están hechos en Lua. Incluso tengo una implementación de QuadTree hecha en Lua. C ++ maneja las cosas gráficas y del sistema de archivos, así como la interfaz con bibliotecas externas.

Esta no es una decisión basada en máquina, sino una basada en programador; Yo código de salida mucho más rápido en Lua que en C ++. Así que paso mis ciclos de programador en nuevas características en lugar de guardar ciclos de computadora. Mis máquinas objetivo (cualquier computadora portátil de los últimos 3 años) son capaces de hacer frente a esta cantidad de Lua con mucha facilidad.

Lua es sorprendentemente baja (eche un vistazo a luaJIT si no lo sabe).

Dicho esto, si alguna vez encuentro un cuello de botella (aún no lo he hecho) perfilaré el juego para encontrar la parte lenta, y traduciré esa parte a C ++ ... solo si no puedo encontrar una a su alrededor utilizando Lua.