This is the Polish translation of In-Practice/2D-Game/Collisions/Ball article of learnopengl.com tutorial series.

W tej chwili mamy poziom pełen cegieł i ruchome wiosło gracza. Jedyne czego brakuje w klasycznym przepisie na Breakout to piłka. Celem jest, aby piłka zderzyła się ze wszystkimi cegłami, aż zniszczona zostanie każda ze zniszczalnych cegieł, ale wszystko to pod warunkiem, że piłka nie dosięgnie dolnej krawędzi ekranu.

Oprócz ogólnych elementów obiektu gry, piłka ma promień i dodatkową wartość boolowską wskazującą, czy piłka jest przyklejona (stuck) do wiosła gracza, czy jest w ruchu. Gdy gra się zaczyna, piłka jest początkowo przyklejona do wiosła gracza, dopóki gracz nie uruchomi gry, naciskając dowolny klawisz.

Ponieważ piłka jest w zasadzie GameObject z kilkoma dodatkowymi właściwościami, warto utworzyć klasę BallObject jako podklasę GameObject:

    class BallObject : public GameObject
    {
        public:
            // Ball state	
            GLfloat   Radius;
            GLboolean Stuck;

            BallObject();
            BallObject(glm::vec2 pos, GLfloat radius, glm::vec2 velocity, Texture2D sprite);

            glm::vec2 Move(GLfloat dt, GLuint window_width);
            void      Reset(glm::vec2 position, glm::vec2 velocity);
    }; 

Konstruktor BallObject inicjuje własne wartości, ale także inicjuje zmienne klasy GameObject. Klasa BallObject udostępnia funkcję Move, która przesuwa piłkę w oparciu o jej prędkość i sprawdza, czy dosięga ona którąkolwiek z krawędzi sceny, a jeśli tak, odwraca prędkość kulki:

    glm::vec2 BallObject::Move(GLfloat dt, GLuint window_width)
    {
        // If not stuck to player board
        if (!this->Stuck)
        { 
            // Move the ball
            this->Position += this->Velocity * dt;
            // Check if outside window bounds; if so, reverse velocity and restore at correct position
            if (this->Position.x <= 0.0f)
            {
                this->Velocity.x = -this->Velocity.x;
                this->Position.x = 0.0f;
            }
            else if (this->Position.x + this->Size.x >= window_width)
            {
                this->Velocity.x = -this->Velocity.x;
                this->Position.x = window_width - this->Size.x;
            }
            if (this->Position.y <= 0.0f)
            {
                this->Velocity.y = -this->Velocity.y;
                this->Position.y = 0.0f;
            }

        }
        return this->Position;
    }  

Oprócz odwrócenia prędkości piłki, chcemy również przenieść piłkę z powrotem wzdłuż krawędzi. Piłka może się poruszać tylko wtedy, gdy nie utknęła.

Ponieważ gracz przegrywa (lub traci życie), jeśli piłka dosięgnie dolną krawędź, nie ma kodu pozwalającego na odbijanie się piłki od dolnej krawędzi. Musimy jednak później wdrożyć tę logikę w kodzie gry.

Poniżej znajdziesz kod obiektu piłki:

Najpierw dodajmy piłkę do gry. Podobnie jak dla wiosła gracza tworzymy obiekt BallObject i definiujemy dwie stałe, których używamy do inicjalizacji piłki. Jako tekstury piłki użyjemy obrazu, który ma sens w grze LearnOpenGL Breakout: tekstura piłki.

    // Initial velocity of the Ball
    const glm::vec2 INITIAL_BALL_VELOCITY(100.0f, -350.0f);
    // Radius of the ball object
    const GLfloat BALL_RADIUS = 12.5f;

    BallObject     *Ball; 

    void Game::Init()
    {
        [...]
        glm::vec2 ballPos = playerPos + glm::vec2(PLAYER_SIZE.x / 2 - BALL_RADIUS, -BALL_RADIUS * 2);
        Ball = new BallObject(ballPos, BALL_RADIUS, INITIAL_BALL_VELOCITY,
            ResourceManager::GetTexture("face"));
    }

Następnie musimy zaktualizować pozycję piłki co klatkę, wywołując jej funkcję Move w funkcji Update kodu gry:

    void Game::Update(GLfloat dt)
    {
        Ball->Move(dt, this->Width);
    }  

Ponadto, ponieważ piłka jest początkowo przyklejona do wiosła, musimy dać graczowi możliwość wybicia piłki. Wybieramy klawisz spacji, aby wybić piłkę z wiosła. Oznacza to, że musimy nieco zmienić funkcję processInput:

    void Game::ProcessInput(GLfloat dt)
    {
        if (this->State == GAME_ACTIVE)
        {
            GLfloat velocity = PLAYER_VELOCITY * dt;
            // Move playerboard
            if (this->Keys[GLFW_KEY_A])
            {
                if (Player->Position.x >= 0)
                {
                    Player->Position.x -= velocity;
                    if (Ball->Stuck)
                        Ball->Position.x -= velocity;
                }
            }
            if (this->Keys[GLFW_KEY_D])
            {
                if (Player->Position.x <= this->Width - Player->Size.x)
                {
                    Player->Position.x += velocity;
                    if (Ball->Stuck)
                        Ball->Position.x += velocity;
                }
            }
            if (this->Keys[GLFW_KEY_SPACE])
                Ball->Stuck = false;
        }
    }

W tym przypadku, jeśli użytkownik naciśnie klawisz spacji, zmienna Stuck piłki zostanie ustawiona na false. Zaktualizowaliśmy również funkcję ProcessInput, aby przesunąć pozycję piłki wzdłuż pozycji wiosła, gdy piłka jest do niego przyklejona.

Na koniec musimy wyrenderować piłkę, co powinno być dość oczywiste:

    void Game::Render()
    {
        if (this->State == GAME_ACTIVE)
        {
            [...]
            Ball->Draw(*Renderer);
        }
    }  

Rezultatem jest piłka podążająca za wiosłem i swobodnie poruszająca się za każdym razem kiedy gracz naciśnie spację. Piłka również odbija się od lewej, prawej i górnej krawędzi, ale zdaje się, że nie koliduje ona z żadną z cegieł, co możemy zobaczyć na poniższym filmie:

Chcemy utworzyć jedną lub kilka funkcji sprawdzających, czy obiekt piłki koliduje z którąkolwiek z cegieł w poziomie, a jeśli tak, to niech zniszczy tę cegłę. Te tzw. Funkcje wykrywania kolizji (ang. collision detection) są tym, na czym się skupimy w następnym samouczku.