Jak Lua działa jako język skryptowy w grach?


67

Jestem trochę zamglony na temat tego, czym dokładnie jest Lua i jak mogłaby z niej korzystać gra zaprogramowana w C ++. Pytam przede wszystkim o to, jak jest skompilowany i uruchamiany.

Na przykład, gdy używasz programu napisanego w C ++, który używa skryptów Lua: czy kod w Lua po prostu wywołuje funkcje w programie głównym napisanym w C ++ i działa jak nieskompilowana klasa czekająca na kompilację i dodanie do sterty pamięci C ++ program?

Czy może działa jak skrypt bash w Linuksie, gdzie po prostu wykonuje programy całkowicie odrębne od programu głównego?

Odpowiedzi:


89

Skrypty to abstrakcja programowania, w której (koncepcyjnie) program (skrypt) działa w innym programie (hoście). W większości przypadków język, w którym piszesz skrypt, jest inny niż język, w którym napisany jest host, ale wszelkie abstrakty wewnątrz programu można uznać za skrypty.

Pod względem koncepcyjnym typowe kroki umożliwiające włączenie skryptów są następujące (użyję pseudo-c dla hosta i pseudo-lua dla skryptu. Nie są to dokładne kroki, ale bardziej ogólny przepływ, w którym włączasz skrypty)

  1. Utwórz maszynę wirtualną w programie hosta:

    VM m_vm = createVM();
  2. Utwórz funkcję i udostępnij ją maszynie wirtualnej:

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

    Zauważ, że nazwa, w której ujawniliśmy funkcję ( print), nie musi być zgodna z nazwą wewnętrzną samej funkcji ( scriptPrintMessage)

  3. Uruchom kod skryptu, który korzysta z funkcji:

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

To wszystko. Następnie program przepływa w następujący sposób:

  1. Zadzwonić doText(). Sterowanie jest następnie przenoszone na maszynę wirtualną, która wykona tekst w środku scriptCode.

  2. Kod skryptu znajduje wcześniej wyeksportowany symbol print. Następnie przekaże kontrolę do funkcji scriptPrintMessage().

  3. Po scriptPrintMessage()zakończeniu kontrola przejdzie z powrotem do maszyny wirtualnej.

  4. Po wykonaniu całego tekstu w scriptCode, doText()zakończy się, a kontrola zostanie przeniesiona z powrotem do twojego programu po linii doText().

Ogólnie rzecz biorąc, wszystko, co robisz, to uruchamianie programu w innym programie. Teoretycznie nie ma nic, co można zrobić ze skryptami, bez którego nie można się obejść, ale ta abstrakcja pozwala naprawdę łatwo robić ciekawe rzeczy. Niektórzy z nich są:

  • Oddzielne obawy: powszechnym wzorem jest pisanie silnika gry w C / C ++, a następnie faktycznej gry w języku skryptowym, takim jak lua. Zrobione dobrze, kod gry można opracować całkowicie niezależnie od samego silnika.

  • Elastyczność: języki skryptowe są powszechnie interpretowane i dlatego zmiana w skrypcie niekoniecznie wymaga przebudowy całego projektu. Zrobione dobrze, możesz nawet zmienić skrypt i zobaczyć wyniki bez ponownego uruchamiania programu!

  • Stabilność i bezpieczeństwo: ponieważ skrypt działa na maszynie wirtualnej, jeśli zostanie wykonany poprawnie, błędny skrypt nie spowoduje awarii programu hosta. Jest to szczególnie ważne, gdy pozwalasz użytkownikom pisać własne skrypty do gry. Pamiętaj, że możesz utworzyć dowolną liczbę niezależnych maszyn wirtualnych! (Kiedyś stworzyłem serwer MMO, na którym każdy mecz działał na osobnej maszynie wirtualnej lua)

  • Funkcje językowe: korzystając z języków skryptowych, w zależności od wyboru języka hosta i języka skryptowego, możesz korzystać z najlepszych funkcji, jakie każdy język ma do zaoferowania. W szczególności, coroutines Luy są bardzo interesującą funkcją, którą bardzo trudno jest zaimplementować w C lub C ++

