Blog (35)
Komentarze (574)
Recenzje (0)
@biomenNotatki programisty: integracja edytora z rdzeniem aplikacji SFML/Box2D

Notatki programisty: integracja edytora z rdzeniem aplikacji SFML/Box2D

Słowem wstępu: jest to już piętnasty wpis z serii „Notatki programisty”. Przez ostatnie czternaście wpisów kod rozrósł się do dość pokaźnych rozmiarów. Według SourceMonitora kod zbliża się do granicy 4 tysięcy linii kodu, czy to dużo czy mało. Trudno mi powiedzieć. Mimo wszystko: uznałem że jest to doskonały moment do publikacji całego prezentowanego projektu w ramach artykułów. Przy prezentowaniu kodu, niestety, nie obyło się bez drobnych potknięć. Opublikowany kod posiada naniesione poprawki do błędów wykrytych przy integracji kodu. Na serwerze znajdują się dwie gałęzie w repozytorium. W niniejszym wpisie wykorzystamy kod zawarty w gałęzi: Release1 i właśnie to jej kod będziemy rozwijać w następnych tekstach. Link.

Budujemy mosty dla Pana starosty

Dotychczas w aplikacji nie posiadaliśmy żadnego pomostu między naszym prototypem edytora a faktycznym rdzeniem aplikacji w którym odbywała się symulacja świata. Pierwszą cegiełką mającą na celu zbudowanie odpowiedniego połączenia będzie dodanie algorytmu tworzenia świata na podstawie obiektu GameMap. Jak wiemy z poprzednich wpisów, w naszym demie świat jest tworzony przy pomocy specjalnego modułu MapBuilder przez co to właśnie jego w pierwszej kolejności musimy zmodyfikować. Do klasy dodamy nową klasę FromMap która będzie wyglądać następująco:


struct FromMap : public Base
{
    FromMap(b2World* world, GameMap& map);

    void createArea(
            std::vector<std::shared_ptr<IdentifiedBody>>& list,
            Assets::Resources& resources);

    void createDynamicBodies(
            std::vector<std::shared_ptr<MovableBody>>& list,
            Assets::Resources& resources);

    GameMap* m_map;

protected:
    struct FactoryMovable
    {
        struct Base
        {
            virtual ~Base() {}
            virtual MovableBody* create(
                    b2World* world,
                    Assets::Resources& resources,
                    GameMap::Item* item) = 0;
        };

        struct asPlayer : public Base
        {
            MovableBody* create(
                    b2World *world,
                    Assets::Resources& resources,
                    GameMap::Item* item);
        };

        struct asEnemy : public Base
        {
            MovableBody* create(
                            b2World *world,
                            Assets::Resources &resources,
                            GameMap::Item* item);
        };
    };

    struct FactoryStatic
    {
        struct Base
        {
            virtual ~Base() {}
            virtual IdentifiedBody* create(
                    b2World* world,
                    Assets::Resources& resources,
                    GameMap::Item* item) = 0;
        };

        struct asMapFlor : public Base
        {
            IdentifiedBody* create(
                    b2World *world,
                    Assets::Resources& resources,
                    GameMap::Item* item);
        };

        struct asMapWall : public Base
        {
            IdentifiedBody* create(
                    b2World *world,
                    Assets::Resources& resources,
                    GameMap::Item* item);
        };
    };

    void initFactoryMovable();
    std::map<std::string, std::shared_ptr<FactoryMovable::Base>> m_factoryMovable;

    void initFactoryStatic();
    std::map<std::string, std::shared_ptr<FactoryStatic::Base>> m_factoryStatic;
};

