Czy istota powinna poradzić sobie z własnym ruchem?
Obecnie tworzę prostą grę turową z pokojem składającym się z tablicy 2D obiektów typu Tile. Mogą to być płytki ścienne lub płytki podłogowe. Obiekty kaflowe mają północnego, wschodniego, południowego i zachodniego sąsiada. Płytki podłogowe mają stos jednostek, więc mogę łatwo narysować górną jednostkę.
Zastanawiam się teraz, czy mój Gracz jest bytem, kiedy gracz się porusza, czy powinien on obsługiwać tę logikę, czy też Grę, która trzyma mojego Gracza?
W pierwszym przypadku byłoby to coś w rodzaju:
class Player : Entity {
public Tile CurrentLocation
public bool CanMoveTo(Direction) {
// Let's say that direction is East
return CurrentLocation.EastNeighbour == FloorTile
}
// This method does the actual move
public void MoveTo(Direction)
}
W drugim przypadku byłoby tak:
class Game {
public Player Player;
public Room Room;
public void Update() {
// Lets say we get direction from some input from somewhere
if Room.TileAt(player.Y, player.X) is FloorTile then MoveThere()
}
}
Nie jestem pewien, która z tych dwóch opcji jest najfajniejsza. Początkowo myślę o pierwszym rozwiązaniu. Każda pomoc lub wskazówki będą mile widziane.
Odpowiedzi
Nie ma dobrych ani złych sposobów robienia rzeczy. Tylko sposoby, które działają dla Ciebie lub nie.
W głównym nurcie architektury gier istnieje kilka konkurencyjnych filozofii.
O: Architektura zorientowana obiektowo
Każdy podmiot powinien być reprezentowany przez obiekt. Obiekty te powinny zawierać całą własną logikę, hermetyzować swój stan wewnętrzny i komunikować się z innymi obiektami za pośrednictwem dobrze zdefiniowanych interfejsów. Podstawowy silnik gry po prostu wywołuje metody „Update” i „Render” wszystkich elementów w pętli, a następnie pozwala jednostkom wykonać resztę.
Gdy masz obiekty, które mają wspólną funkcjonalność, zwykle robisz to poprzez dziedziczenie - wyodrębniając wspólną funkcjonalność do wspólnej klasy bazowej. Na przykład, możesz chcieć mieć klasę Player i klasę Enemy, które dziedziczą po klasie Combatant, która zawiera cały kod bojowy i dziedziczy po Entity, która zawiera podstawowy kod ruchu. Enemy byłby prawdopodobnie klasą abstrakcyjną, która zawierałaby tylko logikę wspólną dla wszystkich wrogów, z różnymi typami wrogów i ich unikalnymi zachowaniami zaimplementowanymi w klasach dziedziczących po Enemy.
Zasadniczo sposób, w jaki powinieneś robić OOP według książki.
Ten styl jest często spotykany u programistów, którzy wywodzili się z rozwoju aplikacji, ponieważ jest tam dominującym stylem. A to, co działa w przypadku aplikacji, niekoniecznie jest całkowicie złe w przypadku gier.
B: Architektura Entity-Component
Ten styl przedkłada kompozycję nad dziedziczenie . Jednostka nie jest jednym obiektem, jest zbiorem obiektów, z których każdy implementuje inną funkcję tej jednostki. Więc nie masz klasy „Player” implementującej każdą rzecz, jaką może zrobić gracz. Masz jednostkę ogólną z komponentem „Sprite”, „Mover”, „Shooter”, „Hitpoints” i „PlayerInput”.
Zaletą tego podejścia jest to, że komponenty można bardzo łatwo łączyć i dopasowywać w celu tworzenia nowych rodzajów elementów gry. Jest to bardzo przydatne w przypadku szybkich zmian w projekcie gry. To ulubione podejście niektórych popularnych silników gier. Na przykład Unity.
C: Architektura Entity-Component-System
Encja powinna być zbiorem komponentów, tak jak w architekturze „Entity-Component”. Ale w tej architekturze te komponenty powinny być po prostu głupimi posiadaczami danych. Zwykle w postaci struktur zawierających tylko zmienne publiczne i bez metod. Cała logika powinna znajdować się w systemach. Każdy system jest odpowiedzialny za zarządzanie jednym typem komponentów lub zestawem komponentów. Na przykład masz MovementSystem, który aktualizuje komponent Position każdego obiektu za pomocą komponentu Movement.
Ale to tylko nadmierne uproszczenie. Jeśli chcesz dowiedzieć się więcej, sprawdź pytanie „Co to jest czysty ECS?” . Możesz również spojrzeć na inne pytania pod naszym tagiem jednostka-komponent-system .
Podejście ECS jest obecnie bardzo modne. Głównym argumentem przemawiającym za tym jest to, że pozwala na bardzo zgrabną optymalizację wydajności (szczególnie jeśli chodzi o lokalizację pamięci i wielowątkowość) bez poświęcania modułowości i szybkości iteracji podejścia Entity-Component. Jednak wadą jest to, że solidna i elastyczna architektura ECS wymaga sporo kodu architektury, który działa „za kulisami” i który może być trudny do wdrożenia dla początkujących.
Którego należy użyć?
To jest coś, co musisz samemu rozgryźć. Jednak niezależnie od tego, które podejście wybierzesz dla swojej gry, dobrze byłoby się go trzymać. Unikaj mieszania i dopasowywania wzorców architektury w swojej grze, gdy jest to możliwe.
Zasadniczo zależy to od ilości danych przechodzących przez twoją sieć gry. Każdy masowy tryb wieloosobowy musi śledzić, kto jest gdzie i co robi (w przeciwnym razie nastąpi masowe oszustwa). Jednak odbieranie i wysyłanie pakietów z serwera do każdego klienta (tak zwanego serwera autorskiego) rzadko jest praktyczne dla dowolnej liczby ważnych użytkowników. Tak więc wydaje się, że filozofia polega na tym, ile logiki można uzyskać od gracza, aby kontrolować go bez wpływu na innych graczy. Innymi słowy, tym, co dotyczy ciebie i tylko ciebie (z wyjątkiem grabieży), może zająć się klient. Natomiast wszystko, co wpływa na kilku graczy, musi być obsługiwane przez serwer. Lub, jeśli nie jest traktowany, przynajmniej autoryzowany przed wystąpieniem jakichkolwiek skutków.