Kodowanie oparte na danych
Każda rzecz, o której wspominasz, jest czymś, co można określić w danych. Dlaczego ładujesz aspecificmap
? Ponieważ konfiguracja gry mówi, że jest to pierwszy poziom, gdy gracz rozpoczyna nową grę, lub ponieważ jest to nazwa bieżącego punktu zapisu w właśnie zapisanym pliku zapisu gracza itp.
Jak znaleźć aspecificmap
? Ponieważ znajduje się w pliku danych, który zawiera identyfikatory map i ich zasoby na dysku.
Potrzebny jest tylko szczególnie mały zestaw „podstawowych” zasobów, które są prawnie trudne lub niemożliwe do uniknięcia na stałe. Przy odrobinie pracy można to ograniczyć do pojedynczej zakodowanej domyślnej nazwy zasobu, takiej jak main.wad
lub podobnej. Ten plik można potencjalnie zmienić w czasie wykonywania, przekazując do gry argument wiersza polecenia, alias game.exe -wad mymain.wad
.
Pisanie kodu opartego na danych opiera się na kilku innych zasadach. Na przykład można uniknąć sytuacji, w której systemy lub moduły proszą o określony zasób, i zamiast tego odwrócić te zależności. Oznacza to, że nie należy DebugDrawer
ładować debug.font
kodu inicjującego; zamiast tego DebugDrawer
weź uchwyt zasobu w jego kodzie inicjującym. Ten uchwyt może zostać załadowany z głównego pliku konfiguracyjnego gry.
Jako konkretne przykłady z naszej bazy kodu mamy obiekt „danych globalnych”, który jest ładowany z bazy danych zasobów (która sama jest domyślnie ./resources
folderem, ale może być przeciążona argumentem wiersza poleceń). Identyfikator bazy danych zasobów tych globalnych danych jest jedyną koniecznie zakodowaną nazwą zasobu w bazie kodu (mamy inne, ponieważ czasami programiści stają się leniwi, ale ostatecznie ostatecznie je naprawiamy / usuwamy). Ten globalny obiekt danych jest pełen komponentów, których jedynym celem jest dostarczenie danych konfiguracyjnych. Jednym ze składników jest składnik danych globalnych interfejsu użytkownika, który zawiera uchwyty zasobów dla wszystkich głównych zasobów interfejsu użytkownika (czcionki, pliki Flash, ikony, dane lokalizacji itp.) Wśród wielu innych elementów konfiguracji. Gdy programista interfejsu użytkownika decyduje się zmienić nazwę głównego zasobu interfejsu użytkownika z /ui/mainmenu.swf
na/ui/lobby.swf
po prostu aktualizują to globalne odniesienie danych; kod silnika nie musi się w ogóle zmieniać.
Używamy tych globalnych danych do wszystkiego. Wszystkie grywalne postacie, wszystkie poziomy, interfejs użytkownika, audio, podstawowe zasoby, konfiguracja sieci, wszystko. (cóż, nie wszystko , ale te inne rzeczy wymagają naprawy.)
To podejście ma wiele innych zalet. Po pierwsze, sprawia, że pakowanie i wiązanie zasobów jest integralną częścią całego procesu. Ścieżki kodowania w silniku również zwykle oznaczają, że te same ścieżki muszą być zakodowane na sztywno w dowolnych skryptach lub narzędziach pakujących zasoby gry, a ścieżki te mogą się potem zsynchronizować. Bazując na pojedynczym kluczowym zasobie i łańcuchach referencyjnych stamtąd, możemy zbudować pakiet zasobów za pomocą jednego polecenia podobnego bundle.exe -root config.data -out main.wad
i wiedzącego, że obejmie on wszystkie potrzebne zasoby. Co więcej, ponieważ producent pakietów po prostu śledzi odniesienia do zasobów, wiemy, że obejmie on tylko potrzebne zasoby i pominie cały pozostały puch, który nieuchronnie gromadzi się przez cały czas trwania projektu (a ponadto możemy automatycznie generować listy tego puch do przycinania).
Trudny przypadek tego wszystkiego jest w skryptach. Stworzenie silnika opartego na danych jest łatwe koncepcyjnie, ale widziałem tak wiele projektów (od hobby do AAA), w których skrypty są uważane za dane i dlatego „wolno” po prostu używać ścieżek zasobów bez rozróżnienia. Nie rób tego Jeśli plik Lua potrzebuje zasobu i po prostu wywołuje funkcję taką jak textures.lua("/path/to/texture.png")
wtedy, potok zasobów będzie miał wiele problemów, wiedząc, że skrypt wymaga /path/to/texture.png
poprawnego działania i może uznać tę teksturę za nieużywaną i niepotrzebną. Skrypty należy traktować jak każdy inny kod: wszelkie potrzebne dane, w tym zasoby lub tabele, powinny być określone we wpisie konfiguracji, który silnik i potok zasobów mogą sprawdzać pod kątem zależności. Dane, które mówią „ładuj skrypt foo.lua
” powinny zamiast tego powiedzieć „foo.lua
i podaj mu te parametry ", gdy parametry obejmują wszelkie niezbędne zasoby. Jeśli skrypt na przykład losowo odradza wrogów, przekaż listę możliwych wrogów do skryptu z tego pliku konfiguracyjnego. Silnik może następnie wstępnie załadować wrogów poziomem ( ponieważ zna pełną listę możliwych odrodzeń), a potok zasobów wie, że łączy wszystkich wrogów z grą (ponieważ są one definitywnie określone w danych konfiguracyjnych). Jeśli skrypty generują ciągi nazw ścieżek i po prostu wywołują load
funkcję, wówczas żadne silnik ani potok zasobów nie mają żadnego sposobu na określenie, które zasoby skrypt może załadować.
W ten sam sposób unikasz twardego kodowania w funkcjach ogólnych.
Podajesz parametry i przechowujesz informacje w plikach konfiguracyjnych.
W tej sytuacji nie ma absolutnie żadnej różnicy w inżynierii oprogramowania między pisaniem silnika a pisaniem klasy.
Następnie kod klienta odczytuje „główny” plik konfiguracyjny ( ten jest albo zakodowany na stałe, albo przekazany jako argument wiersza poleceń), który zawiera informacje określające, gdzie znajdują się pliki zasobów i jaką mapę zawierają.
Stamtąd wszystko sterowane jest przez plik konfiguracyjny „master”.
źródło
Lubię inne odpowiedzi, więc będę trochę przeciwny. ;)
Nie można uniknąć kodowania wiedzy o swoich danych w silniku. Bez względu na to, skąd pochodzą informacje, silnik musi wiedzieć, aby ich szukać. Można jednak uniknąć zakodowania samych informacji w silniku.
Podejście oparte na „czystych” danych wymagałoby uruchomienia pliku wykonywalnego z parametrami wiersza poleceń niezbędnymi do załadowania początkowej konfiguracji, ale silnik musi zostać zakodowany, aby wiedzieć, jak interpretować te informacje. Np jeśli pliki konfiguracyjne są JSON, trzeba ciężko kodem zmienne szukać np silnik będzie musiał wiedzieć, aby szukać
"intro_movies"
i"level_list"
itd.Jednak „dobrze skonstruowany” silnik może działać w wielu różnych grach, po prostu zamieniając dane konfiguracyjne i dane, do których się odwołuje.
Tak więc mantra nie tyle polega na unikaniu twardego kodowania, ile na zapewnianiu, że możesz wprowadzać zmiany przy jak najmniejszym wysiłku.
W przeciwieństwie do podejścia do plików danych (które z całego serca popieram), być może kompilowanie danych w silniku jest w porządku. Jeśli „koszt” zrobienia tego jest niższy, wówczas nie ma prawdziwej szkody; jeśli tylko nad tym pracujesz, możesz odłożyć obsługę plików na później i niekoniecznie się pieprzyć. W moich pierwszych kilku projektach gier zapisano duże tabele danych w samej grze, np. Listę broni i ich różnorodne dane:
Umieszczasz więc te dane w łatwym miejscu i możesz je łatwo edytować w razie potrzeby. Idealnym rozwiązaniem byłoby umieszczenie tych rzeczy w jakimś pliku konfiguracyjnym, ale wtedy trzeba wykonać parsowanie i tłumaczenie oraz cały ten jazz, a także podłączanie referencji między strukturami może stać się dodatkowym problemem, którego tak naprawdę nie chcesz radzić sobie z.
źródło