完美的像素碰撞检测(使用cocos2dx)

时间:2023-02-08 16:28:41

        (第一次翻译国外的文章,好紧张,因为英语比较菜的缘故,翻译起来有些别扭。原文:http://blog.csdn.net/shieryueqing       

         我写这篇文章的原因是,我在*中没有发现怎么做像素碰撞检测这个问题的答案,原以为会有很多像我一样的人在搜索着答案。在大部分的游戏中碰撞检测是重要的组成部分,它能够使用在子弹打中了敌人或者你撞到了墙上等等。当做游戏的碰撞检测的时候,我们需要根据游戏去选择其中一些检测的技术。几乎所有的游戏引擎和框架都使用“Bounding Box”碰撞,这是一个默认的碰撞检测机制。简单来说,精灵或对象所用到的“Bounding Box”碰撞检测系统被视为最小的矩形,这些矩形能够完成覆盖精灵或对象,然后他们两个碰撞盒子被检查是否他们正碰到对方。

       但是有时候这些简单的碰撞检测系统是不精确的,特别是当我们通过alpha值或者旋转一定角度来使用精灵的时候。看一下下面的图片:

­

 

像素检测是一个精确的系统,他能够使我们的碰撞更精确,而不是像刚才那样使用比他们更大尺寸的Bounding Box

警告:这个系统越精确消耗的性能越大,因此,根据你的游戏需要明智地选择不同的系统。

提示:这个系统虽然特别为cocos2dx框架写的,但是你能够轻易的明白并且使用其他的语言去实现。

我们迟早会亲自去做这件事情的。现在我们将为碰撞检测去制作一个单例类和其他一些将要做的东西,需要使用到:

1. Singleton Class – CollisionDetection
2. Opengl Vertex and Fragment Shaders
3. CCRenderTexture Class – Cocos2d-x

 

原理是:

1.       创建一个CCRenderTexture,他将作为辅助缓冲区。

2.       我们首先做一个简单的碰撞(Bounding Box)去检测是否他们两个精灵边界能够相碰。

3.       如果第二步成功了,我们将绘制两个相关的对象在我们第一步已经创建的二次缓冲中。

4.       使用openGL片段着色器我们要画其中一个对象为红色,其他的为蓝色。

 

5.       使用另一个openGL功能glReadPixels,我们要在boundingbox矩形区域内读取全部的像素数据。

6.       我们接着去遍历全部的像素值,检查单个像素是否有红色或者蓝色像素。如果他们有像素说明有碰撞,否则不碰撞。

 

现在写代码来实现以上的步骤。我已经为你写完了代码,你去看看都做了什么事情。如果有什么问题请留下评论我将用我的知识尝试去回答。

 

 

 

CollisionDetection.h

[html] view plaincopyprint?

1.  //  

2.  //  CollisionDetection.h  

3.  //  Created by Mudit Jaju on 30/08/13.  

4.  //  

5.  //  SINGLETON class for checking Pixel Based Collision Detection  

6.    

7.  #ifndef __CollisionDetection__  

8.  #define __CollisionDetection__  

9.    

10. #include <iostream>  

11. #include "cocos2d.h"  

12.   

13. USING_NS_CC;  

14.   

15. class CollisionDetection {  

16. public:  

17.     //Handle for getting the Singleton Object  

18.     static CollisionDetection* GetInstance();  

19.     //Function signature for checking for collision detection spr1, spr2 are the concerned sprites  

20.     //pp is bool, set to true if Pixel Perfection Collision is required. Else set to false  

21.     //_rt is the secondary buffer used in our system  

22.     bool areTheSpritesColliding(CCSprite* spr1, CCSprite* spr2, bool pp, CCRenderTexture* _rt);  

23. private:  

24.     static CollisionDetection* instance;  

25.     CollisionDetection();  

26.   

27.     // Values below are all required for openGL shading  

28.     CCGLProgram *glProgram;  

29.     ccColor4B *buffer;  

30.     int uniformColorRed;  

31.     int uniformColorBlue;  

32.   

33. };  

34.   

35. #endif /* defined(__CollisionDetection__) */  

//
//  CollisionDetection.h
//  Created by Mudit Jaju on 30/08/13.
//
//  SINGLETON class for checking Pixel Based Collision Detection
 
#ifndef __CollisionDetection__
#define __CollisionDetection__
 
#include <iostream>
#include "cocos2d.h"
 
USING_NS_CC;
 
class CollisionDetection {
public:
    //Handle for getting the Singleton Object
    static CollisionDetection* GetInstance();
    //Function signature for checking for collision detection spr1, spr2 are the concerned sprites
    //pp is bool, set to true if Pixel Perfection Collision is required. Else set to false
    //_rt is the secondary buffer used in our system
    bool areTheSpritesColliding(CCSprite* spr1, CCSprite* spr2, bool pp, CCRenderTexture* _rt);
private:
    static CollisionDetection* instance;
    CollisionDetection();
 
    // Values below are all required for openGL shading
    CCGLProgram *glProgram;
    ccColor4B *buffer;
    int uniformColorRed;
    int uniformColorBlue;
 
};
 
#endif /* defined(__CollisionDetection__) */


CollisionDetection.cpp

[html] view plaincopyprint?

1.  //  

2.  //  CollisionDetection.cpp  

3.  //  Created by Mudit Jaju on 30/08/13.  

4.  //  

5.  //  SINGLETON class for checking Pixel Based Collision Detection  

6.    

7.  #include "CollisionDetection.h"  

8.  // Singleton Instance set to NULL initially  

9.  CollisionDetection* CollisionDetection::instance = NULL;  

10.   

11. // Handle to get Singleton Instance  

12. CollisionDetection* CollisionDetection::GetInstance() {  

13.     if (instance == NULL) {  

14.         instance = new CollisionDetection();  

15.     }  

16.     return instance;  

17. }  

18.   

19. // Private Constructor being called from within the GetInstance handle  

20. CollisionDetection::CollisionDetection() {  

21.     // Code below to setup shaders for use in Cocos2d-x  

22.     glProgram = new CCGLProgram();  

23.     glProgram->retain();  

24.     glProgram->initWithVertexShaderFilename("SolidVertexShader.vsh", "SolidColorShader.fsh");  

25.     glProgram->addAttribute(kCCAttributeNamePosition, kCCVertexAttrib_Position);  

26.     glProgram->addAttribute(kCCAttributeNameTexCoord, kCCVertexAttrib_TexCoords);  

27.     glProgram->link();  

28.     glProgram->updateUniforms();  

29.     glProgram->use();  

30.   

31.     uniformColorRed = glGetUniformLocation(glProgram->getProgram(), "u_color_red");  

32.     uniformColorBlue = glGetUniformLocation(glProgram->getProgram(), "u_color_blue");  

33.   

34.     // A large buffer created and re-used again and again to store glReadPixels data  

35.     buffer = (ccColor4B *)malloc( sizeof(ccColor4B) * 10000 );  

36. }  

37.   

38. bool CollisionDetection::areTheSpritesColliding(cocos2d::CCSprite* spr1, cocos2d::CCSprite* spr2, bool pp, CCRenderTexture* _rt) {  

39.     bool isColliding = false;  

40.   

41.     // Rectangle of the intersecting area if the sprites are colliding according to Bounding Box collision  

42.     CCRect intersection;  

43.   

44.     // Bounding box of the Two concerned sprites being saved  

45.     CCRect r1 = spr1->boundingBox();  

46.     CCRect r2 = spr2->boundingBox();  

47.   

48.     // Look for simple bounding box collision  

49.     if (r1.intersectsRect(r2)) {  

50.         // If we're not checking for pixel perfect collisions, return true  

51.         if (!pp) {  

52.             return true;  

53.         }  

54.   

55.         float tempX;  

56.         float tempY;  

57.         float tempWidth;  

58.         float tempHeight;  

59.   

60.         //OPTIMIZE FURTHER  

61.         //CONSIDER THE CASE WHEN ONE BOUDNING BOX IS COMPLETELY INSIDE ANOTHER BOUNDING BOX!  

62.         if (r1.getMaxX() > r2.getMinX()) {  

63.             tempX = r2.getMinX();  

64.             tempWidth = r1.getMaxX() - r2.getMinX();  

65.         } else {  

66.             tempX = r1.getMinX();  

67.             tempWidth = r2.getMaxX() - r1.getMinX();  

68.         }  

69.   

70.         if (r2.getMaxY() < r1.getMaxY()) {  

71.             tempY = r1.getMinY();  

72.             tempHeight = r2.getMaxY() - r1.getMinY();  

73.         } else {  

74.             tempY = r2.getMinY();  

75.             tempHeight = r1.getMaxY() - r2.getMinY();  

76.         }  

77.   

78.         // We make the rectangle for the intersection area  

79.         intersection = CCRectMake(tempX * CC_CONTENT_SCALE_FACTOR(), tempY * CC_CONTENT_SCALE_FACTOR(), tempWidth * CC_CONTENT_SCALE_FACTOR(), tempHeight * CC_CONTENT_SCALE_FACTOR());  

80.   

81.         unsigned int x = intersection.origin.x;  

82.         unsigned int y = intersection.origin.y;  

83.         unsigned int w = intersection.size.width;  

84.         unsigned int h = intersection.size.height;  

85.   

86.         // Total pixels whose values we will get using glReadPixels depends on the Height and Width of the intersection area  

87.         unsigned int numPixels = w * h;  

88.   

89.         // Setting the custom shader to be used  

90.         spr1->setShaderProgram(glProgram);  

91.         spr2->setShaderProgram(glProgram);  

92.         glProgram->use();  

93.   

94.         // Clearing the Secondary Draw buffer of all previous values  

95.         _rt->beginWithClear( 0, 0, 0, 0);  

96.   

97.         // The below two values are being used in the custom shaders to set the value of RED and BLUE colors to be used  

98.         glUniform1i(uniformColorRed, 255);  

99.         glUniform1i(uniformColorBlue, 0);  

100.   

101.         // The blend function is important we don't want the pixel value of the RED color being over-written by the BLUE color.  

102.        // We want both the colors at a single pixel and hence get a PINK color (so that we have both the RED and BLUE pixels)  

103.         spr1->setBlendFunc((ccBlendFunc){GL_SRC_ALPHA, GL_ONE});  

104.   

105.         // The visit() function draws the sprite in the _rt draw buffer its a Cocos2d-x function  

106.         spr1->visit();  

107.   

108.         // Setting the shader program back to the default shader being used by our game          

109. spr1->setShaderProgram(CCShaderCache::sharedShaderCache()->programForKey(kCCShader_PositionTextureColor));  

110.         // Setting the default blender function being used by the game  

111.         spr1->setBlendFunc((ccBlendFunc){CC_BLEND_SRC, CC_BLEND_DST});  

112.   

113.         // Setting new values for the same shader but for our second sprite as this time we want to have an all BLUE sprite  

114.         glUniform1i(uniformColorRed, 0);  

115.         glUniform1i(uniformColorBlue, 255);  

116.         spr2->setBlendFunc((ccBlendFunc){GL_SRC_ALPHA, GL_ONE});  

117.   

118.         spr2->visit();  

119.   

120.         spr2->setShaderProgram(CCShaderCache::sharedShaderCache()->programForKey(kCCShader_PositionTextureColor));  

121.         spr2->setBlendFunc((ccBlendFunc){CC_BLEND_SRC, CC_BLEND_DST});  

122.   

123.         // Get color values of intersection area  

124.         glReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, buffer);  

125.   

126.         _rt->end();  

127.   

128.         // Read buffer  

129.         unsigned int step = 1;  

130.         for(unsigned int i=0; i<numPixels; i+=step) {  

131.             ccColor4B color = buffer[i];  

132.             // Here we check if a single pixel has both RED and BLUE pixels  

133.             if (color.r > 0 && color.b > 0) {  

134.                 isColliding = true;  

135.                 break;  

136.             }  

137.         }  

138.     }  

139.     return isColliding;  

140. }  

