SDL程式設計入門(29)圓形碰撞檢測

血色v殘陽發表於2020-12-30

圓形碰撞檢測

除了矩形碰撞框,圓是最常見的碰撞器形式。在這裡,我們將檢查兩個圓以及一個圓和一個框之間的碰撞。

檢查兩個圓之間的碰撞很容易。 你要做的就是檢查每個圓心之間的距離是否小於其半徑的總和(半徑是半徑的倍數)。

對於框/圓碰撞,必須在碰撞框上找到最接近圓心的點。 如果該點小於圓的半徑,則發生碰撞。

//一個圓結構體
struct Circle
{
    int x, y;
    int r;
};

SDL有一個內建的矩形結構,但我們必須自己製作一個有位置和半徑的圓形結構。

//The dot that will move around on the screen
class Dot
{
    public:
        //The dimensions of the dot
        static const int DOT_WIDTH = 20;
        static const int DOT_HEIGHT = 20;

        //Maximum axis velocity of the dot
        static const int DOT_VEL = 1;

        //Initializes the variables
        Dot( int x, int y );

        //Takes key presses and adjusts the dot's velocity
        void handleEvent( SDL_Event& e );

        //Moves the dot and checks collision
        void move( SDL_Rect& square, Circle& circle );

        //Shows the dot on the screen
        void render();

        //Gets collision circle
        Circle& getCollider();

    private:
        //The X and Y offsets of the dot
        int mPosX, mPosY;

        //The velocity of the dot
        int mVelX, mVelY;
        
        //Dot's collision circle
        Circle mCollider;

        //Moves the collision circle relative to the dot's offset
        void shiftColliders();
};

這裡是以前碰撞檢測教程中的點類,又增加了一些附加功能。移動函式接收了一個圓形和一個矩形,以便在移動時對其進行碰撞檢測。現在,我們也有了一個圓形碰撞器,而不是一個矩形碰撞器。

//圓、圓碰撞檢測器
bool checkCollision( Circle& a, Circle& b );

//圓形、矩形框碰撞檢測器
bool checkCollision( Circle& a, SDL_Rect& b );

//計算兩點之間的距離平方
double distanceSquared( int x1, int y1, int x2, int y2 );

在本教程中,我們有碰撞檢測函式,用於圓、圓和圓、矩形的碰撞。我們還有一個計算兩點之間距離平方的函式。

使用距離平方而不是距離是一個優化,我們將在後面詳細介紹。

Dot::Dot( int x, int y )
{
    //Initialize the offsets
    mPosX = x;
    mPosY = y;

    //設定碰撞圓尺寸
    mCollider.r = DOT_WIDTH / 2;

    //Initialize the velocity
    mVelX = 0;
    mVelY = 0;

    //Move collider relative to the circle
    shiftColliders();
}

建構函式接收一個位置並初始化碰撞器和速度。

void Dot::move( SDL_Rect& square, Circle& circle ){
    //Move the dot left or right
    mPosX += mVelX;
    shiftColliders();

    //If the dot collided or went too far to the left or right
    if( ( mPosX - mCollider.r < 0 ) || ( mPosX + mCollider.r > SCREEN_WIDTH ) || checkCollision( mCollider, square ) || checkCollision( mCollider, circle ) )
    {
        //Move back
        mPosX -= mVelX;
        shiftColliders();
    }

    //Move the dot up or down
    mPosY += mVelY;
    shiftColliders();

    //If the dot collided or went too far up or down
    if( ( mPosY - mCollider.r < 0 ) || ( mPosY + mCollider.r > SCREEN_HEIGHT ) || checkCollision( mCollider, square ) || checkCollision( mCollider, circle ) )
    {
        //Move back
        mPosY -= mVelY;
        shiftColliders();
    }
}

就像之前的碰撞檢測教程一樣,我們沿著x軸移動,對著螢幕邊緣檢查碰撞,對著其他場景物件檢查。如果點撞到了什麼東西,我們就往後移動。一如既往,每當點移動時,它的碰撞器也會隨之移動。

然後,我們再對y軸做一次檢查。

void Dot::render(){
    //Show the dot
    gDotTexture.render( mPosX - mCollider.r, mPosY - mCollider.r );
}

渲染程式碼有點不同。SDL_Rects的位置在左上方,而我們的圓圈結構的位置在中心。這意味著我們需要通過從x和y位置中減去半徑來將渲染位置偏移到圓的左上方。

