LibGDX i Ashley. Wstęp.

W dobrze tworzonej grze, ważny jest podział na moduły i odpowiednia koncepcja zarządzania obiektami. W libGDX możemy wykorzystać do tego celu bibliotekę ashley, dzięki której możemy podzielić funkcję na niezależne systemy. Przy dobrze zaprojektowanym układzie mamy możliwość wielokrotnego używania systemów i komponentów w innych grach lub widokach co znacznie przyśpiesza proces developingu.



Posiłkując się Wiki z strony biblioteki, przedstawię koncepcję frameworka:


Mamy tu następujące obiekty:

  • Entity: jest to bazowy obiekt, do niego wrzucamy komponenty które definiują jego znaczenie. Np. możemy z niego zrobić jednostkę, pochodnię, samochód, przeszkodę itd.
  • Component:Poprzez komponent definiujemy jak dany obiekt będzie przedstawiany w naszej grze. Możemy utworzyć różne komponenty przechowujące informacje na temat obiektu np. jego pozycję w świecie gry, podczepić AI, zrobić go widzialnym. 
  • ComponentMapper: Jest to swego rodzaju wyciągarka danego typu komponentu z obiektu Entity. Np. Chcemy narysować dany obiekt w świecie, więc z danego Entity wyciągamy obiekt implementujący Component danego typu. Z tego komponentu wyciągamy interesujące nas dane i je wykorzystujemy ale także możemy je zaktualizować i nasz komponent będzie je dalej przechowywał.
  • Family: Wyszukiwarka, która zwraca nam listę Entity posiadających dane komponenty.
  • Engine: Silnik naszej aplikacji. Pozwala na dodawanie Entity i EntitySystemów.
  • EntitySystem: Tutaj definiujemy logikę naszej gry.
  • EntityListener: Listener ten służy do nasłuchiwania zdarzeń. Np. Dodania/Usunięcia Entity z silnika.
Jak pisać aplikację przy użyciu ashley?

Tutaj proponowałbym ogólny podział na Stage, a dla każdego Stage osobny Engine.
Pod nazwą Stage mam na myśli jakiś system widoku, który przedstawia np. Menu Główne gry, widok przygody, widok walki wraz z GUI i innymi rzeczami które można spokojnie umieścić w Engine jako Entity i odpowiednie systemu. Proponowałbym zawierać nawet całą logikę w systemach, a w samym Stage tylko budować odpowiedni szkielet Engine wraz z potrzebnymi systemami i komponentami.

Przykład takiego Stage:

*Uwaga nie będę prezentował w tym przypadku systemu do zarządzania Stagami(czasem zwanymi Scenamia), sam używam własnego prostego silnika scen do podmieniania aktualnie renderowanej sceny. Ostatecznie nic nie stoi na przeszkodzie żeby różne Engine mogły służyć za różne sceny, lub stosowanie fabryk do inicjalizacji konkretnych scen.

Tworzymy klasę factory np. SimpleSceneEntityFactory, który posiada metodę createScene(Engine engine), przyjmującą jako argument Engine do którego będzie dodawał wszystkie potrzebne Entity i ich komponenty. Na przykład:

public void createScene(Engine engine){

Entity image = new Entity();
image .addComponent(new BodyComponent(0,0, 128, 256));
image .addComponent(new RenderComponent("myImage.png"));
engine.addEntity(image);


Entity camera = new Entity();
camera .addComponent(new BodyComponent(0,0, 1, 1));
camera .addComponent(new CameraComponent(new OrthographicsCamera(screenWidth, screenHeight));
camera .addComponent(new TouchtableComponent().setGlobalTouch(true).setSpeed(1f));
engine.addEntity(camera );

}

Jak widzimy tworzenie nowych obiektów jest przyjemne, zwłaszcza jeśli porównać to do dziedziczenia i polega na utworzeniu pojemnika na komponenty czyli obiektu Entity a następnie wrzuceniem do niego tego co potrzeba by pełnił swoją rolę. Dodajemy BodyComponent jeśli chcemy by obiekt był widoczny w przestrzeni, RenderComponent jeśli chcemy by był rysowany. CameraComponet jeśli chcemy żeby był kamerą(lub miał kamerę). TouchtableComponent jeśli chcemy aby reagował na dotyk/mysz.
Proszę pamiętać że podane przeze mnie komponenty nie wchodzą w skład biblioteki i trzeba je napisać samemu. Istotne jest również to że znajdować powinny się tam same dane.

Mając już nasze Entity w silniku powinniśmy je teraz obsłużyć. W tym celu po wywołaniu metody createScene() w naszej fabryce warto dodać interesujące nas systemy do silnika:

create(){
SpriteBatch batch = new SpriteBatch();

Engine engine = new Engine();
SimpleSceneEntityFactory factory = new SimpleSceneEntityFactory();
factory.createScene(engine);

engine.addEntitySystem(new PhysicSystem());
engine.addEntitySystem(new CameraControllSystem(batch));
engine.addEntitySystem(new TouchSystem());
engine.addEntitySystem(new RenderSystem(batch));

}

Teraz wystarczy wywoływać aktualizacje engine w metodzie update() i cieszyć się system komponentowym:)

render(){
engine.update(deltaTime);
}

W następnej części pokażę jak napisać komponenty i system do wyświetlania Spritów.