//
//  CollisionDetection.cpp
//  Created by Mudit Jaju on 30/08/13.
//
//  SINGLETON class for checking Pixel Based Collision Detection
 
#include "CollisionDetection.h"
// Singleton Instance set to NULL initially
CollisionDetection* CollisionDetection::instance = NULL;
 
// Handle to get Singleton Instance
CollisionDetection* CollisionDetection::GetInstance() {
    if (instance == NULL) {
        instance = new CollisionDetection();
    }
    return instance;
}
 
// Private Constructor being called from within the GetInstance handle
CollisionDetection::CollisionDetection() {
    // Code below to setup shaders for use in Cocos2d-x
    glProgram = new CCGLProgram();
    glProgram->retain();
    glProgram->initWithVertexShaderFilename("SolidVertexShader.vsh", "SolidColorShader.fsh");
    glProgram->addAttribute(kCCAttributeNamePosition, kCCVertexAttrib_Position);
    glProgram->addAttribute(kCCAttributeNameTexCoord, kCCVertexAttrib_TexCoords);
    glProgram->link();
    glProgram->updateUniforms();
    glProgram->use();
 
    uniformColorRed = glGetUniformLocation(glProgram->getProgram(), "u_color_red");
    uniformColorBlue = glGetUniformLocation(glProgram->getProgram(), "u_color_blue");
 
    // A large buffer created and re-used again and again to store glReadPixels data
    buffer = (ccColor4B *)malloc( sizeof(ccColor4B) * 10000 );
}
 