bool checkCollision( Circle& a, Circle& b ){
    //計算總半徑的平方
    int totalRadiusSquared = a.r + b.r;
    totalRadiusSquared = totalRadiusSquared * totalRadiusSquared;

    //如果圓心之間的距離小於它們的半徑之和
    if( distanceSquared( a.x, a.y, b.x, b.y ) < ( totalRadiusSquared ) )
    {
        //圓圈已經相撞
        return true;
    }

    //否則沒有
    return false;
}

這是我們的圓對圓碰撞檢測器。它簡單地檢查中心之間的距離平方是否小於半徑平方之和。如果小於,則說明發生了碰撞。

為什麼我們要使用距離平方而不是普通的距離?因為計算距離涉及到一個平方根,而計算平方根是一個相對昂貴的操作。幸運的是如果x>y,那麼x2>y2,所以我們只要比較距離平方就可以省去一個平方根的操作。

bool checkCollision( Circle& a, SDL_Rect& b ){
    //碰撞框上的最近點
    int cX, cY;

    //尋找最近的X偏移量
    if( a.x < b.x )
    {
        cX = b.x;
    }
    else if( a.x > b.x + b.w )
    {
        cX = b.x + b.w;
    }
    else
    {
        cX = a.x;
    }
    //尋找最近的y偏移量
    if( a.y < b.y )
    {
        cY = b.y;
    }
    else if( a.y > b.y + b.h )
    {
        cY = b.y + b.h;
    }
    else
    {
        cY = a.y;
    }

    //如果最近的點在圓內
    if( distanceSquared( a.x, a.y, cX, cY ) < a.r * a.r )
    {
        //這個矩形框和圓圈相撞了
        return true;
    }

    //如果圖形沒有碰撞
    return false;
}

要檢查框和圓是否發生碰撞,我們需要在框上找到最近的點。

如果圓的中心在框的左側,則最近點的x位置在框的左側。

如果圓的中心在框的右側,則最近點的x位置在框的右側。

如果圓的中心在框內,則最近點的x位置與圓的x位置相同。

在這裡,我們找到最近的y位置,就像我們找到x位置一樣。如果盒子上最近的點和圓心之間的距離平方小於圓的半徑平方,那麼就會發生碰撞。

double distanceSquared( int x1, int y1, int x2, int y2 ){
    int deltaX = x2 - x1;
    int deltaY = y2 - y1;
    return deltaX*deltaX + deltaY*deltaY;
}

這裡是距離平方函式。它只是一個沒有平方根的距離計算 ( squareRoot( x^2 + y^2 ) ) 。

            //將在螢幕上移動的點
            Dot dot( Dot::DOT_WIDTH / 2, Dot::DOT_HEIGHT / 2 );
            Dot otherDot( SCREEN_WIDTH / 4, SCREEN_HEIGHT / 4 );

            //設定牆面
            SDL_Rect wall;
            wall.x = 300;
            wall.y = 40;
            wall.w = 40;
            wall.h = 400;

在進入主迴圈之前,我們先定義場景物件。

            //While application is running
            while( !quit )
            {
                //Handle events on queue
                while( SDL_PollEvent( &e ) != 0 )
                {
                    //User requests quit
                    if( e.type == SDL_QUIT )
                    {
                        quit = true;
                    }

                    //Handle input for the dot
                    dot.handleEvent( e );
                }

                //Move the dot and check collision
                dot.move( wall, otherDot.getCollider() );

                //Clear screen
                SDL_SetRenderDrawColor( gRenderer, 0xFF, 0xFF, 0xFF, 0xFF );
                SDL_RenderClear( gRenderer );

                //Render wall
                SDL_SetRenderDrawColor( gRenderer, 0x00, 0x00, 0x00, 0xFF );        
                SDL_RenderDrawRect( gRenderer, &wall );
                
                //Render dots
                dot.render();
                otherDot.render();

                //Update screen
                SDL_RenderPresent( gRenderer );
            }

最後在我們的主迴圈中,我們處理輸入,用碰撞檢測移動點,並將場景物件渲染到螢幕上。

這裡下載本教程的媒體和原始碼。

原文連結

關注我的公眾號:程式設計之路從0到1
程式設計之路從0到1

相關文章