Skrypty nie są jednak doskonałe. Istnieje kilka typowych wad korzystania ze skryptów:

  • Debugowanie staje się bardzo trudne: zwykle debuggery zawarte we wspólnych IDE nie są zaprojektowane do debugowania kodu w skryptach. Z tego powodu debugowanie śledzenia konsoli jest znacznie częstsze, niż bym chciał.

    Niektóre języki skryptowe, takie jak lua, mają funkcje debugowania, które można wykorzystać w niektórych środowiskach IDE, takich jak Eclipse. Jest to bardzo trudne i szczerze mówiąc, nigdy nie widziałem, aby debugowanie skryptu działało tak samo jak debugowanie natywne.

    Nawiasem mówiąc, skrajny brak zainteresowania twórców Unity tą kwestią to moja główna krytyka silnika gry i główny powód, dla którego już go nie używam, ale dygresję.

  • Integracja z IDE: jest mało prawdopodobne, aby twoje IDE wiedziało, które funkcje są eksportowane z twojego programu, a zatem takie funkcje, jak IntelliSense i tym podobne, raczej nie będą działać z twoimi skryptami.

  • Wydajność: Będąc powszechnie interpretowanymi programami i przeznaczonymi dla abstrakcyjnej maszyny wirtualnej, której architektura może różnić się od rzeczywistego sprzętu, skrypty są zwykle wolniejsze niż kod natywny. Niektóre maszyny wirtualne, takie jak luaJIT i V8, wykonują jednak naprawdę dobrą robotę. Może to być zauważalne, jeśli używasz bardzo dużego skryptu.

    Zwykle zmiany kontekstu (host na skrypt i skrypt na host) są bardzo drogie, więc możesz je zminimalizować, jeśli masz problemy z wydajnością.

Sposób korzystania ze skryptów zależy od Ciebie. Widziałem skrypty używane do rzeczy tak prostych, jak ładowanie ustawień, do tak skomplikowanych, jak tworzenie całych gier w języku skryptowym za pomocą bardzo cienkiego silnika gry. Kiedyś widziałem nawet bardzo egzotyczny silnik gry, który łączył skrypty lua i JavaScript (przez V8).

Skrypty to tylko narzędzie. To, jak używasz go do tworzenia niesamowitych gier, zależy wyłącznie od Ciebie.


Jestem naprawdę nowy, ale używam Unity. Wspominałeś coś o Unity, czy mógłbyś to rozwinąć? Czy powinienem użyć czegoś innego?
Tokamocha,

1
Nie użyłbym printfw twoim przykładzie (ani przynajmniej użycia printf("%s", message);)
maniakiem zapadkowym

1
@ratchetfreak: Średnik na końcu wiadomości i nawias mrugają do mnie ...
Panda Pajama

1
Jedna z najlepszych odpowiedzi, jakie widziałem na tej stronie od dłuższego czasu; zasługuje na każdą opinię, jaką otrzyma. Bardzo ładnie wykonane.
Steven Stadnicki

1
Dziecko plakatowe dla Lua w grach to World of Warcraft, w którym większość interfejsu użytkownika jest napisana w Lua, a większość z nich może zostać zastąpiona przez gracza. Dziwię się, że o tym nie wspomniałeś.
Michael Hampton

7

Ogólnie rzecz biorąc, wiążesz lub udostępniasz Luie niektóre funkcje natywne (często używając do tego biblioteki narzędziowej , chociaż możesz to zrobić ręcznie). Dzięki temu kod Lua może wywoływać połączenia z natywnym kodem C ++, gdy gra wykonuje ten kod Lua. W tym sensie twoje założenie, że kod Lua po prostu wywołuje kod natywny, jest prawdziwe (chociaż Lua ma własną standardową bibliotekę funkcji; nie musisz wywoływać kodu natywnego we wszystkim).

Sam kod Lua jest interpretowany przez środowisko wykonawcze Lua, które jest kodem C, który łączysz jako bibliotekę (zwykle) we własnym programie. Możesz przeczytać więcej o tym, jak Lua działa na stronie głównej Lua . W szczególności Lua nie jest „klasą nieskompilowaną”, jak się domyślacie, szczególnie jeśli myślisz o klasie C ++, ponieważ C ++ prawie nigdy nie jest dynamicznie kompilowana w praktyce. Jednak środowisko wykonawcze Lua i obiekty utworzone przez skrypty Lua uruchamiane przez grę zajmują miejsce w puli pamięci systemowej gry.


5

Języki skryptowe, takie jak Lua, mogą być używane na kilka sposobów. Jak już powiedziałeś, możesz używać Lua do wywoływania funkcji w programie głównym, ale możesz także wywoływać funkcje Lua ze strony C ++, jeśli chcesz. Zasadniczo tworzysz interfejs, który pozwala na pewną elastyczność z wybranym językiem skryptowym, dzięki czemu możesz używać języka skryptowego w szeregu scenariuszy. Wspaniałą rzeczą w językach skryptowych takich jak Lua jest to, że są one interpretowane zamiast kompilowane, dzięki czemu można modyfikować skrypty Lua w locie, abyś nie musiał siedzieć i czekać, aż gra się skompiluje, a Ty skompilujesz tylko, jeśli zrobione nie odpowiada twoim upodobaniom.

