Animację nadal można idealnie podzielić na logikę i rendering. Stan abstrakcyjnych danych animacji byłby informacją niezbędną do graficznego interfejsu API do renderowania animacji.
Na przykład w grach 2D może to być obszar prostokąta, który oznacza obszar, który wyświetla bieżącą część arkusza duszka, który należy narysować (gdy masz arkusz składający się z powiedzmy 30 rysunków 80x80 zawierających różne kroki twojej postaci skakanie, siadanie, poruszanie się itp.). Mogą to być również wszelkiego rodzaju dane, których nie potrzebujesz do renderowania, ale być może do zarządzania samymi stanami animacji, na przykład czas pozostały do wygaśnięcia bieżącego kroku animacji lub nazwa animacji („chodzenie”, „stój” itp.) Wszystko to można przedstawić w dowolny sposób. To część logiki.
W części dotyczącej renderowania po prostu wykonaj to tak, jak zwykle, pobierz prostokąt z modelu i użyj renderera, aby faktycznie wywoływać interfejs API grafiki.
W kodzie (używając tutaj składni C ++):
class Sprite //Model
{
private:
Rectangle subrect;
Vector2f position;
//etc.
public:
Rectangle GetSubrect()
{
return subrect;
}
//etc.
};
class AnimatedSprite : public Sprite, public Updatable //arbitrary interface for classes that need to change their state on a regular basis
{
AnimationController animation_controller;
//etc.
public:
void Update()
{
animation_controller.Update(); //Good OOP design ;) It will take control of changing animations in time etc. for you
this.SetSubrect(animation_controller.GetCurrentAnimation().GetRect());
}
//etc.
};
To są dane. Twój renderer weźmie te dane i narysuje je. Ponieważ zarówno normalne, jak i animowane duszki są rysowane w ten sam sposób, możesz tutaj użyć polimorfii!
class Renderer
{
//etc.
public:
void Draw(const Sprite &spr)
{
graphics_api_pointer->Draw(spr.GetAllTheDataThatINeed());
}
};
TMV:
Wymyśliłem inny przykład. Powiedz, że masz RPG. Na przykład model reprezentujący mapę świata prawdopodobnie musiałby przechowywać pozycję postaci w świecie jako współrzędne kafelków na mapie. Jednak gdy poruszasz postacią, idą one o kilka pikseli na raz do następnego kwadratu. Czy przechowujesz tę pozycję „między płytkami” w obiekcie animacji? Jak zaktualizować model, kiedy postać wreszcie „dotrze” do następnej współrzędnej kafelka na mapie?
Mapa świata nie wie bezpośrednio o pozycji graczy (nie ma Vector2f lub czegoś takiego, który bezpośrednio przechowuje pozycję gracza =, zamiast tego ma bezpośrednie odniesienie do samego obiektu gracza, co z kolei pochodzi od AnimatedSprite dzięki czemu możesz łatwo przekazać go do renderera i uzyskać z niego wszystkie niezbędne dane.
Ogólnie rzecz biorąc, twoja mapa tilemap nie powinna być w stanie zrobić wszystkiego - miałbym klasę „TileMap”, która zajmuje się zarządzaniem wszystkimi kafelkami, a może także wykrywa kolizję między obiektami, które przekazuję jej i kafelki na mapie. Następnie miałbym inną klasę „RPGMap” lub jakkolwiek chcesz ją nazwać, która zawiera zarówno odniesienie do twojej mapy til, jak i odniesienie do odtwarzacza oraz wykonuje rzeczywiste wywołania Update () do twojego odtwarzacza i do twojego tilemap.
To, jak chcesz zaktualizować model, gdy gracz się porusza, zależy od tego, co chcesz zrobić.
Czy twój gracz może poruszać się pomiędzy płytkami niezależnie (styl Zelda)? Wystarczy poradzić sobie z wejściem i odpowiednio przesunąć odtwarzacz w każdej klatce. A może chcesz, aby gracz nacisnął „w prawo”, a twoja postać automatycznie przesuwa jeden kafelek w prawo? Pozwól swojej klasie RPGMap interpolować pozycję graczy, dopóki nie dotrze do miejsca docelowego, a tymczasem zablokuj całą obsługę klawiszy ruchu.
Tak czy inaczej, jeśli chcesz ułatwić sobie życie, wszystkie twoje modele będą miały metody Update (), jeśli faktycznie potrzebują trochę logiki, aby się zaktualizować (zamiast po prostu zmieniać wartości zmiennych) - Nie oddajesz kontrolera w ten sposób we wzorcu MVC wystarczy przenieść kod z „jednego kroku powyżej” (kontrolera) do modelu, a wszystko, co robi kontroler, to wywołać metodę Update () modelu (w naszym przypadku kontroler byłby RPGMap). Nadal możesz łatwo zamienić kod logiczny - możesz po prostu bezpośrednio zmienić kod klasy lub jeśli potrzebujesz zupełnie innego zachowania, możesz po prostu wyprowadzić się z klasy modelu i zastąpić tylko metodę Update ().
Takie podejście znacznie redukuje wywołania metod i takie rzeczy - co kiedyś było jedną z głównych wad czystego wzorca MVC (w końcu bardzo często wywoływano funkcję GetThis () GetThat ()) - powoduje to, że kod jest zarówno dłuższy, jak i trochę trudniejsze do odczytania, a także wolniejsze - nawet jeśli może to zająć się twój kompilator, który optymalizuje wiele takich rzeczy.
Mogę rozwinąć tę kwestię, jeśli chcesz, ale mam centralny moduł renderujący, któremu każe się rysować w pętli. Zamiast
Mam bardziej podobny system
Klasa renderer zawiera po prostu listę odniesień do rysowalnych komponentów obiektów. Są one przypisane w konstruktorach dla uproszczenia.
Na przykład chciałbym mieć klasę GameBoard z wieloma płytkami. Każda płytka oczywiście zna swoją pozycję i zakładam jakąś animację. Uwzględnij to w jakiejś klasie Animacji, do której należy kafelek, i poproś, aby przekazał swoje odwołanie do klasy Renderer. Tam wszyscy rozdzieleni. Gdy aktualizujesz kafelek, wywołuje on aktualizację animacji .. lub aktualizuje go sam. Po
Renderer.Draw()
wywołaniu rysuje animację.Animacja niezależna od klatki nie powinna zajmować zbyt wiele miejsca w pętli rysowania.
źródło
Ostatnio uczyłem się paradygmatów, więc jeśli ta odpowiedź będzie niepełna, jestem pewien, że ktoś ją doda.
Metodologia, która wydaje się najbardziej sensowna w projektowaniu gier, polega na oddzieleniu logiki od wyniku wyświetlanego na ekranie.
W większości przypadków chciałbyś zastosować podejście wielowątkowe, jeśli nie znasz tego tematu, jest to pytanie samo w sobie, oto podstawowa wiki . Zasadniczo chcesz, aby logika gry była wykonywana w jednym wątku, blokując zmienne, do których musi mieć dostęp, aby zapewnić integralność danych. Jeśli twoja pętla logiczna jest niewiarygodnie szybka (super mega animowany pong 3d?), Możesz spróbować naprawić częstotliwość wykonywaną przez tę pętlę, śpiąc na wątku przez krótki czas (na forum sugerowano 120 Hz dla pętli fizyki gry). Jednocześnie drugi wątek przerysowuje ekran (w innych tematach sugerowano 60 Hz) z zaktualizowanymi zmiennymi, ponownie prosząc o zablokowanie zmiennych przed uzyskaniem do nich dostępu.
W takim przypadku animacje lub przejścia itp. Przechodzą do wątku rysunkowego, ale musisz sygnalizować przez flagę (być może zmienną stanu globalnego), że wątek logiki gry nie może nic robić (lub robić coś innego ... być może nowe parametry mapy).
Po obejrzeniu współbieżności reszta jest dość zrozumiała. Jeśli nie masz doświadczenia w zakresie współbieżności, zdecydowanie zalecamy napisanie kilku prostych programów testowych, aby zrozumieć, jak przebiega przepływ.
Mam nadzieję że to pomoże :)
[edytuj] W systemach, które nie obsługują wielowątkowości, animacja może nadal przechodzić w pętlę losowania, ale chcesz ustawić stan w taki sposób, aby sygnalizował logice, że dzieje się coś innego i nie przetwarza aktualny poziom / mapa / etc ...
źródło