Como Lua funciona como uma linguagem de script em jogos?

67

Estou um pouco confuso sobre o que exatamente Lua é e como um jogo programado em C ++ o utilizaria. Estou perguntando principalmente sobre como ele é compilado e executado.

Por exemplo, quando você usa um programa escrito em C ++ que usa scripts Lua: o código em Lua chama apenas funções no programa principal escrito em C ++ e age como uma classe não compilada esperando para ser compilada e adicionada ao heap de memória do C ++ programa?

Ou ele age como um script bash no Linux, onde apenas executa programas completamente separados do programa principal?

XSoloDolo
fonte

Respostas:

90

O script é uma abstração de programação na qual você (conceitualmente) possui um programa (o script) em execução em outro programa (o host). Na maioria dos casos, o idioma em que você escreve o script é diferente do idioma em que o host está escrito, mas qualquer abstração de programa dentro de um programa pode ser considerada script.

Conceitualmente, as etapas comuns para habilitar o script são as seguintes (usarei pseudo-c para o host e pseudo-lua para o script. Essas não são etapas exatas, mas são mais parecidas com o fluxo geral no qual você habilita o script)

  1. Crie uma máquina virtual no programa host:

    VM m_vm = createVM();
  2. Crie uma função e exponha-a à VM:

    void scriptPrintMessage(VM vm)
    {
        const char* message = getParameter(vm, 0); // first parameter
        printf(message);
    }
    
    //...
    
    createSymbol(m_vm, "print", scriptPrintMessage);

    Observe que o nome no qual expusemos a função ( print) não precisa corresponder ao nome interno da própria função ( scriptPrintMessage)

  3. Execute algum código de script que use a função:

    const char* scriptCode = "print(\"Hello world!\")"; // Could also be loaded from a file though
    doText(m_vm, scriptCode);

É tudo o que há para isso. O programa flui da seguinte maneira:

  1. Você liga doText(). O controle é então transferido para a máquina virtual, que executará o texto dentro scriptCode.

  2. O código do script encontra um símbolo exportado anteriormente print. Ele transferirá o controle para a função scriptPrintMessage().

  3. Quando scriptPrintMessage()terminar, o controle será transferido de volta para a máquina virtual.

  4. Quando todo o texto scriptCodeinserido for executado, doText()será concluído e o controle será transferido de volta para o seu programa na linha seguinte doText().

Portanto, em geral, tudo o que você está fazendo é executar um programa dentro de outro programa. Teoricamente falando, não há nada que você possa fazer com scripts que você não pode fazer sem eles, mas essa abstração permite que você faça coisas interessantes com muita facilidade. Alguns deles são:

  • Separação de preocupações: é um padrão comum escrever um mecanismo de jogo em C / C ++ e depois o jogo real em uma linguagem de script como lua. Feito corretamente, o código do jogo pode ser desenvolvido de forma completamente independente do próprio mecanismo.

  • Flexibilidade: as linguagens de script são comumente interpretadas e, como tal, uma alteração em um script não requer necessariamente uma reconstrução de todo o projeto. Feito corretamente, você pode até alterar um script e ver os resultados sem nem mesmo reiniciar o programa!

  • Estabilidade e segurança: como o script está sendo executado dentro de uma máquina virtual, se bem feito, um script de buggy não irá travar o programa host. Isso é especialmente importante quando você permite que seus usuários escrevam seus próprios scripts no seu jogo. Lembre-se de que você pode criar quantas máquinas virtuais independentes quiser! (Uma vez, criei um servidor MMO no qual cada correspondência era executada em uma máquina virtual lua separada)

  • Recursos de idioma: ao usar idiomas de script, com base na sua escolha para idiomas de host e script, você pode usar os melhores recursos que cada idioma tem a oferecer. Em particular, as corotinas de lua são um recurso muito interessante e difícil de implementar em C ou C ++

