如何使用Cocos2d-x3.0来给unity sprite 遮罩添加遮罩

1430人阅读
技术(12)
程序截图:
  有时候,你在做游戏时,可能需要一种方式来显示精灵的某一部分(就是添加遮罩啦)。
  一种方式就是使用另外一张图片,叫做mask。你把mask图片中间设置成白色,白色区域是被mask图片的可见区域。之后这个白色区域会透明的。
  然后,你可以使用本教程提供的方法来把mask图和原图结合起来,然后创建如上图所示的效果。
  你会发现本教程提供的方法非常方便,用它可以完成许多很有意思的效果。比如,把大头贴,或者像框等等。所以这些内容,你都可以从本教程中学到!
  学习本教程的前提是你要熟悉cocos2d-x,如果你对cocos2d是何物还不清楚的话,建议你先学习一下其它cocos2d-x教程。   
Getting Started
  启动terminal,运行&python /Cocos/cocos2d-x-3.0beta2/tools/project-creator/create_project.py&。把工程命名为MaskedCal,然后选择一个文件夹来保存,最后点Create。
  接下来,请下载本工程所需要的并把它们拖到你的Xcode的Resource分组中,确保“Copy
items into destination group’s folder (if needed)” 并复选中,然后点Finish。
  在开始编码之前,让我们先来一点爵士音乐。同时,由于这里给的图片是480x320的,为了适应各个分辨率,这里需要setDesignResolutionSize,方便在不同的设备上显示。就是告诉游戏引擎,我是针对480x320像素设计的,遇到其他分辨率的设备,劳驾你帮我自动调整。打开AppDelegate.cpp,然后做如下修改:
class=&language-cpp&&
&SimpleAudioEngine.h&
CocosDenshion::SimpleAudioEngine::getInstance()-&playBackgroundMusic(&TeaRoots.mp3&,
EGLView::getInstance()-&setDesignResolutionSize(480,
320, ResolutionPolicy::NO_BORDER);
scene = HelloWorld::sceneWithLastCalendar(0);
director-&runWithScene(scene);
true;&/code&
  这里播放一个由制作的一首非常好听的曲子,然后调用了一个新的方法来创建场景。
  接下来,打开HelloWorldScene.h 并作下面修改:
class=&language-cpp&&
cocos2d::Scene* sceneWithLastCalendar(int
lastCalendar);
cocos2d::Layer* layerWithLastCalendar(int
lastCalendar);&/code&
  在这个场景中,我们将随机显示一张日历图片(从三张里面选择)。在这个类里,我们保存了当前显示的日历图片的序号,然后添加了HelloWorld这个layer的另一个create函数同时替换了createScene函数。layerWithLastCalendar接收一个int型参数来标识将要显示的日历图片。后面,你会看到这个数字会随机从1-3中选择。
  然后,回到HelloWorldScene.cpp,并且作如下修改:
class=&language-cpp&&
HelloWorld::calendarNum = 0;
HelloWorld::sceneWithLastCalendar(int
lastCalendar)
scene = Scene::create();
layer = HelloWorld::layerWithLastCalendar(lastCalendar);
&&&&scene-&addChild(layer);
&&&&return
HelloWorld::layerWithLastCalendar(int
lastCalendar)
&&&&HelloWorld
*pRet = new
HelloWorld();
(pRet && pRet-&init())
&&&&&&&&pRet-&autorelease();
&&&&&&&&Size
winSize = Director::getInstance()-&getWinSize();
&&&&&&&&do
&&&&&&&&&&&&calendarNum
=& (arc4random() % 3) + 1;
(calendarNum == lastCalendar);
&&&&&&&&char
spriteName[100] = {0};
&&&&&&&&sprintf(spriteName,
&Calendar%d.png&,
calendarNum);
&&&&&&&&Sprite
* cal = Sprite::create(spriteName);
&&&&&&&&cal-&setPosition(winSize.width/2,
winSize.height/2);
&&&&&&&&pRet-&addChild(cal);
&&&&&&&&return
&&&&&&&&delete
&&&&&&&&pRet
&&&&&&&&return
  上面cal精灵设置的坐标就是我们的DesignResolutionSize/2,一旦我们设置了designSize,cocos2d-x中的getWinSize就成了我们的designSize。。
  我们在init函数里添加触摸事件响应,同时添加三个回调函数:
class=&language-cpp&&
HelloWorld::init()
( !Layer::init() )
&&&&&&&&return
listener = EventListenerTouchOneByOne::create();
&&&&listener-&setSwallowTouches(true);
&&&&listener-&onTouchBegan
= CC_CALLBACK_2(HelloWorld::onTouchBegan, this);
&&&&listener-&onTouchMoved
= CC_CALLBACK_2(HelloWorld::onTouchMoved, this);
&&&&listener-&onTouchEnded
= CC_CALLBACK_2(HelloWorld::onTouchEnded, this);
&&&&_eventDispatcher-&addEventListenerWithSceneGraphPriority(listener,
&&&&return
HelloWorld::onTouchBegan(cocos2d::Touch *touch, cocos2d::Event *unused_event)
*scene = HelloWorld::sceneWithLastCalendar(calendarNum);
&&&&Director::getInstance()-&replaceScene(TransitionJumpZoom::create(1.0f,
&&&&return
HelloWorld::onTouchMoved(cocos2d::Touch *touch, cocos2d::Event *unused_event)
HelloWorld::onTouchEnded(cocos2d::Touch *touch, cocos2d::Event *unused_event)
  这里只是一些基本的cocos2d-x代码,用来在屏幕中间随机显示一张日历图片。它同时也包含了一些逻辑,当你点击屏幕的时候,可以比较平滑地切换到另一张图片。
  编译并运行,现在你每次点击屏幕就可以看到一些随机的日历图片啦,它们全部都是由原作者完成的:)
  现在,我们把程序框架搭好了,接下来,让我们来实现遮罩效果吧!
遮罩和OpenGL 混合模式(Blend Mode)
  如果你在图片编辑软件里面打开Art\CalendarMask.png图片,它看起来是这样子的:
  我们将使用这张图片来给我们的日历图片添加一个边框,是那种带有波纹效果的边框,而不是四边形的。这张图片透明的部分,就是遮罩效果的部分,而白色区域则是日历图片会显示的区域。
  为了实现这个效果,我们将使用OpenGL的混合模式。
  如果你回过头去看这篇教程的话,我们在那里讨论过OpenGL的混合模式。我在那里提到过一个非常方便的可以用来可见化调节混合模式的效果。
  为了完成我们想要的效果,我们需要采取下面的策略:
我们首先渲染mask精灵,把src color(就是mask精灵)设置为GL_ONE,并且把destination color(一个空的buffer)设置为GL_ZERO。所以,效果就是简单的把mask图片显示来。
接下来,我们渲染日历图片精灵。把src color(日历)设置为GL_DST_ALPHA。意思是,看看mask图片的当前alpha值是多少,如果是0(完全透明),那么就显示mask的。如果是1(完全不透明),那么就显示日历图片。(译者注:如果大家对此不明白,可以参考这个)。然后把dst
color(the mask)设计成GL_ZERO,这样的话,之前渲染上去的mask就消失了。
  很酷吧!你可能会觉得我们只需要先把mask精灵渲染上去,然后再渲染日历精灵,并且指定这两个精灵的blendFunc就行了。可是,实际上这样是行不通的!
  上面所提到的混合算法,当精灵下面还有一些精灵在渲染的时候就会出问题---比如背景图片上面有一个精灵。这是因为,这里作了一个假设,在上面做完1那个步骤之后,imgae buffer里面只存在唯一一张图片,那就是mask。(这个假设当然是不正确的啦,因为你要切换日历图片对不对?)
  因此,我们需要一种方式,可以建立一个干净的“黑板”,然后在那执行1,2步来制作一个遮罩纹理。很幸运的是,用RenderTexture非常方便。
Masking and CCRenderTexture]
  RenderTexture是一个这样的类,它可以让你在屏幕之外的buffer里面渲染。
  它用起来非常方便,主要有以下原因---你可以使用它来给你的游戏截屏,可以高效地缓存用户渲染的内容,可以在运行时动态地创建sprite sheet,或者,就像本教程中一样,可以制作一个mask sprite。
  为了使用RenderTexture,你需要采取以下4步:
创建RenderTexture类,以像素为单位,指定你想要绘制的纹理的宽度和高度.
调用RenderTexture的begin方法来初始化渲染操作。
调用OpenGL函数来绘制实际的内容--但是,这些OpenGL调用最终都会绘制到屏幕之外去,而不会影响游戏中现在渲染的图像。
调用RenderTexture的end方法来结束绘制操作。一旦你完成之后,RenderTexture有一个sprite属性,你可以把它当用Sprite来用。
  不要觉得第3步很奇怪---因为你正在使用cocos2d,90%的情况你是不需要手动直接调用OpenGL函数的。但是,如果你想渲染一个节点的话,你可以直接调用某一个节点的visit方法,如sprite-&visit,然后这个函数会自动为你发射一些OpenGL函数指针给图形硬件去显示。
  这里有一点需要注意的就是坐标问题。(0,0)点是渲染的纹理的左下角位置,所以,你在使用RenderTexture的时候,一定要把坐标设置对。
  好了,你可能听得有些烦了,程序员还是喜欢看代码的。好,让我们开始coding吧!
给精灵添加遮罩: 最终实现
  打开HelloWorldScene.m,然后在init方法上面添加下面的方法,注意这个方法是静态的,在头文件声明时需要注意:
class=&language-cpp&&
HelloWorld::maskedSpriteWithSprite(Sprite* textureSprite, Sprite* maskSprite)
&&&&RenderTexture
* rt = RenderTexture::create( maskSprite-&getContentSize().width,
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&maskSprite-&getContentSize().height
&&&&maskSprite-&setPosition(maskSprite-&getContentSize().width/2,
&&&&&&&&&&&&&&&&&&&&&&&&&&&&maskSprite-&getContentSize().height/2);
&&&&textureSprite-&setPosition(textureSprite-&getContentSize().width/2,
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&textureSprite-&getContentSize().height/2);
&&&&maskSprite-&setBlendFunc(
BlendFunc{GL_ONE, GL_ZERO} );
&&&&textureSprite-&setBlendFunc(
BlendFunc{GL_DST_ALPHA, GL_ZERO} );
&&&&rt-&begin();
&&&&maskSprite-&visit();
&&&&textureSprite-&visit();
&&&&rt-&end();
&&&&Sprite
*retval = Sprite::createWithTexture(rt-&getSprite()-&getTexture());
&&&&retval-&setFlippedY(true);
&&&&return
  让我们一步步来分解下面的操作:
使用mask精灵的大小来创建CCRenderTexture重新设置mask精灵和texture精灵的位置,使它们的左下角是(0,0)按照我们之前讨论的,设置每个精灵的blendFunc。调用CCRenderTexture的begin方法来开始渲染操作,然后依次渲染mask和texture精灵,最后调用end方法。
基于CCRenderTexture的sprite属性的texture创建一个新的精灵,同时翻转y,因为纹理创建出来是倒的。
好了,接下来,我们可以使用上面的函数来制作遮罩的效果了:
class=&language-cpp&&
* mask = Sprite::create(&CalendarMask.png&);
* maskedCal = maskedSpriteWithSprite(cal, mask);
maskedCal-&setPosition(visibleSize.width/2,
visibleSize.height/2);
pRet-&addChild(maskedCal);&/code&
编译并运行,现在,你可以看到一个带有遮罩效果的精灵啦。  
RenderTexture 方法的缺点
对于这个简单的教程,这里提出的方法还比较ok,但是,这种方法也有一些缺点,特别是针对复杂一点的项目的时候:
每一次你应用一次mask的时候,都会在内存里面创建一张额外的纹理图片。 在iphone上面纹理所能占用的内存数量是非常有限的,所以你要非常小心,尽可能减少内存中加载的纹理图片数量。当你一次只给一张图片加mask效果的时候,这种方法很好,但是100张图片需要mask呢?渲染非常耗时.使用RenderTexture来渲染代价非常高,尤其是当纹理大小变大以后。如果你经常使用这种方式去绘图,那么会严重影响性能。
最后注意创建遮罩的代码要写在onEnter()里面,在init()里面需要retain()一下,不然不会创建成功的。附上已测试通过的代码:
Sprite* HelloWorld::maskedSpriteWithSprite(Sprite* textureSprite, Sprite* maskSprite)
&RenderTexture * rt = RenderTexture::create( maskSprite-&getContentSize().width,
&&maskSprite-&getContentSize().height);
&maskSprite-&setPosition(maskSprite-&getContentSize().width/2,
&&maskSprite-&getContentSize().height/2);
&textureSprite-&setPosition(textureSprite-&getContentSize().width/2,
&&textureSprite-&getContentSize().height/2);
&BlendFunc bf1={GL_ONE, GL_ZERO},bf2={GL_DST_ALPHA, GL_ZERO};
&maskSprite-&setBlendFunc( bf1 );
&textureSprite-&setBlendFunc( bf2 );
&rt-&begin();
&maskSprite-&visit();
&textureSprite-&visit();
&rt-&end();
&Sprite *retval = Sprite::createWithTexture(rt-&getSprite()-&getTexture());
&retval-&setFlippedY(true);
void HelloWorld::onEnter()
&Layer::onEnter();
&Size visibleSize = Director::getInstance()-&getVisibleSize();
&Sprite * mask = Sprite::create(&2.png&);
&Sprite *cal=Sprite::create(&1.png&);
&Sprite * maskedCal = maskedSpriteWithSprite(cal, mask);
&maskedCal-&setPosition(480,320);
&addChild(maskedCal);
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:18351次
排名:千里之外
评论:74条
(1)(1)(1)(4)(1)(5)4258人阅读
cocos2d-x学习(10)
& & & &cocos2d-x 3.0版本的事件分发的机制较之之前的版本进行了修改,把事件处理的逻辑分离出来,并通过不同的事件监听器来监听不同的事件。当一个节点收到了事件,就会指派一个事件分发器_eventDispatcher专门来分发这些事件。对于触摸来说,大概的过程就是我们先创建一个对应触摸事件的监听器,然后覆盖触摸事件的函数,并把它们绑定到监听器,然后可以设置一下这个监听器的属性,最后把监听器添加到分发器之中,系统就会自动进行触摸事件的处理。
& & & &我们先看看单点触摸的使用,下面是源代码中关于单点触摸监听器的类,可以看到
class EventListenerTouchOneByOne: public EventListener
static const std::string LISTENER_ID;
static EventListenerTouchOneByOne* create();
virtual~EventListenerTouchOneByOne();
void setSwallowTouches(bool needSwallow);
bool isSwallowTouches();
///Overrides
virtual EventListenerTouchOneByOne* clone()
virtual bool checkAvailable()
std::function&bool(Touch*, Event*)&onTouchB
std::function&void(Touch*, Event*)&onTouchM
std::function&void(Touch*, Event*)&onTouchE
std::function&void(Touch*, Event*)& onTouchC
EventListener TouchOneByOne();
bool init();
std::vector&Touch*&_claimedT
bool _needS
friend class EventD
这个类看上去比较容易理解,下面我们用代码来演示一下怎么使用。在HelloWorld的init函数中注册监听器并添加到事件分发器中去。
//添加一个测试的精灵
autoonion = Sprite::create(&onion.png&);
onion-&setPosition(Point(visibleSize.width/2, visibleSize.height/2));
onion-&setScale(0.2);
this-&addChild(onion);
//创建一个触摸监听器,这里使用单点触摸事件
autoTouchListenr = EventListenerTouchOneByOne::create();
//设置吞噬为true,不让触摸往下传递
TouchListenr-&setSwallowTouches(true);
//和回调函数绑定
TouchListenr-&onTouchBegan= CC_CALLBACK_2(HelloWorld::onTouchBegan,this);
TouchListenr-&onTouchMoved= CC_CALLBACK_2(HelloWorld::onTouchMoved,this);
TouchListenr-&onTouchEnded= CC_CALLBACK_2(HelloWorld::onTouchEnded,this);
//添加监听器到事件分发器中
_eventDispatcher-&addEventListenerWithSceneGraphPriority(TouchListenr,onion);
下面我们覆盖单点触摸中提供的触摸函数:
bool HelloWorld::onTouchBegan(Touch* touch, Event* event)
//获取精灵对象并取得精灵的矩阵
autosprite = static_cast&Sprite*&(event-&getCurrentTarget());
Rect rect = sprite-&getBoundingBox();
//获取触摸点的坐标
Point point = touch-&getLocation();
//判断触摸点是否在精灵的矩阵范围内
if(rect.containsPoint(point))
void HelloWorld::onTouchMoved(Touch* touch, Event* event)
//获取精灵对象
autosprite = static_cast&Sprite*&(event-&getCurrentTarget());
//改变精灵的位置
sprite-&setPosition(sprite-&getPosition()+ touch-&getDelta());
void HelloWorld::onTouchEnded(Touch* touch, Event* event)
CCLog(&touch end!&);
点击调试运行,可以在屏幕中拉动所测试的精灵,如下图:
& & & &在上面的例子中我们看到了,传递过来的参数主要有Touch* touch和Event* event,我们可以进入源代码中查看他们的作用。Touch类是继承了REF,查看源代码中的主要成员如下:
/**returns the current touch location in OpenGL coordinates */
Point getLocation()
/**returns the previous touch location in OpenGL coordinates */
Point getPreviousLocation()
/**returns the start touch location in OpenGL coordinates */
Point getStartLocation()
/**returns the delta of 2 current touches locations in screen coordinates */
Point getDelta()
/**returns the current touch location in screen coordinates */
Point getLocationInView()
/**returns the previous touch location in screen coordinates */
Point getPreviousLocationInView()
/**returns the start touch location in screen coordinates */
Point getStartLocationInView()
& & & &也就是Touch传入的是触摸点的坐标位置,并且Touch类为我们提供一些坐标的写法,那么我们就方便很多了。譬如上面例子中的触摸移动时,Touch就为我提供一个getDelta()来计算点的位移。
而对于Event类,主要是传入处理的对象,看源代码有以下主要成员:
/**Gets the event type */
inline Type getType() const { return_ };
/**Stops propagation for current event */
inline void stopPropagation() { _isStopped = };
/**Checks whether the event has been stopped */
inline bool isStopped() const { return_isS };
/**@brief Gets current target of the event
@return The target with which the eventassociates.
@note It onlys be available when the eventlistener is associated with node.
It returns 0 when the listener isassociated with fixed priority.
inline Node* getCurrentTarget() { return_currentT };
& & & & &那么我们在处理触摸对象的时候和例子那样通过getCurrentTarget()来获取对象并处理即可。
& & & & &另一方面,我们在把监听器添加到事件分发器的时候,会看到代码提示两种方法,一个为:addEventListenerWithSceneGraphPriority,另一个为addEventListenerWithFixedPriority。同样可以查看源代码如下:
/**Adds a event listener for a specified event with the priority of scene graph.
@param listener The listener of a specifiedevent.
@param node The priority of the listener isbased on the draw order of this node.
@note The priority of scene graph will be fixed value 0. So the order oflistener item
in the vector will be ' &0, scenegraph (0 priority), &0'.
voidaddEventListenerWithSceneGraphPriority(EventListener*listener, Node* node);
/**Adds a event listener for a specified event with the fixed priority.
@param listener The listener of a specifiedevent.
@param fixedPriority The fixed priority ofthe listener.
@note A lower priority will be called beforethe ones that have a higher value.
0 priority is forbidden for fixedpriority since it's used for scene graph based priority.
voidaddEventListenerWithFixedPriority(EventListener*listener, int fixedPriority);
& & & &根据注释可以知道,前者的触发优先级是按照第二个参数中的node的显示顺序来确定的,而且默认的fixedPriority为0,也就是如果精灵位置靠前,则会优先响应触摸。而后者按照第二个参数的整形变量值的大小来确定的,而且不能为0,值越小那么优先响应触摸。两者除了优先级不一样,移除的过程也有差异。前者会在触摸对象对应的node析构之后系统会帮我们调用移除触摸,但是后者就需要我们手动移除,这一点要注意。
& & & &下面我们可以修改上述例子的代码,添加多两个精灵,然后在分发事件代码中添加:
_eventDispatcher-&addEventListenerWithSceneGraphPriority(TouchListenr-&clone(),onion2);
_eventDispatcher-&addEventListenerWithSceneGraphPriority(TouchListenr-&clone(),onion3);
& & & &点击运行可以测试一下,没有问题。要是想使用addEventListenerWithFixedPriority的方式的话,虽然可以自定义优先级,但是由于没有绑定node对象,所以还需要在触摸函数中引入需要控制的对象才行。但是最后别忘了使用_eventDispatcher-&removeEventListener(listerName);来实现监听器的移除。
& & & &多点触摸和单点触摸类似,看下面多点触摸监听器的源码。
class EventListenerTouchAllAtOnce: public EventListener
static const std::string LISTENER_ID;
static EventListenerTouchAllAtOnce* create();
virtual ~EventListenerTouchAllAtOnce();
///Overrides
virtua lEventListenerTouchAllAtOnce* clone()
virtual bool checkAvailable()
std::function&void(conststd::vector&Touch*&&,Event*)& onTouchesB
std::function&void(conststd::vector&Touch*&&,Event*)& onTouchesM
std::function&void(conststd::vector&Touch*&&,Event*)& onTouchesE
std::function&void(conststd::vector&Touch*&&,Event*)& onTouchesC
EventListener TouchAllAtOnce();
bool init();
friend class EventD
& & 可以发现多点触摸就是传入多个Touch对象而已,然后处理的时候可以遍历vector&Touch*&来逐个处理每一个点,例子可以参考源码自带的例子MutiTouchTest。
& & 文章写到这里为止,如有理解不当,请不吝赐教,谢谢。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:126644次
积分:1326
积分:1326
排名:千里之外
原创:29篇
转载:18篇
评论:29条
(4)(1)(4)(7)(1)(3)(1)(1)(1)(5)(10)(3)(2)(2)(2)1258人阅读
Cocos2d框架(98)
iOS开发重用(92)
iPhone游戏开发(330)
OpenGL学习(24)
  免责申明(必读!):本博客提供的所有教程的翻译原稿均来自于互联网,仅供学习交流之用,切勿进行商业传播。同时,转载时不要移除本申明。如产生任何纠纷,均与本博客所有人、发表该翻译稿之人无任何关系。谢谢合作!
原文链接地址:
教程截图:
  有时候,你在做游戏时,可能需要一种方式来显示精灵的某一部分(就是添加遮罩啦)。
  一种方式就是使用另外一张图片,叫做mask。你把mask图片中间设置成白色,白色区域是被mask图片的可见区域。之后这个白色区域会透明的。
  然后,你可以使用本教程提供的方法来把mask图和原图结合起来,然后创建如上图所示的效果。
  你会发现本教程提供的方法非常方便,用它可以完成许多很有意思的效果。比如,把大头贴,或者像框等等。所以这些内容,你都可以从本教程中学到!
  本教程会教你如何使用cocos2d 1.0来给一个sprite添加mask,使用一个非常强大的类CCRenderTexture,之前我们在学TinyWings Like游戏的时候已经见过啦:)
  学习本教程的前提是你要熟悉cocos2d,如果你对cocos2d是何物还不清楚的话,建议你先学习本博客上面的。
Getting Started
  启动Xcode,然后选择File\New\New Project,接着选iOS\cocos2d\cocos2d,再点击Next。把工程命名为MaskedCal,点击Next,然后选择一个文件夹来保存,最后点Create。
  接下来,请下载本工程所需要的并把它们拖到你的Xcode的Resource分组中,确保“Copy items into destination group’s folder (if needed)” 并复选中,然后点Finish。
  在开始编码之前,让我们先来一点爵士音乐。打开AppDelegate.m,然后做如下修改:
// Add to top of file
#import &SimpleAudioEngine.h&
// At end of applicationDidFinishLaunching, replace last line with the following 2 lines:
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@&TeaRoots.mp3& loop:YES];
[[CCDirector sharedDirector] runWithScene: [HelloWorldLayer sceneWithLastCalendar:0]];
  这里播放一个由制作的一首非常好听的曲子,然后调用了一个新的方法来加载场景。
  接下来,打开HelloWorldLayer.h 并作下面修改:
// Add new instance variable
int calendarN
// Replace the +(CCScene*) scene declaration at the bottom with the following:
+ (CCScene *) sceneWithLastCalendar:(int)lastC
- (id)initWithLastCalendar:(int)lastC
  在这个场景中,我们将随机显示一张日历图片(从三张里面选择)。在这个类里,我们保存了当前显示的日历图片的序号,然后修改了初始化方法为 initWithLastCalendar。它接收一个int型参数来标识将要显示的日历图片。后面,你会看到这个数字会随机从1-3中选择。
& & 然后,回到HelloWorldLayer.m,并且作如下修改:
// Replace +(CCScene *) scene with the following
+(CCScene *) sceneWithLastCalendar:(int)lastCalendar // new
CCScene *scene = [CCScene node];
HelloWorldLayer *layer = [[[HelloWorldLayer alloc]
initWithLastCalendar:lastCalendar] autorelease]; // new
[scene addChild: layer];
// Replace init with the following
-(id) initWithLastCalendar:(int)lastCalendar
if( (self=[super init])) {
CGSize winSize = [CCDirector sharedDirector].winS
calendarNum = arc4random() %3+1;
} while (calendarNum == lastCalendar);
NSString * spriteName = [NSString
stringWithFormat:@&Calendar%d.png&, calendarNum];
CCSprite * cal = [CCSprite spriteWithFile:spriteName];
// BEGINTEMP
cal.position = ccp(winSize.width/2, winSize.height/2);
[self addChild:cal];
// ENDTEMP
self.isTouchEnabled = YES;
// Add new methods
- (void)registerWithTouchDispatcher {
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self
priority:0 swallowsTouches:YES];
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
CCScene *scene = [HelloWorldLayer sceneWithLastCalendar:calendarNum];
[[CCDirector sharedDirector] replaceScene:
[CCTransitionJumpZoom transitionWithDuration:1.0 scene:scene]];
return TRUE;
& & 这里只是一些基本的cocos2d代码,用来在屏幕中间随机显示一张日历图片。它同时也包含了一些逻辑,当你点击屏幕的时候,可以比较平滑地切换到另一张图片。  
& & 编译并运行,现在你每次点击屏幕就可以看到一些随机的日历图片啦,它们全部都是由我完成的:)
& & 现在,我们把程序框架搭好了,接下来,让我们来实现遮罩效果吧!
遮罩和OpenGL 混合模式(Blend Mode)
& & 如果你在图片编辑软件里面打开Art\CalendarMask.png图片,它看起来是这样子的:
& & 我们将使用这张图片来给我们的日历图片添加一个边框,是那种带有波纹效果的边框,而不是四边形的。这张图片透明的部分,就是遮罩效果的部分,而白色区域则是日历图片会显示的区域。
& & 为了实现这个效果,我们将使用OpenGL的混合模式。
& & 如果你回过头去看这篇教程的话,我们在那里讨论过OpenGL的混合模式。我在那里提到过一个非常方便的可以用来可见化调节混合模式的效果。
& & 为了完成我们想要的效果,我们需要采取下面的策略:
我们首先渲染mask精灵,把src color(就是mask精灵)设置为GL_ONE,并且把destination color(一个空的buffer)设置为GL_ZERO。所以,效果就是简单的把mask图片显示来。
接下来,我们渲染日历图片精灵。把src color(日历)设置为GL_DST_ALPHA。意思是,看看mask图片的当前alpha值是多少,如果是0(完全透明),那么就显示mask的。如果是1(完全不透明),那么就显示日历图片。(译者注:如果大家对此不明白,可以参考这个)。然后把dst
color(the mask)设计成GL_ZERO,这样的话,之前渲染上去的mask就消失了。
& & 很酷吧!你可能会觉得我们只需要先把mask精灵渲染上去,然后再渲染日历精灵,并且指定这两个精灵的blendFunc就行了。可是,实际上这样是行不通的!
  上面所提到的混合算法,当精灵下面还有一些精灵在渲染的时候就会出问题---比如背景图片上面有一个精灵。这是因为,这里作了一个假设,在上面做完1那个步骤之后,imgae buffer里面只存在唯一一张图片,那就是mask。(这个假设当然是不正确的啦,因为你要切换日历图片对不对?)
& & 因此,我们需要一种方式,可以建立一个干净的“黑板”,然后在那执行1,2步来制作一个遮罩纹理。很幸运的是,用CCRenderTexture非常方便。
Masking and CCRenderTexture
& &&CCRenderTexture是一个这样的类,它可以让你在屏幕之外的buffer里面渲染。
& & 它用起来非常方便,主要有以下原因---你可以使用它来给你的游戏截屏,可以高效地缓存用户渲染的内容,可以在运行时动态地创建sprite sheet,或者,就像本教程中一样,可以制作一个mask sprite。
& & 为了使用CCRenderTexture,你需要采取以下4步:
创建CCRenderTexture类,以像素为单位,指定你想要绘制的纹理的宽度和高度.
调用CCRenderTexture的begin方法来初始化渲染操作。
调用OpenGL函数来绘制实际的内容--但是,这些OpenGL调用最终都会绘制到屏幕之外去,而不会影响游戏中现在渲染的图像。
调用CCRenderTexture的end方法来结束绘制操作。一旦你完成之后,CCRenderTexture有一个sprite属性,你可以把它当用CCSprite来用。
  不要觉得第3步很奇怪---因为你正在使用cocos2d,90%的情况你是不需要手动直接调用OpenGL函数的。但是,如果你想渲染一个节点的话,你可以直接调用某一个节点的visit方法,如[sprite visit],然后这个函数会自动为你发射一些OpenGL函数指针给图形硬件去显示。
  这里有一点需要注意的就是坐标问题。(0,0)点是渲染的纹理的左下角位置,所以,你在使用CCRenderTexture的时候,一定要把坐标设置对。
& & 好了,你可能听得有些烦了,程序员还是喜欢看代码的。好,让我们开始coding吧!
给精灵添加遮罩: 最终实现
& & 打开HelloWorldLayer.m,然后在init方法上面添加下面的方法:
- (CCSprite *)maskedSpriteWithSprite:(CCSprite *)textureSprite maskSprite:(CCSprite *)maskSprite {
CCRenderTexture * rt = [CCRenderTexture renderTextureWithWidth:maskSprite.contentSizeInPixels.width height:maskSprite.contentSizeInPixels.height];
maskSprite.position = ccp(maskSprite.contentSize.width/2, maskSprite.contentSize.height/2);
textureSprite.position = ccp(textureSprite.contentSize.width/2, textureSprite.contentSize.height/2);
[maskSprite setBlendFunc:(ccBlendFunc){GL_ONE, GL_ZERO}];
[textureSprite setBlendFunc:(ccBlendFunc){GL_DST_ALPHA, GL_ZERO}];
[rt begin];
[maskSprite visit];
[textureSprite visit];
CCSprite *retval = [CCSprite spriteWithTexture:rt.sprite.texture];
retval.flipY = YES;
  让我们一步步来分解下面的操作:
使用mask精灵的大小来创建CCRenderTexture
重新设置mask精灵和texture精灵的位置,使它们的左下角是(0,0)
按照我们之前讨论的,设置每个精灵的blendFunc。
调用CCRenderTexture的begin方法来开始渲染操作,然后依次渲染mask和texture精灵,最后调用end方法。
基于CCRenderTexture的sprite属性的texture创建一个新的精灵,同时翻转y,因为纹理创建出来是倒的。
& & 好了,接下来,我们可以使用上面的函数来制作遮罩的效果了:
CCSprite * mask = [CCSprite spriteWithFile:@&CalendarMask.png&];
CCSprite * maskedCal = [self maskedSpriteWithSprite:cal maskSprite:mask];
maskedCal.position = ccp(winSize.width/2, winSize.height/2);
[self addChild:maskedCal];
& &编译并运行,现在,你可以看到一个带有遮罩效果的精灵啦。  
CCRenderTexture 方法的缺点
& & 对于这个简单的教程,这里提出的方法还比较ok,但是,这种方法也有一些缺点,特别是针对复杂一点的项目的时候:
每一次你应用一次mask的时候,都会在内存里面创建一张额外的纹理图片。&在iphone上面纹理所能占用的内存数量是非常有限的,所以你要非常小心,尽可能减少内存中加载的纹理图片数量。当你一次只给一张图片加mask效果的时候,这种方法很好,但是100张图片需要mask呢?&
渲染非常耗时.使用CCRenderTexture来渲染代价非常高,尤其是当纹理大小变大以后。如果你经常使用这种方式去绘图,那么会严重影响性能。&
& & 像我之前提到的一样,我还没有在OpenGLEs 1.0里面找到更好的方法来做这种事。但是,通过使用OpenGL ES 2.0,我们可以使用shader,那样会效率高很多。
  这里有本教程的。
  期待下一篇教程吧,下一篇教程我们将使用Cococs2d 2.0,通过编写定制的shader来给图片添加遮罩。
& & & 译注:由于本人最近比较忙,所以近期博客更新可能会有点慢,请见谅。
& & 推荐大家看几本书吧。首先,当然是《Learn iPhone and iPad Cocos2D Game Development》和《Learning Cocos2D》啦,这也是目前市面上介绍cocos2d比较经典和全面的书籍。然后,大家可以学习opengles的知识,同时也是推荐两本书《Learning iOS Game Programming》和《Oreilly.iPhone.3D.Programming.May.2010》。这些书网上都有下载。前面提到的两本cocos2d的书我看过一遍了,感觉很不错,如果大家看书的过程中遇到什么问题,欢迎留言和我讨论。学习游戏开发,数学物理很重要,如果大家有时候就补补数学和物理吧。当然opengl也要有很多数学知识的。
著作权声明:本文由翻译,欢迎转载分享。请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢!
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:1762869次
积分:20303
积分:20303
排名:第335名
原创:230篇
转载:738篇
评论:180条
(3)(1)(1)(2)(10)(18)(13)(10)(5)(6)(12)(7)(2)(7)(2)(1)(4)(2)(11)(5)(5)(10)(5)(8)(2)(2)(25)(1)(4)(8)(32)(13)(1)(9)(49)(55)(32)(49)(54)(54)(75)(58)(37)(9)(28)(48)(52)(21)(4)(4)(14)(8)(2)(72)

我要回帖

更多关于 cocos2dx sprite 换图 的文章

 

随机推荐