Blog (35)
Komentarze (574)
Recenzje (0)

Notatki programisty: Modelowanie świata i modyfikacje edytora dema technologicznego SFML/Box2D

@biomenNotatki programisty: Modelowanie świata i modyfikacje edytora dema technologicznego SFML/Box2D09.02.2017 09:43

W poprzednim wpisie zaprezentowane zostały podstawy funkcjonowania edytora siatkowego do naszego dema. W niniejszym wpisie wprowadzimy do niego kilka kluczowych zmian. Co więcej przygotujemy rdzeń naszej aplikacji pod przyszłą obsługe modelu mapy. Zapraszam do lektury.

Jeśli coś to tam ktoś, jeśli ble to mi się chce

W poprzednim wpisie została przedstawiona prosta klasa do rysowania mapy. Niestety, główna funkcja rysująca opierała się o szereg warunków które decydowały o tym jak będzie wyglądać rysowany obiekt. Rozwiązanie to można uznać za poprawne w przypadku gdy do rysowania będziemy mieli mniej niż 10 elementów. Wraz z wzrostem warunków, czytelność naszego kodu zaczyna diametralnie spadać. Mając wszystkie te ograniczenia i niedogodności na uwadze chciałbym zaproponować następującą modyfikację klasy Graphics z RenderEngine:


struct Graphics : public Base
{
    Graphics();
    ~Graphics();
    void render(InfoForEngine &data);

    struct Painter
    {
        struct Base {
            void virtual draw(
                    GameMap::Item* item,
                    sf::RenderWindow* window) = 0;
        };

        struct MapFlor : public Base {
            void draw(
                    GameMap::Item* item,
                    sf::RenderWindow* window);
        };
    
        struct MapWall : public Base {
            void draw(
                    GameMap::Item* item,
                    sf::RenderWindow* window);
        };
        
        struct MapRotatingPlatform : public Base {
            void draw(
                    GameMap::Item* item,
                    sf::RenderWindow* window);
        };
        
        struct MapInvisableWall : public Base {
            void draw(
                    GameMap::Item* item,
                    sf::RenderWindow* window);
        };

        struct Player : public Base {
            void draw(
                    GameMap::Item* item,
                    sf::RenderWindow* window);
        };
    };
private:
    std::map<
        std::string,
        std::shared_ptr<Painter::Base>> m_painters;
};

Okej, co tu się stało? Plan był taki aby algorytm rysowania każdego elementu przenieść do nowej klasy dzięki czemu każda strategia rysowania jest hermetyzowana w obrębie tylko jednego klasy. Jako że algorytm jakiego będziemy używać zależy od tego jakie ID ma obiekt mapy, wszystkie obiekty trzymamy w std::map. Po prostu mapujemy ID itemu mapy z algorytmem rysowania. Po wprowadzeniu tej modyfikacji nasz konstruktor i klasa rysująca, z klasy Graphics, będą wyglądać jak poniżej:


RenderNT::RenderEngine::Graphics::Graphics()
{

    m_painters[GameObjRegister::get()->list.at(
                GameObjRegister::Index::MapFlor)] =
                    std::shared_ptr<Graphics::Painter::Base>(
                        new Graphics::Painter::MapFlor());

    m_painters[GameObjRegister::get()->list.at(
                GameObjRegister::Index::MapWall)] =
                    std::shared_ptr<Graphics::Painter::Base>(
                        new Graphics::Painter::MapWall());

    m_painters[GameObjRegister::get()->list.at(
                GameObjRegister::Index::MapRotatedPlatform)] =
                    std::shared_ptr<Graphics::Painter::Base>(
                        new Graphics::Painter::MapRotatingPlatform());

    m_painters[GameObjRegister::get()->list.at(
                GameObjRegister::Index::MapInvisableWall)] =
                    std::shared_ptr<Graphics::Painter::Base>(
                        new Graphics::Painter::MapInvisableWall());

    m_painters[GameObjRegister::get()->list.at(
                GameObjRegister::Index::Player)] =
                    std::shared_ptr<Graphics::Painter::Base>(
                        new Graphics::Painter::Player());
}

