Wie funktioniert Lua als Skriptsprache in Spielen?

67

Ich bin ein wenig unsicher, was genau Lua ist und wie ein in C ++ programmiertes Spiel es verwenden würde. Ich frage vor allem, wie es kompiliert und ausgeführt wird.

Wenn Sie beispielsweise ein in C ++ geschriebenes Programm verwenden, das Lua-Skripte verwendet: Ruft der Code in Lua nur Funktionen im in C ++ geschriebenen Hauptprogramm auf und fungiert als nicht kompilierte Klasse, die darauf wartet, kompiliert und dem Speicherhaufen von C ++ hinzugefügt zu werden Programm?

Oder verhält es sich wie ein Bash-Skript in Linux, in dem nur Programme ausgeführt werden, die vom Hauptprogramm völlig getrennt sind?

XSoloDolo
quelle

Antworten:

90

Scripting ist eine Programmierabstraktion, in der (konzeptionell) ein Programm (das Skript) in einem anderen Programm (dem Host) ausgeführt wird. In den meisten Fällen unterscheidet sich die Sprache, in der Sie das Skript schreiben, von der Sprache, in der der Host geschrieben ist. Es kann jedoch jede Programm-innerhalb-eines-Programms-Abstraktion als Skript angesehen werden.

Konzeptionell lauten die allgemeinen Schritte zum Aktivieren von Skripten wie folgt: (Ich verwende Pseudo-C für den Host und Pseudo-Lua für das Skript. Dies sind keine genauen Schritte, sondern ähneln eher dem allgemeinen Ablauf, in dem Sie Skripten aktivieren.)

  1. Erstellen Sie eine virtuelle Maschine im Host-Programm:

    VM m_vm = createVM();
  2. Erstellen Sie eine Funktion und stellen Sie sie der VM zur Verfügung:

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

    Beachten Sie, dass der Name, in dem wir die Funktion ( print) verfügbar gemacht haben, nicht mit dem internen Namen der Funktion selbst ( scriptPrintMessage) übereinstimmen muss.

  3. Führen Sie einen Skriptcode aus, der die Funktion verwendet:

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

Das ist alles dazu. Das Programm läuft dann folgendermaßen ab:

  1. Du rufst an doText(). Die Steuerung wird dann auf die virtuelle Maschine übertragen, die den darin enthaltenen Text ausführt scriptCode.

  2. Der Skriptcode findet ein zuvor exportiertes Symbol print. Die Steuerung wird dann auf die Funktion übertragen scriptPrintMessage().

  3. Wenn der scriptPrintMessage()Vorgang abgeschlossen ist, wird die Steuerung wieder auf die virtuelle Maschine übertragen.

  4. Wenn der gesamte Text in scriptCodeausgeführt wurde, doText()wird beendet, und die Steuerung wird in der Zeile danach wieder an Ihr Programm übertragen doText().

Im Allgemeinen müssen Sie also nur ein Programm in einem anderen Programm ausführen. Theoretisch gibt es nichts, was Sie mit Skripten tun können, ohne die Sie nicht auskommen, aber diese Abstraktion ermöglicht es Ihnen, einige interessante Dinge wirklich einfach zu tun. Einige von ihnen sind:

  • Trennung von Bedenken: Es ist ein gängiges Muster, eine Game-Engine in C / C ++ und dann das eigentliche Spiel in einer Skriptsprache wie lua zu schreiben. Richtig gemacht, kann der Spielcode völlig unabhängig von der Engine selbst entwickelt werden.

  • Flexibilität: Skriptsprachen werden häufig interpretiert, sodass eine Änderung eines Skripts nicht unbedingt eine Neuerstellung des gesamten Projekts erforderlich macht. Richtig gemacht, können Sie sogar ein Skript ändern und die Ergebnisse sehen, ohne dass das Programm neu gestartet werden muss!

  • Stabilität und Sicherheit: Da das Skript in einer virtuellen Maschine ausgeführt wird, kann ein fehlerhaftes Skript das Host-Programm nicht zum Absturz bringen, wenn es richtig ausgeführt wird. Dies ist besonders wichtig, wenn Sie Ihren Benutzern erlauben, eigene Skripte für Ihr Spiel zu schreiben. Denken Sie daran, dass Sie beliebig viele unabhängige virtuelle Maschinen erstellen können! (Ich habe einmal einen MMO-Server erstellt, auf dem jedes Match auf einer separaten virtuellen Lua-Maschine lief.)

  • Sprachfunktionen: Wenn Sie Skriptsprachen verwenden, können Sie basierend auf Ihrer Wahl für Host- und Skriptsprachen die besten Funktionen nutzen, die jede Sprache zu bieten hat. Insbesondere die Coroutinen von lua sind eine sehr interessante Funktion, die in C oder C ++ nur sehr schwer zu implementieren ist

