Mam ukończoną grę, którą chcę odrzucić w innych wersjach. Byłyby to podobne gry, z mniej więcej tym samym wyglądem, ale nie zawsze w zasadzie rzeczy mogą się zmieniać, czasem małe, czasem duże.
Chciałbym, aby kod podstawowy był wersjonowany oddzielnie od gry, więc jeśli powiesz, że naprawię błąd znaleziony w grze A, poprawka będzie obecna w grze B.
Próbuję znaleźć najlepszy sposób, aby sobie z tym poradzić. Moje początkowe pomysły są następujące:
- Utwórz
engine
moduł / folder / cokolwiek, który zawiera wszystko, co można uogólnić i jest w 100% niezależny od reszty gry. Obejmuje to część kodu, ale także ogólne zasoby, które są wspólne dla gier. - Umieść ten silnik we własnym
git
repozytorium, które zostanie uwzględnione w grach jakogit submodule
Część, z którą mam problem, to sposób zarządzania resztą kodu. Załóżmy, że masz scenę menu, ten kod jest specyficzny dla gry, ale także większość z nich ma charakter ogólny i może być ponownie wykorzystana w innych grach. Nie mogę tego włożyć engine
, ale przekodowanie go dla każdej gry byłoby nieefektywne.
Być może użycie jakiegoś wariantu gałęzi git może być skuteczne w zarządzaniu tym, ale nie sądzę, że jest to najlepsza droga.
Czy ktoś ma jakieś pomysły, doświadczenia do podzielenia się lub coś na ten temat?
Odpowiedzi:
Właśnie to robię i działa bardzo dobrze. Mam strukturę aplikacji i bibliotekę renderującą, a każda z nich jest traktowana jako podmoduły moich projektów. Uważam, że SourceTree jest użyteczny, jeśli chodzi o podmoduły, ponieważ dobrze nimi zarządza i nie pozwoli ci niczego zapomnieć, np. Jeśli zaktualizujesz podmoduł silnika w projekcie A, powiadomi cię on o wycofaniu zmian w projekcie B.
Z doświadczeniem przychodzi wiedza na temat tego, jaki kod powinien być w silniku, a co na projekt. Sugeruję, że jeśli jesteś choć trochę niepewny, na razie zatrzymaj go w każdym projekcie. Z biegiem czasu zobaczysz wśród różnych projektów, co pozostaje niezmienne, a następnie możesz stopniowo uwzględniać to w kodzie silnika. Innymi słowy: zduplikuj kod, dopóki nie osiągniesz 100% pewności, że nie zmienia się on dyskretnie dla każdego projektu, a następnie uogólnij go.
Uwaga na temat kontroli źródła i plików binarnych
Pamiętaj tylko, że jeśli oczekujesz częstych zmian zasobów binarnych, możesz nie chcieć umieszczać ich w kontroli tekstu opartej na źródłach, takiej jak git. Mówiąc tylko ... istnieją lepsze rozwiązania dla plików binarnych. Najprostszą rzeczą, jaką możesz teraz zrobić, aby utrzymać czyste i wydajne repozytorium „silnik-źródło”, jest posiadanie oddzielnego repozytorium „binaria silnika”, które zawiera tylko pliki binarne, które również włączasz jako podmoduł do swojego projektu. W ten sposób zmniejszasz szkody wyrządzone w repozytorium „silnik-źródło”, które cały czas się zmienia i w związku z czym potrzebujesz szybkich iteracji: zatwierdzanie, wypychanie, wyciąganie itp. Systemy zarządzania kontrolą źródła, takie jak git, działają na delcie tekstowej , a po wprowadzeniu plików binarnych wprowadzasz ogromne delty z perspektywy tekstowej - co ostatecznie kosztuje Twój czas.Załącznik do GitLab . Google jest twoim przyjacielem.
źródło
W pewnym momencie silnik MUSI specjalizować się i wiedzieć coś o grze. Pójdę tutaj na styczną.
Weź zasoby w RTS. Jedna gra może mieć,
Credits
aCrystal
drugaMetal
iPotatoes
Powinieneś właściwie używać koncepcji OO i sięgać po maks. ponowne użycie kodu. Oczywiste jest, że
Resource
istnieje tutaj koncepcja istnienia.Dlatego decydujemy, że zasoby mają następujące elementy:
int
)Zauważ, że to pojęcie
Resource
może oznaczać zabójstwa lub punkty w grze! To nie jest bardzo potężne.Teraz pomyślmy o grze. Możemy w pewnym sensie mieć walutę, handlując groszami i dodając przecinek dziesiętny do wyniku. Nie możemy zrobić „natychmiastowych” zasobów. Jak powiedz „generowanie sieci energetycznej”
Powiedzmy, że dodajesz
InstantResource
klasę za pomocą podobnych metod. Teraz (zaczynasz) zanieczyszczać silnik zasobami.Problem
Weźmy jeszcze raz przykład RTS. Załóżmy, że gracz cokolwiek przekazuje część
Crystal
innemu graczowi. Chcesz zrobić coś takiego:Jest to jednak naprawdę dość niechlujny. To ogólny cel, ale bałagan. Już narzuca to,
resourceDictionary
co oznacza, że teraz twoje zasoby muszą mieć nazwy! I to jest na gracza, więc nie możesz już mieć zasobów zespołu.To jest „za dużo” abstrakcji (nie jest to świetny przykład, przyznaję), zamiast tego powinieneś osiągnąć punkt, w którym akceptujesz, że twoja gra ma graczy i kryształ, wtedy możesz po prostu mieć (na przykład)
Z klasą
Player
i klasąCurrentPlayer
, gdzieCurrentPlayer
„scrystal
obiektu automatycznie pokaże rzeczy na HUD dla przeniesienia / wysyłania datków.To zanieczyszcza silnik kryształem, przekazuje kryształ, komunikaty na interfejsie dla obecnych graczy i tak dalej. Jest zarówno szybszy, jak i łatwiejszy do odczytu / zapisu / konserwacji (co jest ważniejsze, ponieważ nie jest znacznie szybszy)
Uwagi końcowe
Przypadek zasobów nie jest genialny. Mam jednak nadzieję, że nadal rozumiesz o co chodzi. Jeśli cokolwiek wykazałem, że „zasoby nie należą do silnika” jako to, czego potrzebuje konkretna gra i co dotyczy wszystkich pojęć zasobów, to BARDZO różne rzeczy. Zazwyczaj znajdziesz 3 (lub 4) „warstwy”
creature
lubship
lubsquad
. Korzystając z dziedziczenia otrzymasz klasy, które obejmują wszystkie 3 warstwy (na przykładCrystal
jest to,Resource
co jestGameLoopEventListener
powiedzmy)Tworzenie nowej gry ze starego silnika
Jest to BARDZO powszechne. Faza 1 polega na rozerwaniu warstw 3 i 4 (i 2, jeśli gra jest CAŁKOWICIE innego typu) Załóżmy, że tworzymy RTS ze starego RTS. Wciąż mamy zasoby, po prostu nie kryształy i inne rzeczy - więc klasy podstawowe w warstwach 2 i 1 nadal mają sens, wszystkie kryształy wymienione w 3 i 4 można odrzucić. Tak robimy. Możemy jednak to sprawdzić jako odniesienie do tego, co chcemy zrobić.
Zanieczyszczenia w warstwie 1
To może się zdarzyć. Abstrakcja i wydajność są wrogami. Na przykład UE4 zapewnia wiele zoptymalizowanych przypadków komponowania (więc jeśli chcesz X i Y, ktoś napisał kod, który X i Y naprawdę razem bardzo szybko - wie, że robi oba) i w rezultacie NAPRAWDĘ jest dość duży. To nie jest złe, ale jest czasochłonne. Warstwa 1 decyduje o tym, jak „przekazać dane do shaderów” i jak animować. Robienie tego w najlepszy sposób dla twojego projektu jest ZAWSZE dobre. Po prostu spróbuj zaplanować przyszłość, ponowne użycie kodu jest twoim przyjacielem, dziedzicz tam, gdzie ma to sens.
Klasy klasyfikacyjne
OSTATNIE (obiecuję) nie bój się zbyt wielu warstw. Silnik jest archaicznym terminem z dawnych czasów rurociągów o stałej funkcji, w których silniki działały prawie tak samo graficznie (i w rezultacie miały wiele wspólnego), programowalny rurociąg odwrócił to do góry nogami i jako taka „warstwa 1” została zanieczyszczona niezależnie od efektów, jakie chcieli osiągnąć programiści. AI była cechą wyróżniającą (ze względu na niezliczoną liczbę podejść) silników, teraz jest to AI i grafika.
Twój kod nie powinien być zapisywany w tych warstwach. Nawet słynny silnik Unreal ma WIELE różnych wersji, z których każda jest specyficzna dla innej gry. Istnieje kilka plików (może innych niż struktury danych), które pozostałyby niezmienione. Jest okej! Jeśli chcesz stworzyć nową grę z innej, zajmie to więcej niż 30 minut. Kluczem jest zaplanowanie, wiedzieć, jakie bity skopiować i wkleić, a co zostawić.
źródło
Moją osobistą sugestią, jak radzić sobie z treścią łączącą ogólne i specyficzne, jest uczynienie jej dynamicznym. Jako przykład wezmę ekran menu. Jeśli źle zrozumiałem, o co prosiłeś, daj mi znać, o co chciałeś wiedzieć, a ja dostosuję moją odpowiedź.
Istnieją 3 rzeczy, które są (prawie) zawsze obecne na scenie menu: tło, logo gry i samo menu. Te rzeczy są zwykle różne w zależności od gry. Możesz zrobić dla tej zawartości MenuScreenGenerator w swoim silniku, który przyjmuje 3 parametry obiektu: BackGround, Logo i Menu. Podstawowa struktura tych 3 części jest również częścią twojego silnika, ale twój silnik nie mówi w jaki sposób te części są generowane, tylko jakie parametry powinieneś im nadać.
Następnie w swoim prawdziwym kodzie gry tworzysz obiekty dla BackGround, logo i menu, i przekazujesz to swojemu MenuScreenGenerator. Ponownie, sama gra nie obsługuje generowania menu, to jest dla silnika. Twoja gra musi tylko powiedzieć silnikowi, jak powinien on wyglądać i gdzie powinien być.
Zasadniczo twój silnik powinien być interfejsem API, który gra mówi, co wyświetlać. Jeśli zostanie to wykonane prawidłowo, silnik powinien wykonać ciężką pracę, a sama gra powinna tylko powiedzieć silnikowi, jakie zasoby należy użyć, jakie działania podjąć i jak wygląda świat.
źródło