存档

2013年4月 的存档

Cocos2d-x2.1.1-ClippingNodeTest 深入分析

2013年4月27日 没有评论

[Cocos2d-x相关教程来源于红孩儿的游戏编程之路CSDN博客地址http://blog.csdn.net/honghaier

红孩儿Cocos2d-X学习园地QQ3群:205100149,47870848

Cocos2d-x2.1.1-ClippingNodeTest深入分析

         大家好,许久没有再上课,大家是不是还想念红孩儿的博文呢?哈哈,对不起了,红孩儿最近一直在忙着写新版的“红孩儿工具箱”,进度还算较快,新用工具箱做了一些动画特效,有兴趣的同学可以点击查看:

       <1>《大掌门》九阴白骨爪文字特效动画

       <2>色彩圈与咬人花

         <3>将《名将》的刀手攻击GIF导入工具箱转为关健帧动画


     

         广告回来,话回正题。本节我们来学习一下2.1版本的新功能:ClippingNode。什么是“ClippingNode”?在我看来,“ClippingNode”可以定义为利用模版遮罩来完成对Node进行区域剪切的技术。我们来打个比方,在寒冷的冬季,我们家里的窗户玻璃上常常凝结一层雾以致于我们无法看到玻璃外的景物,这时候我们常常会抹掉这层雾,当然,很多小朋友也会像红孩儿一样兴致勃勃的在抹这层雾时顺手写几笔“数风流人物,还看今朝!”,当雾被抹掉后,玻璃外的景色也就显露出来了。那么,如果我们把窗户玻璃当做显示景物的层,则玻璃上的这层雾就可称为模版遮罩了,实际上,本节的技术无非就是如何在这个模版遮罩上写出那几笔风流大字。

     

         

         在Cocos2d-x中,对于Node进行模版遮罩功能的类被封装在一个称为“CCClippingNode”的类中。它是被放在libcocos2d中的misc_nodes中的。

       Cocos2d-x2.1.1-ClippingNodeTest 深入分析


打开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运行一下:

Cocos2d-x2.1.1-ClippingNodeTest 深入分析

第一个实例:Scroll View Demo

截图:

Cocos2d-x2.1.1-ClippingNodeTest 深入分析

说明:

         用鼠标可以拖动被遮住的精灵结点移动,同时看到模版遮罩区域。

原理:

         通过遮罩结点遮住相应区域中的精灵结点,触屏事件对精灵进行移动。

类代码:

//这是一个剪切结点演示的层基类,用于派生所用到的各个演示层。
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

截图:

Cocos2d-x2.1.1-ClippingNodeTest 深入分析

说明:

         对精灵结点点击鼠标,可以产生枪击它的效果并留下你罪恶的证据。看点是圆形弹孔的镂空。

原理:

         此实例用到了两层模版遮罩处理,第一层是弹孔遮罩,用弹孔图遮住弹痕图。

Cocos2d-x2.1.1-ClippingNodeTest 深入分析

         实际使用时并不会为每个子弹都创建一个模版遮罩结点,而是将所有的弹孔放在一个结点中,并用此结点做为模板遮罩。

         第二层是背景图的区域遮罩,让脱靶的子弹不产生弹孔。

类代码:

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

截图:

Cocos2d-x2.1.1-ClippingNodeTest 深入分析

说明:

         男猪脚不断的放大缩小,被一个旋转的三角形进行模板遮罩的裁切。

原理:

         创建一个三角形结点做为模板遮罩结点并旋转,男猪脚精灵被这个三角形结点重合的部分可以到到,其余部分像素会被剔除。

类代码

//又一个用于演示的基类
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

截图:

Cocos2d-x2.1.1-ClippingNodeTest 深入分析

说明:

         男猪脚不断的放大缩小,被一个旋转的三角形进行模板遮罩的裁切。

原理:

         创建一个三角形结点做为模板遮罩结点并旋转,因为是反向运算,男猪脚精灵被这个三角形结点重合的部分被剔除,其余部分像素保留显示。

类代码:

//由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

截图:

Cocos2d-x2.1.1-ClippingNodeTest 深入分析

说明:

三角形不断的放大后缩小. 被不断的旋转男猪脚进行模板遮罩的裁切。

原理:

         跟上面的实例就是调换了模版遮罩结点和遮挡结点。

类代码:

//当前演示层。
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

截图:

Cocos2d-x2.1.1-ClippingNodeTest 深入分析

说明:

三角形不断的放大后缩小. 被不断的旋转男猪脚精灵进行模板遮罩的裁切。

原理:

         与上面的实例不同的唯一之外就是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

截图:

Cocos2d-x2.1.1-ClippingNodeTest 深入分析

说明:

三角形不断的放大后缩小. 被不断的旋转男猪脚精灵进行模板遮罩的裁切。

原理:

         也没什么好说的,只是模版运算是反向处理。

类代码:

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

截图:

Cocos2d-x2.1.1-ClippingNodeTest 深入分析

说明:

         九个男猪脚进行重叠的模版遮罩的裁切。

原理:

         看来是要测试一下模版缓冲格式,告诉大家如果最多只有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类无关了,卖个关子咱们下节讲。。

 

    嘿嘿.~

分类: 未分类 标签:

COCOS2D-X Lua面向对象编程

2013年4月22日 没有评论

猴子原创,欢迎转载。转载请注明: 转载自Cocos2D开发网–Cocos2Dev.com,谢谢!

原文地址: http://www.cocos2dev.com/?p=425

上次有个同学问我,说lua太简单了,没有什么结构,也不好做面向对象编程。其实可以这样说,Lua中的table就是一个对象。

下面我一点一点介绍Lua的面向对象编程。

一、对象的方法函数:

  Hero = {attack = 100}
  
  function Hero.skill(addAttack)
    Hero.attack = Hero.attack + addAttack
  end
  
  Hero.skill(20)
  print(Hero.attack)	--> 120

上面的先创建了一个函数,并调用该函数。

仔细的同学可能发现了,调用函数的时候使用了全局Hero。我在上一篇细节介绍中提过,Lua尽量减少全局变量的使用。而且这里也有风险,一不小心修改了Hero,可能Hero就不能正常工作了。

那么联想到上一篇写到的local temA =  A,有人可能想到了这样修改上面的函数调用:

  oneHero = Hero; 
  Hero = nil
  oneHero.skill(30)
  print(oneHero.attack) --> 错误,oneHero为nil

这样也是错误的。因为Hero已经为nil了。

正确的如何修改呢?我们可以使用this/self 来实现:

  Hero = {attack = 100}
  
  function Hero.skill(self,addAttack)
    self.attack = self.attack + addAttack
  end
  
  oneHero = Hero; 
  Hero = nil
  oneHero.skill(oneHero,30)
  print(oneHero.attack) 	--> 130

OK,这下可以了,但是self每次需要自己传,看着就麻烦,其实Lua也可以隐性调用self,我们在修改下:

  Hero = {attack = 100}
  
  function Hero:skill(addAttack)
    self.attack = self.attack + addAttack
  end
  
  oneHero = Hero; 
  Hero = nil
  oneHero:skill(30)
  print(oneHero.attack) --> 130

请注意上面 :  的使用,冒号可以在方法中添加一个额外的隐藏参数。上面其实也看到了Hero.skill() 和Hero:skill()的区别。

二、类,将table作为自己的元表
例如上面的Hero例子,我们修改下:

  Hero = {}
  
  function Hero:new(o)
    o = o or {}
    setmetatable(o,self)
    self.__index = self
    return o
  end
  
  function Hero:skill(addAttack)
    self.attack = self.attack + addAttack
  end
  
  oneHero = Hero:new{attack = 90}
  oneHero:skill(10)
  print(oneHero.attack) -->100

创建一个新英雄的时候,oneHero将Hero设置为自己的元表,当oneHero:skill(10)的时候,在table oneHero中查找skill,没有找到后,会进一步搜索元表的__index。

所以等价于:getmetatable(oneHero).__index.skill(oneHero,10)

而getmetatable(oneHero) 是Hero,Hero.__index还是Hero

所以等价于Hero.skill(oneHero,10)

三、继承
例如:

 Hero = {attack = 0}
  
  function Hero:new(o)
    o = o or {}
    setmetatable(o,self)
    self.__index = self
    return o
  end
  
  function Hero:skill(addAttack)
    self.attack = self.attack + addAttack
  end
  
  function Hero:injured(loseAttack)
    if loseAttack > self.attack then error"not engouth attack" end
    self.attack = self.attack - loseAttack
  end
  
  HumanHero = Hero:new()
  
  oneHero = HumanHero:new{attack = 100}
  oneHero:skill(10)
  
  print(oneHero.attack) -->110

每个对象没有的方法都会去父类中查找(这里理解为父类),所以如果某个对象需要一些新的属性方法,只需要在该对象中实现就可以了。

分类: cocos2d, cocos2d-lua 标签:

COCOS2D-X 快速熟悉LUA细节问题

2013年4月19日 没有评论

猴子原创,欢迎转载。转载请注明: 转载自Cocos2D开发网–Cocos2Dev.com,谢谢!

原文地址: http://www.cocos2dev.com/?p=423

这篇博文主要是接着《COCOS2D-X 快速熟悉LUA基本细节问题》往下说了下,所以如果是cocos2dx开始学习Lua的话,请先看下《COCOS2D-X 快速熟悉LUA基本细节问题 》,主要快速讲了一些Lua的特性,所以快速学习Lua的话,建议先看一遍。

一、{},构造式

{a = 1, b= 2} 等价于 {["a"] = 1,["b"] = 2} 

{"x","y","z"} 等价于 {[1] = "x",[2] = "y", [3] = "z"}


如果真的需要以0为数组的话:

a = {[0] = "x","y","z"}  — 不推荐这样

二、尽量使用局部变量,便于垃圾收集器释放内存

如果是全局变量,局部使用的时候,有时可以考虑:

local temA = A


使用局部变量保存全局变量的值,如果后面函数会改变A的值,可以考虑这样,也可以加快当前域的访问速度。



三、控制语句


1、if then else


2、while

while true or false do
-- todo
end

3、repeat-until

repeat
-- todo
until true or false

4、for


for 起始,变量, 步长(不写默认1) do

–todo

end


5、iterator

for k,v in ipairs(a) do
-- k是索引
-- v是对应的value
end

for k in pairs(a) do
-- k是索引
end

可以用来逆向table:

revA = {}
for k,v in ipairs(a) do
revA[v] = k
end

注意:其他的switch、do-while 没有

五、return、break ,只能放在一个块的结束语句(end、else、until)前面

function func()
local x = 0
x = 1
print(x)
return  -- 想在这里跳出,这样是错误的。
if(x>1) then
x = 10
end
end 

修改成:

function func()
local x = 0
x = 1
print(x)
do
return -- 这样才是正确的,可以执行return
end
if(x>1) then
x = 10
end
end 

六、function,函数可以有多个返回值

例如:

function func() 
return "a","b" --返回2个值
end


1、如果函数是表达式的最后一个,则函数保留尽可能多的返回值用来赋值变量

x = func() 		-- x = "a",返回值"b"不要
x,y = func()  		-- x = "a", y = "b"
x,y,z = 1,func()     -- x = 1, y = "a" , z = "b"  注意func是表达式的最后一个
x,y,z = func()	     	-- x = "a", y = "b" , z = nil  返回值不够用nil补充


2、如果函数不是表达式的最后一个,则只返回一个值

x,y,z = func(),1	-- x = "a", y = 1, z = nil  


3、函数在table构造式中,如果函数在构造式的最后一个位置,返回所有值,否则返回一个值

t = {func()} --t = {"a","b"}
t = {func(),2} --t = {"a",2}

4、函数在return之后,返回所有值


5、如果函数在()中,则只返回一个值

print(func()) --输出 a b
print((func())) --函数被放在了()中,输出 a

6、函数实参可以是变长参数,如果函数实参同时包含了固定参数和变长参数,变长参数必须放在固定参数后面。请注意…的用法

七、如何写带描述的函数实参

//OC代码:
- (void) createRect:(float)X y:(float)Y width:(float)Width height:(float)Height
{
//TODO...
}

有时候函数参数很多,想用oc这种带描述的参数。lua虽然不支持,但是可以用table实现。

function createRect(rectOptions)
if type(rectOptions.title) ~= "string" then
error "need title"
end
end

createRect({x = 0,y = 0 , width = 200,height = 300 , title = "hello sunny"})

提示:函数实参只有一个table时候,可以不要(),所以可以写成:

createRect{x = 0,y = 0 , width = 200,height = 300 , title = "hello sunny"}

分类: cocos2d, cocos2d-lua 标签:

cocos2d-x 快速熟悉Lua基本细节问题

2013年4月18日 没有评论

猴子原创,欢迎转载。转载请注明: 转载自Cocos2D开发网–Cocos2Dev.com,谢谢!
原文地址: http://www.cocos2dev.com/?p=416


一、程序块的写法:

a = 1 
b = a*2

a = 1;
b = a*2;

a= 1; b = a*2

a = 1 b = a*2

这四个程序块都是等价的。


二、Lua 变量区分大小,~= 是 不等于


三、注释符号

--[[
  这里是多行注释符号
  这里是多行注释符号
]]
print("Sunny")  -- 两个连接符就是单行注释符号

注意:多行注释符号一般这样写:

--[[
 print("Sunny")  --已经被注释了
--]]

优点:假如你想把注释去掉,直接在开头注释符号前面加一个连接符就可以了。(多行注释符号被改成了单行)

---[[
 print("Sunny")  --没有注释
--]]

四、全局变量不用声明,赋值就可以了,不用的时候赋值nil,未赋值的全局变量为nil


五、boolean,只有false和nil是假,其他值都是真(包括0和空字符串)


六、number ,表示双精度浮点数,Lua没有整数类型


七、string,可以赋值为任意二进制数据

1、注意Lua的字符串是不可变的值,不能像c++那样修改字符串里的字符,需要修改的话请创建一个新的字符创。

a = "hello world" --也可以是单引号,注意统一风格就可以了
b = string.gsub(a,"world","sunny")
print(a) --输出 hello world
print(b) --输出 hello sunny

2、在字符串上面进行算术操作,lua会将字符串转化成数字

print("1" + 1) --输出 2
print("a" + 1) --错误:a string value

3、tonumber(),字符串转数字

print(tonumber("123")) --输出数字 123
print(tonumber("a123")) --输出 nil

3、tostring(),数字转字符串(也可以使用连接符,123 .. "" )

print(type(tostring(123))) --输出 string
print(type(123 .. ""))	 --输出 string

4、#,长度操作符,获取字符串长度

a = "sunny"
print(#a)	--输出 5

八、table,一种关联数组,也就是一种有索引方式的数组,索引值可以是整数、字符串或其他类型。(nil不可以作为table索引值)

a = {} --创建table,将table的引用存到a
a["x"] = 1  --增加一个新条目 "x":1
b = "y"
a[b] = 2    --增加一个新条目 "y":2

c = a         --c和a引用了同一个table

print(a["x"]) --输出 1
print(a["y"]) --输出 2
print(c["x"]) --输出 1
print(c["y"]) --输出 2
print(c.y)     --输出 2

注意:c.y 等价与 c["y"],但是不同于c[y]


常用技巧:

a = {}
for i = 1,9 do
a[i] = i 
end

for i = 0,#a do --#a 返回a的最后索引值
print(a[i]) --利用#输出所有行
end

print(a[#a]) --输出最后一个值

a[#a] = nil --删除最后一个值

a[#a+1] = 10 --添加一个值到最后

注意:不推荐使用索引0为起始索引,大多数内建函数都假设数组索引开始于1,所以为0的索引数组可能会出现错误。

分类: cocos2d, cocos2d-lua 标签:

How to draw images on Terrain in unity

2013年4月18日 没有评论

I am working on game in unity in which i need some images to be placed on terrain as in the attached image yellow arrows and “P in blue circle” are rendered on surface in unity.

Any idea or method will be appreciated.

How to draw images on Terrain in unity

There’s no built-in support for decals in Unity. You could just create separate gameObjects with transparent texture and place them above the terrain here, or use one of several packages for decals in Unity Asset Store, like this one. (I have only briefly tried it and can’t say anything about it’s quality).

You could try putting a plane with the texture aligned with the surface normal slightly above the surface. Or you could try an extension that does decals for you. This is what i found:

Decal System for Unity3D

I know it’s an old topic – but for those who are still not satisfied:

I would recommend using Easy Decal.

It’s a very powerful decal projector. It’s easy to use and you can stick decals also on uneven surfaces like bumpy terrains.

分类: stackoverflow精选, unity3d 标签:

Is there a common technique for drawing a “stretchy” line

2013年4月17日 没有评论

I’m trying to figure out how to draw an stretchy/elastic line between two points in openGL/Cocos2d on iPhone. Something like this

Is there a common technique for drawing a &ldquo;stretchy&rdquo; line

Where the “band” get’s thinner as the line gets longer. iOS uses the same technique I’m aiming for in the Mail.app, pull to refresh.

First of all, is there a name for this kind of thing?

My first thought was to plot a point on the radius of the starting and ending circles based on the angle between to the two, and draw a quadratic bezier curve using the distance/2 as a control point. But I’m not a maths whizz so I’m struggling to figure out how to place the control point which will adjust the thickness of the path.

But a bigger problem is that I need to fill the shape with a colour, and that doesn’t seem to be possible with OpenGL bezier curves as far as I can tell since curves don’t seem to form part of a shape that can be filled.

So I looked at using a spline created using a point array, but that opens up a whole new world of mathematical pain as I’d have to figure out where all the points along the edge of the path are.

So before I go down that rabbit hole, I’m wondering wether there’s something simpler that I’m overlooking, or if anyone can point me towards the most effective technique.

I’m not sure about a “common” technique that people use, other than calculating it mathematically, but this project, SlimeyRefresh, is a good example of how to accomplish this.

分类: cocos2d, stackoverflow精选 标签:

Cocos2d-x 深入解析系列 : 以XML文件方式保存用户数据

2013年4月17日 没有评论

[Cocos2d-x相关教程来源于红孩儿的游戏编程之路CSDN博客地址http://blog.csdn.net/honghaier

红孩儿Cocos2d-X学习园地QQ3群:205100149,47870848

 

           Cocos2d-x 深入解析系列:以XML文件方式保存用户数据

另:本章所用Cocos2d-x版本为: 

2.1.1 (2013-01-28)


              大家好,今天我们来学习一下如何使用XML文件方式来保存游戏中的用户数据。在使用Cocos2d-x开发游戏的过程中,我们经常会使用XML来存储用户存档数据,而这些XML我们该如何生成呢?Cocos2d-x提供了一个类CCUserDefault以方便我们随时将需要的数据生成XML文件。

 

打开CCUserDefault.h:

#ifndef __SUPPORT_CCUSERDEFAULT_H__
#define __SUPPORT_CCUSERDEFAULT_H__
//加入平台所用的头文件
#include "platform/CCPlatformMacros.h"
#include <string>
//使用Cocos2d命名空间
NS_CC_BEGIN

//定义类CCUserDefault
class CC_DLL CCUserDefault
{
public:
	//析构
    ~CCUserDefault();

    //从指定的键中取得布尔值
    bool    getBoolForKey(const char* pKey);
	//从指定的键中取得布尔值,如果没有则返回默认参数
    bool    getBoolForKey(const char* pKey, bool defaultValue);
    //从指定的键中取得整数值
    int     getIntegerForKey(const char* pKey);
	//从指定的键中取得整数值,如果没有则返回默认参数
    int     getIntegerForKey(const char* pKey, int defaultValue);
     //从指定的键中取得浮点值
    float    getFloatForKey(const char* pKey);
	//从指定的键中取得浮点值,如果没有则返回默认参数
    float    getFloatForKey(const char* pKey, float defaultValue);
     //从指定的键中取得双精度值
    double  getDoubleForKey(const char* pKey);
	//从指定的键中取得双精度值,如果没有则返回默认参数
    double  getDoubleForKey(const char* pKey, double defaultValue);
     //从指定的键中取得字符串值
    std::string getStringForKey(const char* pKey);
	//从指定的键中取得字符串值,如果没有则返回默认参数
    std::string getStringForKey(const char* pKey, const std::string & defaultValue);

    //设置指定键的布尔值
    void    setBoolForKey(const char* pKey, bool value);
    //设置指定键的整数值
    void    setIntegerForKey(const char* pKey, int value);
	//设置指定键的浮点值
    void    setFloatForKey(const char* pKey, float value);
	//设置指定键的双精度值
    void    setDoubleForKey(const char* pKey, double value);
	//设置指定键的字符串值
    void    setStringForKey(const char* pKey, const std::string & value);
    //立即将XML数据写入文件
    void    flush();
	//取得单例的指针
    static CCUserDefault* sharedUserDefault();
	//释放单例
    static void purgeSharedUserDefault();
	//取得保存后的XML文件路径
    const static std::string& getXMLFilePath();

private:
	//因为是单例,构造函数私有化
    CCUserDefault();
	//创建XML文件
    static bool createXMLFile();
	//XML文件是否存在
    static bool isXMLFileExist();
	//初始化XML文件
    static void initXMLFilePath();
    //单例的指针
    static CCUserDefault* m_spUserDefault;
	//XML文件的路径
    static std::string m_sFilePath;
	//XML文件是否已经被初始化
    static bool m_sbIsFilePathInitialized;
};

NS_CC_END

#endif // __SUPPORT_CCUSERDEFAULT_H__


CCUserDefault.cpp:

#include "CCUserDefault.h"
#include "platform/CCCommon.h"
#include "platform/CCFileUtils.h"
#include <libxml/parser.h>
#include <libxml/tree.h>

// XML的根节点名称
#define USERDEFAULT_ROOT_NAME    "userDefaultRoot"
//默认的XML文件名称
#define XML_FILE_NAME "UserDefault.xml"
//使用C++标准库的命名空间
using namespace std;
//使用Cocos2d命名空间
NS_CC_BEGIN
//单例的指针
static xmlDocPtr g_sharedDoc = NULL;

//静态全局函数,用于取得一个键的XML结点指针
static xmlNodePtr getXMLNodeForKey(const char* pKey, xmlNodePtr *rootNode)
{
	//定义用于存储返回结果的临时指针变量并置空
    xmlNodePtr curNode = NULL;

    //键值的有效性判断
    if (! pKey)
    {
        return NULL;
    }

    do 
    {
        //取得根结点
        *rootNode = xmlDocGetRootElement(g_sharedDoc);
        if (NULL == *rootNode)
        {
            CCLOG("read root node error");
            break;
        }

        //循环查询相应的键结点
        curNode = (*rootNode)->xmlChildrenNode;
        while (NULL != curNode)
        {
			  //如果键结点名称与查询键名称一致中断退出循环
            if (! xmlStrcmp(curNode->name, BAD_CAST pKey))
            {
                break;
            }
			 //否则指针指向下一个结点继续循环
            curNode = curNode->next;
        }
    } while (0);
	//返回结点指针
    return curNode;
}
//取得相应的键值
static inline const char* getValueForKey(const char* pKey)
{
	//定义用于存储返回结果的临时字符指针变量并置空
    const char* ret = NULL;
	//定义结点指针变量取得相应的键结点。
    xmlNodePtr rootNode;
    xmlNodePtr node = getXMLNodeForKey(pKey, &rootNode);

    // 如果找到了相应的结点,取得结点的内存值转换为字符指针返回。
    if (node)
    {
        ret = (const char*)xmlNodeGetContent(node);
    }

    return ret;
}
//设置相应的键值
static void setValueForKey(const char* pKey, const char* pValue)
{
    xmlNodePtr rootNode;
    xmlNodePtr node;

    // 有效性判断
    if (! pKey || ! pValue)
    {
        return;
    }

    // 取得相应的键结点
    node = getXMLNodeForKey(pKey, &rootNode);

    // 如果找到,设置结点的值为pValue
    if (node)
    {
        xmlNodeSetContent(node, BAD_CAST pValue);
    }
    else
    {
		 //如果找不到键值,则生成相应的键结点和键值结点并放入根结点下。
        if (rootNode)
        {
			 //先创建键结点。
            xmlNodePtr tmpNode = xmlNewNode(NULL, BAD_CAST pKey);
			 //再创建健值结点。
            xmlNodePtr content = xmlNewText(BAD_CAST pValue);
			 //将键点点放到根结点下。
            xmlAddChild(rootNode, tmpNode);
			 //将键帧结点放到键结点下。
            xmlAddChild(tmpNode, content);
        }    
    }
}

//初始化单例指针置空
CCUserDefault* CCUserDefault::m_spUserDefault = 0;
//初始化XML文件路径为空
string CCUserDefault::m_sFilePath = string("");
//初始化文件路径是否被初始化的标记值为false
bool CCUserDefault::m_sbIsFilePathInitialized = false;

//析构
CCUserDefault::~CCUserDefault()
{
	//将数据写入文件
    flush();
	//释放相应的XML文件
    if (g_sharedDoc)
    {
        xmlFreeDoc(g_sharedDoc);
        g_sharedDoc = NULL;
    }
	//单例指针置空
    m_spUserDefault = NULL;
}
//构造
CCUserDefault::CCUserDefault()
{
	//读取相应的XML文件。
    g_sharedDoc = xmlReadFile(getXMLFilePath().c_str(), "utf-8", XML_PARSE_RECOVER);
}
//释放单例
void CCUserDefault::purgeSharedUserDefault()
{
    CC_SAFE_DELETE(m_spUserDefault);
    m_spUserDefault = NULL;
}
//从指定的键中取得布尔值
bool CCUserDefault::getBoolForKey(const char* pKey)
 {
     return getBoolForKey(pKey, false);
 }
//从指定的键中取得布尔值,如果没有则返回默认参数。
bool CCUserDefault::getBoolForKey(const char* pKey, bool defaultValue)
{
    const char* value = getValueForKey(pKey);
    bool ret = defaultValue;

    if (value)
    {
        ret = (! strcmp(value, "true"));
        xmlFree((void*)value);
    }

    return ret;
}
//从指定的键中取得整数值
int CCUserDefault::getIntegerForKey(const char* pKey)
{
    return getIntegerForKey(pKey, 0);
}
//从指定的键中取得整数值,如果没有则返回默认参数
int CCUserDefault::getIntegerForKey(const char* pKey, int defaultValue)
{
    const char* value = getValueForKey(pKey);
    int ret = defaultValue;

    if (value)
    {
        ret = atoi(value);
        xmlFree((void*)value);
    }

    return ret;
}
//从指定的键中取得浮点值
float CCUserDefault::getFloatForKey(const char* pKey)
{
    return getFloatForKey(pKey, 0.0f);
}
//从指定的键中取得浮点值,如果没有则返回默认参数。
float CCUserDefault::getFloatForKey(const char* pKey, float defaultValue)
{
    float ret = (float)getDoubleForKey(pKey, (double)defaultValue);
 
    return ret;
}
//从指定的键中取得双精度值
double  CCUserDefault::getDoubleForKey(const char* pKey)
{
    return getDoubleForKey(pKey, 0.0);
}
//从指定的键中取得双精度值,如果没有则返回默认参数。
double CCUserDefault::getDoubleForKey(const char* pKey, double defaultValue)
{
    const char* value = getValueForKey(pKey);
    double ret = defaultValue;

    if (value)
    {
        ret = atof(value);
        xmlFree((void*)value);
    }

    return ret;
}
//从指定的键中取得字符串值
std::string CCUserDefault::getStringForKey(const char* pKey)
{
    return getStringForKey(pKey, "");
}
//从指定的键中取得字符串值,如果没有则返回默认参数
string CCUserDefault::getStringForKey(const char* pKey, const std::string & defaultValue)
{
    const char* value = getValueForKey(pKey);
    string ret = defaultValue;

    if (value)
    {
        ret = string(value);
        xmlFree((void*)value);
    }

    return ret;
}
//设置指定键的布尔值
void CCUserDefault::setBoolForKey(const char* pKey, bool value)
{
    // save bool value as string

    if (true == value)
    {
        setStringForKey(pKey, "true");
    }
    else
    {
        setStringForKey(pKey, "false");
    }
}
//设置指定键的整数值
void CCUserDefault::setIntegerForKey(const char* pKey, int value)
{
    // check key
    if (! pKey)
    {
        return;
    }

    // format the value
    char tmp[50];
    memset(tmp, 0, 50);
    sprintf(tmp, "%d", value);

    setValueForKey(pKey, tmp);
}
//设置指定键的浮点值
void CCUserDefault::setFloatForKey(const char* pKey, float value)
{
    setDoubleForKey(pKey, value);
}
//设置指定键的双精度值
void CCUserDefault::setDoubleForKey(const char* pKey, double value)
{
    // check key
    if (! pKey)
    {
        return;
    }

    // format the value
    char tmp[50];
    memset(tmp, 0, 50);
    sprintf(tmp, "%f", value);

    setValueForKey(pKey, tmp);
}
//设置指定键的字符串值
void CCUserDefault::setStringForKey(const char* pKey, const std::string & value)
{
    // check key
    if (! pKey)
    {
        return;
    }

    setValueForKey(pKey, value.c_str());
}
//取得单例
CCUserDefault* CCUserDefault::sharedUserDefault()
{
	//初始化XML文件
    initXMLFilePath();

    //如果文件不存在则创建,如果创建不成功返回失败。
    if ((! isXMLFileExist()) && (! createXMLFile()))
    {
        return NULL;
    }
	//如果当前单例指针为空,创建单例
    if (! m_spUserDefault)
    {
        m_spUserDefault = new CCUserDefault();
    }
	//返回单例指针
    return m_spUserDefault;
}
//XML文件是否存在。
bool CCUserDefault::isXMLFileExist()
{
	//创建一个文件指针打开相应的文件。
    FILE *fp = fopen(m_sFilePath.c_str(), "r");
    bool bRet = false;
	//看是否能打开以判断是否存在。
    if (fp)
    {
        bRet = true;
        fclose(fp);
    }

    return bRet;
}
//初始化XML文件路径
void CCUserDefault::initXMLFilePath()
{
	//如果初始化的标记为false,组合出文件字符串。
    if (! m_sbIsFilePathInitialized)
    {
		//文件路径名为文件系统的写入路径[后面详解]下的“UserDefault.xml”。
        m_sFilePath += CCFileUtils::sharedFileUtils()->getWriteablePath() + XML_FILE_NAME;
        m_sbIsFilePathInitialized = true;
    }    
}

//创建XML文件
bool CCUserDefault::createXMLFile()
{
    bool bRet = false;
	//定义临时的XML文档指针
    xmlDocPtr doc = NULL;
	//使用do-while框架结构来随时中断
    do 
    {
        // 创建一个新的1.0版的XML文档
        doc = xmlNewDoc(BAD_CAST"1.0");
        if (doc == NULL)
        {
            CCLOG("can not create xml doc");
            break;
        }

        // 创建根结点
        xmlNodePtr rootNode = xmlNewNode(NULL, BAD_CAST USERDEFAULT_ROOT_NAME);
        if (rootNode == NULL)
        {
            CCLOG("can not create root node");
            break;
        }

        //设置创建的结点为XML文档中的根结点
        xmlDocSetRootElement(doc, rootNode);

        //保存XML文件
        xmlSaveFile(m_sFilePath.c_str(), doc);

        bRet = true;
    } while (0);

    // 释放文档指针
    if (doc)
    {
        xmlFreeDoc(doc);
    }
	//返回成败
    return bRet;
}
//取得XML文件路径
const string& CCUserDefault::getXMLFilePath()
{
    return m_sFilePath;
}
//立即将XML数据写入文件
void CCUserDefault::flush()
{
    // 如果文档有效则进行保存文档到文件中。
    if (g_sharedDoc)
    {
        xmlSaveFile(CCUserDefault::sharedUserDefault()->getXMLFilePath().c_str(), g_sharedDoc);
    }
}

NS_CC_END


              这引CCUserDefault类写的真是不错。非常简洁好用。但我们要明白“文件系统的写入路径”是什么?

              在CCFileUtils.cpp中找到相应的函数:

//取得文件写入路径

string CCFileUtils::getWriteablePath()
{
	// 取得当前程序所在目录
	char full_path[_MAX_PATH + 1];
	::GetModuleFileNameA(NULL, full_path, _MAX_PATH + 1);

	// 如果是Release模式
#ifndef _DEBUG
		// 取得移除路径的文件名
		char *base_name = strrchr(full_path, '//');

		if(base_name)
		{
			char app_data_path[_MAX_PATH + 1];

			// 取得系统文件夹应用程序数据目录,如C:/Documents and Settings/username/Local Settings/Application Data,可参看: http://wenku.baidu.com/view/412cfc02f78a6529647d53e5.html

			if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, app_data_path)))
			{
				//创建字符串ret存放取出的路径
				string ret((char*)app_data_path);

				//字符串尾部加上文件名。
				ret += base_name;

				// 去除扩展名并加上”//”
				ret = ret.substr(0, ret.rfind("."));
				ret += "//";

				// 创建相应的目录
				if (SUCCEEDED(SHCreateDirectoryExA(NULL, ret.c_str(), NULL)))
				{
					//如果成功返回ret。
					return ret;
				}
			}
		}
#endif // not defined _DEBUG

	//创建字符串ret存放当前程序所在目录。
	string ret((char*)full_path);

	// 截取带”//”部分的路径。
	ret =  ret.substr(0, ret.rfind("//") + 1);
	//返回ret。
	return ret;
}


 

              这个函数对于DEBUG和RELEASE模式有区别处理,DEBUG模式取出的路径即为当前程序所在目录,RELEASE模式则在系统目录下创建当前程序名称的目录并返回。

 

              接下来我们看一下Cocos2d-x在例子中的具体使用,找到TestCpp中的UserDefaultTest。

UserDefaultTest.h:

#ifndef _USERDEFAULT_TEST_H_
#define _USERDEFAULT_TEST_H_

#include "cocos2d.h"
#include "../testBasic.h"
//创建一个层用于处理XML数据
class UserDefaultTest : public CCLayer
{
public:
    UserDefaultTest();
    ~UserDefaultTest();

private:
    void doTest();
};
//演示用的场景
class UserDefaultTestScene : public TestScene
{
public:
    virtual void runThisTest();
};
#endif // _USERDEFAULT_TEST_H_


对应的CPP:

// 开启COCOS2D的DEBUG标记
#define COCOS2D_DEBUG 1
#include "UserDefaultTest.h"
#include "stdio.h"
#include "stdlib.h"
//层的构造函数。
UserDefaultTest::UserDefaultTest()
{
	//取得屏幕大小,创建文字标签提示。
    CCSize s = CCDirector::sharedDirector()->getWinSize();
CCLabelTTF* label = CCLabelTTF::create("CCUserDefault test see log", "Arial", 28);
//将标签放到当前层中并置于屏幕中央。
    addChild(label, 0);
    label->setPosition( ccp(s.width/2, s.height-50) );
	//调用测试函数。
    doTest();
}

void UserDefaultTest::doTest()
{
	//开始打印日志。
    CCLOG("********************** init value ***********************");

    // 创建CCUserDefault单例并创建相应的数据类型键,设置其键值。

    CCUserDefault::sharedUserDefault()->setStringForKey("string", "value1");
    CCUserDefault::sharedUserDefault()->setIntegerForKey("integer", 10);
    CCUserDefault::sharedUserDefault()->setFloatForKey("float", 2.3f);
    CCUserDefault::sharedUserDefault()->setDoubleForKey("double", 2.4);
    CCUserDefault::sharedUserDefault()->setBoolForKey("bool", true);

    // 设置完后,打印各类型键取出的值。
    string ret = CCUserDefault::sharedUserDefault()->getStringForKey("string");
    CCLOG("string is %s", ret.c_str());

    double d = CCUserDefault::sharedUserDefault()->getDoubleForKey("double");
    CCLOG("double is %f", d);

    int i = CCUserDefault::sharedUserDefault()->getIntegerForKey("integer");
    CCLOG("integer is %d", i);

    float f = CCUserDefault::sharedUserDefault()->getFloatForKey("float");
    CCLOG("float is %f", f);

    bool b = CCUserDefault::sharedUserDefault()->getBoolForKey("bool");
    if (b)
    {
        CCLOG("bool is true");
    }
    else
    {
        CCLOG("bool is false");
    }
    
    //CCUserDefault::sharedUserDefault()->flush();
    CCLOG("********************** after change value ***********************");

    // 改变相应键的键值。

    CCUserDefault::sharedUserDefault()->setStringForKey("string", "value2");
    CCUserDefault::sharedUserDefault()->setIntegerForKey("integer", 11);
    CCUserDefault::sharedUserDefault()->setFloatForKey("float", 2.5f);
    CCUserDefault::sharedUserDefault()->setDoubleForKey("double", 2.6);
    CCUserDefault::sharedUserDefault()->setBoolForKey("bool", false);

	//将XML数据保存到相应文件中。
    CCUserDefault::sharedUserDefault()->flush();

    // 再次打印各键值。

    ret = CCUserDefault::sharedUserDefault()->getStringForKey("string");
    CCLOG("string is %s", ret.c_str());

    d = CCUserDefault::sharedUserDefault()->getDoubleForKey("double");
    CCLOG("double is %f", d);

    i = CCUserDefault::sharedUserDefault()->getIntegerForKey("integer");
    CCLOG("integer is %d", i);

    f = CCUserDefault::sharedUserDefault()->getFloatForKey("float");
    CCLOG("float is %f", f);

    b = CCUserDefault::sharedUserDefault()->getBoolForKey("bool");
    if (b)
    {
        CCLOG("bool is true");
    }
    else
    {
        CCLOG("bool is false");
    }
}

//析构
UserDefaultTest::~UserDefaultTest()
{
}
//场景启动时调用。
void UserDefaultTestScene::runThisTest()
{
	//创建一个演示用的层并放到当前演示场景中。
    CCLayer* pLayer = new UserDefaultTest();
    addChild(pLayer);
	//启动当前场景。
    CCDirector::sharedDirector()->replaceScene(this);
    pLayer->release();
}


当我们在DEBUG模式下运行此演示后程序截图为:


Cocos2d-x 深入解析系列 : 以XML文件方式保存用户数据

在程序的运行目录会出现:

Cocos2d-x 深入解析系列 : 以XML文件方式保存用户数据


              用IE打开后可以看到相应的键值数据。这样我们便学会了如何存储游戏中用到的数据到XML文件中。下课!


分类: 未分类 标签:

Unity/iOS – MoPub installation – Xcode compilation errors

2013年4月10日 没有评论

I’ve done everything EXACTLY as it is written in “Build instructions” here:
https://github.com/mopub/mopub-unity-ios-plugin

  1. Import UnityPlugin’s MoPubiOS.unitypackage into your Unity iOS Project

  2. (Optional) Add UnityPlugin’s Plugins/MoPub/testSupport/MoPubTestScene to the Unity build window [contains the MoPubGUIManager.cs example]

  3. Use Unity Player to build your Unity iOS Project into an Xcode project

  4. Copy the following source folders and files to the Classes/ folder of your Xcode project
    a. The complete contents of the MoPubSdk’s MoPubSDK/ directory
    b. MoPubSdk’s TouchJSON/ directory (including the parent directory)
    c. The MoPubBinding.m, MoPubManager.h, and MoPubManager.mm files from UnityPlugin

  5. Make modifications to the included frameworks in Xcode’s Build Phases
    a. Add the StoreKit.framework
    b. Add the AdSupport.framework and set it to Optional

  6. Build and run your Xcode project

and, when I am trying to build the Xcode project I get 9 linker errors:

Undefined symbols for architecture armv7:
“_moPubShowInterstitialAd”, referenced from:
RegisterMonoModules() in RegisterMonoModules.o
_moPubRequestInterstitialAd”, referenced from:
RegisterMonoModules() in RegisterMonoModules.o
“_moPubShowBanner”, referenced from:
RegisterMonoModules() in RegisterMonoModules.o
_moPubReportApplicationOpen”, referenced from:
RegisterMonoModules() in RegisterMonoModules.o
“_moPubRefreshAd”, referenced from:
RegisterMonoModules() in RegisterMonoModules.o
_moPubEnableLocationSupport”, referenced from:
RegisterMonoModules() in RegisterMonoModules.o
“_moPubDestroyBanner”, referenced from:
RegisterMonoModules() in RegisterMonoModules.o
_moPubCreateBanner”, referenced from:
RegisterMonoModules() in RegisterMonoModules.o
ld: symbol(s) not found for architecture armv7
clang: error: linker command failed with exit code 1 (use -v to see invocation)

but I’ve copied files int Xcode classes folder (not Xcode project group “Classes”) – as it is in point 4 exactly written

I tried copying into Xcode project “Classes” group as well (with option “copy if needed”). But then I have 1 error:

Lexical or preprocessor issue
/Users/xxx/Desktop/UnityProjects/Xcode/Classes/MPAdView.h:11:9: ‘MPGlobal.h’ file not found
But I have that file in Classes/Internal/Common Xcode project group as well as in the Classes Classes/Internal/Common Xcode project folder

Could someone help me with that please?

I got it working one time by adding CoreTelephony.framework

Try adding it.

If you get:

MOPUB: Banner view failed. Error: Error Domain=com.mopub.iossdk Code=0 "The operation couldn’t be completed. (com.mopub.iossdk error 0.)

Then welcome to my club.. I haven’t figured how to solve it yet.

分类: stackoverflow精选, unity3d 标签:

cocos2dx-lua中如何使用自定义类以及tolua++的使用

2013年4月9日 没有评论

猴子原创,欢迎转载。转载请注明: 转载自Cocos2D开发网–Cocos2Dev.com,谢谢!

原文地址: http://www.cocos2dev.com/?p=405


cocos2dx-lua中如何使用自定义类以及tolua++的使用


使用cocos2dx-lua开发,免不了自己定义类,但是如何使用自定义的类的?


先了解下lua如何调用c++的:

lua脚本代码->通过coocs2dx中间解析层代码->将其转换并调用cocos2dx c++的前端代码

coocs2dx中间解析层代码都在libs/lua/cocos2dx_support/LuaCocos2d.cpp 这个文件中,想了解的可以自己去看下这个文件。


也就是说,你自己定义了一个类,lua能够调用你自己定义的类,你的自定义类就必须在LuaCocos2d.cpp这个中间解析文件中申明。


看了LuaCocos2d.cpp这个文件,可能有的同学都晕了,不知道怎么在LuaCocos2d.cpp中申明自己的定义的类。不过,不用担心,cocos2dx已经提供了tolua++这个工具自动编译生成新的LuaCocos2d.cpp文件。


下面开始进入正题。

一、创建一个coocs2dx-lua 的Demo工程,然后在class中自己定义个类。

SNSprite.h

//
//  SNSprite.h
//  LuaDemo
//
//  Created by LiuYanghui on 13-4-8.
//
//

#ifndef __LuaDemo__SNSprite__
#define __LuaDemo__SNSprite__

#include "cocos2d.h"
USING_NS_CC;

class  SNSprite : public CCSprite{
public:
    static SNSprite* create(const char* name);
    
private:
    void initData();
};

#endif /* defined(__LuaDemo__SNSprite__) */


SNSprite.cpp

//
//  SNSprite.cpp
//  LuaDemo
//
//  Created by LiuYanghui on 13-4-8.
//
//

#include "SNSprite.h"

SNSprite* SNSprite::create(const char* name){
    SNSprite* sprite = new SNSprite();
    if(sprite && sprite->initWithFile(name)){
        sprite->initData();
        sprite->autorelease();
        return sprite;
    }
    CC_SAFE_DELETE(sprite);
    return NULL;
}

void SNSprite::initData(){
    CCActionInterval* action = CCSequence::createWithTwoActions(CCMoveTo::create(1.0, ccp(300,300)),
                                                                CCMoveTo::create(1.0, ccp(100,100))
                                                                );
    this->runAction(CCRepeatForever::create(action));
}


上面是我定义个一个类,很简单的功能。


二、使用tolua++将自定义的类添加的LuaCocos2d.cpp文件中。

tolua++工具在/tools/tolua++/目录下。

1、编写pkg文件

首先我们看下里面会发现有很多pkg文件,所以先为自定义的类编写pkg文件,文件的编写规则在README中,我简单说下编写规则:

Writing .pkg files
    1) enum keeps the same  //枚举类型保留不变
    2) remove CC_DLL for the class defines, pay attention to multi inherites  //删除cc_dll的类定义,改用多继承
    3) remove inline keyword for declaration and implementation  //删除内联关键字声明和实现
    4) remove public protect and private //删除访问限定词
    5) remove the decalration of class member variable  //删除成员变量
    6) keep static keyword  //保留静态关键词
    7) remove memeber functions that declared as private or protected //非public的函数都删除