void RenderNT::RenderEngine::Graphics::render(InfoForEngine &data)
{
    int startI = 0;
    int finishI = data.map->height;

    int startJ = 0;
    int finishJ = data.map->width;

    bool widthCondition = (
                data.renderSurface.WidthX.x >= 0 &&
                data.renderSurface.WidthX.y <= data.map->width);

    bool heightCondition = (
                data.renderSurface.HeightY.x >= 0 &&
                data.renderSurface.HeightY.y <= data.map->height);

    if(widthCondition && heightCondition)
    {
        startI = data.renderSurface.HeightY.x;
        finishI = data.renderSurface.HeightY.y;

        startJ = data.renderSurface.WidthX.x;
        finishJ = data.renderSurface.WidthX.y;
    }

    for(int i = startI; i < finishI; ++i)
    {
        for(int j = startJ; j < finishJ; ++j)
        {
            GameMap::Item* item = &data.map->array[ i ][ j ];

            try
            {
                Graphics::Painter::Base* painter = this->m_painters.at(item->ID).get();
                painter->draw(item, data.window);

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

            }
        }
    }
}

Jak widzimy nasza klasa do rysowania mapy została poważnie odchudzona. Oczywiście zdajemy sobie sprawę z tego że nie zawsze będziemy posiadali kod który obsługuje wszystkie ID obiektów mapy przez co jako zabezpieczenia używamy bloku try-catch by w trakcie działania aplikacja się nam ona nie wyłożyła. Poniżej zostały zaprezentowane implementacje funkcji rysujących:


void
RenderNT::RenderEngine::Graphics::Painter::
MapFlor::draw(
        GameMap::Item* item,
        sf::RenderWindow* window)
{
    sf::Vector2f position(item->x, item->y);
    sf::RectangleShape shape(sf::Vector2f(0,0));
    shape.setSize(sf::Vector2f(item->width, item->height));
    shape.setPosition(position);
    shape.setFillColor(sf::Color::Red);
    window->draw(shape);
}

void
RenderNT::RenderEngine::Graphics::Painter::
MapWall::draw(
        GameMap::Item* item,
        sf::RenderWindow* window)
{
    sf::Vector2f position(item->x, item->y);
    sf::RectangleShape shape(sf::Vector2f(0,0));
    shape.setSize(sf::Vector2f(item->width, item->height));
    shape.setPosition(position);
    shape.setFillColor(sf::Color::Yellow);
    window->draw(shape);
}

void
RenderNT::RenderEngine::Graphics::Painter::
MapRotatingPlatform::draw(
        GameMap::Item* item,
        sf::RenderWindow* window)
{
    sf::Vector2f position(item->x, item->y);
    sf::RectangleShape shape(sf::Vector2f(0,0));
    shape.setSize(sf::Vector2f(item->width, item->height));
    shape.setPosition(position);
    shape.setFillColor(sf::Color::Cyan);
    window->draw(shape);
}

void
RenderNT::RenderEngine::Graphics::Painter::
MapInvisableWall::draw(
        GameMap::Item* item, sf::RenderWindow* window)
{
    sf::Vector2f position(item->x, item->y);
    sf::RectangleShape shape(sf::Vector2f(0,0));
    shape.setSize(sf::Vector2f(item->width, item->height));
    shape.setPosition(position);
    shape.setFillColor(sf::Color::Blue);
    window->draw(shape);
}

void
RenderNT::RenderEngine::Graphics::Painter::
Player::draw(
        GameMap::Item* item,
        sf::RenderWindow* window)
{
    sf::Vector2f position(item->x, item->y);
    sf::RectangleShape shape(sf::Vector2f(0,0));
    shape.setSize(sf::Vector2f(item->width, item->height));
    shape.setPosition(position);
    shape.setFillColor(sf::Color::Green);
    window->draw(shape);
}