Skripte sind allerdings nicht perfekt. Die Verwendung von Skripten hat einige häufige Nachteile:

  • Das Debuggen wird sehr schwierig: In der Regel sind die in allgemeinen IDEs enthaltenen Debugger nicht zum Debuggen des Codes in Skripten vorgesehen. Aus diesem Grund ist das Debuggen von Konsolentraces weitaus häufiger als gewünscht.

    Einige Skriptsprachen wie lua verfügen über Debugging-Funktionen, die in einigen IDEs wie Eclipse genutzt werden können. Dies zu tun ist sehr schwierig, und ich habe ehrlich gesagt noch nie gesehen, dass das Debuggen von Skripten so gut funktioniert wie das native Debuggen.

    Übrigens ist der extreme Mangel an Interesse, den die Unity-Entwickler in dieser Angelegenheit haben, meine Hauptkritik an ihrer Spiele-Engine und der Hauptgrund, warum ich sie nicht mehr benutze, aber ich schweife ab.

  • IDE-Integration: Es ist unwahrscheinlich, dass Ihre IDE weiß, welche Funktionen aus Ihrem Programm exportiert werden, und daher funktionieren Funktionen wie IntelliSense und dergleichen wahrscheinlich nicht mit Ihren Skripten.

  • Leistung: Da es sich um häufig interpretierte Programme handelt, die für eine abstrakte virtuelle Maschine gedacht sind, deren Architektur sich möglicherweise von der tatsächlichen Hardware unterscheidet, ist die Ausführung von Skripten in der Regel langsamer als bei nativem Code. Einige VMs wie luaJIT und V8 machen ihren Job wirklich gut. Dies kann auffallen, wenn Sie Skripte sehr häufig verwenden.

    In der Regel sind Kontextänderungen (Host-zu-Skript und Skript-zu-Host) sehr teuer. Sie sollten sie daher minimieren, wenn Leistungsprobleme auftreten.

Wie Sie Ihre Skripte verwenden, liegt ganz bei Ihnen. Ich habe gesehen, wie Skripte für Dinge verwendet wurden, die so einfach sind wie das Laden von Einstellungen oder so komplex wie das Erstellen ganzer Spiele in der Skriptsprache mit einer sehr dünnen Spiel-Engine. Ich habe sogar einmal eine sehr exotische Spiel-Engine gesehen, die Lua- und JavaScript-Skripte (über V8) gemischt hat.

Scripting ist nur ein Werkzeug. Wie Sie damit großartige Spiele erstellen, liegt ganz bei Ihnen.

Panda-Pyjama
quelle
Ich bin wirklich neu darin, aber ich benutze Unity. Sie haben etwas über Unity erwähnt. Können Sie das näher erläutern? Soll ich etwas anderes verwenden?
Tokamocha
1
Ich würde nicht printfin Ihrem Beispiel verwenden (oder zumindest verwenden printf("%s", message);)
Ratschenfreak
1
@ Ratchetfreak: Das Semikolon am Ende Ihrer Nachricht, gefolgt von der Klammer, zwinkert mir zu ...
Panda Pyjama
1
Eine der besten Antworten, die ich seit langem auf dieser Website gesehen habe. Es verdient jede Aufwertung, die es bekommt. Sehr schön gemacht.
Steven Stadnicki
1
Das Aushängeschild für Lua in Spielen ist World of Warcraft, in dem der größte Teil der Benutzeroberfläche in Lua geschrieben ist und der größte Teil vom Spieler ersetzt werden kann. Ich bin überrascht, dass du es nicht erwähnt hast.
Michael Hampton
7