bool CollisionDetection::areTheSpritesColliding(cocos2d::CCSprite* spr1, cocos2d::CCSprite* spr2, bool pp, CCRenderTexture* _rt) {
    bool isColliding = false;
 
    // Rectangle of the intersecting area if the sprites are colliding according to Bounding Box collision
    CCRect intersection;
 
    // Bounding box of the Two concerned sprites being saved
    CCRect r1 = spr1->boundingBox();
    CCRect r2 = spr2->boundingBox();
 
    // Look for simple bounding box collision
    if (r1.intersectsRect(r2)) {
        // If we're not checking for pixel perfect collisions, return true
        if (!pp) {
            return true;
        }
 
        float tempX;
        float tempY;
        float tempWidth;
        float tempHeight;
 
        //OPTIMIZE FURTHER
        //CONSIDER THE CASE WHEN ONE BOUDNING BOX IS COMPLETELY INSIDE ANOTHER BOUNDING BOX!
        if (r1.getMaxX() > r2.getMinX()) {
            tempX = r2.getMinX();
            tempWidth = r1.getMaxX() - r2.getMinX();
        } else {
            tempX = r1.getMinX();
            tempWidth = r2.getMaxX() - r1.getMinX();
        }
 
        if (r2.getMaxY() < r1.getMaxY()) {
            tempY = r1.getMinY();
            tempHeight = r2.getMaxY() - r1.getMinY();
        } else {
            tempY = r2.getMinY();
            tempHeight = r1.getMaxY() - r2.getMinY();
        }
 
        // We make the rectangle for the intersection area
        intersection = CCRectMake(tempX * CC_CONTENT_SCALE_FACTOR(), tempY * CC_CONTENT_SCALE_FACTOR(), tempWidth * CC_CONTENT_SCALE_FACTOR(), tempHeight * CC_CONTENT_SCALE_FACTOR());
 
        unsigned int x = intersection.origin.x;
        unsigned int y = intersection.origin.y;
        unsigned int w = intersection.size.width;
        unsigned int h = intersection.size.height;
 
        // Total pixels whose values we will get using glReadPixels depends on the Height and Width of the intersection area
        unsigned int numPixels = w * h;
 
        // Setting the custom shader to be used
        spr1->setShaderProgram(glProgram);
        spr2->setShaderProgram(glProgram);
        glProgram->use();
 
        // Clearing the Secondary Draw buffer of all previous values
        _rt->beginWithClear( 0, 0, 0, 0);
 
        // The below two values are being used in the custom shaders to set the value of RED and BLUE colors to be used
        glUniform1i(uniformColorRed, 255);
        glUniform1i(uniformColorBlue, 0);
 
        // The blend function is important we don't want the pixel value of the RED color being over-written by the BLUE color.
       // We want both the colors at a single pixel and hence get a PINK color (so that we have both the RED and BLUE pixels)
        spr1->setBlendFunc((ccBlendFunc){GL_SRC_ALPHA, GL_ONE});
 
        // The visit() function draws the sprite in the _rt draw buffer its a Cocos2d-x function
        spr1->visit();
 
        // Setting the shader program back to the default shader being used by our game        
spr1->setShaderProgram(CCShaderCache::sharedShaderCache()->programForKey(kCCShader_PositionTextureColor));
        // Setting the default blender function being used by the game
        spr1->setBlendFunc((ccBlendFunc){CC_BLEND_SRC, CC_BLEND_DST});
 
        // Setting new values for the same shader but for our second sprite as this time we want to have an all BLUE sprite
        glUniform1i(uniformColorRed, 0);
        glUniform1i(uniformColorBlue, 255);
        spr2->setBlendFunc((ccBlendFunc){GL_SRC_ALPHA, GL_ONE});
 
        spr2->visit();
 
        spr2->setShaderProgram(CCShaderCache::sharedShaderCache()->programForKey(kCCShader_PositionTextureColor));
        spr2->setBlendFunc((ccBlendFunc){CC_BLEND_SRC, CC_BLEND_DST});
 
        // Get color values of intersection area
        glReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
 
        _rt->end();
 
        // Read buffer
        unsigned int step = 1;
        for(unsigned int i=0; i<numPixels; i+=step) {
            ccColor4B color = buffer[i];
            // Here we check if a single pixel has both RED and BLUE pixels
            if (color.r > 0 && color.b > 0) {
                isColliding = true;
                break;
            }
        }
    }
    return isColliding;
}


 SolidColorShader.fsh