Ktoś mógłby zapytać: „dlaczego tak komplikujemy sobie życie skoro kod się różni przeznaczonym kolorem, nie łatwiej zrobić jakiś sprytny przełącznik? To jak używanie koparki do przesadzania kwiatów”. Odpowiedź w zależności od humoru brzmi: „because we can” lub bardziej sensownie. Mianowicie w przyszłości nie mamy pewności jak będzie wyglądać implementacja funkcji rysujących stąd lepiej zabezpieczyć się na przyszłość, pod rozbudowę. Nasz kod będzie łatwiejszy w modyfikacji a budowa naszej aplikacji bardziej modułowa.

Narzędzia to podstawa

Sytuacja analogiczna co do wyżej przedstawionej, znajduje się w sekcji edytora. Mianowicie gdy tworzymy mapę, mamy do dyspozycji proste menu z wyborem elementu i dla każdego obiektu definiujemy sposób w jaki ma zostać zmodyfikowany element. W przypadku stosowania prymitywnych warunków musielibyśmy pisać całe litanie. Przygotujemy sobie prostą klasę ze sposobami na modyfikację obiektu GameMap::Item. Roboczo będzie ona wyglądać następująco:


struct MapModifier
{
    struct Base
    {
        virtual void change(GameMap::Item* item) = 0;
    };

    struct asEmpty : public Base
    {
        void change(GameMap::Item* itemPtr);
    };

    struct asRotatingPlatform : public Base
    {
        void change(GameMap::Item* itemPtr);
    };

    struct asStandardItem : public Base
    {
        void change(GameMap::Item* itemPtr);
    };

    struct asStandardMapTextureUp : public Base
    {
        void change(GameMap::Item* itemPtr);
    };

    static void init(std::map<
                GameObjRegister::Index,
                std::shared_ptr<MapModifier::Base>>& map);
};

I implementacja:


void
MapModifier::asEmpty::change(
        GameMap::Item* itemPtr)
{

}

void
MapModifier::asRotatingPlatform::change(
        GameMap::Item* itemPtr)
{
    itemPtr->height = 40;
    itemPtr->width = 120;
}

void MapModifier::asStandardItem::change(
        GameMap::Item* itemPtr)
{
    itemPtr->height = 40;
    itemPtr->width = 40;
}

void
MapModifier::asStandardMapTextureUp::change(
        GameMap::Item* itemPtr)
{
    itemPtr->height = 40;
    itemPtr->width = 40;
}

void MapModifier::init(
        std::map<
            GameObjRegister::Index,
            std::shared_ptr<MapModifier::Base>>& map)
{
    map[GameObjRegister::Index::Empty] =
            std::shared_ptr<MapModifier::Base>(
                new MapModifier::asEmpty());

    map[GameObjRegister::Index::MapRotatedPlatform ] =
                    std::shared_ptr<MapModifier::Base>(
                        new MapModifier::asRotatingPlatform());

    map[GameObjRegister::Index::MapFlor] =
                std::shared_ptr<MapModifier::Base>(
                        new MapModifier::asStandardItem());

    map[GameObjRegister::Index::Player] =
                std::shared_ptr<MapModifier::Base>(
                        new MapModifier::asStandardItem());

    map[GameObjRegister::Index::MapWall] =
                std::shared_ptr<MapModifier::Base>(
                        new MapModifier::asStandardItem());

    map[GameObjRegister::Index::MapFlorUp] =
                std::shared_ptr<MapModifier::Base>(
                    new MapModifier::asStandardMapTextureUp());
}

Po wszystkich tych modyfikacjach funkcja startowa naszego edytora będzie wyglądać jak poniżej:


