(第一次翻译国外的文章,好紧张,因为英语比较菜的缘故,翻译起来有些别扭。原文: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. }