Im Allgemeinen binden Sie einige native Funktionen an Lua oder machen sie für diese verfügbar (häufig verwenden Sie dazu eine Dienstprogrammbibliothek , obwohl Sie dies von Hand tun können). Dadurch kann Lua-Code Aufrufe in Ihren nativen C ++ - Code ausführen, wenn Ihr Spiel diesen Lua-Code ausführt. In diesem Sinne ist Ihre Annahme , dass der Lua - Code nur in den nativen Code ruft wahr ist (obwohl Lua eine eigene Standard - Bibliothek von Funktionalität zur Verfügung, Sie nicht brauchen , für alles , was in Ihren nativen Code zu nennen).

Der Lua-Code selbst wird von der Lua-Laufzeit interpretiert. Hierbei handelt es sich um C-Code, den Sie (normalerweise) als Bibliothek in Ihrem eigenen Programm verknüpfen. Weitere Informationen zur Funktionsweise von Lua finden Sie auf der Lua-Homepage . Insbesondere ist Lua keine "nicht kompilierte Klasse", wie Sie vermuten, insbesondere wenn Sie an eine C ++ - Klasse denken, da C ++ in der Praxis so gut wie nie dynamisch kompiliert wird. Die Lua-Laufzeit und die von Lua-Skripten erstellten Objekte, die Ihr Spiel ausführt, belegen jedoch Speicherplatz im Systemspeicher Ihres Spiels.

Josh
quelle
5

Skriptsprachen wie Lua können auf verschiedene Arten verwendet werden. Wie Sie sagten, können Sie Lua verwenden, um Funktionen im Hauptprogramm aufzurufen, aber Sie können Lua-Funktionen auch nur von der C ++ - Seite aufrufen lassen, wenn Sie möchten. Im Allgemeinen bauen Sie eine Oberfläche auf, um Flexibilität mit der Skriptsprache Ihrer Wahl zu ermöglichen, sodass Sie die Skriptsprache in einer Reihe von Szenarien verwenden können. Das Tolle an Skriptsprachen wie Lua ist, dass sie interpretiert und nicht kompiliert werden, sodass Sie die Lua-Skripte im laufenden Betrieb ändern können, sodass Sie nicht darauf warten müssen, dass Ihr Spiel kompiliert wird, sondern nur darauf, dass Sie es erneut kompilieren, wenn Sie es möchten hab nicht deinen geschmack getroffen.

Lua-Befehle werden nur aufgerufen, wenn das C ++ - Programm sie ausführen möchte. Daher wird der Code nur interpretiert, wenn er aufgerufen wird. Ich denke, Sie können Lua als ein Bash-Skript betrachten, das separat vom Hauptprogramm ausgeführt wird.

Im Allgemeinen möchten Sie Skriptsprachen für Dinge verwenden, die Sie später aktualisieren oder wiederholen möchten. Ich habe viele Unternehmen gesehen, die es für die grafische Benutzeroberfläche verwendet haben, so dass die Benutzeroberfläche in hohem Maße anpassbar ist. Wenn Sie wissen, wie sie ihre Lua-Oberfläche erstellt haben, können Sie die GUI auch selbst ändern. Es gibt jedoch zahlreiche andere Wege, die Sie zur Verwendung von Lua einschlagen können, z. B. KI-Logik, Informationen zu Waffen, Dialoge von Charakteren usw.

M Davies
quelle
3

Die meisten Skriptsprachen, einschließlich Lua, arbeiten auf einer virtuellen Maschine (Virtual Machine, VM ), bei der es sich im Grunde um ein System handelt, das einen Skriptbefehl einem "echten" CPU-Befehl oder Funktionsaufruf zuordnet. Die Lua VM wird normalerweise im selben Prozess wie die Hauptanwendung ausgeführt. Dies gilt insbesondere für Spiele, die es verwenden. Die Lua-API bietet Ihnen verschiedene Funktionen, die Sie in der nativen Anwendung aufrufen, um Skriptdateien zu laden und zu kompilieren. ZB luaL_dofile()kompiliert das angegebene Skript in Lua- Bytecode und führt es dann aus. Dieser Bytecode wird dann von der VM, die in der API ausgeführt wird, in systemeigenen Maschinenanweisungen und Funktionsaufrufen zugeordnet.