以上是pkg文件的编写规则。

下面编写自定义文件的pkg文件。纯文本,后缀改成pkg

SNSprite.pkg

class  SNSprite : public CCSprite{
    static SNSprite* create(const char* name);
};


注意对比之前的头文件声明,可以进一步了解pkg文件的编写规则。将编写好的pkg文件放在tolua++中,和所有的pkg文件放在一起。


2、在Cocos2d.pkg文件中注册添加的pkg文件

用文本编辑器打开Cocos2d.pkg文件,在最后一行加入我们的注册声明:

$pfile "SNSprite.pkg"


3、配置build.sh编译脚本

在tolua++文件夹里面有个tolua++.Mac.zip,解压得到mac版的tolua++

打开build.sh文件,修改成下面的:

#!/bin/bash
#
# Invoked build.xml, overriding the lolua++ property


SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
TOLUA=/APP/cocos2d-2.1rc0-x-2.1.2/tools/tolua++/tolua++
if [ -z "${TOLUA}" ]; then
    TOLUA=`which tolua++5.1`
fi


if [ -z "${TOLUA}" ]; then
    echo "Unable to find tolua++ (or tolua++5.1) in your PATH."
    exit 1
fi


cd ${SCRIPT_DIR}
${TOLUA} -L basic.lua -o /Users/liuyanghui/Desktop/LuaDemo/LuaDemo/libs/lua/cocos2dx_support/LuaCocos2d.cpp Cocos2d.pkg