Polecenia Lua są wywoływane tylko wtedy, gdy program C ++ chce je wykonać. W związku z tym kod będzie interpretowany tylko wtedy, gdy zostanie wywołany. Myślę, że możesz uznać Luę za skrypt bash, który wykonuje się oddzielnie od programu głównego.

Ogólnie rzecz biorąc, chcesz używać języków skryptowych do rzeczy, które możesz chcieć zaktualizować lub iterować dalej. Widziałem wiele firm używających go do GUI, więc zapewnia on wiele możliwości dostosowania interfejsu. Jeśli wiesz, w jaki sposób stworzyli interfejs Lua, możesz również samodzielnie zmodyfikować GUI. Ale istnieje wiele innych ścieżek do korzystania z Lua, takich jak logika AI, informacje o broni, dialogi postaci itp.


3

Większość języków skryptowych, w tym Lua, działa na maszynie wirtualnej ( VM ), która jest w zasadzie systemem mapującym instrukcję skryptu na „prawdziwą” instrukcję procesora lub wywołanie funkcji. Lua VM zazwyczaj działa w tym samym procesie co główna aplikacja. Jest to szczególnie prawdziwe w przypadku gier, które z niego korzystają. Interfejs API Lua udostępnia kilka funkcji wywoływanych w aplikacji macierzystej w celu ładowania i kompilowania plików skryptów. Np. luaL_dofile()Kompiluje dany skrypt do kodu bajtowego Lua, a następnie uruchamia go. Ten kod bajtowy zostanie następnie zmapowany przez maszynę wirtualną działającą w interfejsie API na instrukcje natywnej maszyny i wywołania funkcji.

Proces łączenia języka ojczystego, takiego jak C ++, z językiem skryptowym nazywa się wiązaniem . W przypadku Lua jego interfejs API udostępnia funkcje, które pomagają ujawnić funkcje rodzime w kodzie skryptu. Możesz na przykład zdefiniować funkcję C ++ say_hello()i ustawić tę funkcję na żądanie ze skryptu Lua. Interfejs API Lua zapewnia również metody tworzenia zmiennych i tabel za pomocą kodu C ++, które będą widoczne dla skryptów po ich uruchomieniu. Łącząc te funkcje, możesz udostępnić Lua całe klasy C ++. Możliwe jest również odwrotność, Lua API pozwala użytkownikowi modyfikować zmienne Lua i wywoływać funkcje Lua z natywnego kodu C ++.

Większość, jeśli nie wszystkie języki skryptowe zapewniają interfejsy API ułatwiające wiązanie kodu skryptu z kodem rodzimym. Większość jest również kompilowana do kodu bajtowego i uruchamiana na maszynie wirtualnej, ale niektóre mogą być interpretowane wiersz po wierszu.

Mam nadzieję, że pomoże to wyjaśnić niektóre z twoich pytań.


3

Ponieważ nikt o tym nie wspomniał, dodam go tutaj dla zainteresowanych. Istnieje cała książka na ten temat zatytułowana Game Scripting Mastery . To fantastyczny tekst, który został napisany jakiś czas temu, ale nadal pozostaje aktualny.

Ta książka nie tylko pokaże ci, jak języki skryptowe pasują do natywnego kodu, ale także uczy, jak zaimplementować swój własny język skryptowy. Chociaż będzie to nadmiar umiejętności dla 99% użytkowników, nie ma lepszego sposobu na zrozumienie czegoś niż faktyczne wdrożenie (nawet w bardzo podstawowej formie).

Jeśli kiedykolwiek chcesz napisać silnik gry (lub pracujesz tylko z silnikiem renderowania), ten tekst jest nieoceniony, ponieważ pozwala zrozumieć, w jaki sposób język skryptowy można najlepiej włączyć do silnika / gry.

A jeśli kiedykolwiek chcesz stworzyć własny język skryptowy, jest to jedno z najlepszych miejsc do rozpoczęcia (o ile wiem).


2

Po pierwsze, języki skryptowe ZWYKLE nie są kompilowane . To duża część tego, co ogólnie określa je jako języki skryptowe. Zamiast tego są często „interpretowani”. Oznacza to w istocie, że istnieje inny język (taki, który jest kompilowany, najczęściej), który czyta tekst w czasie rzeczywistym i wykonuje operacje wiersz po wierszu.