[html] view plaincopyprint?

1.  #ifdef GL_ES  

2.  precision lowp float;  

3.  #endif  

4.    

5.  varying vec2 v_texCoord;  

6.  uniform sampler2D u_texture;  

7.  uniform int u_color_red;  

8.  uniform int u_color_blue;  

9.    

10. void main()  

11. {  

12.     vec4 color = texture2D(u_texture, v_texCoord);  

13.     gl_FragColor = vec4(u_color_red, 0, u_color_blue, color.a);  

14.   

15. }  

#ifdef GL_ES
precision lowp float;
#endif
 
varying vec2 v_texCoord;
uniform sampler2D u_texture;
uniform int u_color_red;
uniform int u_color_blue;
 
void main()
{
  vec4 color = texture2D(u_texture, v_texCoord);
    gl_FragColor = vec4(u_color_red, 0, u_color_blue, color.a);
 
}


 SolidVertexShader.vsh

[html] view plaincopyprint?

1.  attribute vec4 a_position;                            

2.  attribute vec2 a_texCoord;                            

3.  attribute vec4 a_color;                               

4.    

5.  #ifdef GL_ES                                          

6.  varying lowp vec4 v_fragmentColor;                    

7.  varying mediump vec2 v_texCoord;                      

8.  #else                                                 

