[Cocos2d-x相关教程来源于红孩儿的游戏编程之路CSDN博客地址:http://blog.csdn.net/honghaier
大家好,许久没有再上课,大家是不是还想念红孩儿的博文呢?哈哈,对不起了,红孩儿最近一直在忙着写新版的“红孩儿工具箱”,进度还算较快,新用工具箱做了一些动画特效,有兴趣的同学可以点击查看:
<1>《大掌门》九阴白骨爪文字特效动画
<2>色彩圈与咬人花
<3>将《名将》的刀手攻击GIF导入工具箱转为关健帧动画
广告回来,话回正题。本节我们来学习一下2.1版本的新功能:ClippingNode。什么是“ClippingNode”?在我看来,“ClippingNode”可以定义为利用模版遮罩来完成对Node进行区域剪切的技术。我们来打个比方,在寒冷的冬季,我们家里的窗户玻璃上常常凝结一层雾以致于我们无法看到玻璃外的景物,这时候我们常常会抹掉这层雾,当然,很多小朋友也会像红孩儿一样兴致勃勃的在抹这层雾时顺手写几笔“数风流人物,还看今朝!”,当雾被抹掉后,玻璃外的景色也就显露出来了。那么,如果我们把窗户玻璃当做显示景物的层,则玻璃上的这层雾就可称为模版遮罩了,实际上,本节的技术无非就是如何在这个模版遮罩上写出那几笔风流大字。
在Cocos2d-x中,对于Node进行模版遮罩功能的类被封装在一个称为“CCClippingNode”的类中。它是被放在libcocos2d中的misc_nodes中的。
打开CCClippingNode.h,看下源码:
#ifndef __MISCNODE_CCCLIPPING_NODE_H__ #define __MISCNODE_CCCLIPPING_NODE_H__ //用到结点头文件与OPENGL深度缓冲定义头文件 #include "base_nodes/CCNode.h" #include "CCGL.h" //使用Cocos2d命名空间 NS_CC_BEGIN // CCClippingNode类,我们可以认为它即是那个窗户玻璃。 class CC_DLL CCClippingNode : public CCNode { protected: //所用的模版缓冲遮罩结点,也就是那层玻璃上的雾像。 CCNode* m_pStencil; //ALPHA的测试参考值,用于进行ALPHA测试比较所用,一般比较算法为小于此值的像素直接会被舍弃。这样就可以实现图像的镂空。 GLfloat m_fAlphaThreshold; //这个值其实是指的遮罩运算是否按取反设置。 bool m_bInverted; public: //静态创建函数,创建相应的结点。 static CCClippingNode* create(); //静态创建函数,参数指定所用的模版缓冲遮罩结点。 static CCClippingNode* create(CCNode *pStencil); //析构 virtual ~CCClippingNode(); //基础初始化函数。 virtual bool init(); //扩展初始化函数,参数指定所用的模版缓冲遮罩结点。 virtual bool init(CCNode *pStencil); //重载基类CCNode的相应函数。 virtual void onEnter(); virtual void onEnterTransitionDidFinish(); virtual void onExitTransitionDidStart(); virtual void onExit(); virtual void visit(); //取得模版缓冲遮罩结点。 CCNode* getStencil() const; //设置模版缓冲遮罩结点。 void setStencil(CCNode *pStencil); //取得ALPHA的测试参考值。 GLfloat getAlphaThreshold() const; //设置ALPHA的测试参考值。 void setAlphaThreshold(GLfloat fAlphaThreshold); //取得遮罩运算是否按取反设置。 bool isInverted() const; //设置遮罩运算是否按取反设置。 void setInverted(bool bInverted); private: //构造 CCClippingNode(); }; NS_CC_END #endif // __MISCNODE_CCCLIPPING_NODE_H__
嗯,不错,这个类除了设置模版缓冲遮罩结果,还可以做ALPHA镂空和遮罩的取反运算。可以做出很多很棒的效果了。
继续看CPP:
//包含头文件 #include "CCClippingNode.h" //使用数学库中的矩阵相关头文件,矩阵压栈要用。 #include "kazmath/GL/matrix.h" //使用Shader的相关头文件,ALPHA镂空要用。 #include "shaders/CCGLProgram.h" #include "shaders/CCShaderCache.h" //设备头文件。 #include "CCDirector.h" //位置点结构处理相关头文件 #include "support/CCPointExtension.h" //绘制图形函数相关头文件 #include "draw_nodes/CCDrawingPrimitives.h" //使用Cocos2d命名空间 NS_CC_BEGIN //这里定义一个静态变量值,用于保存当前设备运行程序时模版缓冲的位数,这个位数由设备的深度缓冲区格式决定,一般深度缓冲用D24S8,即24位深度缓冲+8位模版缓冲,所以这个值一般为8。 static GLint g_sStencilBits = -1; //静态函数,为一个结点设置所用的Shader代码片段。 static void setProgram(CCNode *n, CCGLProgram *p) { //调用相应的函数来为结点设置Shader。 n->setShaderProgram(p); //如果没有子结点直接返回。 if (!n->getChildren()) return; //如果有子结点,遍历子结点容器为每个子结点设置相应的Shader代码片段。 CCObject* pObj = NULL; CCARRAY_FOREACH(n->getChildren(), pObj) { setProgram((CCNode*)pObj, p); } } //私有构造函数,做些变量初始化工作。 CCClippingNode::CCClippingNode() : m_pStencil(NULL) , m_bInverted(false) , m_fAlphaThreshold(0.0f) {} //析构函数,既然要离开这个世界了,那就别再占用人家外部创建的模版缓冲结点了,SO,对占用的模版缓冲结点的计数器减一, CCClippingNode::~CCClippingNode() { CC_SAFE_RELEASE(m_pStencil); } //静态创建函数。 CCClippingNode* CCClippingNode::create() { //new出一个新的CCClippingNode。然后初始化并设置交由内存管理器进行相应的计数器管理。 CCClippingNode *pRet = new CCClippingNode(); if (pRet && pRet->init()) { pRet->autorelease(); } else { //如果失败,删除并置空 CC_SAFE_DELETE(pRet); } //返回结果。 return pRet; } //静态创建函数,通过参数指定相应的模版缓冲遮罩结点。 CCClippingNode* CCClippingNode::create(CCNode *pStencil) { //new出一个新的CCClippingNode。然后用参数进行初始化并设置交由内存管理器进行相应的计数器管理。 CCClippingNode *pRet = new CCClippingNode(); if (pRet && pRet->init(pStencil)) { pRet->autorelease(); } else { //如果失败,删除并置空 CC_SAFE_DELETE(pRet); } //返回结果。 return pRet; } //基础初始化函数。 bool CCClippingNode::init() { //传NULL调用带参数的初始化函数。 return init(NULL); } //扩展初始化函数,参数指定所用的模版缓冲遮罩结点。 bool CCClippingNode::init(CCNode *pStencil) { //老的模版缓冲遮罩结点要更换为新的,则先放弃对老的模版缓冲遮罩结点的占用,计数器减一。 CC_SAFE_RELEASE(m_pStencil); //重设指针 m_pStencil = pStencil; //占用新的模版缓冲遮罩结点,计数器加一 CC_SAFE_RETAIN(m_pStencil); //ALPHA参考值设为1 m_fAlphaThreshold = 1; //不需用反向运算 m_bInverted = false; //静态布尔变量,用于记录第一次时取出当前设备运行程序时的模版缓冲区位数。 static bool once = true; if (once) { //OPENGL相应的接口函数取得程序运行在当前设备的模版缓冲区位数。 glGetIntegerv(GL_STENCIL_BITS, &g_sStencilBits); //如果小于0,则代表设备不支持模版缓冲 if (g_sStencilBits <= 0) { CCLOG("Stencil buffer is not enabled."); } once = false; } return true; } //重载基类CCNode的相应函数。 void CCClippingNode::onEnter() { CCNode::onEnter(); //模版缓冲遮罩结点一并调用。 m_pStencil->onEnter(); } void CCClippingNode::onEnterTransitionDidFinish() { CCNode::onEnterTransitionDidFinish(); //模版缓冲遮罩结点一并调用。 m_pStencil->onEnterTransitionDidFinish(); } void CCClippingNode::onExitTransitionDidStart() { m_pStencil->onExitTransitionDidStart(); //模版缓冲遮罩结点一并调用。 CCNode::onExitTransitionDidStart(); } void CCClippingNode::onExit() { //模版缓冲遮罩结点一并调用。 m_pStencil->onExit(); CCNode::onExit(); } //重载CCNode显示更新时调用的函数。在看下面这个函数前请先了解一下模版参数运算值的定义,可参考:http://www.cnblogs.com/chesterlee/articles/2014334.html void CCClippingNode::visit() { // 如果不支持模版缓冲,直接调用基类相应函数。 if (g_sStencilBits < 1) { // draw everything, as if there where no stencil CCNode::visit(); return; } //如果没有设置模版缓冲遮罩结点,那也没什么可做,还是直接调用基类相应函数。 if (!m_pStencil || !m_pStencil->isVisible()) { if (m_bInverted) { // draw everything CCNode::visit(); } return; } //定义静态变量,用于记录当前程序一共用到的模版缓冲遮罩数量。 static GLint layer = -1; // 如果这个数量已经把模版缓冲的位数都占光了,那也洗洗上床睡吧。怎么才会占光呢?后面再讲。 if (layer + 1 == g_sStencilBits) { // warn once static bool once = true; if (once) { char warning[200]; snprintf(warning, 50, "Nesting more than %d stencils is not supported. Everything will be drawn without stencil for this node and its childs.", g_sStencilBits); CCLOG(warning); once = false; } // draw everything, as if there where no stencil CCNode::visit(); return; } //如果还可以继续使用新的模版缓冲位,那可以干正事了。 //对数量值加一,也就是占下相应的模版缓冲位。 layer++; // 计算出当前模版缓冲位的参数值。 GLint mask_layer = 0x1 << layer; // 计算出当前模版缓冲位的参数值减1.那结果值肯定是当前位数以下的值都取反,即掩码值。 GLint mask_layer_l = mask_layer - 1; // 上面两个值做或运算的结果值。 GLint mask_layer_le = mask_layer | mask_layer_l; // 这里是模版运算的一大堆相关值。 //是否使用模版缓冲。 GLboolean currentStencilEnabled = GL_FALSE; //写入的模版参数 GLuint currentStencilWriteMask = ~0; //模版运算的判断 GLenum currentStencilFunc = GL_ALWAYS; GLint currentStencilRef = 0; GLuint currentStencilValueMask = ~0; GLenum currentStencilFail = GL_KEEP; GLenum currentStencilPassDepthFail = GL_KEEP; GLenum currentStencilPassDepthPass = GL_KEEP; currentStencilEnabled = glIsEnabled(GL_STENCIL_TEST); //取得上面的这些变量值。 glGetIntegerv(GL_STENCIL_WRITEMASK, (GLint *)¤tStencilWriteMask); glGetIntegerv(GL_STENCIL_FUNC, (GLint *)¤tStencilFunc); glGetIntegerv(GL_STENCIL_REF, ¤tStencilRef); glGetIntegerv(GL_STENCIL_VALUE_MASK, (GLint *)¤tStencilValueMask); glGetIntegerv(GL_STENCIL_FAIL, (GLint *)¤tStencilFail); glGetIntegerv(GL_STENCIL_PASS_DEPTH_FAIL, (GLint *)¤tStencilPassDepthFail); glGetIntegerv(GL_STENCIL_PASS_DEPTH_PASS, (GLint *)¤tStencilPassDepthPass); //开始模版测试。 glEnable(GL_STENCIL_TEST); //OPENGL检错。 CHECK_GL_ERROR_DEBUG(); //设置模版缓冲的掩码值。 glStencilMask(mask_layer); //取得是否可以定入模版掩码参数。 GLboolean currentDepthWriteMask = GL_TRUE; glGetBooleanv(GL_DEPTH_WRITEMASK, ¤tDepthWriteMask); //禁止写入深度缓冲。 glDepthMask(GL_FALSE); //下面这一句是指永远不能通过测试。 glStencilFunc(GL_NEVER, mask_layer, mask_layer); //根据是否反向运算来决定如果测试不能通过时是否将相应像素位置的模版缓冲位的值设为0。 glStencilOp(!m_bInverted ? GL_ZERO : GL_REPLACE, GL_KEEP, GL_KEEP); //用白色绘制一下屏幕矩形,因为都不能通过嘛,所以就全屏的模版缓冲位的值都被设为0。 ccDrawSolidRect(CCPointZero, ccpFromSize(CCDirector::sharedDirector()->getWinSize()), ccc4f(1, 1, 1, 1)); //永远不能通过测试。 glStencilFunc(GL_NEVER, mask_layer, mask_layer); //根据是否反向运算来决定如果测试不能通过时是否将相应像素位置的模版缓冲位的值设为当前参数值。 glStencilOp(!m_bInverted ? GL_REPLACE : GL_ZERO, GL_KEEP, GL_KEEP); //平台相关处理,其实就是开启ALPHA测试,看ALPHA参数值是否有必要做ALPHA镂空处理。 #if (CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WINDOWS || CC_TARGET_PLATFORM == CC_PLATFORM_LINUX) GLboolean currentAlphaTestEnabled = GL_FALSE; GLenum currentAlphaTestFunc = GL_ALWAYS; GLclampf currentAlphaTestRef = 1; #endif if (m_fAlphaThreshold < 1) { //如果ALPHA参数值小于1,则开启ALPHA镂空处理。根据平台类型选择是使用固定管线还是Shader来进行ALPHA测试处理。 #if (CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WINDOWS || CC_TARGET_PLATFORM == CC_PLATFORM_LINUX) // manually save the alpha test state currentAlphaTestEnabled = glIsEnabled(GL_ALPHA_TEST); glGetIntegerv(GL_ALPHA_TEST_FUNC, (GLint *)¤tAlphaTestFunc); glGetFloatv(GL_ALPHA_TEST_REF, ¤tAlphaTestRef); // enable alpha testing glEnable(GL_ALPHA_TEST); // check for OpenGL error while enabling alpha test CHECK_GL_ERROR_DEBUG(); // pixel will be drawn only if greater than an alpha threshold glAlphaFunc(GL_GREATER, m_fAlphaThreshold); #else // since glAlphaTest do not exists in OES, use a shader that writes // pixel only if greater than an alpha threshold CCGLProgram *program = CCShaderCache::sharedShaderCache()->programForKey(kCCShader_PositionTextureColorAlphaTest); GLint alphaValueLocation = glGetUniformLocation(program->getProgram(), kCCUniformAlphaTestValue); // set our alphaThreshold program->setUniformLocationWith1f(alphaValueLocation, m_fAlphaThreshold); // we need to recursively apply this shader to all the nodes in the stencil node // XXX: we should have a way to apply shader to all nodes without having to do this setProgram(m_pStencil, program); #endif } //将当前环境所用矩阵压栈后应用相应的矩阵变化再调用模版遮罩精灵结点的渲染函数,完事了矩阵出栈恢复环境所用矩阵。 kmGLPushMatrix(); transform(); m_pStencil->visit(); kmGLPopMatrix(); //恢复ALPHA测试前的设置。 if (m_fAlphaThreshold < 1) { #if (CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WINDOWS || CC_TARGET_PLATFORM == CC_PLATFORM_LINUX) // manually restore the alpha test state glAlphaFunc(currentAlphaTestFunc, currentAlphaTestRef); if (!currentAlphaTestEnabled) { glDisable(GL_ALPHA_TEST); } #else // XXX: we need to find a way to restore the shaders of the stencil node and its childs #endif } // 恢复深度写入 glDepthMask(currentDepthWriteMask); //if (currentDepthTestEnabled) { // glEnable(GL_DEPTH_TEST); //} //这里设置如果当前模版缓冲中的模版值与运算结果相等则保留相应像素。这里为什么要用mask_layer_le而不是mask_layer呢?下面再说。 glStencilFunc(GL_EQUAL, mask_layer_le, mask_layer_le); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); //调用基类渲染函数,做为窗外风景的子类结点都受到模板结果的裁剪。如果子结点也是ClippingNode,则可能会继续进行模板运算,那么模板的位数layer值就必须加1,使用新的模版缓冲位来进行测试,可能造成模版缓冲的位数都占光。而且位数的增加在模版运算时要考虑进去,所以上面的模版缓冲运算的参数是mask_layer_le而不是mask_layer。 CCNode::visit(); //恢复相应的模版缓冲运算设置 glStencilFunc(currentStencilFunc, currentStencilRef, currentStencilValueMask); glStencilOp(currentStencilFail, currentStencilPassDepthFail, currentStencilPassDepthPass); glStencilMask(currentStencilWriteMask); if (!currentStencilEnabled) { glDisable(GL_STENCIL_TEST); } //结束使用当前模版缓冲位数,就减1.以保证下次还能正常使用。 layer--; } //取得模版缓冲遮罩结点。 CCNode* CCClippingNode::getStencil() const { return m_pStencil; } //设置模版缓冲遮罩结点。 void CCClippingNode::setStencil(CCNode *pStencil) { //老的模版缓冲遮罩结点要更换为新的,则先放弃对老的模版缓冲遮罩结点的占用,计数器减一。 CC_SAFE_RELEASE(m_pStencil); //重设指针 m_pStencil = pStencil; //占用新的模版缓冲遮罩结点,计数器加一 CC_SAFE_RETAIN(m_pStencil); } //取得ALPHA测试参考值。 GLfloat CCClippingNode::getAlphaThreshold() const { return m_fAlphaThreshold; } //设置ALPHA测试参考值。 void CCClippingNode::setAlphaThreshold(GLfloat fAlphaThreshold) { m_fAlphaThreshold = fAlphaThreshold; } //取得遮罩运算是否按取反设置。 bool CCClippingNode::isInverted() const { return m_bInverted; } //设置遮罩运算是否按取反设置。 void CCClippingNode::setInverted(bool bInverted) { m_bInverted = bInverted; } NS_CC_END
结束。应该讲的还算清楚吧。下面我们来看一下Cocos2d-x中的实例是如何应用的。
打开TestCpp中的ClippingNodeTest运行一下:
第一个实例:Scroll View Demo
截图:
说明:
用鼠标可以拖动被遮住的精灵结点移动,同时看到模版遮罩区域。
原理:
通过遮罩结点遮住相应区域中的精灵结点,触屏事件对精灵进行移动。
类代码:
//这是一个剪切结点演示的层基类,用于派生所用到的各个演示层。 class BaseClippingNodeTest : public CCLayer { public: //析构 ~BaseClippingNodeTest(); //初始化 virtual bool init(); //取得标题 virtual std::string title(); //取得幅标题 virtual std::string subtitle(); //加载层时调用的处理 virtual void setup(); //响应按钮进行前一个演示。 void backCallback(CCObject* sender); //响应按钮进行下一个演示。 void nextCallback(CCObject* sender); //响应按钮重启当前演示。 void restartCallback(CCObject* sender); }; //第一个演示类 class ScrollViewDemo : public BaseClippingNodeTest { public: virtual std::string title(); virtual std::string subtitle(); virtual void setup(); //触屏事件响应 //按下屏幕时的响应处理 virtual void ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent); //按下后在屏幕上移动时的响应处理 virtual void ccTouchesMoved(CCSet *pTouches, CCEvent *pEvent); //松开时的响应处理 virtual void ccTouchesEnded(CCSet *pTouches, CCEvent *pEvent); private: //被遮罩遮住的图形是否在被拖放中 bool m_bScrolling; //用于保存上次的位置坐标值,在每次移动时用于计算偏移用。 CCPoint m_lastPoint; };
对应的CPP:
//定义一些宏用于设置结点的查询标记名 enum { kTagTitleLabel = 1, kTagSubtitleLabel = 2, kTagStencilNode = 100, kTagClipperNode = 101, kTagContentNode = 102, }; //静态创建宏,相当于new出一个相应的类的实例对象,返回实例指针。 TESTLAYER_CREATE_FUNC(ScrollViewDemo); TESTLAYER_CREATE_FUNC(HoleDemo); TESTLAYER_CREATE_FUNC(ShapeTest); TESTLAYER_CREATE_FUNC(ShapeInvertedTest); TESTLAYER_CREATE_FUNC(SpriteTest); TESTLAYER_CREATE_FUNC(SpriteNoAlphaTest); TESTLAYER_CREATE_FUNC(SpriteInvertedTest); TESTLAYER_CREATE_FUNC(NestedTest); TESTLAYER_CREATE_FUNC(RawStencilBufferTest); TESTLAYER_CREATE_FUNC(RawStencilBufferTest2); TESTLAYER_CREATE_FUNC(RawStencilBufferTest3); TESTLAYER_CREATE_FUNC(RawStencilBufferTest4); TESTLAYER_CREATE_FUNC(RawStencilBufferTest5); TESTLAYER_CREATE_FUNC(RawStencilBufferTest6); //函数指针数组。用于存储创建类实例对象的函数。 static NEWTESTFUNC createFunctions[] = { CF(ScrollViewDemo), CF(HoleDemo), CF(ShapeTest), CF(ShapeInvertedTest), CF(SpriteTest), CF(SpriteNoAlphaTest), CF(SpriteInvertedTest), CF(NestedTest), CF(RawStencilBufferTest), CF(RawStencilBufferTest2), CF(RawStencilBufferTest3), CF(RawStencilBufferTest4), CF(RawStencilBufferTest5), CF(RawStencilBufferTest6) }; //静态场景索引值。 static int sceneIdx=-1; //最大的演示层的ID,等于演示层的数量 #define MAX_LAYER (sizeof(createFunctions) / sizeof(createFunctions[0])) //创建下一个演示层并返回。 static CCLayer* nextAction() { sceneIdx++; sceneIdx = sceneIdx % MAX_LAYER; CCLayer* pLayer = (createFunctions[sceneIdx])(); pLayer->init(); pLayer->autorelease(); return pLayer; } //创建上一个演示层并返回。 static CCLayer* backAction() { sceneIdx--; int total = MAX_LAYER; if( sceneIdx < 0 ) sceneIdx += total; CCLayer* pLayer = (createFunctions[sceneIdx])(); pLayer->init(); pLayer->autorelease(); return pLayer; } //创建当前演示层并返回。 static CCLayer* restartAction() { CCLayer* pLayer = (createFunctions[sceneIdx])(); pLayer->init(); pLayer->autorelease(); return pLayer; } //剪切结点演示的层基类初始化函数 bool BaseClippingNodeTest::init() { //调用层基类的相应函数。 if (CCLayer::init()) { //取得屏幕大小。 CCSize s = CCDirector::sharedDirector()->getWinSize(); //创建一个背景网络图精灵结点,设置锚点和位置后放入当前层中,Z值在当前层之下,背景嘛,肯定放在最下面了。 CCSprite *background = CCSprite::create(s_back3); background->setAnchorPoint( CCPointZero ); background->setPosition( CCPointZero ); this->addChild(background, -1); //创建标题文字 CCLabelTTF *label = CCLabelTTF::create(this->title().c_str(), "Arial", 32); this->addChild(label, 1, kTagTitleLabel); label->setPosition( ccp(s.width / 2, s.height - 50)); //创建幅标题文字。 std::string subtitleText = this->subtitle(); if (subtitleText.length() > 0) { CCLabelTTF *subtitle = CCLabelTTF::create(subtitleText.c_str(), "Thonburi", 16); this->addChild(subtitle, 1, kTagSubtitleLabel); subtitle->setPosition(ccp(s.width / 2, s.height - 80)); } //创建控制演示的菜单按钮。 CCMenuItemImage *item1 = CCMenuItemImage::create(s_pPathB1, s_pPathB2, this, menu_selector(BaseClippingNodeTest::backCallback)); CCMenuItemImage *item2 = CCMenuItemImage::create(s_pPathR1, s_pPathR2, this, menu_selector(BaseClippingNodeTest::restartCallback)); CCMenuItemImage *item3 = CCMenuItemImage::create(s_pPathF1, s_pPathF2, this, menu_selector(BaseClippingNodeTest::nextCallback)); CCMenu *menu = CCMenu::create(item1, item2, item3, NULL); menu->setPosition( CCPointZero ); item1->setPosition( ccp(s.width / 2 - item2->getContentSize().width * 2, item2->getContentSize().height / 2)); item2->setPosition( ccp(s.width / 2, item2->getContentSize().height / 2)); item3->setPosition( ccp(s.width / 2 + item2->getContentSize().width * 2, item2->getContentSize().height / 2)); this->addChild(menu, 1); this->setup(); return true; } return false; } //析构 BaseClippingNodeTest::~BaseClippingNodeTest() { CCTextureCache::sharedTextureCache()->removeUnusedTextures(); } //取得标题 std::string BaseClippingNodeTest::title() { return "Clipping Demo"; } //取得幅标题 std::string BaseClippingNodeTest::subtitle() { return ""; } //重新启动当前的演示。 void BaseClippingNodeTest::restartCallback(CCObject* sender) { CCScene *s = new ClippingNodeTestScene(); s->addChild(restartAction()); CCDirector::sharedDirector()->replaceScene(s); s->release(); } //进行下一个演示。 void BaseClippingNodeTest::nextCallback(CCObject* sender) { CCScene *s = new ClippingNodeTestScene(); s->addChild(nextAction()); CCDirector::sharedDirector()->replaceScene(s); s->release(); } //返回上一个演示。 void BaseClippingNodeTest::backCallback(CCObject* sender) { CCScene *s = new ClippingNodeTestScene(); s->addChild(backAction()); CCDirector::sharedDirector()->replaceScene(s); s->release(); } //加载层时调用的处理 void BaseClippingNodeTest::setup() { }
第一个演示层的相关处理:
//取得标题。 std::string ScrollViewDemo::title() { return "Scroll View Demo"; } //取得幅标题。 std::string ScrollViewDemo::subtitle() { return "Move/drag to scroll the content"; } //加载层时调用的处理 void ScrollViewDemo::setup() { //创建一个CCClippingNode。 CCClippingNode *clipper = CCClippingNode::create(); //设置其标记名称 clipper->setTag( kTagClipperNode ); //设置大小 clipper->setContentSize( CCSizeMake(200, 200) ); //设置锚点 clipper->setAnchorPoint( ccp(0.5, 0.5) ); //设置位置 clipper->setPosition( ccp(this->getContentSize().width / 2, this->getContentSize().height / 2) ); //设置此层进行无限循环的动画。每秒循环45度。 clipper->runAction(CCRepeatForever::create(CCRotateBy::create(1, 45))); //当此层加入到当前层中。 this->addChild(clipper); //创建一个CCDrawNode。注:CCDrawNode是一个用于图形绘制的结点类,它提供了几个方便的图形绘制函数,用于点,线段,区域的图形绘制。 CCDrawNode *stencil = CCDrawNode::create(); //创建一个矩形并填充为clipper的大小。 CCPoint rectangle[4]; rectangle[0] = ccp(0, 0); rectangle[1] = ccp(clipper->getContentSize().width, 0); rectangle[2] = ccp(clipper->getContentSize().width, clipper->getContentSize().height); rectangle[3] = ccp(0, clipper->getContentSize().height); //由图形绘制结点来调用区域绘制函数将白色填充相应的矩形区域。 ccColor4F white = {1, 1, 1, 1}; stencil->drawPolygon(rectangle, 4, white, 1, white); //将图形绘制结点设置为clipper的模版缓冲遮罩结点。 clipper->setStencil(stencil); //创建一个被遮罩遮住的精灵结点。 CCSprite *content = CCSprite::create(s_back2); //设置其标记名称。 content->setTag( kTagContentNode ); //设置锚点。 content->setAnchorPoint( ccp(0.5, 0.5) ); //设置位置,就放在clipper的中心位置。 content->setPosition( ccp(clipper->getContentSize().width / 2, clipper->getContentSize().height / 2) ); //放入到clipper中。 clipper->addChild(content); //拖动标记为false。 m_bScrolling = false; //设置当前层响应触屏 this->setTouchEnabled(true); } //按下事件被响应时的处理 void ScrollViewDemo::ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent) { //取得触点,取得ClippingNode CCTouch *touch = (CCTouch*)pTouches->anyObject(); CCNode *clipper = this->getChildByTag(kTagClipperNode); //计算出点击在ClippingNode上的坐标位置,注意啊,是“点击在ClippingNode上的坐标位置”。不是屏幕坐标。 CCPoint point = clipper->convertToNodeSpace(CCDirector::sharedDirector()->convertToGL(touch->getLocationInView())); //取得ClippingNode的矩形区域 CCRect rect = CCRectMake(0, 0, clipper->getContentSize().width, clipper->getContentSize().height); //通过判断点是否在区域里来设置拖动标记 m_bScrolling = rect.containsPoint(point); //记录当前点。 m_lastPoint = point; } //触点移动时的事件响应处理。 void ScrollViewDemo::ccTouchesMoved(CCSet *pTouches, CCEvent *pEvent) { //如果触点在按下时不在ClippingNode区域内,那咱白忙了,赶早回家上床休息。 if (!m_bScrolling) return; //如果在区域内,按之前方法取得触点在ClippingNode中的位置坐标。 CCTouch *touch = (CCTouch*)pTouches->anyObject(); CCNode *clipper = this->getChildByTag(kTagClipperNode); CCPoint point = clipper->convertToNodeSpace(CCDirector::sharedDirector()->convertToGL(touch->getLocationInView())); //通过坐标之差来取得每次触点移动的偏移。 CCPoint diff = ccpSub(point, m_lastPoint); //找到被遮罩遮住的精灵结点并移动相应偏移。 CCNode *content = clipper->getChildByTag(kTagContentNode); content->setPosition( ccpAdd(content->getPosition(), diff) ); //故计重施。 m_lastPoint = point; } //松开触点时的事件响应处理。 void ScrollViewDemo::ccTouchesEnded(CCSet *pTouches, CCEvent *pEvent) { //如果触点在按下时不在ClippingNode区域内,那咱又白忙了,继续回家上床休息。 if (!m_bScrolling) return; //重置这个用于判断的变量。 m_bScrolling = false; }
第二个实例:Hole Demo
截图:
说明:
对精灵结点点击鼠标,可以产生枪击它的效果并留下你罪恶的证据。看点是圆形弹孔的镂空。
原理:
此实例用到了两层模版遮罩处理,第一层是弹孔遮罩,用弹孔图遮住弹痕图。
实际使用时并不会为每个子弹都创建一个模版遮罩结点,而是将所有的弹孔放在一个结点中,并用此结点做为模板遮罩。
第二层是背景图的区域遮罩,让脱靶的子弹不产生弹孔。
类代码:
class HoleDemo : public BaseClippingNodeTest { public: //析构 ~HoleDemo(); //重载基类的相应函数。 virtual void setup(); virtual std::string title(); virtual std::string subtitle(); //在相应位置进行枪击处理 void pokeHoleAtPoint(CCPoint point); //触点按下时的响应 virtual void ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent); private: //区域裁剪结点。 CCClippingNode* m_pOuterClipper; //弹痕精灵结点。 CCNode* m_pHoles; //弹孔精灵结点。 CCNode* m_pHolesStencil; }; //析构 HoleDemo::~HoleDemo() { //释放占用的结点 CC_SAFE_RELEASE(m_pOuterClipper); CC_SAFE_RELEASE(m_pHoles); CC_SAFE_RELEASE(m_pHolesStencil); } //标题。 std::string HoleDemo::title() { return "Hole Demo"; } //幅标题。 std::string HoleDemo::subtitle() { return "Touch/click to poke holes"; } //加载当前层时的处理。 void HoleDemo::setup() { //创建一个背景图结点,设置锚点和缩放值 。 CCSprite *target = CCSprite::create(s_pPathBlock); target->setAnchorPoint(CCPointZero); target->setScale(3); //创建ClippingNode m_pOuterClipper = CCClippingNode::create(); //手动引用计数器加一。 m_pOuterClipper->retain(); //取得一个单位矩阵 CCAffineTransform tranform = CCAffineTransformMakeIdentity(); //对矩阵进行X,Y方向按背景图结点相应缩放的处理 tranform = CCAffineTransformScale(tranform, target->getScale(), target->getScale()); //设置大小。 m_pOuterClipper->setContentSize( CCSizeApplyAffineTransform(target->getContentSize(), tranform)); //设置锚点在中心。 m_pOuterClipper->setAnchorPoint( ccp(0.5, 0.5) ); //放置在背景层结点的中心。 m_pOuterClipper->setPosition( ccpMult(ccpFromSize(this->getContentSize()), 0.5f) ); //运行无限循环旋转的动画,一秒旋转45度。 m_pOuterClipper->runAction(CCRepeatForever::create(CCRotateBy::create(1, 45))); //将背景图结点设置为此ClippingNode的模版缓冲遮罩结点。 m_pOuterClipper->setStencil( target ); //创建另一个ClippingNode CCClippingNode *holesClipper = CCClippingNode::create(); //设置它在模版缓冲运算时按反向处理。 holesClipper->setInverted(true); //设置ALPHA镂空的参考值。 holesClipper->setAlphaThreshold( 0.05f ); //将结点精灵放入到这个ClippingNode中。 holesClipper->addChild(target); //创建用于包含所有弹痕的结点pHoles。 m_pHoles = CCNode::create(); //手动引用计数器加一。 m_pHoles->retain(); //将pHoles放入到ClippingNode中做为要遮挡的结点。 holesClipper->addChild(m_pHoles); //再创建一个用于包含所有弹孔的结点pHolesStencil。 m_pHolesStencil = CCNode::create(); //手动引用计数器加一。 m_pHolesStencil->retain(); //ClippingNode设置pHolesStencil做为模版遮罩结点。 holesClipper->setStencil( m_pHolesStencil); //再将第二个创建的ClippingNode放入第一个创建的ClippingNode做为被遮罩影响的结点。大家想想前面说的最大缓冲位数量,这里就会用到。 m_pOuterClipper->addChild(holesClipper); //将第一个ClippingNode放入当前层中。 this->addChild(m_pOuterClipper); //设置当前层响应事件处理。 this->setTouchEnabled(true); } //在相应位置进行枪击处理。 void HoleDemo::pokeHoleAtPoint(CCPoint point) { //缩放和旋转参数。 float scale = CCRANDOM_0_1() * 0.2 + 0.9; float rotation = CCRANDOM_0_1() * 360; //创建一个子弹痕结点。 CCSprite *hole = CCSprite::create("Images/hole_effect.png"); //设置位置。 hole->setPosition( point ); //设置旋转。 hole->setRotation( rotation ); //设置缩放。 hole->setScale( scale ); //将弹痕结点放到pHoles中。 m_pHoles->addChild(hole); //创建一个弹孔精灵结点用于模版遮罩 CCSprite *holeStencil = CCSprite::create("Images/hole_stencil.png"); //设置位置和旋转。 holeStencil->setPosition( point ); holeStencil->setRotation( rotation ); holeStencil->setScale( scale ); //将弹孔结点放到pHolesStencil中。 m_pHolesStencil->addChild(holeStencil); //让m_pOuterClipper运行一个短时间缩放并恢复的动画,模拟被击中后的闪动效果,还真是有模有样啊。 m_pOuterClipper->runAction(CCSequence::createWithTwoActions(CCScaleBy::create(0.05f, 0.95f), CCScaleTo::create(0.125f, 1))); } //触点按下时的事件响应处理。 void HoleDemo::ccTouchesBegan(CCSet* touches, CCEvent* event) { //取得触点 CCTouch *touch = (CCTouch *)touches->anyObject(); //取得点击在m_pOuterClipper上的位置。 CCPoint point = m_pOuterClipper->convertToNodeSpace(CCDirector::sharedDirector()->convertToGL(touch->getLocationInView())); //判断是否点击在其上。 CCRect rect = CCRectMake(0, 0, m_pOuterClipper->getContentSize().width, m_pOuterClipper->getContentSize().height); //如果没点中,好吧,脱靶,你知道该去哪里面闭思过。 if (!rect.containsPoint(point)) return; //击中了,生成弹孔。 this->pokeHoleAtPoint(point); }
第三个实例:ShapeTest
截图:
说明:
男猪脚不断的放大缩小,被一个旋转的三角形进行模板遮罩的裁切。
原理:
创建一个三角形结点做为模板遮罩结点并旋转,男猪脚精灵被这个三角形结点重合的部分可以到到,其余部分像素会被剔除。
类代码
//又一个用于演示的基类 class BasicTest : public BaseClippingNodeTest { public: //基类相应虚函数 virtual std::string title(); virtual std::string subtitle(); virtual void setup(); //创建一个无限循环旋转动画 virtual CCAction* actionRotate(); //创建一个无限循环放大再缩小的动画。 virtual CCAction* actionScale(); //创建一个图形动画 virtual CCDrawNode* shape(); //创建一个男主角。 virtual CCSprite* grossini(); //创建所用的模版缓冲遮罩结点。 virtual CCNode* stencil(); //创建所用的CCClippingNode。 virtual CCClippingNode* clipper(); //创建被遮挡的结点。 virtual CCNode* content(); }; 对应的CPP: //取得标题 std::string BasicTest::title() { return "Basic Test"; } //取得幅标题。 std::string BasicTest::subtitle() { return ""; } //加载当前层时的处理 void BasicTest::setup() { //取得屏幕大小 CCSize s = CCDirector::sharedDirector()->getWinSize(); //创建模版遮罩结点并设置相应位置。 CCNode *stencil = this->stencil(); stencil->setTag( kTagStencilNode ); stencil->setPosition( ccp(50, 50) ); //创建所用的CCClippingNode并初始化。 CCClippingNode *clipper = this->clipper(); clipper->setTag( kTagClipperNode ); clipper->setAnchorPoint(ccp(0.5, 0.5)); clipper->setPosition( ccp(s.width / 2 - 50, s.height / 2 - 50) ); //设置其使用的模版遮罩结点 clipper->setStencil(stencil); //放入当前层。 this->addChild(clipper); //创建被遮挡的结点. CCNode *content = this->content(); content->setPosition( ccp(50, 50) ); clipper->addChild(content); } //创建一个无限循环旋转动画 CCAction* BasicTest::actionRotate() { return CCRepeatForever::create(CCRotateBy::create(1.0f, 90.0f)); } //创建一个无限循环放大再缩小的动画。 CCAction* BasicTest::actionScale() { CCScaleBy *scale = CCScaleBy::create(1.33f, 1.5f); return CCRepeatForever::create(CCSequence::create(scale, scale->reverse(), NULL)); } //创建一个三角形图形结点。 CCDrawNode* BasicTest::shape() { CCDrawNode *shape = CCDrawNode::create(); static CCPoint triangle[3]; triangle[0] = ccp(-100, -100); triangle[1] = ccp(100, -100); triangle[2] = ccp(0, 100); static ccColor4F green = {0, 1, 0, 1}; //调用DrawNode的绘制图形函数来渲染这个绿色三角形。 shape->drawPolygon(triangle, 3, green, 0, green); return shape; } //创建所用的男主角 CCSprite* BasicTest::grossini() { CCSprite *grossini = CCSprite::create(s_pPathGrossini); grossini->setScale( 1.5 ); return grossini; } //创建模版遮罩结点 CCNode* BasicTest::stencil() { return NULL; } //创建所用的CCClippingNode。 CCClippingNode* BasicTest::clipper() { return CCClippingNode::create(); } //创建被遮挡的结点。 CCNode* BasicTest::content() { return NULL; } //当前演示实例 class ShapeTest : public BasicTest { public: //基类相应虚函数 virtual std::string title(); virtual std::string subtitle(); virtual CCNode* stencil(); virtual CCNode* content(); }; 相应CPP: //取得标题 std::string ShapeTest::title() { return "Shape Basic Test"; } //取得幅标题 std::string ShapeTest::subtitle() { return "A DrawNode as stencil and Sprite as content"; } //创建所用的模版缓冲遮罩结点。 CCNode* ShapeTest::stencil() { //创建三角形图形结点,运行无限循环旋转动画。 CCNode *node = this->shape(); node->runAction(this->actionRotate()); return node; } //创建被遮挡的结点。 CCNode* ShapeTest::content() { //男猪脚上场,运行无限循环放大再缩小的动画。 CCNode *node = this->grossini(); node->runAction(this->actionScale()); return node; }
第四个实例:ShapeInvretTest
截图:
说明:
男猪脚不断的放大缩小,被一个旋转的三角形进行模板遮罩的裁切。
原理:
创建一个三角形结点做为模板遮罩结点并旋转,因为是反向运算,男猪脚精灵被这个三角形结点重合的部分被剔除,其余部分像素保留显示。
类代码:
//由ShapeTest派生即可。 class ShapeInvertedTest : public ShapeTest { public: //重载基类函数。 virtual std::string title(); virtual std::string subtitle(); virtual CCClippingNode* clipper(); }; 对应CPP: //重载基类函数。 std::string ShapeInvertedTest::title() { return "Shape Inverted Basic Test"; } std::string ShapeInvertedTest::subtitle() { return "A DrawNode as stencil and Sprite as content, inverted"; } CCClippingNode* ShapeInvertedTest::clipper() { CCClippingNode *clipper = ShapeTest::clipper(); //关健之处也就是这一句模版运算的反向设置。 clipper->setInverted(true); return clipper; }
第五个实例:SpriteTest
截图:
说明:
三角形不断的放大后缩小. 被不断的旋转男猪脚进行模板遮罩的裁切。
原理:
跟上面的实例就是调换了模版遮罩结点和遮挡结点。
类代码:
//当前演示层。 class SpriteTest : public BasicTest { public: //重载基类函数 virtual std::string title(); virtual std::string subtitle(); virtual CCNode* stencil(); virtual CCClippingNode* clipper(); virtual CCNode* content(); }; CPP: std::string SpriteTest::title() { return "Sprite Basic Test"; } std::string SpriteTest::subtitle() { return "A Sprite as stencil and DrawNode as content"; } CCNode* SpriteTest::stencil() { //创建男猪脚精灵结点做为模板遮罩 CCNode *node = this->grossini(); //运行无限循环动画。 node->runAction(this->actionRotate()); return node; } CCClippingNode* SpriteTest::clipper() { //创建CCClippingNode CCClippingNode *clipper = BasicTest::clipper(); //设置ALPHA镂空运算的参数值。 clipper->setAlphaThreshold(0.05f); return clipper; } CCNode* SpriteTest::content() { //创建一个三角形图形结点 CCNode *node = this->shape(); //让图形运行无限循环放大后缩小的动画。 node->runAction(this->actionScale()); return node; }
第六个实例:SpriteNoAlphaTest
截图:
说明:
三角形不断的放大后缩小. 被不断的旋转男猪脚精灵进行模板遮罩的裁切。
原理:
与上面的实例不同的唯一之外就是ALPHA测试的结果是都可通过,男主角精灵的纹理图片全部被镂空掉了,这时模版遮罩精灵区域所有像素都可保留相对应的绿色三角形像素。
类代码:
//演示层类 class SpriteNoAlphaTest : public SpriteTest { public: //重载基类相应函数。 virtual std::string title(); virtual std::string subtitle(); virtual CCClippingNode* clipper(); }; 对应CPP: //重载基类相应函数。 std::string SpriteNoAlphaTest::title() { return "Sprite No Alpha Basic Test"; } std::string SpriteNoAlphaTest::subtitle() { return "A Sprite as stencil and DrawNode as content, no alpha"; } CCClippingNode* SpriteNoAlphaTest::clipper() { CCClippingNode *clipper = SpriteTest::clipper(); //这里设置成1。即所有像素都被镂空掉。 clipper->setAlphaThreshold(1); return clipper; }
第七个实例:SpriteInvertedTest
截图:
说明:
三角形不断的放大后缩小. 被不断的旋转男猪脚精灵进行模板遮罩的裁切。
原理:
也没什么好说的,只是模版运算是反向处理。
类代码:
class SpriteInvertedTest : public SpriteTest { public: virtual std::string title(); virtual std::string subtitle(); virtual CCClippingNode* clipper(); }; CPP: std::string SpriteInvertedTest::title() { return "Sprite Inverted Basic Test"; } std::string SpriteInvertedTest::subtitle() { return "A Sprite as stencil and DrawNode as content, inverted"; } CCClippingNode* SpriteInvertedTest::clipper() { CCClippingNode *clipper = SpriteTest::clipper(); //使用ALPHA测试,像素ALPHA值小于0.05的都镂空掉。 clipper->setAlphaThreshold(0.05f); //设置模版遮罩运算使用反向运算。 clipper->setInverted(true); return clipper; }
第八个实例:NestedTest
截图:
说明:
九个男猪脚进行重叠的模版遮罩的裁切。
原理:
看来是要测试一下模版缓冲格式,告诉大家如果最多只有8位模版值,则第九个会失败的。
类代码:
//演示层 class NestedTest : public BaseClippingNodeTest { public: virtual std::string title(); virtual std::string subtitle(); virtual void setup(); }; 对应CPP: std::string NestedTest::title() { return "Nested Test"; } std::string NestedTest::subtitle() { return "Nest 9 Clipping Nodes, max is usually 8"; } void NestedTest::setup() { //定义CCClippingNode数量。 static int depth = 9; //定义一个父指针结点。 CCNode *parent = this; //循环创建相应的CCClippingNode for (int i = 0; i < depth; i++) { int size = 225 - i * (225 / (depth * 2)); //创建CCClippingNode并设置大小,锚点。位置,ALPHA镂空参考值。 CCClippingNode *clipper = CCClippingNode::create(); clipper->setContentSize(CCSizeMake(size, size)); clipper->setAnchorPoint(ccp(0.5, 0.5)); clipper->setPosition( ccp(parent->getContentSize().width / 2, parent->getContentSize().height / 2) ); clipper->setAlphaThreshold(0.05f); //让它运行一个无限循环的动画,并具循环中的每一个都不样。 clipper->runAction(CCRepeatForever::create(CCRotateBy::create(i % 3 ? 1.33 : 1.66, i % 2 ? 90 : -90))); //放入到parent结点下。 parent->addChild(clipper); //创建精灵结点用于做为模版遮罩结点。 CCNode *stencil = CCSprite::create(s_pPathGrossini); //设置缩放,锚点。位置,并在初始时都设为不显示。 stencil->setScale( 2.5 - (i * (2.5 / depth)) ); stencil->setAnchorPoint( ccp(0.5, 0.5) ); stencil->setPosition( ccp(clipper->getContentSize().width / 2, clipper->getContentSize().height / 2) ); stencil->setVisible(false); //让这些模版遮罩结点都设置为等i秒再显示的。 stencil->runAction(CCSequence::createWithTwoActions(CCDelayTime::create(i), CCShow::create())); //设置CCClippingNode所对应的模版遮罩结点。 clipper->setStencil(stencil); //设置要遮挡的结点。 clipper->addChild(stencil); //设置parent为当前CCClippingNode。 parent = clipper; } }
嗯,ClippingNode的实例也就算基本完事儿了。喝口可乐让我们下课吧?
什么?还有一些后面的例子没讲?让我看看,嗯,,,,后面的这些例子是模版缓冲的底层操作实例,实际上与ClippingNode类无关了,卖个关子咱们下节讲。。
嘿嘿.~