第17课 - 碰撞检测


更新时间:2012年8月3日


这节课要说的是游戏编程中的一个重要概念。

这里,我们有一个方块和一堵墙,我们希望能确保方块不能穿墙而过。为了能做到这一点,我们要检查方块和墙是否发生了碰撞。

  在这里我们会讲一种简单的方法来检测两个物体之间的碰撞。
//墙
SDL_Rect wall;
这是我们要拿来给方块撞的墙,没什么好多解释的了吧?
//方块
class Square
{
    private:
    //方块的碰撞检测矩形
    SDL_Rect box;
    
    //方块的速度
    int xVel, yVel;
    
    public:
    //初始化方块
    Square();
    
    //按键时调整方块速度的函数
    void handle_input();
    
    //移动方块
    void move();
    
    //在屏幕上显示方块
    void show();
};
这便是我们要拿来撞墙的方块类了。你多半已经注意到,这和我们先前在运动一课中使用的圆点类颇为类似。

唯一显著的区别是,方块的X,Y坐标都被储存在一个SDL_Rect结构体中,这样方块的大小也能储存其中。除此之外,别的都一样。
bool check_collision( SDL_Rect A, SDL_Rect B )
{
    //矩形的各边
    int leftA, leftB;
    int rightA, rightB;
    int topA, topB;
    int bottomA, bottomB;

    //计算矩形A的各边
    leftA = A.x;
    rightA = A.x + A.w;
    topA = A.y;
    bottomA = A.y + A.h;
        
    //计算矩形B的各边
    leftB = B.x;
    rightB = B.x + B.w;
    topB = B.y;
    bottomB = B.y + B.h;
现在我们开始写碰撞检测的函数代码了。

首先我们载入SDL_Rect并计算它们各边的位置。
    //如果矩形A的任意一条边都在矩形B外
    if( bottomA <= topB )
    {
        return false;
    }
    
    if( topA >= bottomB )
    {
        return false;
    }
    
    if( rightA <= leftB )
    {
        return false;
    }
    
    if( leftA >= rightB )
    {
        return false;
    }
    
    //如果没有一条边在矩形B外
    return true;
}
在此我们检测碰撞。

矩形碰撞检测的基本原理是,检查一个矩形的四条边是否都在另一个矩形的外侧。

好好想想便会发现,如果矩形的四条边都在另一个矩形的外侧的话,它们之间就不可能发生碰撞,就像这样。
不管是哪种情况,B的每条边都在A外面。

接下来,我们要考虑另一种情况:
矩形B至少有一条边再矩形A中的情况。

这样我们能确定矩形B中没有一条边再矩形A内,如果没有碰撞发生我们返回false,如果有则返回true。

注意我使用的是大于等于和小于等于。也就是说只有当两个矩形重合的时候才会被判定碰撞。如果你使用的是大于和小于,那么图中的两个矩形:
即便只是靠在一起也会被判定为碰撞。

究竟该使用哪种模式要视实际情况而定。
Square::Square()
{
    //初始化坐标
    box.x = 0;
    box.y = 0;
    
    //设置矩形的大小
    box.w = SQUARE_WIDTH;
    box.h = SQUARE_HEIGHT;
    
    //初始化速度
    xVel = 0;
    yVel = 0;
}
在方块的构造函数中我们对方块的坐标,大小,和速度像以前一样进行了初始化。
void Square::move()
{
    //将方块左右移动
    box.x += xVel;
       
    //当方块移出屏幕或是与墙发生了碰撞
    if( ( box.x < 0 ) || ( box.x + SQUARE_WIDTH > SCREEN_WIDTH ) || ( check_collision( box, wall ) ) )
    {
        //退回去
        box.x -= xVel;
    }
    
    //将方块上下移动
    box.y += yVel;
    
    //当方块移出屏幕或是与墙发生了碰撞
    if( ( box.y < 0 ) || ( box.y + SQUARE_HEIGHT > SCREEN_HEIGHT ) || ( check_collision( box, wall ) ) )
    {
        //退回去
        box.y -= yVel;
    }   
}
这便是我们用来移动方块的move()函数。每次移动都要检测方块是否移出窗口或是撞上了墙,如果方块走到了不该走的地方,我们便要撤销刚才的移动。
    //设置墙壁
    wall.x = 300;
    wall.y = 40;
    wall.w = 40;
    wall.h = 400;
在我们的主函数中,在初始化并加载了所有的东西后,我们便要设置墙体的参数。
    //当用户还未退出
    while( quit == false )
    {
        //启动帧计时器
        fps.start();
        
        //当有事件需要处理
        while( SDL_PollEvent( &event ) )
        {
            //处理方块的事件
            mySquare.handle_input();
            
            //当用户关闭窗口
            if( event.type == SDL_QUIT )
            {
                //退出程序
                quit = true;
            }
        }
        
        //移动方块
        mySquare.move();
        
        //填充白色背景
        SDL_FillRect( screen, ≻reen->clip_rect, SDL_MapRGB( screen->format, 0xFF, 0xFF, 0xFF ) );
        
        //显示墙体
        SDL_FillRect( screen, &wall, SDL_MapRGB( screen->format, 0x77, 0x77, 0x77 ) );
            
        //将方块显示在屏幕上
        mySquare.show();
        
        //更新窗口
        if( SDL_Flip( screen ) == -1 )
        {
            return 1;    
        }
        
        //限制帧率
        if( fps.get_ticks() < 1000 / FRAMES_PER_SECOND )
        {
            SDL_Delay( ( 1000 / FRAMES_PER_SECOND ) - fps.get_ticks() );
        }
    }
这就是我们的主循环了。我们依次移动方块,填充背景,显示墙体,显示方块,最后更新屏幕并限制帧率。

本课所用的多媒体文件和源代码可从此处下载

下一课:逐像素的碰撞检测