Nowa klasa będzie służyć do tworzenia obiektów GameWorld na podstawie naszego modelu mapy. Co warto zauważyć: funkcje odpowiedzialne za tworzenie obiektu mapy na podstawie identyfikatora wydzieliliśmy do pomniejszych klasy dzięki czemu wyizolowaliśmy je od reszty kodu. Następnie je zmapujemy z identyfikatorem z klasy GameObjRegister. Podobny mechanizm zastosowaliśmy w naszym edytorze przez co warto zapoznać się z tym patentem na programowanie gdyż dosyć często będzie nam pomocny. Przykłady mapowania algorytmów były omawiane wcześniej stąd powyższy widok nie powinien nas przerażać. Czas na implementacje nowego budowniczego i jego pomocników:


/* From Map Model */
MapBuilder::FromMap::FromMap(b2World* world, GameMap& map)
    : Base(world),
      m_map(&map)
{
    this->initFactoryMovable();
    this->initFactoryStatic();
}

void MapBuilder::FromMap::initFactoryMovable()
{
    try
    {

    m_factoryMovable[GameObjRegister::get()->list.at(GameObjRegister::Index::Enemy)]
            = std::shared_ptr<FactoryMovable::Base>(new FactoryMovable::asEnemy());

    m_factoryMovable[GameObjRegister::get()->list.at(GameObjRegister::Index::Player)]
            = std::shared_ptr<FactoryMovable::Base>(new FactoryMovable::asPlayer());

    } catch (const std::out_of_range& ex){
        std::cout << __func__ << " " << ex.what() << "\n";
    }
}

void MapBuilder::FromMap::initFactoryStatic()
{
    try
    {

    m_factoryStatic[GameObjRegister::get()->list.at(GameObjRegister::Index::MapFlor)]
            = std::shared_ptr<FactoryStatic::Base>(new FactoryStatic::asMapFlor());

    m_factoryStatic[GameObjRegister::get()->list.at(GameObjRegister::Index::MapWall)]
            = std::shared_ptr<FactoryStatic::Base>(new FactoryStatic::asMapWall());

    } catch (const std::out_of_range& ex) {
        std::cout << __func__ << " " << ex.what() << "\n";
    }
}

void MapBuilder::FromMap::createArea(
        std::vector<std::shared_ptr<IdentifiedBody>>& list,
        Assets::Resources& resources)
{

    for(int i = 0; i < m_map->height; ++i)
    {
        for(int j = 0; j < m_map->width; ++j)
        {
            GameMap::Item* item = &m_map->array[ i ][ j ];
            MapBuilder::FromMap::FactoryStatic::Base* ptr = nullptr;
            try
            {
                ptr = m_factoryStatic.at(item->ID).get();

            } catch (const std::out_of_range& ex){
                ptr = nullptr;
            }

            if(ptr){

                list.push_back(
                        std::shared_ptr<IdentifiedBody>(
                            ptr->create(m_world, resources, item)));
            }
        }
    }
}

void MapBuilder::FromMap::createDynamicBodies(
        std::vector<std::shared_ptr<MovableBody>>& list,
        Assets::Resources& resources)
{
    for(int i = 0; i < m_map->height; ++i)
    {
        for(int j = 0; j < m_map->width; ++j)
        {
            GameMap::Item* item = &m_map->array[ i ][ j ];
            MapBuilder::FromMap::FactoryMovable::Base* ptr = nullptr;
            try
            {
                ptr = m_factoryMovable.at(item->ID).get();

            } catch (const std::out_of_range& ex){

                ptr = nullptr;
            }

            if(ptr){

                list.push_back(
                        std::shared_ptr<MovableBody>(
                            ptr->create(m_world, resources, item)));

            }
        }
    }
}

/* Algorytmy tworzenia */
MovableBody*
MapBuilder::FromMap::FactoryMovable::
asPlayer::create(b2World *world, Assets::Resources& resources, GameMap::Item* item)
{
    sf::Vector2f oneFrameSize(104.f, 150.f);
    sf::Vector2f playerBodySize(75.f, 100.f);

    sf::RectangleShape* shapeForAnimation = new sf::RectangleShape();
    shapeForAnimation->setSize(playerBodySize);
    shapeForAnimation->setOrigin(
            shapeForAnimation->getSize().x/2,
            shapeForAnimation->getSize().y/2);
    shapeForAnimation->setPosition(sf::Vector2f(250, 250.f));
    shapeForAnimation->setTexture(
                resources.getTexture(
                    Assets::Textures::Player));

    b2Body* myBody = BoxCreators::createDynamicBody(world, playerBodySize.x,playerBodySize.y);
    Animator* animatorPtr = new Animator(shapeForAnimation,oneFrameSize);

    DrawableBodyAnimated* drawablePtr = new DrawableBodyAnimated(myBody, animatorPtr);

    MovableBody* playerItem = new MovableBody(drawablePtr, BodyUserData::Type::Player);

    playerItem->getRenderBody()->setPosition(item->x, item->y);
    playerItem->getMover()->setJumpForce(4.5f);
    playerItem->getMover()->setMaxSpeed(3.5);

    return playerItem;
}

MovableBody*
MapBuilder::FromMap::FactoryMovable::
asEnemy::create(b2World *world, Assets::Resources &resources, GameMap::Item* item)
{
    b2Body* body = BoxCreators::createDynamicBody(world, 90, 60);
    DrawableBodyGenerated* drawable = new DrawableBodyGenerated(body);

    MovableBody* enemyItemB = new MovableBody(drawable,BodyUserData::Type::Enemy);

    enemyItemB->getRenderBody()->setTexture(*resources.getTexture(Assets::Textures::Enemy));
    enemyItemB->getRenderBody()->setPosition(item->x, item->y);
    enemyItemB->getMover()->setJumpForce(3.f);
    enemyItemB->getMover()->setMaxSpeed(2.5f);

    return enemyItemB;
}

IdentifiedBody*
MapBuilder::FromMap::FactoryStatic::
asMapWall::create(
                b2World *world,
                Assets::Resources &resources,
                GameMap::Item* item)
{
    b2Body* boxBody =
            BoxCreators::createStaticBody(
                world, item->width, item->height);

    IdentifiedBody* newItem =
            new IdentifiedBody(
                new DrawableBodyGenerated(boxBody),
                BodyUserData::Type::Wall);

    newItem->getRenderBody()->setPosition(item->x, item->y);
    newItem->getRenderBody()->setTexture(
                *resources.getTexture(Assets::Textures::Wall));

    return newItem;
}

IdentifiedBody*
MapBuilder::FromMap::FactoryStatic::
asMapFlor::create(
                b2World *world,
                Assets::Resources &resources,
                GameMap::Item* item)
{
    b2Body* boxBody =
            BoxCreators::createStaticBody(
                world, item->height, item->width);

    IdentifiedBody* newItem =
            new IdentifiedBody(
                new DrawableBodyGenerated(boxBody),
                BodyUserData::Type::Map);

    newItem->getRenderBody()->setPosition(item->x, item->y);
    newItem->getRenderBody()->setTexture(
                *resources.getTexture(Assets::Textures::Map));

    return newItem;
}

Okej, czyli ekipę budowlaną mamy z głowy. Teraz konieczne jest aby nasz pracownik biura budowlanego potrafił obsłużyć klienta który przychodzi do niego z planem budowy. Do klasy GameWorld dodamy nowy konstruktor który będzie prezentować się następująco:


/* Nowy konstruktor */
GameWorld::GameWorld(Assets::Resources &resources, GameMap &map):
    m_boxWorld(BoxCreators::createWorld()),
    m_builder(new MapBuilder::FromMap(m_boxWorld.get(), map))
{
    m_boxWorld.get()->SetContactListener(&m_contactDetector);

    m_builder->createArea(listIdentifiedBodies, resources);
    m_builder->createDynamicBodies(listMovableBodies, resources);
}