Das Verbinden einer Muttersprache wie C ++ mit einer Skriptsprache wird als Bindung bezeichnet . Im Fall von Lua bietet die API Funktionen, mit denen Sie native Funktionen für den Skriptcode verfügbar machen können. So können Sie beispielsweise eine C ++ - Funktion definieren say_hello()und diese Funktion von einem Lua-Skript aus aufrufen. Die Lua-API bietet auch Methoden zum Erstellen von Variablen und Tabellen über C ++ - Code, die für die Skripte sichtbar sind, wenn sie ausgeführt werden. Durch die Kombination dieser Funktionen können Sie ganze C ++ - Klassen für Lua verfügbar machen. Das Gegenteil ist auch möglich. Die Lua-API ermöglicht dem Benutzer, Lua-Variablen zu ändern und Lua-Funktionen aus dem nativen C ++ - Code aufzurufen.

Die meisten, wenn nicht alle Skriptsprachen bieten APIs, um die Bindung von Skriptcode mit systemeigenem Code zu erleichtern. Die meisten werden auch in Bytecode kompiliert und in einer VM ausgeführt, einige können jedoch zeilenweise interpretiert werden.

Ich hoffe, dies hilft Ihnen dabei, einige Ihrer Fragen zu klären.

glampert
quelle
3

Da dies niemand erwähnte, werde ich es hier für die Interessenten hinzufügen. Es gibt ein ganzes Buch zum Thema Game Scripting Mastery . Dies ist ein fantastischer Text, der vor einiger Zeit geschrieben wurde, aber bis heute immer noch aktuell ist.

In diesem Buch erfahren Sie nicht nur, wie Skriptsprachen in systemeigenen Code passen, sondern auch, wie Sie Ihre eigene Skriptsprache implementieren. Während dies für 99% der Benutzer zu viel ist, gibt es keinen besseren Weg, etwas zu verstehen, als es tatsächlich zu implementieren (selbst in einer sehr einfachen Form).

Wenn Sie jemals selbst eine Game-Engine schreiben möchten (oder nur mit einer Render-Engine arbeiten), ist dieser Text von unschätzbarem Wert, um zu verstehen, wie eine Skriptsprache am besten in Ihre Engine / Ihr Spiel integriert werden kann.

Und wenn Sie jemals eine eigene Skriptsprache erstellen möchten, ist dies der beste Ausgangspunkt (soweit ich weiß).

free3dom
quelle
2

Erstens werden Skriptsprachen normalerweise nicht kompiliert . Das ist ein großer Teil dessen, was sie allgemein als Skriptsprachen definiert. Sie werden stattdessen oft "interpretiert". Was bedeutet dies im Wesentlichen ist , dass es eine andere Sprache (eine , die ist zusammengestellt, mehr als oft nicht), die im Text, in Echtzeit zu lesen, und Operationen, Line-by-line.

Der Unterschied zwischen dieser und anderen Sprachen besteht darin, dass Skriptsprachen in der Regel einfacher sind (im Allgemeinen als "höhere Ebene" bezeichnet). Sie sind jedoch tendenziell auch etwas langsamer, da Compiler dazu neigen, viele der Probleme zu optimieren, die mit dem "menschlichen Element" der Codierung einhergehen, und die resultierende Binärdatei für die Maschine tendenziell kleiner und schneller lesbar ist. Darüber hinaus ist weniger Aufwand für ein anderes Programm erforderlich, das zum Lesen des ausgeführten Codes mit kompilierten Programmen ausgeführt werden muss.

Sie denken jetzt vielleicht: "Nun, ich verstehe, dass es ein bisschen einfacher ist, aber warum in aller Welt würde jemand diese ganze Leistung für ein bisschen mehr Benutzerfreundlichkeit aufgeben?"

Mit dieser Annahme wären Sie nicht allein, aber das Maß an Leichtigkeit, das Sie mit Skriptsprachen normalerweise erreichen, kann je nach dem, was Sie mit ihnen machen, das Opfer an Leistung wert sein.

