用ClippingNode实现文字AVG游戏的对话字幕效果

来源:互联网 发布:9块9包邮淘宝店 编辑:程序博客网 时间:2024/06/11 05:20

玩文字AVG游戏主要的行为就是阅读角色的对话(当然还包括欣赏立绘和CG音乐),然后在适时地做出选择。玩过的朋友都知道一般对话都是一个字一个字显示出来的,文字显示的速度可以调节,点击鼠标就把整句话显示出来。这个效果如何用Cocos2dx做出来呢?经过网上一番搜索,基本上用ClippingNode可以实现。主要参考的文章有这么几篇:

http://shahdza.blog.51cto.com/2410787/1561937

http://blog.csdn.net/ac_huang/article/details/39554967

可以先学习一下ClippingNode的基本原理。


其实有点像剪纸,主要需要考虑四个问题:

1. 剪哪张纸?

2. 剪什么形状的?

3. 使用剪下来的哪一部分?

4. 贴到什么上面?

第一个问题决定了ClippingNode对象的child都有谁,可以有多个Node,要通过addChild添加,多个Node结合成为要剪的纸。

第二个问题决定了stencil,stencil本身也是一个Node,所以也可以包含多个子Node,比如我们需要多个图形的时候就把各个sprite分别添加到Node中,多个Node组合出的图形就是要剪的形状。然后再把Node用setStencil把这个节点加到ClippingNode对象里,每个ClippingNode对象只能有一个stencil。需要注意的是该Node本身的texture并没有意义,需要的只是轮廓。

第三个问题决定是否reverse。ClippingNode有一个reverse属性,默认是false,也就是取剪下来的部分。比如在一张正方形的纸上剪一个圆下来,false的话就是把实心的圆形本身贴到背景上,如果是true的话就是把一个有圆形镂空的正方形贴上去。

第四个问题与clippingnode对象本身无关,你要考虑的是背景,就是你把clippingnode对象add到哪个layer。


然后回过头来考虑我们的需求,我们需要的是一段文字逐字显示,这里采用的方案是“剪一张写好整段文字的纸,一个字一个字的剪,把剪下来的的字贴到背景上”。

问题一:一张有一段对话的纸,也就是一个Label对象。

问题二:因为汉字基本是正方形,所以这里用LayerColor这样一个对象生成一个正方形,边长等于字号大小,颜色任意。那么如何一个字一个字的呢,如果每个字都生成一个LayerColor对象的话构造对象的开销未免过大,这里采用改变X的Scale的方式,有几行就需要几个LayerColor对象,然后为每行设置不同的DelayTime。

问题三:我们需要的是剪下来的文字,所以默认的false就可以。

问题四:剪下来的文字贴到已有的对话框上,所以ClippingNode的ZOrder值应大于对话框背景。


基本的思路已经确定了,下面就开始动手做了。

因为我有参数需要设置文字区域的大小,包括左上角和右下角的坐标,我需要把Label的左上角对其到参数左上角坐标,这里直接设置Label的ArchorPoint为左上角(0,1),

<span style="white-space:pre"></span>auto label = Label::create();<span style="white-space:pre"></span>label->setDimensions(400, 100);label->setLineBreakWithoutSpace(true);label->setString("123456789012345678901234567890 123456789012345678901234567890 12345678901234567890");label->setSystemFontName("Arial");label->setSystemFontSize(24); label->setAnchorPoint(Vec2(0, 1));label->setPosition(Vec2(100, 100));

所以ClippingNode也是需要同样的设置

    <span style="white-space:pre"></span>auto sprite = Sprite::create("images/bk.jpg");<span style="white-space:pre"></span>sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));<span style="white-space:pre"></span>auto clip1 = LayerColor::create(Color4B::BLACK, 24, 24);<span style="white-space:pre"></span>Node* stencil = Node::create();<span style="white-space:pre"></span>stencil->addChild(clip1);<span style="white-space:pre"></span>ClippingNode* clippingNode = ClippingNode::create(stencil);<span style="white-space:pre"></span>clippingNode->addChild(sprite);<span style="white-space:pre"></span>clippingNode->setAnchorPoint(Vec2(0, 1));<span style="white-space:pre"></span>clippingNode->setPosition(Vec2(100, 100));<span style="white-space:pre"></span>addChild(clippingNode, 2);