void Editor::run()
{
    int width = 0;
    std::cout << "Get width (X): ";
    std::cin >> width;

    int height = 0;
    std::cout << "Get height(Y): ";
    std::cin >> height;

    std::shared_ptr<GameMap> map(new GameMap(width,height));

    sf::RenderWindow window(
                sf::VideoMode(800,600, 16),
                std::string("hello"),
                sf::Style::Close);

    sf::View camera;
    camera = window.getDefaultView();

    sf::Vector2f startCameraPosition = camera.getCenter();

    const int viewMoveValue = 5;

    RenderNT render(&window);
    render.refresh(camera);

    MouseManager mouseIndex(&window);

    /* Modyfikatory obiektow mapy. */
    std::map<
        GameObjRegister::Index,
        std::shared_ptr<MapModifier::Base>> theModders;

    MapModifier::init(theModders);

    /* Ustawiamy stan poczatkowy na pusty element. */
    GameObjRegister::Index selectedIndex = GameObjRegister::Index::Empty;
    MapModifier::Base* magicPoint = theModders.at(selectedIndex).get();

    while(window.isOpen())
    {
        sf::Event myEvent;
        while(window.pollEvent(myEvent))
        {
            if(myEvent.type == sf::Event::Closed){
                window.close();
            }

            if(sf::Keyboard::isKeyPressed(sf::Keyboard::W))
            {
                Json::Generator dec(map.get());
                QJsonDocument jsonDoc = dec.getJsonDoc();

                const std::string fileName = std::string("hello.json");

                Json::File fileDec;
                fileDec.saveJsonMap(jsonDoc, QString::fromStdString(fileName));

                std::cout << std::string("Saved as: ") + fileName;
            }

            if(sf::Keyboard::isKeyPressed(sf::Keyboard::R))
            {
                const std::string fileName = std::string("hello.json");

                Json::File fileDec;
                QJsonDocument jsonDoc = fileDec.readJsonMap(QString::fromStdString(fileName));

                Json::Generator dec;
                map.reset(dec.getGameMapFromJson(jsonDoc));

                std::cout << std::string("Readed from: ") + fileName;

            }

            if(sf::Keyboard::isKeyPressed(sf::Keyboard::Tab))
            {
                int input = 0;
                for(auto& pair :GameObjRegister::get()->list )
                {
                    std::cout
                        << "No. "
                        << pair.first
                        << " Content: "
                        << pair.second
                        << "\n";
                }
                std::cin >> input;

                try
                {
                    selectedIndex = (GameObjRegister::Index)input;
                    magicPoint = theModders.at(selectedIndex).get();

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

                    std::cout << ex.what() << "\n";
                    magicPoint = nullptr;
                }
            }

            if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
            {
                if(camera.getCenter().x >= startCameraPosition.x+10)
                {
                    render.refresh(camera);
                    camera.move(-viewMoveValue, 0);
                    window.setView(camera);
                }
            }

            if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
            {
                if(camera.getCenter().x + (window.getSize().x/2) <= (map->width*40)-10)
                {
                    render.refresh(camera);
                    camera.move(viewMoveValue, 0);
                    window.setView(camera);
                }
            }

            if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
            {
                if(camera.getCenter().y >= startCameraPosition.y+10)
                {
                    render.refresh(camera);
                    camera.move(0, -viewMoveValue);
                    window.setView(camera);
                }
            }

            if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
            {
                if(camera.getCenter().y + (window.getSize().y/2) <= (map->height*40)-10)
                {
                    render.refresh(camera);
                    camera.move(0, viewMoveValue);
                    window.setView(camera);
                }
            }

            if (sf::Mouse::isButtonPressed(sf::Mouse::Left))
            {
                MouseManager::Info infoMouse =
                        mouseIndex.getIndex(map.get(), &camera);
                if (infoMouse.isOk)
                {

                    GameMap::Item* itemPtr =
                            &map->array[infoMouse.x][infoMouse.y];

                    const std::string selectedIndexValue =
                            GameObjRegister::get()->list.at(selectedIndex);

                    if(itemPtr->ID != selectedIndexValue &&
                            magicPoint != nullptr)
                    {
                        itemPtr->ID = selectedIndexValue;
                        magicPoint->change(itemPtr);
                    }
                }
            }
        }

        /* Render */
        window.clear(sf::Color::Black);

        render.drawMesh();
        render.render(*map);

        window.display();
    }
}

Nasz własny Czarnobyl

Do tej pory tworzyliśmy świat przy pomocy bezpańskich metod, listy ewidencji też leżały luzem. Generalnie nie posiadaliśmy żadnej spójnej klasy w której moglibyśmy zamknąć wszystkie niezbędne elementy dla naszej symulacji. Aby zmienić ten stan rzeczy wprowadzimy do naszego dema następującą klasę:


class GameWorld
{
public:
    GameWorld(Assets::Resources& resources);