W tym momencie mamy wszystko czego nam potrzeba do zastosowania naszego pomostu. Dodajmy do klasy CoreApps która przechowuje klasy startowe naszych przykładów, nową funkcje core() która naszym przykładem:


void CoreApps::core(GameMap& map)
{
    sf::RenderWindow* windowItem =
                        new sf::RenderWindow(
                            sf::VideoMode(800, 600, 32),
                            std::string("SFML/Box2D - tech demo"),
                            sf::Style::Default);

    std::shared_ptr<sf::RenderWindow> window(windowItem);
    std::shared_ptr<ControlKeys>playerControl(new ControlKeys());

    FpsStabilizer stabilizer(60);

    Assets::Resources resources("data.zip");
    GameWorld world(resources, map);

    MovableBody* playerPtr = world.getPlayer();
    ContactDetector* contactDetector = world.getContactDetector();

    while(window->isOpen())
    {
        /* OTHER */
        stabilizer.work();

        for(auto& item : world.listMovableBodies){
            item->getRenderBody()->setColor(sf::Color::White);
        }

        /* EVENTS */
        sf::Event myEvent;
        while(window->pollEvent(myEvent))
        {
            if(myEvent.type == sf::Event::Closed){
                window->close();
            }
        }

        if(sf::Keyboard::isKeyPressed(playerControl->MOVE_JUMP)) {
            playerPtr->getMover()->move(
                        BodyMover::Direction::Jump);
        }

        if(sf::Keyboard::isKeyPressed(playerControl->MOVE_RIGHT)) {
            playerPtr->getMover()->move(
                        BodyMover::Direction::Right);

            DrawableBodyAnimated* ptr =
                    (DrawableBodyAnimated*)playerPtr->getRenderBody();

            ptr->getAnimator()->setAnimation(0);

        }

        if(sf::Keyboard::isKeyPressed(playerControl->MOVE_LEFT)) {
            playerPtr->getMover()->move(
                        BodyMover::Direction::Left);

            DrawableBodyAnimated* ptr =
                    (DrawableBodyAnimated*)playerPtr->getRenderBody();

            ptr->getAnimator()->setAnimation(1);
        }

        if(sf::Keyboard::isKeyPressed(sf::Keyboard::Escape)){
            window->close();
        }

        /* BOX2D */
        world.prepareWorld();

        for(auto& enemyItem : world.listMovableBodies)
        {
            break;
        }

        const bool contactCondition =
                (!contactDetector->isContactListIsEmpty()) &&
                    contactDetector->isContactListContains(
                        ContactDetector::Contact::Type::PlayerTouchEnemy);

        if(contactCondition)
        {
            std::vector<ContactDetector::Contact::Info> enemyContacts =
                    contactDetector->getContactList(
                        ContactDetector::Contact::Type::PlayerTouchEnemy);

            if(!enemyContacts.empty()){

                for(auto& contact : enemyContacts)
                {
                    break;
                }
            }
        }

        /* RENDER */
        window->clear(sf::Color::Black);

        for(auto& item : world.listMovableBodies){
            item->getRenderBody()->update();
            item->getRenderBody()->render(*window);
        }

        for(auto& item : world.listIdentifiedBodies){
            item->getRenderBody()->render(*window);
        }

        window->display();
    }
}

Jeszcze wywołanie z funkcji Editor::run() naszego rdzenia pod klawiszem:


if(sf::Keyboard::isKeyPressed(sf::Keyboard::F2))
{
    CoreApps::core(*map.get());
 }

I viola. Efekt prezentuje się jak poniżej:

615502

Co prawda moglibyśmy przeprowadzić głębszą integrację edytora z rdzeniem poprzez przekazanie wskaźnika do okna aby zarówno edytor jak i rdzeń pracował w jednym oknie aczkolwiek uznałem to za zbędną kosmetykę.

Jak zawsze dzięki za uwagę!

Wybrane dla Ciebie
Komentarze (4)