Os scripts não são perfeitos. Existem algumas desvantagens comuns no uso de scripts:

  • A depuração se torna muito difícil: geralmente, os depuradores incluídos nos IDEs comuns não são projetados para depurar o código nos scripts. Por esse motivo, a depuração de rastreamento do console é muito mais comum do que eu gostaria.

    Algumas linguagens de script como lua possuem recursos de depuração que podem ser aproveitados em alguns IDEs como Eclipse. Fazer isso é muito difícil e sinceramente nunca vi a depuração de scripts funcionando, bem como a depuração nativa.

    A propósito, a extrema falta de interesse que os desenvolvedores do Unity têm nesse assunto é minha principal crítica ao mecanismo de jogo e o principal motivo pelo qual não o uso mais, mas discordo.

  • Integração do IDE: É improvável que o seu IDE saiba quais funções estão sendo exportadas do seu programa e, como tal, recursos como IntelliSense e similares provavelmente não funcionarão com seus scripts.

  • Desempenho: sendo programas comumente interpretados e destinados a uma máquina virtual abstrata cuja arquitetura pode ser diferente do hardware real, os scripts geralmente são mais lentos de executar do que o código nativo. Algumas VMs, como luaJIT e V8, fazem um bom trabalho. Isso pode ser notado se você fizer um uso muito pesado de scripts.

    Normalmente, as alterações de contexto (host para script e script para host) são muito caras, portanto, você pode minimizá-las se estiver com problemas de desempenho.

A decisão de como você usa seus scripts é sua. Vi scripts usados ​​para coisas tão simples quanto carregar configurações, tão complexas quanto criar jogos inteiros na linguagem de script com um mecanismo de jogo muito fino. Certa vez, vi um mecanismo de jogo muito exótico que misturava scripts lua e JavaScript (via V8).

O script é apenas uma ferramenta. Como você o usa para criar jogos incríveis é com você.

Panda Pajama
fonte
Eu sou realmente novo nisso, mas eu uso o Unity. Você mencionou algo sobre o Unity, poderia elaborar isso? Devo usar outra coisa?
Tokamocha
11
Eu não usaria printfno seu exemplo (ou pelo menos usar printf("%s", message);)
catraca aberração
11
@ratchetfreak: O ponto e vírgula no final da sua mensagem seguido pelo parêntese está piscando para mim ...
Panda Pajama
11
Uma das melhores respostas que já vi neste site há muito tempo; merece todos os votos que recebe. Muito bem feito.
Steven Stadnicki
11
O filho do pôster de Lua nos jogos é o World of Warcraft, no qual a maior parte da interface do usuário é escrita em Lua e a maioria pode ser substituída pelo jogador. Estou surpreso que você não tenha mencionado.
Michael Hampton
7

Geralmente, você liga ou expõe algumas funções nativas a Lua (geralmente usando uma biblioteca de utilitários para fazer isso, embora você possa fazê-lo manualmente). Isso permite que o código Lua faça chamadas para o código C ++ nativo quando o jogo executa esse código Lua. Nesse sentido, sua suposição de que o código Lua chama apenas o código nativo é verdadeira (embora Lua tenha sua própria biblioteca padrão de funcionalidade disponível; você não precisa chamar o código nativo para tudo).

O próprio código Lua é interpretado pelo tempo de execução Lua, que é o código C que você vincula como uma biblioteca (geralmente) em seu próprio programa. Você pode ler mais sobre como Lua funciona na página inicial de Lua . Em particular, Lua não é "uma classe não compilada", como você supõe, especialmente se você está pensando em uma classe C ++, porque quase nunca o C ++ é compilado dinamicamente na prática. No entanto, o tempo de execução de Lua e os objetos criados pelos scripts Lua executados pelo jogo consomem espaço no pool de memória do sistema do jogo.

Josh
fonte
5

Linguagens de script como Lua podem ser usadas de várias maneiras. Como você disse, você pode usar Lua para chamar funções no programa principal, mas também pode chamar as funções Lua do lado do C ++, se desejar. Geralmente, você cria uma interface para permitir alguma flexibilidade com a linguagem de script de sua escolha, para poder usar a linguagem de script em uma série de cenários. O melhor de linguagens de script como Lua é que elas são interpretadas em vez de compiladas, para que você possa modificar os scripts Lua em tempo real, para que não precise ficar esperando que o jogo seja compilado apenas para que você recompile se quiser. feito não se adequa ao seu gosto.

Os comandos Lua são chamados apenas quando o programa C ++ deseja executá-los. Como tal, o código só será interpretado quando for chamado. Eu acho que você pode considerar Lua como um script bash que é executado separadamente do programa principal.

Geralmente, você deseja usar as linguagens de script para coisas que você deseja atualizar ou iterar mais adiante. Já vi muitas empresas usá-lo para a GUI, por isso fornece muita personalização à interface. Desde que você saiba como eles criaram a interface Lua, você também pode modificar a GUI. Mas existem vários outros caminhos que você pode seguir para usar o Lua, como lógica de IA, informações sobre armas, diálogo de personagens etc.

M Davies
fonte
3

A maioria das linguagens de script, incluindo Lua, opera em uma máquina virtual ( VM ), que é basicamente um sistema para mapear uma instrução de script para uma instrução de CPU "real" ou chamada de função. A Lua VM normalmente é executada no mesmo processo que o aplicativo principal. Isto é especialmente verdade para jogos que o utilizam. A API Lua fornece várias funções que você chama no aplicativo nativo para carregar e compilar arquivos de script. Por exemplo, luaL_dofile()compila o script fornecido no bytecode Lua e depois o executa. Esse bytecode será mapeado pela VM em execução na API em instruções nativas da máquina e chamadas de função.

O processo de conectar uma linguagem nativa, como C ++, a uma linguagem de script é chamado de ligação . No caso de Lua, sua API fornece funções que ajudam a expor funções nativas ao código do script. Assim, você pode, por exemplo, definir uma função C ++ say_hello()e tornar essa função acessível a partir de um script Lua. A API Lua também fornece métodos para criar variáveis ​​e tabelas via código C ++ que serão visíveis para os scripts quando eles forem executados. Ao combinar esses recursos, você pode expor classes C ++ inteiras a Lua. O contrário também é possível, a API Lua permite ao usuário modificar variáveis ​​Lua e chamar funções Lua a partir do código C ++ nativo.

A maioria das linguagens de script, se não todas, fornece APIs para facilitar a ligação do código de script ao código nativo. A maioria também é compilada no bytecode e executada em uma VM, mas algumas podem ser interpretadas linha a linha.

Espero que isso ajude a esclarecer algumas de suas perguntas.

glampert
fonte
3

Como ninguém mencionou isso, vou adicioná-lo aqui para os interessados. Há um livro inteiro sobre o assunto, chamado Game Scripting Mastery . Este é um texto fantástico que foi escrito há muito tempo, mas permanece completamente relevante hoje.

Este livro não apenas mostrará como as linguagens de script se encaixam no código nativo, mas também ensina como implementar sua própria linguagem de script. Embora isso seja um exagero para 99% dos usuários, não há melhor maneira de entender algo do que realmente implementá-lo (mesmo de uma forma muito básica).

Se você quiser escrever um mecanismo de jogo você mesmo (ou trabalhar apenas com um mecanismo de renderização), este texto será inestimável para entender como uma linguagem de script pode ser melhor incorporada ao seu mecanismo / jogo.

E se você quiser criar sua própria linguagem de script, este é um dos melhores lugares para começar (tanto quanto eu sei).

free3dom
fonte
2

Em primeiro lugar, as linguagens de script geralmente não são compiladas . Essa é uma grande parte do que geralmente os define como linguagens de script. Em geral, eles são "interpretados". O que isso significa essencialmente é que há outro idioma (que é compilado, mais frequentemente do que não) que está lendo o texto, em tempo real, e realizando operações, linha por linha.

A diferença entre este e outros idiomas é que os idiomas de script tendem a ser mais simples (geralmente chamados de "nível superior"). No entanto, eles também tendem a ser um pouco mais lentos, pois os compiladores tendem a otimizar muitos dos problemas que acompanham o "elemento humano" da codificação, e o binário resultante tende a ser menor e mais rápido de ler, para a máquina. Além disso, há menos sobrecarga de outro programa que precisa ser executado para ler o código que está sendo executado, com programas compilados.

Agora, você pode estar pensando: "Bem, eu entendo que é um pouco mais fácil, mas por que alguém desistiria de todo esse desempenho por um pouco mais de facilidade de uso?"

Você não ficaria sozinho nessa suposição; no entanto, o nível de facilidade que você tende a obter com as linguagens de script, dependendo do que está fazendo com elas, pode valer a pena o sacrifício no desempenho.

Basicamente: Nos casos em que a velocidade de desenvolvimento é mais importante que a velocidade do programa em execução, use uma linguagem de script. Existem muitas situações como essa no desenvolvimento de jogos. Especialmente ao lidar com coisas triviais, como manipulação de eventos de alto nível.

Edit: O motivo pelo qual lua tende a ser bastante popular no desenvolvimento de jogos é porque é indiscutivelmente uma das linguagens de script mais rápidas (se não as mais rápidas) disponíveis publicamente no mundo. No entanto, com essa velocidade extra, ele sacrificou parte de sua conveniência. Dito isto, ainda é sem dúvida mais conveniente do que trabalhar com C ou C ++ direto.

Edição importante: Após mais pesquisas, descobri que há muito mais controvérsia sobre a definição de uma linguagem de script (consulte a dicotomia de Ousterhout ). A principal crítica da definição de uma linguagem como uma "linguagem de script" é que ela não é significativa para a sintaxe nem a semântica da linguagem em que é interpretada ou compilada.

Embora as linguagens geralmente consideradas "linguagens de script" sejam geralmente interpretadas tradicionalmente, em vez de compiladas, o longo e curto de sua definição como "linguagens de script" depende realmente de uma combinação de como as pessoas as veem e como seus criadores as definiram.

De um modo geral, um idioma pode ser facilmente considerado um idioma de script (supondo que você concorde com a dicotomia de Ousterhout) se atender aos seguintes critérios (de acordo com o artigo vinculado acima):

  • Eles são digitados dinamicamente
  • Eles têm pouca ou nenhuma provisão para estruturas de dados complexas
  • Os programas neles (scripts) são interpretados

Além disso, geralmente é aceito que uma linguagem é uma linguagem de script se for projetada para interagir e funcionar com outra linguagem de programação (geralmente uma que não é considerada uma linguagem de script).

Gurgadurgen
fonte
11
Eu discordo de você na primeira linha em que você escreveu "as linguagens de script não são compiladas". Lua é de fato compilada no bytecode intermediário antes da execução por um compilador JIT. Alguns são obviamente interpretados linha por linha, mas não todos.
glampert
Eu discordo de sua definição de "linguagem de script". Existem idiomas com usos duplos (como Lua ou C #) que são relativamente comumente usados ​​na forma compilada, em vez da forma de script (ou vice-versa, como é o caso do C #). Quando uma linguagem é usada como linguagem de script, é estritamente definida como uma linguagem que é interpretada e não compilada.
Gurgadurgen 21/04
É justo, sua definição está mais alinhada com a Wikipedia: "Uma linguagem de script ou linguagem de script é uma linguagem de programação que suporta scripts, programas criados para um ambiente de tempo de execução especial que pode interpretar (em vez de compilar) ...", não importa meu comentário então.
glampert
11
Mesmo Lua regular é completamente compilada no bytecode, o compilador JIT pode até produzir binário nativo. Então não, Lua não é interpretada.
amigos estão dizendo sobre oleg V.
A primeira parte é duvidosa, estou tentada a votar. Ele está certo de que, em última análise, é interpretado e a compilação do @ OlegV.Volkov JIT não cria nada compilado. A compilação é definida pelo tempo de compilação, o que Lua possui, o código de bytes Lua não (JIT ou nenhum JIT). Não vamos confundir nosso termo.
Alec Teal