Tworzenie gry w C# z użyciem silnika Ogre - cz.1

09.08.2010 - Mateusz Osowski
TrudnośćTrudność

Klasa Level

Rozpoczniemy teraz tworzenie klasy odpowiedzialnej za planszę, na które rozgrywać się będzie akcja. Utwórz plik Level.cs i zaimportuj w nim Mogre i MogreNewt. Używanie siatki, która jest wyświetlana jako siatki kolizji często nie jest dobrym pomysłem:

  • Zazwyczaj siatka graficzna posiada za dużą szczegółowość, przez co w roli siatki kolizyjnej może mieć zły wpływ na wydajność
  • Często nie chcemy, aby gracz mógł gdzieś się dostać, lub wspiąć - wówczas możemy zastosować niewidzialną ścianę istniejącą jedynie w siatce kolizyjnej
  • Z siatki kolizyjnej można usunąć drobne elementy, o które postać gracza mogłaby przypadkowo zahaczać.

Potrzebujemy więc osobnego obiektu dla każdego z tych dwóch typów siatek.

1
2
3
4
5
6
7
class Level
{
    Entity GraphicsEntity;
    SceneNode GraphicsNode;       
 
    Entity CollisionEntity;
    SceneNode CollisionNode;    
Level.cs


Klasa SceneNode to jedna z podstawowych klas silnika Ogre. Reprezentuje ona węzeł sceny. Scena bowiem przechowywana jest w silniku w formie drzewa, dzięki czemu możliwe jest tworzenie hierarchii połączonych ze sobą obiektów, co jest bardzo przydatne - można chociażby przypiąć węzeł miecza do ręki postaci jako dziecko, co zwalnia nas z ręcznego ustalania pozycji miecza. Sama klasa SceneNode nie reprezentuje jednak niczego widzialnego. Dopiero Entity nadaje kształt obiektowi. Klasa ta reprezentuje kopię siatki i może być przypięta do SceneNode, podobnie jak wiele innych obiektów graficznych.


Przykładowa budowa sceny

Aby siatka kolizyjna mogła funkcjonować jak należy, musi być powiązana z reprezentacją w silniku Newton. Służy do tego klasa Body.

1
        Body Body;
Level.cs


Pierwszą metodą będzie utworzenie graficznej reprezentacji planszy. Metoda jest bardzo prosta. Najpierw tworzymy instancję SceneNode jako dziecko korzenia sceny, następnie tworzymy instancję Entity siatki z pliku o nazwie podanej w argumencie, a na koniec łączymy ją z węzłem sceny i wyłączamy rzucanie cieni.

1
2
3
4
5
6
7
8
9
10
11
12
    public void SetGraphicsMesh(String meshFile)
    {
                // Tworzymy węzeł
        GraphicsNode = 
            Engine.Singleton.SceneManager.RootSceneNode.CreateChildSceneNode();
                // Tworzymy obiekt graficzny
        GraphicsEntity = Engine.Singleton.SceneManager.CreateEntity(meshFile);            
                // Wiążemy obiekt graficzny z węzłem
        GraphicsNode.AttachObject(GraphicsEntity);
                // Wyłączamy rzucanie cieni
        GraphicsEntity.CastShadows = false;
    }
Level.cs


Druga metoda odpowiadać będzie za ustawianie reprezentacji fizycznej planszy. Podobnie jak w przypadku pierwszej metody, tworzymy węzeł i instancję siatki:

1
2
3
4
5
6
7
8
9
    public void SetCollisionMesh(String meshFile)
    {
        CollisionNode = 
            Engine.Singleton.SceneManager.RootSceneNode.CreateChildSceneNode();
        CollisionEntity = Engine.Singleton.SceneManager.CreateEntity(meshFile);
        CollisionNode.AttachObject(CollisionEntity);
 
        // Nie chcemy aby siatka kolizyjna była widoczna
        CollisionNode.SetVisible(false);
Level.cs


Teraz musimy wydobyć z utworzonego obiektu informacje o wierzchołkach i wielokątach siatki, a następnie przekazać je Newtonowi. OgreNewt przychodzi nam z pomocą dzięki klasie TreeCollisionSceneParser, która wydobywa potrzebne informacje od obiektów Entity powiązanych z zadanym węzłem i jego dziećmi. W bardzo prosty sposób tworzymy obiekt kolizji, następnie tworzymy ciało Newtonowskie, po czym zwalniamy już niepotrzebny obiekt kolizji.

1
2
3
4
5
6
7
8
9
10
                // Tworzymy parser sceny
        MogreNewt.CollisionPrimitives.TreeCollisionSceneParser collision = 
            new MogreNewt.CollisionPrimitives.TreeCollisionSceneParser(
                Engine.Singleton.NewtonWorld);
                // Wydobywamy informacje o siatce związanej z węzłem
        collision.ParseScene(CollisionNode, true, 1);
                // Tworzymy ciało nadając mu wczytany kształt
        Body = new Body(Engine.Singleton.NewtonWorld, collision);
                // Niszczymy kształt, który już został wykorzystany do utworzenia ciała
        collision.Dispose();
Level.cs


Każde ciało Newtona powinno być związane z węzłem SceneNode, który reprezentuje. W przeciwnym wypadku Newton nie wiedział by, na jak węzeł jego obliczenia powinny wywierać wpływ. Aby dokonać takiego powiązania, wystarczy wywołać metodę:

1
2
3
        Body.AttachNode(CollisionNode);
    }
}
Level.cs


To już cała klasa poziomu. Dodajmy publiczną referencję do obiektu poziomu w klasie Engine, w pliku Engine.cs:

1
    public Level CurrentLevel;
Engine.cs


Abstrakcyjna klasa GameObject

Wbrew niepokojącego określenia - "abstrakcyjna" - klasa GameObject będzie bardzo prosta. Jej przydatność na chwilę obecną będzie znikoma, ponieważ w grze posiadać będziemy tylko jeden typ obiektów. Jednak w późniejszym etapie tworzenia gry uogólnienia tego typu stają się bardzo pomocne. Klasa GameObject mówi tyle, że każdy obiekt w świecie gry powinien się w jakiś sposób aktualizować.
Plik GameObject.cs:

1
2
3
4
abstract class GameObject
{
    public abstract void Update();
}
GameObject.cs


Menadżer obiektów

Klasa ta również nie będzie rozbudowana. Menadżer obiektów, zgodnie z projektem, będzie pamiętać listę wszystkich obiektów gry znajdujących się na planszy. Klasa List zachowuje się podobnie do std::vector znanego z C++. W platformie .NET wszystko jest obiektem, więc musimy utworzyć obiekt listy w konstruktorze menadżera.

1
2
3
4
5
6
7
8
class ObjectManager
{
    List<GameObject> Objects;
 
    public ObjectManager()
    {
        Objects = new List<GameObject>();
    }
ObjectManager.cs


Ponieważ lista obiektów nie jest polem publicznym, musimy stworzyć metodę, która pozwoli dodawać nowe obiekty do gry:

1
2
3
4
    public void Add(GameObject gameObject)
    {
        Objects.Add(gameObject);
    }
ObjectManager.cs


Menadżer obiektów pozwoli w łatwy sposób zaktualizować stan wszystkich obiektów w grze dzięki wykorzystaniu abstrakcyjnej klasy.

1
2
3
4
5
6
    public void Update()
    {
        foreach (GameObject gameObject in Objects)
            gameObject.Update();
    }
}
ObjectManager.cs


5
Twoja ocena: Brak Ocena: 5 (8 ocen)

Copyright © 2008-2010 Wrocławski Portal Informatyczny

design: rafalpolito.com