~~~~我的生活,我的点点滴滴!!
此例子是ClippingNode中一个例子,说实话我还没有弄太明白,先暂时把自己的一点点理解放这里,ClippingNode是利用模板遮罩来完成
对Node区域裁剪的技术,那么我们要先理解一下遮罩是个什么样子的东西?看下图:
所谓模板,就是一个形状,透过该形状可看到底板上的图层,如果底板上没有任何内容,则直接看到Layer上的内容,而底板上的东西又不会
妨碍Layer上的东西,即模板在底板之外的空间对于Layer来说是透明的。假如有个精灵在模板之外的底板上运动,我们是看不到它的。除非它进入
模板的区域,我们才能看到它。这说得我自己都不懂,像绕口令!
我们看看HoleDemo是什么样子的?
鼠标点击上面的图片,然后产生像中弹一样的效果,然后会留下疑似弹孔的痕迹。此实例用到了两层模版遮罩处理,第一层是弹孔遮罩,用弹孔图
遮住弹痕图。
实际使用时并不会为每个子弹都创建一个模版遮罩结点,而是将所有的弹孔放在一个结点中,并用此结点做为模板遮罩。第二层是背景图的区域遮罩,
让脱靶的子弹不产生弹孔。
看看代码是如何进行的(虽然代码不多,但是看得我是焦头烂额啊)
//背景的网格线是一张图片,并不是时时画的
bool BaseClippingNodeTest::init()
{
if (BaseTest::init()) {
//在这里添加了一张网格图片当背景
auto background = Sprite::create(s_back3);
background->setAnchorPoint( Vec2::ZERO );
background->setPosition( Vec2::ZERO );
this->addChild(background, -1);
this->setup();
return true;
}
return false;
}
//释放掉所有资源
HoleDemo::~HoleDemo()
{
CC_SAFE_RELEASE(_outerClipper);
CC_SAFE_RELEASE(_holes);
CC_SAFE_RELEASE(_holesStencil);
}
//模版遮罩
void HoleDemo::setup()
{
auto target = Sprite::create(s_pathBlock);
target->setAnchorPoint(Point::ZERO);
log("before %lf, %lf", target->getContentSize().width, target->getContentSize().height);
//放大3倍
target->setScale(3);
//放大与缩小是不会改变物体原始的size大小的。
log("after %lf, %lf", target->getContentSize().width, target->getContentSize().height);
_outerClipper = ClippingNode::create();
_outerClipper->retain();
//仿射变换,搜索全文,你会发现找不到这个仿射对象在哪初始化的,根进源码才发现 IDENTITY 为
//静态成员变量,那就意味着,他在此对象产生前就已经初始化好了,我们看下他的源码:
/************************************************************************
*
* AffineTransform AffineTransformMakeIdentity()
* {
* return __CCAffineTransformMake(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
* }
* extern const AffineTransform AffineTransformIdentity = AffineTransformMakeIdentity();
* const AffineTransform AffineTransform::IDENTITY = AffineTransformMakeIdentity();
*
************************************************************************/
//这样看来,这里这样使用就理所当然了,统一了仿射标准矩阵,即单位矩阵。
AffineTransform tranform = AffineTransform::IDENTITY;
//背景图结点按x轴与y轴都扩大3倍,仿射拉伸。
tranform = AffineTransformScale(tranform, target->getScale(), target->getScale());
//SizeApplyAffineTransform根据仿射矩阵来调整坐标值,得到目标坐标,此处是拉伸。
_outerClipper->setContentSize( SizeApplyAffineTransform(target->getContentSize(), tranform));
_outerClipper->setAnchorPoint( Point(0.5, 0.5) );
_outerClipper->setPosition(Point(this->getContentSize()) * 0.5f);
_outerClipper->runAction(RepeatForever::create(RotateBy::create(1, 45)));
//将背景图结点设置为此ClippingNode的模版缓冲遮罩结点。
//也就是对layer层使用了遮罩
_outerClipper->setStencil( target );
//创建一个裁剪结点
auto holesClipper = ClippingNode::create();
//设置它在模版缓冲运算时按反向处理,通过这个剪裁节点,我们将在效果图中抠出来一个图形
holesClipper->setInverted(true);
//设置ALPHA的测试参考值,ALPHA的测试参考值,用于进行ALPHA测试比较所用,一般比较算法为小于此值的像素直接会被舍弃。
//这样就可以实现图像的镂空,这样运行后的效果就像是产生了一个洞一样。
holesClipper->setAlphaThreshold( 0.05f );
holesClipper->addChild(target);
//创建用于包含所有弹痕的结点_holes
_holes = Node::create();
_holes->retain();
//将_holes放入到ClippingNode中做为要遮挡的结点
holesClipper->addChild(_holes);
//再创建一个用于包含所有弹孔的结点holesClipper
_holesStencil = Node::create();
_holesStencil->retain();
//ClippingNode设置_holesStencil做为模版遮罩结点
holesClipper->setStencil( _holesStencil);
//将第二个创建的ClippingNode放入第一个创建的ClippingNode做为被遮罩影响的结点
_outerClipper->addChild(holesClipper);
//将第一个ClippingNode放入当前层中
this->addChild(_outerClipper);
auto listener = EventListenerTouchAllAtOnce::create();
listener->onTouchesBegan = CC_CALLBACK_2(HoleDemo::onTouchesBegan, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
}
理顺一下上面的思路:
创建两个ClippingNode结点,一个是子弹的痕迹,也就是弹孔“周围的环境”在这里是_holes,另一个是弹孔,也就是透过去的那个“小眼”在这里
是_holesStencil(这就是我们模板--遮罩),我们要把弹孔也就是小眼放到弹痕上,然后在把弹痕放到裁剪区域图上(_outerClipper)。
//产生弹孔效果代码
void HoleDemo::pokeHoleAtPoint(Vec2 point)
{
//随即产生缩放效果与旋转角度, 这样看起来有打击感和自然感
float scale = CCRANDOM_0_1() * 0.2 + 0.9;
float rotation = CCRANDOM_0_1() * 360;
//开始蒙皮, 看得不是太懂,所以这里的注释写的少
auto hole = Sprite::create("Images/hole_effect.png");
hole->setPosition( point );
hole->setRotation( rotation );
hole->setScale( scale );
//加进来,到时候好释放,不然那一块空间永远无法访问了
_holes->addChild(hole);
auto holeStencil = Sprite::create("Images/hole_stencil.png");
holeStencil->setPosition( point );
holeStencil->setRotation( rotation );
holeStencil->setScale( scale );
//加进来,到时候好释放,不然那一块空间永远无法访问了
_holesStencil->addChild(holeStencil);
_outerClipper->runAction(Sequence::createWithTwoActions(ScaleBy::create(0.05f, 0.95f),
ScaleTo::create(0.125f, 1)));
}
会产生被击效果
void HoleDemo::onTouchesBegan(const std::vector<Touch*>& touches, Event* event)
{
Touch *touch = (Touch *)touches[0];
//cocos2dx和opengl使用的是相同的坐标系
Vec2 screenPoint = touch->getLocationInView();
Vec2 UIToGL = Director::getInstance()->convertToGL(screenPoint);
//上面两行看似复杂的代码其实直接用touch->getLocation();就行了
Vec2 point = _outerClipper->convertToNodeSpace(UIToGL);
auto rect = Rect(0, 0, _outerClipper->getContentSize().width, _outerClipper->getContentSize().height);
if (!rect.containsPoint(point)) return;
this->pokeHoleAtPoint(point);
//下面这个方法其实和上面一样的
if( touch )
{
Vec2 localPoint = touch->getLocation();
//但是setPosition使用是他的父类node的相对坐标,所以下面的值还是要转换成NodeSpace
Vec2 p = _outerClipper->convertToNodeSpace(localPoint);
Rect layerRect = Rect(0, 0, _outerClipper->getContentSize().width, _outerClipper->getContentSize().height);
if( layerRect.containsPoint(p) )
{
log("xx");
pokeHoleAtPoint(p);
}
else
{
return ;
}
}
}
看上面的onTouchesBegan函数,我最开始会以为有一个bug,就是当精灵旋转到45度后,我点击4个角时,他不会正确的响应,因为看区域函数
Rect(0, 0, _outerClipper->getContentSize().width, _outerClipper->getContentSize().height);
他产生的效果是一个规整的形状(也就是与x轴平行的一个区域)那么当物体旋转45度后,4个角就已经挤出这个区域,应该是对象的长度了, 但是了,
他巧妙的把点击的坐标转换成了以自己为坐标系相对自己的坐标,那么这种情况就不会产生了。