    MovableBody* getPlayer();
    ContactDetector* getContactDetector();

    void prepareWorld();

    std::vector<std::shared_ptr<IdentifiedBody>> listIdentifiedBodies;
    std::vector<std::shared_ptr<MovableBody>> listMovableBodies;

    b2World* getBoxWorld() const;

private:
    std::shared_ptr<b2World> m_boxWorld;
    ContactDetector m_contactDetector;

    std::shared_ptr<MapBuilder::Base> m_builder;
};

Dodatkowo aby uzyskać bardziej niezależną budowę aplikacji, algorytm którym decyduje o sposóbie tworzenia świata wrzucamy do nowej klasy dziedziczącej po MapBuilder::Base. Dzięki temu zabiegowi będziemy mogli w przyszłości dysponować kilkoma oddzielnymi modułami do budowania mapy. Po oswojeniu się ze strategiami z tekstu o mapach i algorytmach w edytorze, zasada działania powinna być jasna. Na ten moment skupimy się na przeniesieniu kodu z poprzednich przykładów do klasy naszego budowniczego. Oto jak będzie wyglądać moduł budowniczych:


struct MapBuilder
{
    struct Base
    {
    public:
        Base(b2World* world) : m_world(world) {}

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

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

    protected:
        b2World* m_world;
    };

    struct TechDemo : public Base
    {
        TechDemo(b2World* world);

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

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

    };
};

Dodatkowo aby uporządkować kod, dotychczasowe funkcje odpowiedzialne za tworzenie ciał Box2D przeniesiemy do nowej klasy aby nic w projekcie nie było pozostawione same sobie, oto jak będzie wyglądać nasz nowy nagłówek:


struct BoxCreators
{
    static b2World* createWorld();

    static b2Body* createStaticBody(
                b2World* world,
                const double height,
                const double width);

    static b2Body* createDynamicBody(
                b2World* world,
                const float width,
                const float height);

   static b2Body* createPlayerBody(b2World* world);
};

Okej, na ten moment mamy nowe klasy do przechowywania danych symulacji, sposobu budowania mapy i trzymania starych funkcji w jednym miejscu. Implementacje nowych klas prezentują się jak poniżej. GameWorld:


GameWorld::GameWorld(Assets::Resources& resources) :
    m_boxWorld(BoxCreators::createWorld()),
    m_builder(new MapBuilder::TechDemo(m_boxWorld.get()))
{
   m_boxWorld.get()->SetContactListener(&m_contactDetector);

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

ContactDetector* GameWorld::getContactDetector()
{
    return &m_contactDetector;
}

MovableBody* GameWorld::getPlayer()
{
    for(std::shared_ptr<MovableBody>& item : listMovableBodies)
    {
        if(item->getBodyType() == BodyUserData::Type::Player){
            return item.get();
        }
    }
    return nullptr;
}

b2World* GameWorld::getBoxWorld() const
{
    return m_boxWorld.get();
}

void GameWorld::prepareWorld()
{
    const float32 timeStep = 1.0f / 60.0f;
    const int32 velocityIterations = 6;
    const int32 positionIterations = 2;
    m_boxWorld->Step(
            timeStep,
            velocityIterations,
            positionIterations);
}

Moduł budowy map:


MapBuilder::TechDemo::TechDemo(b2World* world) : Base(world)
{

}

void MapBuilder::TechDemo::createArea(
        std::vector<std::shared_ptr<IdentifiedBody>>& list,
        Assets::Resources& resources)
{
    IdentifiedBody mapPlatformTop(
                new DrawableBodyGenerated(
                    BoxCreators::createStaticBody(m_world, 800, 40)),
                BodyUserData::Type::Map);

    mapPlatformTop.getRenderBody()->setPosition(400.f, 0.f);
    mapPlatformTop.getRenderBody()->setTexture(
                *resources.getTexture(Assets::Textures::Map));

    IdentifiedBody mapPlatformBottom(
                new DrawableBodyGenerated(
                    BoxCreators::createStaticBody(m_world, 800, 40)),
                BodyUserData::Type::Map);

    mapPlatformBottom.getRenderBody()->setPosition(400.f, 600.f);
    mapPlatformBottom.getRenderBody()->setTexture(
                *resources.getTexture(Assets::Textures::Map));

    IdentifiedBody mapWallLeft(
                new DrawableBodyGenerated(
                    BoxCreators::createStaticBody(m_world, 40, 800)),
                BodyUserData::Type::Wall);

    mapWallLeft.getRenderBody()->setPosition(0.f, 400.f);
    mapWallLeft.getRenderBody()->setVisable(false);
    mapWallLeft.getRenderBody()->setTexture(
                *resources.getTexture(Assets::Textures::Wall));

    IdentifiedBody mapWallRight(
                new DrawableBodyGenerated(
                    BoxCreators::createStaticBody(m_world, 40, 800)),
                BodyUserData::Type::Wall);

    mapWallRight.getRenderBody()->setPosition(800.f, 400.f);
    mapWallRight.getRenderBody()->setVisable(false);
    mapWallRight.getRenderBody()->setTexture(
                *resources.getTexture(Assets::Textures::Wall));

    list.push_back(std::make_shared<IdentifiedBody>(mapPlatformTop));
    list.push_back(std::make_shared<IdentifiedBody>(mapPlatformBottom));
    list.push_back(std::make_shared<IdentifiedBody>(mapWallLeft));
    list.push_back(std::make_shared<IdentifiedBody>(mapWallRight));
}

void MapBuilder::TechDemo::createDynamicBodies(
        std::vector<std::shared_ptr<MovableBody>>& list,
        Assets::Resources& resources)
{
    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::PlayerAnimated));

    MovableBody playerItem(
                    new DrawableBodyAnimated(
                        BoxCreators::createDynamicBody(
                            m_world,
                            playerBodySize.x,
                            playerBodySize.y),
                        new Animator(
                                shapeForAnimation,
                                oneFrameSize)),
                BodyUserData::Type::Player);

    playerItem.getRenderBody()->setPosition(400.f, 10.f);
    playerItem.getMover()->setJumpForce(4.5f);
    playerItem.getMover()->setMaxSpeed(3.5);

    /* Enemy A. */
    MovableBody enemyItem(
                new DrawableBodyGenerated(
                        BoxCreators::createDynamicBody(m_world, 90, 60)),
                BodyUserData::Type::Enemy);

    enemyItem.getRenderBody()->setTexture(
                *resources.getTexture(Assets::Textures::Enemy));

    enemyItem.getRenderBody()->setPosition(600.f, 50.f);
    enemyItem.getMover()->setJumpForce(3.f);
    enemyItem.getMover()->setMaxSpeed(2.5f);

    /* Enemy B. */
    MovableBody enemyItemB(
                new DrawableBodyGenerated(
                        BoxCreators::createDynamicBody(m_world, 90, 60)),
                BodyUserData::Type::Enemy);

    enemyItemB.getRenderBody()->setTexture(
                *resources.getTexture(Assets::Textures::Enemy));

    enemyItemB.getRenderBody()->setPosition(150.f, 50.f);
    enemyItemB.getMover()->setJumpForce(3.f);
    enemyItemB.getMover()->setMaxSpeed(2.5f);

    /* Ewidencja wrogow. */
    list.push_back(std::make_shared<MovableBody>(playerItem));
    list.push_back(std::make_shared<MovableBody>(enemyItem));
    list.push_back(std::make_shared<MovableBody>(enemyItemB));
}

Implementacje funkcji tworzenia zostały bez zmian. Po tych wszystkich zabiegach funkcja startowa naszego dema technologicznego wygląda o wiele schludniej:


