2010-04-14

MVC w obsłudze aktorów (obiektów) gry

Projektując obsługę agentów gry na potrzeby SpaceFight, nieświadomie zaaplikowałem wzorzec MVC (Model-View-Controller) - rozdzielenie aktora na model (czyli obiekt z danymi), widok (encja silnika graficznego/dźwiękowego) i kontroler. Podejście takie skutkuje sporą elastycznością w zakresie działania AI, stąd postaram się klarownie przedstawić koncepcję realizacji MVC w grze.

Aby MVC miało jakikolwiek sens, konieczne jest rozdzielenie rzeczywistego obiektu gry na trzy podobiekty - zbiór danych obiektu (dalej zwany modelem), audiowizualną reprezentację obiektu (dalej zwaną widokiem) oraz kod/skrypt kontrolujący logikę obiektu (zwany... a zgadnij ;-) ). Korzystając z gotowego silnika (w moim przypadku: OGRE), podział da się łatwo uzyskać - właściwy obiekt aktora jest jednocześnie modelem, zawierającym odniesienie (referencję) do encji graficznej (widoku) i abstrakcyjnej klasy kontrolera.

Sam model zawiera wszystkie "fizyczne" dane obiektu - parametry w mechanice gry, dane dotyczące ruchu i pozycji, wszelkie inne potrzebne dane. Update stanu obiektu jest wywoływany przez update stanu modelu - to model odpytuje kontrolera o akcje do wykonania i informuje widok o tym, gdzie i jak ma się rysować/odtwarzać. W przypadku SpaceFight model to abstrakcyjna klasa dziedzicząca z SpaceObject - zbiór tych klas jest jednocześnie zbiorem aktorów dla danej sceny (misji).

Widok to zbiór encji silnika, "włożonych" w jeden scene node. Samodzielnie nie wykonuje żadnych akcji (poza załadowaniem się z zasobów i narysowaniem na żądanie silnika), jest to tylko prezentacja istniejącego obiektu.

W przypadku kontrolera, sytuacja jest zabawniejsza. Mianowicie, żeby stosowanie kontrolera miało sens, musi być on zależny od typu obiektu. Moim rozwiązaniem było osadzenie go w konkretnej klasie modelu (np. klasa Ship posiada odniesienie do ShipController, klasa Missile do MissileController itd.) i wywoływanie aktualizacji stanu kontrolera z poziomu modelu. Mimo to, zostawiłem sobie pewną furtkę w stronę wielowątkowości - każdy model posiada osobną instancję kontrolera, która może być aktualizowana w osobnym wątku. Zadaniem kontrolera jest analiza globalnej sytuacji (w miarę konieczności odczytując dane z modelu) i wysłanie odpowiednich komend do kontrolowanego aktora.

Komunikacja między modelem i kontrolerem została przygotowana w możliwie najprostszy sposób: kontroler wysyła (za pośrednictwem ogólnej kolejki komunikatów) rozkaz do modelu, który w momencie update'u jest wykonywany i usuwany z kolejki. Pozwala to na znaczne uniezależnienie implementacji modelu i kontrolera, ponadto dla AI pozwala na dobrą symulację rzeczywistego gracza (wysyłane są komendy zgodne z możliwym podpięciem sterowania dla gracza).

Moje podejście do kontrolera ułatwiło jeszcze jedną sprawę: obsługę aktora gracza. Dla gracza stosowany jest ogólnosystemowy model z ogólnosystemowym widokiem, wyjątkiem jest wyłącznie kontroler, który zamiast samodzielnie analizować sytuację odczytuje informacje z wejścia. W efekcie gra teoretycznie umożliwia natychmiastowe przełączanie gracza między aktorami, jak również częściową lub całkowitą kontrolę aktora gracza przez AI. Przy planowanym stopniu skomplikowania takie rozwiązanie może okazać się użyteczne.

0 komentarze:

Prześlij komentarz