Come funziona Lua come linguaggio di scripting nei giochi?


67

Sono un po 'confuso su cosa sia esattamente Lua e su come un gioco programmato in C ++ lo userebbe. Sto chiedendo principalmente su come viene compilato ed eseguito.

Ad esempio, quando si utilizza un programma scritto in C ++ che utilizza gli script Lua: il codice in Lua chiama semplicemente le funzioni nel programma principale scritto in C ++ e agisce come una classe non compilata in attesa di essere compilata e aggiunta all'heap di memoria del C ++ programma?

O si comporta come uno script bash in Linux dove esegue solo programmi completamente separati dal programma principale?

Risposte:


90

Lo scripting è un'astrazione di programmazione in cui (concettualmente) si ha un programma (lo script) in esecuzione all'interno di un altro programma (l'host). Nella maggior parte dei casi, la lingua in cui si scrive lo script è diversa dalla lingua in cui è scritto l'host, ma qualsiasi astrazione all'interno di un programma può essere considerata come script.

Concettualmente, i passaggi comuni per abilitare gli script sono i seguenti (userò pseudo-c per l'host e pseudo-lua per lo script. Questi non sono passaggi esatti, ma più simili al flusso generale in cui si abilita lo scripting)

  1. Creare una macchina virtuale nel programma host:

    VM m_vm = createVM();
  2. Crea una funzione ed esponila alla VM:

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

    Si noti che il nome in cui è stata esposta la funzione ( print) non deve corrispondere al nome interno della funzione stessa ( scriptPrintMessage)

  3. Esegui un codice di script che utilizza la funzione:

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

Questo è tutto quello che c'è da fare. Il programma scorre quindi nel modo seguente:

  1. Tu chiami doText(). Il controllo viene quindi trasferito alla macchina virtuale, che eseguirà il testo all'interno scriptCode.

  2. Il codice dello script trova un simbolo precedentemente esportato print. Quindi trasferirà il controllo alla funzione scriptPrintMessage().

  3. Al scriptPrintMessage()termine, il controllo tornerà alla macchina virtuale.

  4. Quando tutto il testo in scriptCodeè stato eseguito, doText()finirà e il controllo verrà trasferito al tuo programma sulla riga successiva doText().

Quindi, in generale, tutto ciò che stai facendo è eseguire un programma all'interno di un altro programma. Teoricamente parlando, non c'è niente che tu possa fare con gli script di cui non puoi farne a meno, ma questa astrazione ti consente di fare alcune cose interessanti molto facilmente. Alcuni di loro sono:

  • Separazione delle preoccupazioni: è un modello comune scrivere un motore di gioco in C / C ++ e quindi il gioco reale in un linguaggio di script come lua. Fatto bene, il codice di gioco può essere sviluppato in modo completamente indipendente dal motore stesso.

  • Flessibilità: i linguaggi di scripting sono comunemente interpretati e, come tali, un cambiamento in uno script non richiederà necessariamente una ricostruzione dell'intero progetto. Fatto bene, puoi anche cambiare uno script e vedere i risultati senza nemmeno riavviare il programma!

  • Stabilità e sicurezza: poiché lo script è in esecuzione all'interno di una macchina virtuale, se fatto correttamente, uno script difettoso non bloccherà il programma host. Ciò è particolarmente importante quando permetti ai tuoi utenti di scrivere i propri script sul tuo gioco. Ricorda che puoi creare quante macchine virtuali indipendenti vuoi! (Una volta ho creato un server MMO in cui ogni partita veniva eseguita su una macchina virtuale lua separata)

  • Funzionalità linguistiche: quando si utilizzano linguaggi di scripting, in base alla propria scelta per i linguaggi host e di script, è possibile utilizzare le migliori funzionalità che ogni lingua ha da offrire. In particolare, le coroutine di lua sono una caratteristica molto interessante che è molto difficile da implementare in C o C ++

Gli script non sono perfetti però. Ci sono alcuni svantaggi comuni nell'uso degli script:

  • Il debug diventa molto difficile: di solito, i debugger inclusi negli IDE comuni non sono progettati per eseguire il debug del codice all'interno degli script. Per questo motivo, il debug della traccia della console è molto più comune di quanto mi piacerebbe.

    Alcuni linguaggi di scripting come lua hanno funzionalità di debug che possono essere sfruttate in alcuni IDE come Eclipse. Fare questo è molto difficile e onestamente non ho mai visto il debug degli script funzionare così come il debug nativo.

    A proposito, l'estrema mancanza di interesse degli sviluppatori Unity in questa materia è la mia critica principale al loro motore di gioco, e il motivo principale per cui non lo uso più, ma sto divagando.

  • Integrazione IDE: è improbabile che il tuo IDE sappia quali funzioni vengono esportate dal tuo programma e, come tale, è improbabile che funzioni come IntelliSense e simili funzionino con i tuoi script.

  • Prestazioni: essendo programmi comunemente interpretati e pensati per una macchina virtuale astratta la cui architettura potrebbe essere diversa dall'hardware reale, gli script sono generalmente più lenti da eseguire rispetto al codice nativo. Alcune VM come luaJIT e V8 fanno comunque un ottimo lavoro. Questo può essere evidente se si fa un uso molto pesante degli script.

    Di solito, i cambiamenti di contesto (da host a script e da script a host) sono molto costosi, quindi potresti volerli minimizzare se riscontri problemi di prestazioni.

Come usi i tuoi script dipende da te. Ho visto degli script usati per cose semplici come il caricamento delle impostazioni, tanto complessi quanto la creazione di interi giochi nel linguaggio di scripting con un motore di gioco molto sottile. Una volta ho persino visto un motore di gioco molto esotico che mescolava script lua e JavaScript (via V8).

Lo scripting è solo uno strumento. Il modo in cui lo usi per creare fantastici giochi dipende completamente da te.


Sono davvero nuovo a questo, ma utilizzo Unity. Hai menzionato qualcosa su Unity, potresti approfondirlo? Dovrei usare qualcos'altro?
Tokamocha,

1
Non userei printfnel tuo esempio (o almeno l'uso printf("%s", message);)
maniaco del cricchetto,

1
@ratchetfreak: il punto e virgola alla fine del messaggio seguito dalla parentesi mi fa l'occhiolino ...
Panda Pajama,

1
Una delle migliori risposte che ho visto su questo sito da molto tempo; merita ogni voto ottenuto. Molto ben fatto.
Steven Stadnicki,

1
Il bambino poster di Lua nei giochi è World of Warcraft, in cui la maggior parte dell'interfaccia utente è scritta in Lua e la maggior parte dei quali può essere sostituita dal giocatore. Sono sorpreso che tu non l'abbia menzionato.
Michael Hampton,

7

In genere, si associano o espongono alcune funzioni native a Lua (spesso utilizzando una libreria di utilità per farlo, anche se è possibile farlo manualmente). Ciò consente al codice Lua di effettuare chiamate nel codice C ++ nativo quando il gioco esegue quel codice Lua. In questo senso, la tua supposizione che il codice Lua chiami solo nel codice nativo è vera (anche se Lua ha la sua libreria standard di funzionalità disponibile; non è necessario chiamare il tuo codice nativo per tutto).

Il codice Lua stesso è interpretato dal runtime Lua, che è il codice C che si collega come libreria (di solito) nel proprio programma. Puoi leggere di più su come funziona Lua nella homepage di Lua . In particolare, Lua non è "una classe non compilata" come si suppone, soprattutto se si sta pensando a una classe C ++, poiché nella pratica C ++ non viene quasi mai compilato dinamicamente. Tuttavia, il runtime Lua e gli oggetti creati dagli script Lua eseguiti dal gioco consumano spazio nel pool di memoria del sistema del gioco.


5

I linguaggi di scripting come Lua possono essere utilizzati in diversi modi. Come hai detto, puoi usare Lua per chiamare le funzioni nel programma principale, ma puoi anche avere funzioni Lua chiamate dal lato C ++ se vuoi. Generalmente crei un'interfaccia per consentire una certa flessibilità con il linguaggio di scripting di tua scelta in modo da poter utilizzare il linguaggio di scripting in una serie di scenari. La cosa grandiosa dei linguaggi di scripting come Lua è che vengono interpretati anziché compilati in modo da poter modificare gli script Lua al volo in modo da non doverti sedere in attesa che il tuo gioco si compili solo per te per ricompilare se ciò che hai fatto non soddisfa i tuoi gusti.

I comandi Lua vengono chiamati solo quando il programma C ++ vuole eseguirli. Pertanto, il codice verrà interpretato solo quando viene chiamato. Immagino che tu possa considerare Lua come uno script bash che viene eseguito separatamente dal programma principale.

Generalmente si desidera utilizzare i linguaggi di scripting per cose che si potrebbero voler aggiornare o ripetere più avanti. Ho visto molte aziende usarlo per la GUI, quindi offre molta personalizzazione all'interfaccia. Se sai come hanno realizzato la loro interfaccia Lua, puoi anche modificare la GUI da solo. Ma ci sono numerosi altri percorsi che puoi seguire per usare Lua, come la logica dell'IA, le informazioni sulle armi, il dialogo dei personaggi, ecc.


3

La maggior parte dei linguaggi di scripting, incluso Lua, opera su una macchina virtuale ( VM ), che è fondamentalmente un sistema per mappare un'istruzione di script su un'istruzione CPU "reale" o una chiamata di funzione. La VM Lua funziona normalmente nello stesso processo dell'applicazione principale. Questo è particolarmente vero per i giochi che lo usano. L'API Lua offre diverse funzioni chiamate nell'applicazione nativa per caricare e compilare file di script. Ad esempio luaL_dofile()compila lo script specificato nel bytecode Lua e quindi lo esegue. Questo bytecode verrà quindi mappato dalla VM in esecuzione all'interno dell'API in istruzioni macchina native e chiamate di funzione.

Il processo di connessione di una lingua nativa, come C ++, con un linguaggio di scripting è chiamato binding . Nel caso Lua, la sua API fornisce funzioni che consentono di esporre le funzioni native al codice dello script. Quindi, ad esempio, puoi definire una funzione C ++ say_hello()e rendere questa funzione richiamabile da uno script Lua. L'API Lua fornisce anche metodi per la creazione di variabili e tabelle tramite codice C ++ che saranno visibili per gli script quando vengono eseguiti. Combinando queste funzionalità è possibile esporre intere classi C ++ a Lua. È anche possibile il contrario, l'API Lua consente all'utente di modificare le variabili Lua e chiamare le funzioni Lua dal codice C ++ nativo.

La maggior parte, se non tutti, i linguaggi di scripting forniscono API per facilitare l'associazione del codice di script con il codice nativo. La maggior parte viene anche compilata in bytecode ed eseguita in una macchina virtuale, ma alcuni possono essere interpretati riga per riga.

Spero che questo aiuti a chiarire alcune delle tue domande.


3

Dal momento che nessuno lo ha menzionato, lo aggiungerò qui per coloro che sono interessati. C'è un intero libro sull'argomento chiamato Game Scripting Mastery . Questo è un testo fantastico che è stato scritto un po 'di tempo fa, ma rimane completamente rilevante oggi.

Questo libro non ti mostrerà solo come i linguaggi di script si adattano al codice nativo, ma ti insegna anche come implementare il tuo linguaggio di scripting. Mentre questo sarà eccessivo per il 99% degli utenti, non c'è modo migliore per capire qualcosa che implementarlo effettivamente (anche in una forma molto semplice).

Se vuoi mai scrivere un motore di gioco da solo (o lavori solo con un motore di rendering), questo testo è prezioso per capire come un linguaggio di scripting può essere incorporato nel tuo motore / gioco.

E se mai vuoi creare il tuo linguaggio di scripting, questo è uno dei posti migliori da cui iniziare (per quanto ne so).


2

In primo luogo, i linguaggi di scripting NON vengono generalmente compilati . Questa è una grande parte di ciò che generalmente li definisce come linguaggi di scripting. Spesso vengono invece "interpretati". Ciò significa essenzialmente che esiste un'altra lingua (una che viene compilata, il più delle volte) che sta leggendo il testo, in tempo reale, e sta eseguendo operazioni riga per riga.

La differenza tra questo e altri linguaggi è che i linguaggi di scripting tendono ad essere più semplici (comunemente definiti "livello superiore"). Tuttavia, tendono anche ad essere un po 'più lenti, poiché i compilatori tendono a ottimizzare molti dei problemi che derivano dall'elemento umano della codifica e il binario risultante tende a essere più piccolo e più veloce da leggere per la macchina. Inoltre, è necessario eseguire meno sovraccarichi da un altro programma per leggere il codice in esecuzione, con i programmi compilati.

Ora potresti pensare: "Beh, capisco che è un po 'più facile, ma perché mai qualcuno dovrebbe rinunciare a tutte quelle prestazioni per un po' più di facilità d'uso?"

Non saresti solo in questa ipotesi, tuttavia il livello di facilità che tendi ad avere con i linguaggi di scripting, a seconda di cosa stai facendo con loro, può valere la pena sacrificare le prestazioni.

Fondamentalmente: per i casi in cui la velocità di sviluppo è più importante della velocità del programma in esecuzione, utilizzare un linguaggio di scripting. Ci sono MOLTE situazioni come questa nello sviluppo del gioco. Soprattutto quando si tratta di cose insignificanti come la gestione di eventi di alto livello.

Modifica: il motivo per cui lua tende ad essere piuttosto popolare nello sviluppo del gioco è perché è probabilmente uno dei linguaggi di scripting pubblicamente disponibili più veloci (se non il più veloce) sulla terra. Tuttavia, con questa velocità extra, ha sacrificato alcune delle sue comodità. Detto questo, è ancora probabilmente più conveniente che lavorare con C o C ++ direttamente.

Modifica importante: dopo ulteriori ricerche, ho scoperto che ci sono molte più controversie sulla definizione di un linguaggio di scripting (vedi la dicotomia di Ousterhout ). La critica principale alla definizione di un linguaggio come "linguaggio di scripting" è che non è né significativo per la sintassi né per la semantica del linguaggio che viene interpretato o compilato.

Mentre le lingue che sono generalmente considerate "linguaggi di scripting" sono generalmente tradotte tradizionalmente, piuttosto che compilate, la lunga e breve della loro definizione di "linguaggi di scripting" dipende in realtà da una combinazione di come le persone le vedono e di come le definiscono i loro creatori.

In generale, una lingua potrebbe essere facilmente considerata un linguaggio di scripting (supponendo che tu sia d'accordo con la dicotomia di Ousterhout) se soddisfa i seguenti criteri (secondo l'articolo collegato sopra):

  • Sono digitati in modo dinamico
  • Hanno poca o nessuna disposizione per strutture dati complesse
  • I programmi in essi contenuti (script) vengono interpretati

Inoltre, è spesso accettato che un linguaggio sia un linguaggio di scripting se è progettato per interagire e funzionare insieme a un altro linguaggio di programmazione (di solito uno che non è considerato un linguaggio di scripting).


1
Non sarei d'accordo con te nella prima riga in cui hai scritto "i linguaggi di scripting non sono compilati". Lua è infatti compilato in bytecode intermedio prima dell'esecuzione da parte di un compilatore JIT. Alcuni sono ovviamente interpretati riga per riga, ma non tutti.
glampert

Non sarei d'accordo con la tua definizione di "linguaggio di scripting". Esistono linguaggi con duplice uso (come Lua o C #) che sono relativamente comunemente usati nella loro forma compilata, piuttosto che nella forma di script (o viceversa, come nel caso di C #). Quando una lingua viene utilizzata come linguaggio di scripting, viene rigorosamente definita come una lingua interpretata e non compilata.
Gurgadurgen,

Abbastanza giusto, la tua definizione è più in linea con Wikipedia: "Un linguaggio di script o linguaggio di script è un linguaggio di programmazione che supporta script, programmi scritti per un ambiente run-time speciale che può interpretare (piuttosto che compilare) ...", non importa il mio commento quindi.
glampert

1
Anche il normale Lua è completamente compilato in bytecode, il compilatore JIT può persino produrre binari nativi. Quindi no, Lua non viene interpretata.
Oleg V. Volkov,

La prima parte è incerta, sono tentato di votare. Ha ragione nel senso che alla fine viene interpretato e la compilazione @ OlegV.Volkov JIT non crea qualcosa compilato. La compilazione è definita dal tempo di compilazione, che Lua ha, il codice byte Lua no (JIT o no JIT). Non confondiamo il termine.
Alec Teal,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.