运行之会显示这样的效果:



ClippingNode的位置好像不太对,比文本高了一个字符的高度,如果同样setPosition是(100,100)的话那只能说明ClippingNode的AnchorPoint不对,可是明明是(0,1)了啊。于是只能debug一下:



这里我们发现_anchorPoint确实是(0,1)了,可是它上面那个属性_anchorPointInPoints却是(0,0)!这是怎么回事?只能去看看setter的代码了:

void Node::setAnchorPoint(const Vec2& point){#if CC_USE_PHYSICS    if (_physicsBody != nullptr && !point.equals(Vec2::ANCHOR_MIDDLE))    {        CCLOG("Node warning: This node has a physics body, the anchor must be in the middle, you cann't change this to other value.");        return;    }#endif        if( ! point.equals(_anchorPoint))    {        _anchorPoint = point;        _anchorPointInPoints = Vec2(_contentSize.width * _anchorPoint.x, _contentSize.height * _anchorPoint.y );        _transformUpdated = _transformDirty = _inverseDirty = true;    }}

注意看这两行
        _anchorPoint = point;        _anchorPointInPoints = Vec2(_contentSize.width * _anchorPoint.x, _contentSize.height * _anchorPoint.y );
_anchorPoint是直接赋值的,但同时也给_anchorPointInPoints做了赋值操作,区别是_anchorPointInPoints是实际的坐标,需要在_anchorPoint的基础上乘以_contentSize,再回到我们debug的窗口看看,ClippingNode的_contentSize居然是0!

这和我们想象的不一样啊,ClippingNode里面既有child又有stencil怎么contentSize会是0呢?

继续看代码:

void ClippingNode::setStencil(Node *stencil){    CC_SAFE_RETAIN(stencil);    CC_SAFE_RELEASE(_stencil);    _stencil = stencil;}
设置stencil的时候只是给一个成员变量赋值,并不会改变ClippingNode的contentSize,再看看addChild方法,直接跟到了Node的addChild,堆栈比较多就不把代码贴上来了,有兴趣的朋友可以自己跟一下,你会发现里面也并没有改变contentSize的地方。也就是说addChild方法本身并不会改变该节点的contentSize,继续在CCNode.cpp里面搜索一下_contentSize,会发现除了setContentSize方法以外几乎没有修改这个变量的地方,而调用setContentSize的时候也会更新_anchorPointInPoints变量。

那么我们继续看一下ClippingNode里面其他的组件,先看看stencil,它里面_anchorPoint、_anchorPointInPoints和_contentSize也全都是0,之前说过stencil这个Node里面可以有child,那么继续看他的child

这时候你会发现这几个变量终于不是0了,也就是说其实是载入texture的那一层node保存着contentSize,只有他们知道size到底是多少,就好像代码其实都是最基层的程序员写的☺,既然如此,那我们就在这一层直接设置anchorpoint好了,改bug还得找干活的程序员对吧☺

但后面你会发现无论你怎么设置stencil子节点的anchorPoint都无法改变显示的效果,尽管debug时候显示的变量值是正确的,好像最终依然会将这些子节点的anchorpoint覆盖为(0,0),这恐怕是因为stencil本身也是一个不含contentsize的节点。

因此,我们得出一个结论,要想设置遮罩的位置,就直接调用stencil子节点的setPosition,anchor point固定是左下角。

Node* stencil = Node::create();auto clip1 = LayerColor::create(Color4B::BLACK, 24, 24);clip1->setPosition(Vec2(100,76));stencil->addChild(clip1);ClippingNode* clippingNode = ClippingNode::create(stencil);clippingNode->addChild(sprite);addChild(clippingNode, 2);


0 0
原创粉丝点击