void CoreApps::startExample()
{
    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);

    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);
        }

        /* 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();
    }
}

Dzięki powyżej przedstawionym modyfikacjom, naszym następnym celem będzie zbudowanie silnika do budowy map w oparciu o obiekt GameMap z edytora. Ale to temat na przyszły tekst. Jak zawsze dzięki za uwagę!

Szanowna Użytkowniczko! Szanowny Użytkowniku!
×
Aby dalej móc dostarczać coraz lepsze materiały redakcyjne i udostępniać coraz lepsze usługi, potrzebujemy zgody na dopasowanie treści marketingowych do Twojego zachowania. Twoje dane są u nas bezpieczne, a zgodę możesz wycofać w każdej chwili na podstronie polityka prywatności.

Kliknij "PRZECHODZĘ DO SERWISU" lub na symbol "X" w górnym rogu tej planszy, jeżeli zgadzasz się na przetwarzanie przez Wirtualną Polskę i naszych Zaufanych Partnerów Twoich danych osobowych, zbieranych w ramach korzystania przez Ciebie z usług, portali i serwisów internetowych Wirtualnej Polski (w tym danych zapisywanych w plikach cookies) w celach marketingowych realizowanych na zlecenie naszych Zaufanych Partnerów. Jeśli nie zgadzasz się na przetwarzanie Twoich danych osobowych skorzystaj z ustawień w polityce prywatności. Zgoda jest dobrowolna i możesz ją w dowolnym momencie wycofać zmieniając ustawienia w polityce prywatności (w której znajdziesz odpowiedzi na wszystkie pytania związane z przetwarzaniem Twoich danych osobowych).

Od 25 maja 2018 roku obowiązuje Rozporządzenie Parlamentu Europejskiego i Rady (UE) 2016/679 (określane jako "RODO"). W związku z tym chcielibyśmy poinformować o przetwarzaniu Twoich danych oraz zasadach, na jakich odbywa się to po dniu 25 maja 2018 roku.

Kto będzie administratorem Twoich danych?

Administratorami Twoich danych będzie Wirtualna Polska Media Spółka Akcyjna z siedzibą w Warszawie, oraz pozostałe spółki z grupy Wirtualna Polska, jak również nasi Zaufani Partnerzy, z którymi stale współpracujemy. Szczegółowe informacje dotyczące administratorów znajdują się w polityce prywatności.

O jakich danych mówimy?

Chodzi o dane osobowe, które są zbierane w ramach korzystania przez Ciebie z naszych usług, portali i serwisów internetowych udostępnianych przez Wirtualną Polskę, w tym zapisywanych w plikach cookies, które są instalowane na naszych stronach przez Wirtualną Polskę oraz naszych Zaufanych Partnerów.

Dlaczego chcemy przetwarzać Twoje dane?

Przetwarzamy je dostarczać coraz lepsze materiały redakcyjne, dopasować ich tematykę do Twoich zainteresowań, tworzyć portale i serwisy internetowe, z których będziesz korzystać z przyjemnością, zapewniać większe bezpieczeństwo usług, udoskonalać nasze usługi i maksymalnie dopasować je do Twoich zainteresowań, pokazywać reklamy dopasowane do Twoich potrzeb. Szczegółowe informacje dotyczące celów przetwarzania Twoich danych znajdują się w polityce prywatności.

Komu możemy przekazać dane?

Twoje dane możemy przekazywać podmiotom przetwarzającym je na nasze zlecenie oraz podmiotom uprawnionym do uzyskania danych na podstawie obowiązującego prawa – oczywiście tylko, gdy wystąpią z żądaniem w oparciu o stosowną podstawę prawną.

Jakie masz prawa w stosunku do Twoich danych?

Masz prawo żądania dostępu, sprostowania, usunięcia lub ograniczenia przetwarzania danych. Możesz wycofać zgodę na przetwarzanie, zgłosić sprzeciw oraz skorzystać z innych praw wymienionych szczegółowo w polityce prywatności.

Jakie są podstawy prawne przetwarzania Twoich danych?

Podstawą prawną przetwarzania Twoich danych w celu świadczenia usług jest niezbędność do wykonania umów o ich świadczenie (tymi umowami są zazwyczaj regulaminy). Podstawą prawną przetwarzania danych w celu pomiarów statystycznych i marketingu własnego administratorów jest tzw. uzasadniony interes administratora. Przetwarzanie Twoich danych w celach marketingowych realizowanych przez Wirtualną Polskę na zlecenie Zaufanych Partnerów i bezpośrednio przez Zaufanych Partnerów będzie odbywać się na podstawie Twojej dobrowolnej zgody.