Grundsätzlich gilt: In Fällen, in denen die Entwicklungsgeschwindigkeit wichtiger ist als die Ausführungsgeschwindigkeit des Programms, sollten Sie eine Skriptsprache verwenden. In der Spieleentwicklung gibt es VIELE solcher Situationen. Vor allem, wenn es um triviale Dinge wie die Handhabung von Ereignissen auf hohem Niveau geht.

Bearbeiten: Der Grund, warum lua in der Spieleentwicklung eher beliebt ist, ist, dass es wohl eine der schnellsten (wenn nicht die schnellste) öffentlich verfügbaren Skriptsprachen der Welt ist. Mit dieser zusätzlichen Geschwindigkeit hat es jedoch einen Teil seiner Bequemlichkeit geopfert. Trotzdem ist es immer noch bequemer als mit C oder C ++ zu arbeiten.

Wichtige Änderung: Nach weiteren Recherchen habe ich festgestellt, dass die Definition einer Skriptsprache sehr viel kontroverser ist (siehe Ousterhouts Dichotomie ). Die Hauptkritik an der Definition einer Sprache als "Skriptsprache" besteht darin, dass es weder für die Syntax noch für die Semantik der Sprache von Bedeutung ist, dass sie interpretiert oder kompiliert wird.

Während Sprachen, die normalerweise als "Skriptsprachen" betrachtet werden, normalerweise eher traditionell interpretiert als kompiliert werden, hängt das lange und kurze ihrer Definition als "Skriptsprachen" in Wirklichkeit davon ab, wie die Leute sie sehen und wie ihre Schöpfer sie definieren.

Im Allgemeinen kann eine Sprache leicht als Skriptsprache betrachtet werden (vorausgesetzt, Sie stimmen der Dichotomie von Ousterhout zu), wenn sie die folgenden Kriterien erfüllt (gemäß dem oben verlinkten Artikel):

  • Sie werden dynamisch eingegeben
  • Sie haben wenig oder gar keine Vorkehrungen für komplexe Datenstrukturen
  • Programme in ihnen (Skripte) werden interpretiert

Darüber hinaus wird häufig angenommen, dass eine Sprache eine Skriptsprache ist, wenn sie so konzipiert ist, dass sie zusammen mit einer anderen Programmiersprache (in der Regel einer, die nicht als Skriptsprache gilt) interagiert und funktioniert .

Gurgadurgen
quelle
1
In der ersten Zeile, in der Sie "Skriptsprachen werden nicht kompiliert" geschrieben haben, würde ich Ihnen nicht zustimmen. Tatsächlich wird Lua vor der Ausführung durch einen JIT-Compiler in einen Zwischenbytecode kompiliert. Einige werden natürlich zeilenweise interpretiert, aber nicht alle.
Glampert
Ich würde Ihrer Definition von "Skriptsprache" nicht zustimmen. Es gibt Sprachen mit doppeltem Verwendungszweck (wie Lua oder C #), die relativ häufig in kompilierter Form anstelle der Skriptform verwendet werden (oder umgekehrt, wie dies bei C # der Fall ist). Wenn eine Sprache als Skriptsprache verwendet wird, ist sie streng als eine Sprache definiert, die interpretiert und nicht kompiliert wird.
Gurgadurgen
Fairerweise passt Ihre Definition eher zu Wikipedia: "Eine Skriptsprache oder Skriptsprache ist eine Programmiersprache, die Skripts unterstützt, Programme, die für eine spezielle Laufzeitumgebung geschrieben wurden, die interpretieren (anstatt kompilieren) kann ...", egal mein Kommentar dann.
Glampert
1
Selbst wenn reguläres Lua vollständig zu Bytecode kompiliert ist, kann der JIT-Compiler sogar native Binärdateien erzeugen. Also nein, Lua wird nicht interpretiert.
Oleg V. Volkov
Der erste Teil ist zweifelhaft, ich bin versucht, abzulehnen. Er hat insofern ein gutes Recht, als es letztendlich interpretiert wird und das Kompilieren von @ OlegV.Volkov JIT nichts Kompiliertes ergibt. Die Kompilierung wird durch die Kompilierungszeit definiert, die Lua hat, Lua-Bytecode nicht (JIT oder kein JIT). Lassen Sie uns unseren Begriff nicht durcheinander bringen.
Alec Teal