Różnica między tym a innymi językami polega na tym, że języki skryptowe są zwykle prostsze (powszechnie określane jako „wyższy poziom”). Jednak są one również nieco wolniejsze, ponieważ kompilatory zwykle optymalizują wiele problemów związanych z „ludzkim elementem” kodowania, a wynikowy plik binarny jest zwykle mniejszy i szybszy do odczytania dla maszyny. Dodatkowo, mniejszy jest narzut związany z innym programem, który należy uruchomić, aby odczytać uruchamiany kod, za pomocą skompilowanych programów.

Być może zastanawiasz się: „Cóż, rozumiem, że jest to trochę łatwiejsze, ale dlaczego, u licha, ktoś miałby rezygnować z całej wydajności, aby uzyskać dodatkową łatwość użycia?”

W tym założeniu nie byłbyś sam, jednak poziom łatwości uzyskiwania przez Ciebie języków skryptowych, w zależności od tego, co z nimi robisz, może być wart poświęcenia w wydajności.

Zasadniczo: W przypadkach, w których szybkość rozwoju jest ważniejsza niż szybkość uruchomionego programu, użyj języka skryptowego. Istnieje wiele takich sytuacji podczas tworzenia gier. Zwłaszcza w przypadku błahych rzeczy, takich jak obsługa zdarzeń na wysokim poziomie.

Edycja: Powodem, dla którego lua jest raczej popularna w tworzeniu gier jest to, że jest prawdopodobnie jednym z najszybszych (jeśli nie najszybszych) publicznie dostępnych języków skryptowych na świecie. Jednak dzięki tej dodatkowej prędkości poświęcił trochę swojej wygody. To powiedziawszy, nadal jest prawdopodobnie wygodniejsze niż praca z prostym C lub C ++.

Ważna edycja: po dalszych badaniach odkryłem, że istnieje znacznie więcej kontrowersji dotyczących definicji języka skryptowego (patrz dychotomia Ousterhouta ). Podstawowa krytyka definiowania języka jako „języka skryptowego” polega na tym, że nie ma on znaczenia dla składni ani semantyki języka, który jest interpretowany lub kompilowany.

Podczas gdy języki, które są zwykle uważane za „języki skryptowe”, są zwykle interpretowane tradycyjnie, a nie kompilowane, długa i krótka definicja „języków skryptowych” zależy od kombinacji tego, jak ludzie je postrzegają i jak ich twórcy zdefiniowali.

Mówiąc ogólnie, język można łatwo uznać za język skryptowy (zakładając, że zgadzasz się z dychotomią Ousterhouta), jeśli spełnia on następujące kryteria (zgodnie z linkiem do powyższego artykułu):

  • Są wpisywane dynamicznie
  • Mają niewielką lub żadną rezerwę na złożone struktury danych
  • Programy w nich (skrypty) są interpretowane

Ponadto często przyjmuje się, że język jest językiem skryptowym, jeśli został zaprojektowany do interakcji i działania wraz z innym językiem programowania (zwykle takim, który nie jest uważany za język skryptowy).


1
Nie zgadzam się z tobą w pierwszym wierszu, w którym napisałeś „języki skryptowe nie są kompilowane”. Lua jest w rzeczywistości kompilowany do pośredniego kodu bajtowego przed wykonaniem przez kompilator JIT. Niektóre są oczywiście interpretowane wiersz po wierszu, ale nie wszystkie.
glampert

Nie zgadzam się z twoją definicją „języka skryptowego”. Istnieją języki o podwójnym zastosowaniu (takie jak Lua lub C #), które są stosunkowo powszechnie używane w ich skompilowanej formie, a nie w formie skryptu (lub odwrotnie, jak ma to miejsce w przypadku C #). Kiedy język jest używany jako język skryptowy, jest on ściśle zdefiniowany jako język, który jest interpretowany, a nie kompilowany.
Gurgadurgen

Szczerze mówiąc, twoja definicja jest bardziej zgodna z Wikipedią: „Język skryptowy lub język skryptowy to język programowania, który obsługuje skrypty, programy napisane dla specjalnego środowiska wykonawczego, które może interpretować (zamiast kompilować) ...”, nieważne więc mój komentarz.
glampert

1
Nawet zwykła Lua jest całkowicie skompilowana do kodu bajtowego, kompilator JIT może nawet tworzyć natywny plik binarny. Więc nie, Lua nie jest interpretowana.
Oleg V. Volkov

Pierwsza część jest niepewna, mam ochotę przegłosować. Ma rację, ponieważ jest ostatecznie interpretowany, a kompilacja JIT @ OlegV.Volkov nie powoduje kompilacji. Kompilacja jest definiowana przez czas kompilacji, który ma Lua, kod bajtowy Lua nie (JIT lub brak JIT). Nie dajmy się zwieść naszemu terminowi.
Alec Teal
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.