9.  varying vec4 v_fragmentColor;                         

10. varying vec2 v_texCoord;                              

11. #endif                                                

12.   

13. void main()                                           

14. {                                                     

15.     gl_Position = CC_MVPMatrix * a_position;          

16.     v_fragmentColor = a_color;                        

17.     v_texCoord = a_texCoord;                          

18. }  

attribute vec4 a_position;           
attribute vec2 a_texCoord;           
attribute vec4 a_color;            
 
#ifdef GL_ES               
varying lowp vec4 v_fragmentColor;        
varying mediump vec2 v_texCoord;        
#else                  
varying vec4 v_fragmentColor;         
varying vec2 v_texCoord;           
#endif                   
 
void main()                 
{                    
    gl_Position = CC_MVPMatrix * a_position;   
  v_fragmentColor = a_color;         
  v_texCoord = a_texCoord;         
}


For using the Collision Detection Class:

1. Initialize the CCRenderTexture object

[html] view plaincopyprint?

1.  _rt = CCRenderTexture::create(visibleSize.width * 2, visibleSize.height * 2);  

2.  _rt->setPosition(ccp(visibleSize.width, visibleSize.height));  

3.  _rt->retain();  

4.  _rt->setVisible(false);  

    _rt = CCRenderTexture::create(visibleSize.width * 2, visibleSize.height * 2);
    _rt->setPosition(ccp(visibleSize.width, visibleSize.height));
    _rt->retain();
    _rt->setVisible(false);


2. Call the Singleton function whenever collision detection required

[html] view plaincopyprint?

1.  if (CollisionDetection::GetInstance()->areTheSpritesColliding(pSprite, pCurrentSpriteToDrag, true, _rt)) {  

2.       //Code here  

3.  }