Szybkie czasy iteracji są kluczem do tworzenia gier, o wiele bardziej niż wymyślna grafika i silniki z mnóstwem funkcji moim zdaniem. Nic dziwnego, że wielu małych programistów wybiera języki skryptowe.
Sposób Unity 3D polegający na możliwości wstrzymania gry i zmodyfikowania zasobów ORAZ kodu, a następnie kontynuowania i natychmiastowego wprowadzenia zmian, jest absolutnie doskonały. Moje pytanie brzmi: czy ktoś zaimplementował podobny system w silnikach gier C ++?
Słyszałem, że robią to niektóre naprawdę supernowoczesne silniki, ale jestem bardziej zainteresowany dowiedzieć się, czy jest sposób na to w domowej maszynie lub grze.
Oczywiście wystąpiłyby kompromisy i nie mogę sobie wyobrazić, że możesz po prostu ponownie skompilować kod, gdy gra jest wstrzymana, aby została ponownie załadowana i działała w każdych okolicznościach i na każdej platformie.
Ale być może jest to możliwe w przypadku programowania AI i prostych modułów logicznych poziomu. To nie tak, że chcę to zrobić w jakimkolwiek projekcie w perspektywie krótko- lub długoterminowej, ale byłem ciekawy.
źródło
Wymiana na gorąco jest bardzo, bardzo trudnym problemem do rozwiązania za pomocą kodu binarnego. Nawet jeśli kod zostanie umieszczony w osobnych bibliotekach linków dynamicznych, kod musi zapewnić, że nie będzie bezpośrednich odwołań do funkcji w pamięci (ponieważ adres funkcji może ulec zmianie przy rekompilacji). Zasadniczo oznacza to, że nie używa się funkcji wirtualnych, a wszystko, co korzysta ze wskaźników funkcji, robi to za pomocą pewnego rodzaju tabeli wysyłki.
Owłosione, krępujące rzeczy. Lepiej jest użyć języka skryptowego dla kodu, który wymaga szybkiej iteracji.
W pracy nasza obecna baza kodu to połączenie C ++ i Lua. Lua też jest niemałą częścią naszego projektu - to prawie podział 50/50. Wdrożyliśmy przeładowywanie Lua w locie, abyś mógł zmienić linię kodu, przeładować i kontynuować. W rzeczywistości możesz to zrobić, aby naprawić błędy awaryjne występujące w kodzie Lua, bez ponownego uruchamiania gry!
źródło
(Możesz chcieć wiedzieć o terminach „łatanie małp” lub „wykaczanie kaczek”, jeśli tylko dla humorystycznego obrazu mentalnego.)
Poza tym: jeśli Twoim celem jest skrócenie czasu iteracji dla zmian „zachowania”, wypróbuj kilka metod, które zapewnią ci najwięcej możliwości, i połącz je ładnie, aby umożliwić więcej tego w przyszłości.
(To wyjdzie na trochę styczne, ale obiecuję, że wróci!)
Wiele z tych punktów jest korzystnych, nawet jeśli nie uda ci się przeładować ani danych, ani kodu.
Wspierające anegdoty:
Na dużym PC RTS (~ 120-osobowy zespół, głównie C ++), istniał niesamowicie głęboki system oszczędzania stanu, który był wykorzystywany do co najmniej trzech celów:
Od tego czasu używałem deterministycznego nagrywania / odtwarzania w grze karcianej C ++ i Lua dla DS. Połączyliśmy się z interfejsem API zaprojektowanym dla AI (po stronie C ++) i zarejestrowaliśmy wszystkie działania użytkownika i AI. Wykorzystaliśmy tę funkcję w grze (aby zapewnić powtórkę dla odtwarzacza), ale także w celu zdiagnozowania problemów: gdy wystąpił awaria lub dziwne zachowanie, wystarczyło pobrać plik zapisu i odtworzyć go w kompilacji debugowania.
Od tego czasu używałem nakładek więcej niż kilka razy i połączyliśmy to z naszym „automatycznie przeglądającym ten katalog i przesyłam nowe treści do systemu podręcznego”. Wszystko, co musielibyśmy zrobić, to opuścić scenę przerywnikową / poziom / cokolwiek i wrócić i nie tylko nowe dane (duszki, układ poziomów itp.) Załadują się, ale także nowy kod w nakładce. Niestety, staje się to coraz trudniejsze w przypadku nowszych urządzeń przenośnych ze względu na ochronę przed kopiowaniem i mechanizmy zapobiegające hakowaniu, które specjalnie traktują kod. Nadal jednak robimy to dla skryptów lua.
Last but not least: możesz (i ja, w różnych bardzo małych szczególnych okolicznościach) zrobić trochę dziurkowania, bezpośrednio łatając kody instrukcji. Działa to najlepiej, jeśli jesteś na stałej platformie i kompilatorze, a ponieważ jest prawie nie do utrzymania, bardzo podatny na błędy i ograniczony w tym, co możesz szybko osiągnąć, używam go głównie do zmiany trasy kodu podczas debugowania. To nie nauczy Cię cholernie dużo o swojej zestaw instrukcji architektury w pośpiechu, choć.
źródło
Możesz zrobić coś takiego jak wymiana modułów w czasie wykonywania, implementując moduły jako biblioteki linków dynamicznych (lub biblioteki współdzielone w systemie UNIX) i używając dlopen () i dlsym () do dynamicznego ładowania funkcji z biblioteki.
W systemie Windows odpowiednikami są LoadLibrary i GetProcAddress.
Jest to metoda języka C i wiąże się z pewnymi pułapkami przy użyciu jej w języku C ++, o czym możesz przeczytać tutaj .
źródło
Jeśli używasz programu Visual Studio C ++, w rzeczywistości możesz w pewnych okolicznościach wstrzymać i ponownie skompilować kod. Visual Studio obsługuje edycję i kontynuację . Dołącz do gry w debuggerze, zatrzymaj ją w punkcie przerwania, a następnie zmodyfikuj kod po punkcie przerwania. Jeśli chcesz zapisać, a następnie kontynuować, Visual Studio podejmie próbę ponownej kompilacji kodu, włóż go ponownie do działającego pliku wykonywalnego i kontynuuj. Jeśli wszystko pójdzie dobrze, wprowadzona zmiana zostanie zastosowana do uruchomionej gry bez konieczności wykonywania całego cyklu kompilacji-kompilacji-testu. Jednak następujące przestaną działać:
Użyłem opcji Edytuj i kontynuuj, aby radykalnie poprawić szybkość takich czynności, jak iteracja interfejsu użytkownika. Załóżmy, że masz działający interfejs użytkownika zbudowany całkowicie w kodzie, z wyjątkiem tego, że przypadkowo zamieniłeś kolejność rysowania dwóch pól, więc nic nie widzisz. Zmieniając 2 wiersze kodu na żywo, możesz zaoszczędzić sobie 20-minutowy cykl kompilacji / kompilacji / testowania, aby sprawdzić trywialną poprawkę interfejsu użytkownika.
To NIE jest pełne rozwiązanie dla środowiska produkcyjnego i znalazłem najlepsze rozwiązanie, aby przenieść jak najwięcej logiki do plików danych, a następnie sprawić, by te pliki danych mogły zostać ponownie załadowane.
źródło
Jak powiedzieli inni, jest to trudny problem, dynamicznie łącząc C ++. Ale jest to rozwiązany problem - mogłeś słyszeć o COM lub o jednej z nazw marketingowych, które były stosowane do niego przez lata: ActiveX.
COM ma nieco złą nazwę z punktu widzenia programisty, ponieważ implementacja komponentów C ++, które ujawniają ich funkcjonalność przy użyciu tego narzędzia, może być bardzo trudna (chociaż jest to łatwiejsze dzięki ATL - bibliotece szablonów ActiveX). Z punktu widzenia konsumenta ma złą nazwę, ponieważ aplikacje, które go używają, na przykład do osadzania arkusza kalkulacyjnego Excel w dokumencie Word lub diagramu Visio w arkuszu kalkulacyjnym Excel, miały tendencję do zawieszania się dość dawno temu. I to sprowadza się do tych samych problemów - nawet przy wszystkich wskazówkach, które oferuje Microsoft, COM / ActiveX / OLE było / trudno jest dobrze zrozumieć.
Podkreślę, że technologia COM sama w sobie nie jest zła. Po pierwsze, DirectX wykorzystuje interfejsy COM, aby ujawnić swoją funkcjonalność, która działa wystarczająco dobrze, podobnie jak wiele aplikacji, które osadzają Internet Explorera za pomocą kontrolki ActiveX. Po drugie, jest to jeden z najprostszych sposobów dynamicznego łączenia kodu C ++ - interfejs COM jest w gruncie rzeczy czystą klasą wirtualną. Chociaż ma IDL, taki jak CORBA, nie musisz go używać, zwłaszcza jeśli zdefiniowane przez Ciebie interfejsy są używane tylko w twoim projekcie.
Jeśli nie piszesz dla systemu Windows, nie myśl, że COM nie jest warte rozważenia. Mozilla ponownie zaimplementowała go w swojej bazie kodu (używanej w przeglądarce Firefox), ponieważ potrzebowała sposobu na skomponowanie kodu C ++.
źródło
Jest to realizacja runtime-kompilowane kodu C ++ dla rozgrywki tutaj . Wiem też, że istnieje co najmniej jeden zastrzeżony silnik gry, który robi to samo. To trudne, ale wykonalne.
źródło