tło
Zajmuję się tworzeniem gier jako hobby i szukam lepszego sposobu na ich zaprojektowanie. Obecnie używam standardowego podejścia OOP (zajmuję się rozwojem przedsiębiorstwa od 8 lat, więc przychodzi to w ujęciu kwartalnym). Weźmy na przykład „złego”
public class Baddie:AnimatedSprite //(or StaticSprite if needed, which inherit Sprite)
{
//sprite base will have things like what texture to use,
//what the current position is, the base Update/Draw/GetInput methods, etc..
//an AnimatedSprite contains helpers to animated the player while
//a StaticSprite is just one that will draw whatever texture is there
}
Problem
Powiedzmy, że tworzę platformówkę 2D i potrzebuję baddie, aby móc skakać. Zwykle robię to, dodając odpowiedni kod w metodach Update / GetInput. Więc jeśli będę musiał zmusić gracza do czołgania się, kaczkowania, wspinania się itp., Kod się tam pojawi.
Jeśli nie jestem ostrożny, metody te są zaśmiecone, więc w końcu tworzę takie pary metod
CheckForJumpAction(Input input)
i DoJump()
CheckforDuckAction(Input input)
i DoDuck()
tak wygląda GetInput
public void DoInput(Input input)
{
CheckForJumpAction(input);
CheckForDuckAction(input);
}
i wygląda aktualizacja
public void Update()
{
DoJump();
DoDuck();
}
Jeśli pójdę i stworzę inną grę, w której gracz musi skakać i uchylać się, zwykle wchodzę do gry, która ma tę funkcjonalność i kopiuję ją. Bałagan, wiem. Właśnie dlatego szukam czegoś lepszego.
Rozwiązanie?
Bardzo podoba mi się, jak Blend ma zachowania, które mogę dołączyć do elementu. Myślałem o zastosowaniu tej samej koncepcji w moich grach. Spójrzmy więc na te same przykłady.
Stworzyłbym podstawowy obiekt Behavior
public class Behavior
{
public void Update()
Public void GetInput()
}
Za pomocą tego mogę tworzyć zachowania. JumpBehavior:Behavior
iDuckBehavior:Behavior
Następnie mogę dodać kolekcję zachowań do bazy Sprite i dodać to, czego potrzebuję do każdej jednostki.
public class Baddie:AnimatedSprite
{
public Baddie()
{
this.behaviors = new Behavior[2];
this.behaviors[0] = new JumpBehavior();
//etc...
}
public void Update()
{
//behaviors.update
}
public GetInput()
{
//behaviors.getinput
}
}
Więc teraz Jeśli chciałbym używać Jump and Duck w wielu grach, mogę po prostu zapanować nad zachowaniami. Mógłbym nawet stworzyć bibliotekę dla zwykłych.
Czy to działa?
Nie potrafię rozgryźć, jak podzielić stan między nimi. Patrząc na Skok i Kaczkę, oba wpływają nie tylko na bieżącą część rysowanej tekstury, ale także na stan gracza. (Skok zastosuje z czasem malejącą siłę skierowaną w górę, podczas gdy kaczka po prostu zatrzyma ruch, zmieni teksturę i rozmiar zderzenia baddiego.
Jak mogę to powiązać, aby działało? Czy powinienem utworzyć właściwości zależności między zachowaniami? Czy każde zachowanie powinno wiedzieć o rodzicu i bezpośrednio je modyfikować? Jedną rzeczą, o której myślałem, jest możliwość przekazania delegata każdemu zachowaniu, aby zostało wykonane po jego uruchomieniu.
Jestem pewien, że analizuję więcej problemów, ale moim głównym celem jest umożliwienie łatwego ponownego wykorzystania tych zachowań między grami i bytami w tej samej grze.
Więc oddaję to tobie. Czy chcesz wyjaśnić, jak / jeśli można to zrobić? Czy masz lepszy pomysł? Zamieniam się w słuch.
Odpowiedzi:
Spójrz na tę prezentację. Brzmi dość blisko rodzaju wzoru, którego szukasz. Ten wzorzec obsługuje zachowania i właściwości dołączalne. Nie wydaje mi się, żeby prezentacja o tym wspominała, ale można również tworzyć dołączalne wydarzenia. Ten pomysł jest podobny do właściwości zależności używanych w WPF.
źródło
Dla mnie brzmi to jak podręcznikowy przypadek użycia Wzorca Strategii . W tym schemacie twoje ogólne zachowania można zdefiniować w interfejsach, które pozwolą ci wymieniać różne implementacje zachowań w czasie wykonywania (pomyśl o tym, jak wzmocnienie może wpłynąć na zdolność skakania lub biegania twojej postaci).
Dla (wymyślonego) przykładu:
Teraz możesz wdrożyć różne rodzaje skoków, z których może korzystać Twoja postać, niekoniecznie znając szczegóły dotyczące każdego konkretnego skoku:
Wszystkie postacie, które chcesz mieć możliwość skakania, mogą teraz zawierać odniesienie do IJumpable i mogą zacząć korzystać z różnych implementacji
Wówczas Twój kod może wyglądać mniej więcej tak:
Teraz możesz z łatwością ponownie wykorzystać te zachowania, a nawet rozszerzyć je później, aby dodać więcej rodzajów skoków lub stworzyć więcej gier zbudowanych na jednej platformie Side-scrolling.
źródło
Rzuć okiem na architekturę DCI, która może pomóc w programowaniu OO.
Implementacja architektury w stylu DCI w języku C # wymaga użycia prostych klas domen oraz użycia obiektów (zachowań) kontekstowych z metodami rozszerzeń i interfejsami, aby umożliwić klasom domeny współpracę w ramach różnych ról. Interfejsy służą do oznaczania ról wymaganych w scenariuszu. Klasy domen implementują interfejsy (role), które dotyczą ich zamierzonego zachowania. Współpraca między rolami odbywa się w obiektach kontekstu (zachowania).
Można utworzyć bibliotekę typowych zachowań i ról, które mogą być współużytkowane przez obiekty domeny z oddzielnych projektów.
Zobacz DCI w C # dla przykładów kodu w C #.
źródło
Co powiesz na przekazanie obiektu kontekstu do zachowania, które zapewnia metody zmiany stanu duszków / obiektów, na które zachowanie ma wpływ? Teraz, jeśli obiekt ma kilka zachowań, każde z nich jest wywoływane w pętli, co daje im szansę na zmianę kontekstu. Jeśli pewna modyfikacja właściwości wyklucza / ogranicza modyfikację innych właściwości, obiekt kontekstowy może zapewnić metody ustawiania pewnego rodzaju flag wskazujących na ten fakt lub może po prostu zaprzeczyć niepożądanym modyfikacjom.
źródło