(第一次翻译国外的文章,好紧张,因为英语比较菜的缘故,翻译起来有些别扭。原文:http://blog.csdn.net/shieryueqing)
我写这篇文章的原因是,我在StackOverflow中没有发现怎么做像素碰撞检测这个问题的答案,原以为会有很多像我一样的人在搜索着答案。在大部分的游戏中碰撞检测是重要的组成部分,它能够使用在子弹打中了敌人或者你撞到了墙上等等。当做游戏的碰撞检测的时候,我们需要根据游戏去选择其中一些检测的技术。几乎所有的游戏引擎和框架都使用“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. //CreatedbyMuditJajuon30/08/13.
4. //
5. //SINGLETONclassforcheckingPixelBasedCollisionDetection
6.
7. #ifndef__CollisionDetection__
8. #define__CollisionDetection__
9.
10. #include<iostream>
11. #include"cocos2d.h"
12.
13. USING_NS_CC;
14.
15. classCollisionDetection{
16. public:
17. //HandleforgettingtheSingletonObject
18. staticCollisionDetection*GetInstance();
19. //Functionsignatureforcheckingforcollisiondetectionspr1,spr2aretheconcernedsprites
20. //ppisbool,settotrueifPixelPerfectionCollisionisrequired.Elsesettofalse
21. //_rtisthesecondarybufferusedinoursystem
22. boolareTheSpritesColliding(CCSprite*spr1,CCSprite*spr2,boolpp,CCRenderTexture*_rt);
23. private:
24. staticCollisionDetection*instance;
25. CollisionDetection();
26.
27. //ValuesbelowareallrequiredforopenGLshading
28. CCGLProgram*glProgram;
29. ccColor4B*buffer;
30. intuniformColorRed;
31. intuniformColorBlue;
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. //CreatedbyMuditJajuon30/08/13.
4. //
5. //SINGLETONclassforcheckingPixelBasedCollisionDetection
6.
7. #include"CollisionDetection.h"
8. //SingletonInstancesettoNULLinitially
9. CollisionDetection*CollisionDetection::instance=NULL;
10.
11. //HandletogetSingletonInstance
12. CollisionDetection*CollisionDetection::GetInstance(){
13. if(instance==NULL){
14. instance=newCollisionDetection();
15. }
16. returninstance;
17. }
18.
19. //PrivateConstructorbeingcalledfromwithintheGetInstancehandle
20. CollisionDetection::CollisionDetection(){
21. //CodebelowtosetupshadersforuseinCocos2d-x
22. glProgram=newCCGLProgram();
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. //Alargebuffercreatedandre-usedagainandagaintostoreglReadPixelsdata
35. buffer=(ccColor4B*)malloc(sizeof(ccColor4B)*10000);
36. }
37.
38. boolCollisionDetection::areTheSpritesColliding(cocos2d::CCSprite*spr1,cocos2d::CCSprite*spr2,CCRenderTexture*_rt){
39. boolisColliding=false;
40.
41. //RectangleoftheintersectingareaifthespritesarecollidingaccordingtoBoundingBoxcollision
42. CCRectintersection;
43.
44. //BoundingBoxoftheTwoconcernedspritesbeingsaved
45. CCRectr1=spr1->boundingBox();
46. CCRectr2=spr2->boundingBox();
47.
48. //LookforsimpleboundingBoxcollision
49. if(r1.intersectsRect(r2)){
50. //Ifwe'renotcheckingforpixelperfectcollisions,returntrue
51. if(!pp){
52. returntrue;
53. }
54.
55. floattempX;
56. floattempY;
57. floattempWidth;
58. floattempHeight;
59.
60. //OPTIMIZEFURTHER
61. //CONSIDERTHECASEWHENONEBOUDNINGBoxISCOMPLETELYINSIDEANOTHERBOUNDINGBox!
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. //Wemaketherectanglefortheintersectionarea
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. unsignedintx=intersection.origin.x;
82. unsignedinty=intersection.origin.y;
83. unsignedintw=intersection.size.width;
84. unsignedinth=intersection.size.height;
85.
86. //TotalpixelswhosevalueswewillgetusingglReadPixelsdependsontheHeightandWidthoftheintersectionarea
87. unsignedintnumPixels=w*h;
88.
89. //Settingthecustomshadertobeused
90. spr1->setShaderProgram(glProgram);
91. spr2->setShaderProgram(glProgram);
92. glProgram->use();
93.
94. //ClearingtheSecondaryDrawbufferofallprevIoUsvalues
95. _rt->beginWithClear(0,0);
96.
97. //ThebelowtwovaluesarebeingusedinthecustomshaderstosetthevalueofREDandBLUEcolorstobeused
98. glUniform1i(uniformColorRed,255);
99. glUniform1i(uniformColorBlue,0);
100.
101. //Theblendfunctionisimportantwedon'twantthepixelvalueoftheREDcolorbeingover-writtenbytheBLUEcolor.
102. //WewantboththecolorsatasinglepixelandhencegetaPINKcolor(sothatwehaveboththeREDandBLUEpixels)
103. spr1->setBlendFunc((ccBlendFunc){GL_SRC_ALPHA,GL_ONE});
104.
105. //Thevisit()functiondrawsthespriteinthe_rtdrawbufferitsaCocos2d-xfunction
106. spr1->visit();
107.
108. //Settingtheshaderprogrambacktothedefaultshaderbeingusedbyourgame
109. spr1->setShaderProgram(CCShaderCache::sharedShaderCache()->programForKey(kCCShader_PositionTextureColor));
110. //Settingthedefaultblenderfunctionbeingusedbythegame
111. spr1->setBlendFunc((ccBlendFunc){CC_BLEND_SRC,CC_BLEND_DST});
112.
113. //SettingnewvaluesforthesameshaderbutforoursecondspriteasthistimewewanttohaveanallBLUEsprite
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. //Getcolorvaluesofintersectionarea
124. glReadPixels(x,y,w,h,GL_RGBA,GL_UNSIGNED_BYTE,buffer);
125.
126. _rt->end();
127.
128. //Readbuffer
129. unsignedintstep=1;
130. for(unsignedinti=0;i<numPixels;i+=step){
131. ccColor4Bcolor=buffer[i];
132. //HerewecheckifasinglepixelhasbothREDandBLUEpixels
133. if(color.r>0&&color.b>0){
134. isColliding=true;
135. break;
136. }
137. }
138. }
139. returnisColliding;
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,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);
// 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,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. #ifdefGL_ES
2. precisionlowpfloat;
3. #endif
4.
5. varyingvec2v_texCoord;
6. uniformsampler2Du_texture;
7. uniformintu_color_red;
8. uniformintu_color_blue;
9.
10. voidmain()
11. {
12. vec4color=texture2D(u_texture,v_texCoord);
13. gl_FragColor=vec4(u_color_red,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,color.a);
}
SolidVertexShader.vsh
[html] view plaincopyprint?
1. attributevec4a_position;
2. attributevec2a_texCoord;
3. attributevec4a_color;
4.
5. #ifdefGL_ES
6. varyinglowpvec4v_fragmentColor;
7. varyingmediumpvec2v_texCoord;
8. #else
9. varyingvec4v_fragmentColor;
10. varyingvec2v_texCoord;
11. #endif
12.
13. voidmain()
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. //Codehere
3. }