¿Cómo funciona Lua como lenguaje de script en los juegos?
67
Estoy un poco confuso sobre qué es exactamente Lua y cómo lo usaría un juego programado en C ++. Estoy preguntando principalmente sobre cómo se compila y ejecuta.
Por ejemplo, cuando usa un programa escrito en C ++ que usa scripts de Lua: ¿el código en Lua solo llama a funciones en el programa principal escrito en C ++ y actúa como una clase sin compilar que espera ser compilada y agregada al montón de memoria de C ++? ¿programa?
¿O actúa como un script bash en Linux donde solo ejecuta programas completamente separados del programa principal?
La secuencia de comandos es una abstracción de programación en la que (conceptualmente) tiene un programa (la secuencia de comandos) que se ejecuta dentro de otro programa (el host). En la mayoría de los casos, el idioma en el que escribe el script es diferente del idioma en el que se escribe el host, pero cualquier abstracción de programa dentro de un programa podría considerarse scripting.
Conceptualmente, los pasos comunes para habilitar las secuencias de comandos son los siguientes (utilizaré pseudo-c para el host y pseudo-lua para la secuencia de comandos. Estos no son pasos exactos, sino más bien el flujo general en el que habilita las secuencias de comandos)
Observe que el nombre en el que exponemos la función ( print) no tiene que coincidir con el nombre interno de la función en sí ( scriptPrintMessage)
Ejecute un código de script que use la función:
constchar* scriptCode ="print(\"Hello world!\")";// Could also be loaded from a file though
doText(m_vm, scriptCode);
Eso es todo al respecto. El programa entonces fluye de la siguiente manera:
Tu llamas doText(). El control se transfiere a la máquina virtual, que ejecutará el texto dentro scriptCode.
El código del script encuentra un símbolo exportado previamente print. Luego transferirá el control a la función scriptPrintMessage().
Cuando scriptPrintMessage()finalice, el control se transferirá nuevamente a la máquina virtual.
Cuando scriptCodese haya ejecutado todo el texto , doText()finalizará y el control se transferirá nuevamente a su programa en la línea posterior doText().
Entonces, en general, todo lo que está haciendo es ejecutar un programa dentro de otro programa. Teóricamente hablando, no hay nada que puedas hacer con los scripts que no puedas hacer sin ellos, pero esta abstracción te permite hacer algunas cosas interesantes con mucha facilidad. Algunos de ellos son:
Separación de preocupaciones: es un patrón común escribir un motor de juego en C / C ++ y luego el juego real en un lenguaje de script como lua. Bien hecho, el código del juego se puede desarrollar de manera completamente independiente del motor mismo.
Flexibilidad: los lenguajes de secuencias de comandos se interpretan comúnmente y, como tal, un cambio en una secuencia de comandos no necesariamente requerirá una reconstrucción de todo el proyecto. Bien hecho, ¡incluso puede cambiar un script y ver los resultados sin siquiera reiniciar el programa!
Estabilidad y seguridad: dado que el script se ejecuta dentro de una máquina virtual, si se hace correctamente, un script con errores no bloqueará el programa host. Esto es especialmente importante cuando permites que tus usuarios escriban sus propios scripts en tu juego. ¡Recuerde que puede crear tantas máquinas virtuales independientes como desee! (Una vez creé un servidor MMO en el que cada partida se ejecutaba en una máquina virtual lua separada)
Características del idioma: cuando utilice lenguajes de secuencias de comandos, en función de su elección para los idiomas de host y de secuencias de comandos, puede utilizar las mejores características que ofrece cada idioma. En particular, las corutinas de lua son una característica muy interesante que es muy difícil de implementar en C o C ++
Sin embargo, los guiones no son perfectos. Existen algunas desventajas comunes al usar secuencias de comandos:
La depuración se vuelve muy difícil: por lo general, los depuradores incluidos en IDE comunes no están diseñados para depurar el código dentro de los scripts. Debido a esto, la depuración de seguimiento de la consola es mucho más común de lo que me gustaría.
Algunos lenguajes de secuencias de comandos como lua tienen funciones de depuración que se pueden aprovechar en algunos IDE como Eclipse. Hacer esto es muy difícil y, sinceramente, nunca he visto que la depuración de scripts funcione tan bien como la depuración nativa.
Por cierto, la extrema falta de interés que tienen los desarrolladores de Unity en este asunto es mi principal crítica de su motor de juego, y la razón principal por la que ya no lo uso, pero estoy divagando.
Integración IDE: es poco probable que su IDE sepa qué funciones se están exportando desde su programa y, como tal, es poco probable que funciones como IntelliSense y similares funcionen con sus scripts.
Rendimiento: al ser programas comúnmente interpretados, y destinados a una máquina virtual abstracta cuya arquitectura puede ser diferente del hardware real, los scripts suelen ser más lentos de ejecutar que el código nativo. Sin embargo, algunas máquinas virtuales como luaJIT y V8 hacen un muy buen trabajo. Esto puede ser notable si hace un uso muy intenso de los scripts.
Por lo general, los cambios de contexto (host-a-script y script-a-host) son muy caros, por lo que es posible que desee minimizarlos si tiene problemas de rendimiento.
La forma en que use sus scripts depende de usted. He visto scripts utilizados para cosas tan simples como cargar configuraciones, hasta tan complejas como hacer juegos completos en el lenguaje de scripts con un motor de juego muy delgado. Una vez incluso vi un motor de juego muy exótico que combinaba secuencias de comandos lua y JavaScript (a través de V8).
La secuencia de comandos es solo una herramienta. Cómo lo usas para crear juegos increíbles depende completamente de ti.
Soy realmente nuevo en esto, pero uso Unity. Mencionaste algo sobre Unity, ¿podrías explicarlo? ¿Debo usar algo más?
Tokamocha
1
No usaría printfen tu ejemplo (o al menos uso printf("%s", message);)
frenético de trinquete
1
@ratchetfreak: el punto y coma al final de su mensaje seguido del paréntesis me está guiñando un ojo ...
Panda Pyjama
1
Una de las mejores respuestas que he visto en este sitio en mucho tiempo; se merece cada voto positivo que recibe. Muy bien hecho.
Steven Stadnicki
1
El póster de Lua en los juegos es World of Warcraft, en el que la mayor parte de la interfaz de usuario está escrita en Lua, y la mayoría de los cuales pueden ser reemplazados por el jugador. Me sorprende que no lo hayas mencionado.
Michael Hampton
7
En general, vincula o expone algunas funciones nativas a Lua (a menudo utiliza una biblioteca de utilidades para hacerlo, aunque puede hacerlo a mano). Eso permite que el código Lua realice llamadas a su código C ++ nativo cuando su juego ejecuta ese código Lua. En este sentido, su suposición de que el código Lua solo llama al código nativo es cierta (aunque Lua tiene su propia biblioteca estándar de funcionalidad disponible; no necesita llamar a su código nativo para todo).
El código de Lua en sí mismo es interpretado por el tiempo de ejecución de Lua, que es un código C que usted vincula como una biblioteca (generalmente) en su propio programa. Puede leer más sobre cómo funciona Lua en la página de inicio de Lua . En particular, Lua no es "una clase sin compilar" como supones, especialmente si estás pensando en una clase de C ++, porque C ++ casi nunca se compila dinámicamente en la práctica. Sin embargo, el tiempo de ejecución de Lua y los objetos creados por los scripts de Lua que ejecuta su juego consumen espacio en el grupo de memoria del sistema de su juego.
Los lenguajes de script como Lua se pueden usar de varias maneras. Como dijiste, puedes usar Lua para llamar a funciones en el programa principal, pero también puedes llamar a funciones de Lua desde C ++ si lo deseas. En general, crea una interfaz para permitir cierta flexibilidad con el lenguaje de secuencias de comandos de su elección para que pueda usar el lenguaje de secuencias de comandos en una serie de escenarios. Lo mejor de los lenguajes de scripting como Lua es que se interpretan en lugar de compilarse para que pueda modificar los scripts de Lua sobre la marcha para que no tenga que quedarse esperando a que su juego se compile solo para que usted vuelva a compilar si lo desea. Lo he hecho no se adapta a tus gustos.
Los comandos de Lua solo se llaman cuando el programa C ++ quiere ejecutarlos. Como tal, el código solo se interpretará cuando se llame. Supongo que puedes considerar a Lua como un script bash que se ejecuta por separado del programa principal.
En general, desea utilizar lenguajes de secuencias de comandos para las cosas que desee actualizar o iterar más adelante. He visto que muchas empresas lo usan para la GUI, por lo que proporciona mucha personalización a la interfaz. Siempre que sepa cómo han creado su interfaz Lua, también puede modificar la GUI usted mismo. Pero hay muchos otros caminos que puede seguir para usar Lua, como la lógica de IA, información sobre armas, diálogo de personajes, etc.
La mayoría de los lenguajes de script, incluido Lua, funcionan en una máquina virtual ( VM ), que es básicamente un sistema para asignar una instrucción de script a una instrucción de CPU "real" o llamada a función. La máquina virtual Lua normalmente se ejecuta en el mismo proceso que la aplicación principal. Esto es especialmente cierto para los juegos que lo usan. La API de Lua le proporciona varias funciones a las que llama en la aplicación nativa para cargar y compilar archivos de script. Por ejemplo, luaL_dofile()compila el script dado en el código de bytes de Lua y luego lo ejecuta. Este código de bytes luego será mapeado por la VM que se ejecuta dentro de la API en instrucciones de máquina nativas y llamadas a funciones.
El proceso de conectar un lenguaje nativo, como C ++, con un lenguaje de script se llama enlace . En el caso de Lua, su API proporciona funciones que lo ayudan a exponer funciones nativas al código del script. Entonces, por ejemplo, puede definir una función C ++ say_hello()y hacer que esta función sea invocable desde un script Lua. La API de Lua también proporciona métodos para crear variables y tablas a través del código C ++ que serán visibles para los scripts cuando se ejecuten. Al combinar estas características, puede exponer clases completas de C ++ a Lua. También es posible lo contrario, la API de Lua permite al usuario modificar las variables de Lua y llamar a las funciones de Lua desde el código nativo de C ++.
La mayoría, si no todos, los lenguajes de script proporcionan API para facilitar la vinculación del código de script con el código nativo. La mayoría también se compila en bytecode y se ejecuta en una VM, pero algunos pueden interpretarse línea por línea.
Espero que esto ayude a aclarar algunas de sus preguntas.
Como nadie mencionó esto, lo agregaré aquí para aquellos interesados. Hay un libro completo sobre el tema llamado Game Scripting Mastery . Este es un texto fantástico que se escribió hace bastante tiempo, pero hoy sigue siendo completamente relevante.
Este libro no solo le mostrará cómo los lenguajes de secuencias de comandos encajan en el código nativo, sino que también le enseñará cómo implementar su propio lenguaje de secuencias de comandos. Si bien esto será excesivo para el 99% de los usuarios, no hay mejor manera de entender algo que implementarlo realmente (incluso en una forma muy básica).
Si alguna vez desea escribir un motor de juego usted mismo (o trabaja solo con un motor de renderizado), este texto es invaluable para comprender cómo un lenguaje de secuencias de comandos se puede incorporar mejor a su motor / juego.
Y si alguna vez desea crear su propio lenguaje de secuencias de comandos, este es uno de los mejores lugares para comenzar (que yo sepa).
En primer lugar, los lenguajes de scripting NO ESTÁN compilados . Esa es una gran parte de lo que generalmente los define como lenguajes de secuencias de comandos. A menudo, en cambio, son "interpretados". Lo que esto significa esencialmente es que hay otro idioma (uno que se compila, la mayoría de las veces) que lee en el texto, en tiempo real, y realiza operaciones, línea por línea.
La diferencia entre este y otros lenguajes es que los lenguajes de secuencias de comandos tienden a ser más simples (comúnmente conocidos como "nivel superior"). Sin embargo, también tienden a ser un poco más lentos, ya que los compiladores tienden a optimizar muchos de los problemas que vienen con el "elemento humano" de la codificación, y el binario resultante tiende a ser más pequeño y más rápido de leer para la máquina. Además, hay menos sobrecarga de otro programa que necesita ejecutarse para leer el código que se ejecuta, con programas compilados.
Ahora, podría estar pensando: "Bueno, entiendo que es un poco más fácil, pero ¿por qué alguien renunciaría a todo ese rendimiento por un poco más de facilidad de uso?"
No estaría solo en este supuesto, sin embargo, el nivel de facilidad que tiende a obtener con los lenguajes de scripting, dependiendo de lo que esté haciendo con ellos, puede valer la pena el sacrificio en el rendimiento.
Básicamente: para los casos en que la velocidad de desarrollo es más importante que la velocidad del programa que se ejecuta, utilice un lenguaje de script. Hay MUCHAS situaciones como esta en el desarrollo del juego. Especialmente cuando se trata de cosas triviales como el manejo de eventos de alto nivel.
Editar: La razón por la que lua tiende a ser bastante popular en el desarrollo de juegos es porque es posiblemente uno de los lenguajes de script más rápidos (si no el más rápido) disponibles públicamente en la tierra. Sin embargo, con esta velocidad extra, ha sacrificado parte de su conveniencia. Dicho esto, todavía podría decirse que es más conveniente que trabajar directamente con C o C ++.
Edición importante: tras una investigación más profunda, descubrí que hay mucha más controversia sobre la definición de un lenguaje de scripting (ver la dicotomía de Ousterhout ). La crítica principal de definir un lenguaje como un "lenguaje de script" es que no es significativo para la sintaxis ni la semántica del lenguaje que se interpreta o compila.
Si bien los lenguajes que generalmente se consideran "lenguajes de secuencias de comandos" generalmente se interpretan tradicionalmente, en lugar de compilarse, la definición larga y corta de "lenguajes de secuencias de comandos" depende realmente de una combinación de cómo las personas los ven y cómo los definieron sus creadores.
En términos generales, un idioma podría considerarse fácilmente un lenguaje de secuencias de comandos (suponiendo que esté de acuerdo con la dicotomía de Ousterhout) si cumple con los siguientes criterios (según el artículo vinculado anteriormente):
Se escriben dinámicamente
Tienen poca o ninguna provisión para estructuras de datos complejas
Los programas en ellos (scripts) se interpretan
Además, a menudo se acepta que un lenguaje es un lenguaje de secuencias de comandos si está diseñado para interactuar y funcionar junto con otro lenguaje de programación (generalmente uno que no se considera un lenguaje de secuencias de comandos).
No estoy de acuerdo con usted en la primera línea donde escribió "no se compilan los lenguajes de secuencias de comandos". De hecho, Lua se compila en un bytecode intermedio antes de que un compilador JIT lo ejecute. Algunos, por supuesto, se interpretan línea por línea, pero no todos.
glampert
No estoy de acuerdo con su definición de "lenguaje de script". Hay lenguajes con usos duales (como Lua o C #) que se usan con relativa frecuencia en su forma compilada, en lugar de la forma del script (o viceversa, como es el caso de C #). Cuando un lenguaje se utiliza como lenguaje de secuencias de comandos, se define estrictamente como un lenguaje que se interpreta y no se compila.
Gurgadurgen
Es justo que su definición esté más en línea con Wikipedia: "Un lenguaje de script o lenguaje de script es un lenguaje de programación que admite scripts, programas escritos para un entorno de tiempo de ejecución especial que puede interpretar (en lugar de compilar) ...", no importa Mi comentario entonces.
glampert
1
Incluso el Lua normal está completamente compilado en bytecode, el compilador JIT puede incluso producir binarios nativos. Entonces no, Lua no se interpreta.
Oleg V. Volkov
La primera parte es dudosa, tengo la tentación de votar en contra. Tiene razón en que finalmente se interpreta y la compilación @ OlegV.Volkov JIT no hace que se compile algo. La compilación se define por el tiempo de compilación, que tiene Lua, el código de bytes de Lua no (JIT o no JIT). No dejemos que nuestro término se confunda.
printf
en tu ejemplo (o al menos usoprintf("%s", message);
)En general, vincula o expone algunas funciones nativas a Lua (a menudo utiliza una biblioteca de utilidades para hacerlo, aunque puede hacerlo a mano). Eso permite que el código Lua realice llamadas a su código C ++ nativo cuando su juego ejecuta ese código Lua. En este sentido, su suposición de que el código Lua solo llama al código nativo es cierta (aunque Lua tiene su propia biblioteca estándar de funcionalidad disponible; no necesita llamar a su código nativo para todo).
El código de Lua en sí mismo es interpretado por el tiempo de ejecución de Lua, que es un código C que usted vincula como una biblioteca (generalmente) en su propio programa. Puede leer más sobre cómo funciona Lua en la página de inicio de Lua . En particular, Lua no es "una clase sin compilar" como supones, especialmente si estás pensando en una clase de C ++, porque C ++ casi nunca se compila dinámicamente en la práctica. Sin embargo, el tiempo de ejecución de Lua y los objetos creados por los scripts de Lua que ejecuta su juego consumen espacio en el grupo de memoria del sistema de su juego.
fuente
Los lenguajes de script como Lua se pueden usar de varias maneras. Como dijiste, puedes usar Lua para llamar a funciones en el programa principal, pero también puedes llamar a funciones de Lua desde C ++ si lo deseas. En general, crea una interfaz para permitir cierta flexibilidad con el lenguaje de secuencias de comandos de su elección para que pueda usar el lenguaje de secuencias de comandos en una serie de escenarios. Lo mejor de los lenguajes de scripting como Lua es que se interpretan en lugar de compilarse para que pueda modificar los scripts de Lua sobre la marcha para que no tenga que quedarse esperando a que su juego se compile solo para que usted vuelva a compilar si lo desea. Lo he hecho no se adapta a tus gustos.
Los comandos de Lua solo se llaman cuando el programa C ++ quiere ejecutarlos. Como tal, el código solo se interpretará cuando se llame. Supongo que puedes considerar a Lua como un script bash que se ejecuta por separado del programa principal.
En general, desea utilizar lenguajes de secuencias de comandos para las cosas que desee actualizar o iterar más adelante. He visto que muchas empresas lo usan para la GUI, por lo que proporciona mucha personalización a la interfaz. Siempre que sepa cómo han creado su interfaz Lua, también puede modificar la GUI usted mismo. Pero hay muchos otros caminos que puede seguir para usar Lua, como la lógica de IA, información sobre armas, diálogo de personajes, etc.
fuente
La mayoría de los lenguajes de script, incluido Lua, funcionan en una máquina virtual ( VM ), que es básicamente un sistema para asignar una instrucción de script a una instrucción de CPU "real" o llamada a función. La máquina virtual Lua normalmente se ejecuta en el mismo proceso que la aplicación principal. Esto es especialmente cierto para los juegos que lo usan. La API de Lua le proporciona varias funciones a las que llama en la aplicación nativa para cargar y compilar archivos de script. Por ejemplo,
luaL_dofile()
compila el script dado en el código de bytes de Lua y luego lo ejecuta. Este código de bytes luego será mapeado por la VM que se ejecuta dentro de la API en instrucciones de máquina nativas y llamadas a funciones.El proceso de conectar un lenguaje nativo, como C ++, con un lenguaje de script se llama enlace . En el caso de Lua, su API proporciona funciones que lo ayudan a exponer funciones nativas al código del script. Entonces, por ejemplo, puede definir una función C ++
say_hello()
y hacer que esta función sea invocable desde un script Lua. La API de Lua también proporciona métodos para crear variables y tablas a través del código C ++ que serán visibles para los scripts cuando se ejecuten. Al combinar estas características, puede exponer clases completas de C ++ a Lua. También es posible lo contrario, la API de Lua permite al usuario modificar las variables de Lua y llamar a las funciones de Lua desde el código nativo de C ++.La mayoría, si no todos, los lenguajes de script proporcionan API para facilitar la vinculación del código de script con el código nativo. La mayoría también se compila en bytecode y se ejecuta en una VM, pero algunos pueden interpretarse línea por línea.
Espero que esto ayude a aclarar algunas de sus preguntas.
fuente
Como nadie mencionó esto, lo agregaré aquí para aquellos interesados. Hay un libro completo sobre el tema llamado Game Scripting Mastery . Este es un texto fantástico que se escribió hace bastante tiempo, pero hoy sigue siendo completamente relevante.
Este libro no solo le mostrará cómo los lenguajes de secuencias de comandos encajan en el código nativo, sino que también le enseñará cómo implementar su propio lenguaje de secuencias de comandos. Si bien esto será excesivo para el 99% de los usuarios, no hay mejor manera de entender algo que implementarlo realmente (incluso en una forma muy básica).
Si alguna vez desea escribir un motor de juego usted mismo (o trabaja solo con un motor de renderizado), este texto es invaluable para comprender cómo un lenguaje de secuencias de comandos se puede incorporar mejor a su motor / juego.
Y si alguna vez desea crear su propio lenguaje de secuencias de comandos, este es uno de los mejores lugares para comenzar (que yo sepa).
fuente
En primer lugar, los lenguajes de scripting NO ESTÁN compilados . Esa es una gran parte de lo que generalmente los define como lenguajes de secuencias de comandos. A menudo, en cambio, son "interpretados". Lo que esto significa esencialmente es que hay otro idioma (uno que se compila, la mayoría de las veces) que lee en el texto, en tiempo real, y realiza operaciones, línea por línea.
La diferencia entre este y otros lenguajes es que los lenguajes de secuencias de comandos tienden a ser más simples (comúnmente conocidos como "nivel superior"). Sin embargo, también tienden a ser un poco más lentos, ya que los compiladores tienden a optimizar muchos de los problemas que vienen con el "elemento humano" de la codificación, y el binario resultante tiende a ser más pequeño y más rápido de leer para la máquina. Además, hay menos sobrecarga de otro programa que necesita ejecutarse para leer el código que se ejecuta, con programas compilados.
Ahora, podría estar pensando: "Bueno, entiendo que es un poco más fácil, pero ¿por qué alguien renunciaría a todo ese rendimiento por un poco más de facilidad de uso?"
No estaría solo en este supuesto, sin embargo, el nivel de facilidad que tiende a obtener con los lenguajes de scripting, dependiendo de lo que esté haciendo con ellos, puede valer la pena el sacrificio en el rendimiento.
Básicamente: para los casos en que la velocidad de desarrollo es más importante que la velocidad del programa que se ejecuta, utilice un lenguaje de script. Hay MUCHAS situaciones como esta en el desarrollo del juego. Especialmente cuando se trata de cosas triviales como el manejo de eventos de alto nivel.
Editar: La razón por la que lua tiende a ser bastante popular en el desarrollo de juegos es porque es posiblemente uno de los lenguajes de script más rápidos (si no el más rápido) disponibles públicamente en la tierra. Sin embargo, con esta velocidad extra, ha sacrificado parte de su conveniencia. Dicho esto, todavía podría decirse que es más conveniente que trabajar directamente con C o C ++.
Edición importante: tras una investigación más profunda, descubrí que hay mucha más controversia sobre la definición de un lenguaje de scripting (ver la dicotomía de Ousterhout ). La crítica principal de definir un lenguaje como un "lenguaje de script" es que no es significativo para la sintaxis ni la semántica del lenguaje que se interpreta o compila.
Si bien los lenguajes que generalmente se consideran "lenguajes de secuencias de comandos" generalmente se interpretan tradicionalmente, en lugar de compilarse, la definición larga y corta de "lenguajes de secuencias de comandos" depende realmente de una combinación de cómo las personas los ven y cómo los definieron sus creadores.
En términos generales, un idioma podría considerarse fácilmente un lenguaje de secuencias de comandos (suponiendo que esté de acuerdo con la dicotomía de Ousterhout) si cumple con los siguientes criterios (según el artículo vinculado anteriormente):
Además, a menudo se acepta que un lenguaje es un lenguaje de secuencias de comandos si está diseñado para interactuar y funcionar junto con otro lenguaje de programación (generalmente uno que no se considera un lenguaje de secuencias de comandos).
fuente