两个地方注意修改:

a、TOLUA=/APP/cocos2d-2.1rc0-x-2.1.2/tools/tolua++/tolua++  这个是你的mac版tolua++的地址,请修改成你自己的。

b、${TOLUA} -L basic.lua -o /Users/liuyanghui/Desktop/LuaDemo/LuaDemo/libs/lua/cocos2dx_support/LuaCocos2d.cpp Cocos2d.pkg  

参数-o 后面的 /Users/liuyanghui/Desktop/LuaDemo/LuaDemo/libs/lua/cocos2dx_support/LuaCocos2d.cpp 是生成新的LuaCocos2d.cpp的存放路径,请修改成你自己的。


4、执行编译脚本,生成LuaCocos2d.cpp文件

终端cd到build.sh目录。执行:

./build.sh

执行后之后,在你定义的输出目录就生成了新的LuaCocos2d.cpp文件。我们自己定义的类也添加进去了。


三、在lua中调用自定义类

在刚才新的cocos2dx-lua的Demo工程中,在hello.lua文件中添加调用自定义类的函数:

local function createSunnyLayer()
        local layerSunny = CCLayer:create()
 
        local labTips = CCLabelTTF:create("这个icon图标就是使用的自定义类", "Arial", 18)
        labTips:setPosition(ccp(240,280))
        layerSunny:addChild(labTips)
 
        local sp  = SNSprite:create("Icon.png")
        sp:setPosition(ccp(100,100))
        layerSunny:addChild(sp)
 
        return layerSunny
    end
    -- play background music, preload effec t


添加到scene中:

-- run
    local sceneGame = CCScene:create() -- 创建场景
    --sceneGame:addChild(createLayerFarm()) -- 将农场层加入场景
    --sceneGame:addChild(createLayerMenu()) -- 将菜单界面层加入场景
    sceneGame:addChild(createSunnyLayer())
    CCDirector:sharedDirector():runWithScene(sceneGame)


OK,xcode编译运行,就看到效果了。








分类: cocos2d, cocos2d-lua 标签:

Int number 0000 cocos2d

2013年4月5日 没有评论

I have a starting number to work from which is 0000 and increment it by one, i have that done, but the result is 1,2,3 instead of 0001,0002,0003 etc. How can I achieve this?

I use this string:

stringa = [NSString stringWithFormat:@"Score: %d",score];

Thank you.

stringa = [NSString stringWithFormat:@"Score: %04d",score];

should do what you want.

“4” is the (minimum) field width, and the “0” is for padding with zeroes instead of spaces.
stringWithFormat understands (almost) all the “printf” formats
(which are described in detail here: http://pubs.opengroup.org/onlinepubs/009695399/functions/printf.html ).

分类: cocos2d, stackoverflow精选 标签: