Intense.js – systemy

Były już encje i były komponenty. Obiekty i ich właściwości, ale jak nimi zarządzać? Odpowiedzią na to pytanie są systemy, które przy okazji są ostatnim fundamentem mojego frameworka.

Systemy frameworka intense opisują logikę zachodzącą w grze. W ciele systemu przetwarzane są dane encji, ale tylko i wyłącznie tych encji, które posiadają wymagane komponenty przez dany system. Systemy pozwalają również uprościć przebieg aktualizacji gry, ponieważ pętla gry nie musi zajmować się modyfikacją danych jednostek. Cała praca związana z obsługą obiektów przekazywana jest do systemów. Framework intense usprawnia pracę z systemami dzieląc je na następujące typy:

  • update – typ domyślny dla tworzonych systemów. Systemy tego typu powinny odpowiadać za fizykę i częste obliczenia w grze, ponieważ są wykonywane w pętli aktualizacji fizyki.
  • render – systemy wywoływane w standardowej pętli gry, służące aktualizacji tego co widzi użytkownik na ekranie. Stosowane głównie w przypadku rysowania elementów na stronie. Mogą również aktualizować stan jednostek w oparciu o przekazaną deltę czasu a także odbierać od użytkownika komunikaty o aktywnych klawiszach.
  • net – systemy wykonywane w osobnej pętli gry o stałym interwale przebiegu, aby zachować determinizm częstotliwości wysyłanych informacji na serwer. Służą głównie do wysyłania wiadomości, mogą jednak również wykonywać zadania, które wymagają niezmiennego przebiegu pętli, niezależnego od mocy obliczeniowej komputera.

Przykładowe utworzenie systemu wygląda następująco:

intense.system("testSystem", { 
	components: ["test"], 
	init: function (data) { 
		this.data = data; 
	}, 
	update: function () { 
		this.metoda1(); 
	}, 
	metoda1: function () { 
		return this.entities.size(); 
	} 
});

Powyższy przykład tworzy system o nazwie testSystem, który operuje jedynie na encjach posiadających komponent test i posiada opcjonalną metodę metoda1. Aby system mógł zostać utworzony musi posiadać nazwę, natomiast w obiekcie konfiguracyjnym musi ostać zdefiniowana metoda update, ponieważ metoda ta jest uruchamiana dla każdego systemu w głównej pętli gry. Oprócz metody update, obiekt konfiguracyjny systemu posiada następujące elementy:

  • init – funkcja wywoływana tylko jeden raz w momencie tworzenia instancji systemu.
  • manual – właściwość definiująca czy obiekt systemu ma zostać utworzony automatycznie w trakcie uruchamiania pętli gry, czy też manualnie przez programistę. Domyślnie wszystkie systemy są tworzone automatycznie w momencie inicjalizacji frameworka.
  • components – tablica komponentów systemu. Na podstawie zawartych w niej komponentów tworzony jest zbiór encji, który następnie zostaje przypisany do właściwości systemu pod nazwą entities.
  • type – wskazuje typ systemu. Domyślnym typem jest „update”.
  • priority – wartość numeryczna określająca priorytet systemu. Systemy o najniższej wartości priorytetu zostaną uruchomione w pętli gry jako pierwsze ze względu na ich domyślne sortowanie.

Jeżeli oprócz powyższych elementów w obiekcie konfiguracyjnym pojawią sie także inne, zdefiniowane przez programistę elementy, to w zależności od ich typu zostaną przyporządkowane systemowi. Właściwości staną się atrybutami instancji systemu, natomiast metody trafią do jego prototypu dzięki czemu będą współdzielone między wieloma instancjami.

Od wartości atrybutu components zależy na jakich encjach operuje dany system. Systemy mogłyby za każdym razem iterować po kolekcji wszystkich encji i szukać tych, które spełniają założenia co do wymaganych komponentów. Jest to jednak rozwiązanie, które dodaje niepotrzebny narzut obliczeniowy i spowalnia pracę systemów. Framework Ash tworzy w tym celu dodatkową strukturę nazwaną węzłem, która stanowi połączenie niezbędnych do operowania komponentów. Systemy frameworka intense tworzą w swoim wnętrzu zbiór encji (EntitySet) o podanych komponentach, do którego można się odnieść jak do pola obiektu. W przypadku kilku systemów, które operują na tych samych encjach, mógłby powstać problem odnoszenia się do encji, która została wcześniej usunięta. Innym problemem mogłoby być utworzenie nowych encji w trakcie działania programu, spełniających warunek co do komponentów. Zbiór encji rozwiązuje ten problem poprzez rejestrację wymienionych wyżej zdarzeń i aktualizuje się automatycznie. Gwarantuje to, że wszystkie systemy będą działały na aktualnym zbiorze encji spełniającym wymogi komponentowe. Za systemy odpowiada manager systemów, który trzyma instancje systemów w kolejkach w zależności od typu i sortuje je gdy zostaną dodane nowe. Komunikacja ze wszystkimi systemami ze strony pętli gry odbywa się poprzez wywołanie odpowiedniej metody w zależności od typu systemu: dla systemów typu „update” jest to updateSystems, dla systemów typu „render” renderSystems a dla systemów typu „net” będzie to netUpdateSystems. Lista pozostałych metod managera systemów:

  • sortSystemQueue – sortuje instancje systemów w kolejkach rosnąco lub malejąco.
  • prepareSystems – przygotowuje systemy do pracy w głównej pętli poprzez utworzenie ich instancji, dodanie do kolejek i sortowanie.
  • addToQueue – dodaje system do kolejki.
  • inQueue – sprawdza czy system znajduje się w określonej bądź we wszystkich kolejkach.
  • dequeueSystem – usuwa system z kolejki.
  • dequeueSystems – usuwa wszystkie systemy z kolejek.
  • getSystem – zwraca konstruktor systemu.
  • getSystemList – zwraca listę dostępnych systemów.
  • getSystems – zwraca systemy w postaci słownika.
  • getObjectsCount – zwraca liczbę utworzonych obiektów systemów.
  • getObjects – zwraca listę utworzonych obiektów systemów systemów.
  • removeSystem – usuwa wybrany system.
  • removeSystems – usuwa wszystkie systemy.
  • createSystem – tworzy nowy system.
  • isSystem – sprawdza czy przekazany obiekt jest systemem.

Podsumowanie

Przedstawiłem w tym poście ostatni z ważnych elementów frameworka intense czyli systemy. Na chwilę obecną muszę odłożyć kolejne posty o intense.js na przyszłość, ponieważ projekt VRath musi wreszcie zacząć nabierać kształtów. Ale do intense.js z pewnością powrócę!