教程截图:
有时候,你在做游戏时,可能需要一种方式来显示精灵的某一部分(就是添加遮罩啦)。
一种方式就是使用另外一张图片,叫做mask。你把mask图片中间设置成白色,白色区域是被mask图片的可见区域。之后这个白色区域会透明的。
然后,你可以使用本教程提供的方法来把mask图和原图结合起来,然后创建如上图所示的效果。
你会发现本教程提供的方法非常方便,用它可以完成许多很有意思的效果。比如,把大头贴,或者像框等等。所以这些内容,你都可以从本教程中学到!
本教程会教你如何使用cocos2d 1.0来给一个sprite添加mask,使用一个非常强大的类CCRenderTexture,之前我们在学TinyWings Like游戏的时候已经见过啦:)
学习本教程的前提是你要熟悉cocos2d,如果你对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]];
这里播放一个由Kevin MacLeod制作的一首非常好听的曲子,然后调用了一个新的方法来加载场景。
接下来,打开HelloWorldLayer.h 并作下面修改:
// Add new instance variable
int calendarNum;
// Replace the +(CCScene*) scene declaration at the bottom with the following:
+ (CCScene *) sceneWithLastCalendar:(int)lastCalendar;
- (id)initWithLastCalendar:(int)lastCalendar;
在这个场景中,我们将随机显示一张日历图片(从三张里面选择)。在这个类里,我们保存了当前显示的日历图片的序号,然后修改了初始化方法为 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];
return scene;
}
// Replace init with the following
-(id) initWithLastCalendar:(int)lastCalendar
{
if( (self=[super init])) {
CGSize winSize = [CCDirector sharedDirector].winSize;
do {
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;
}
return self;
}
// 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的混合模式。
如果你回过头去看《如何使用CCRenderTexture来动态创建纹理》这篇教程的话,我们在那里讨论过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 {
// 1
CCRenderTexture * rt = [CCRenderTexture renderTextureWithWidth:maskSprite.contentSizeInPixels.width height:maskSprite.contentSizeInPixels.height];
// 2
maskSprite.position = ccp(maskSprite.contentSize.width/2, maskSprite.contentSize.height/2);
textureSprite.position = ccp(textureSprite.contentSize.width/2, textureSprite.contentSize.height/2);
// 3
[maskSprite setBlendFunc:(ccBlendFunc){GL_ONE, GL_ZERO}];
[textureSprite setBlendFunc:(ccBlendFunc){GL_DST_ALPHA, GL_ZERO}];
// 4
[rt begin];
[maskSprite visit];
[textureSprite visit];
[rt end];
// 5
CCSprite *retval = [CCSprite spriteWithTexture:rt.sprite.texture];
retval.flipY = YES;
return retval;
}
让我们一步步来分解下面的操作:
- 使用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也要有很多数学知识的。
------------------------------------------------------------------------------------------------------
因为教程是IOS平台的,我在Android平台上参考例子,用cocos2d-x实现了一次,贴上主要代码:
bool HelloWorld::init()
{
/**///////////////////////////////
// 1. super init first
if (!CCLayer::init() )
{
return false;
}
CCSize s = CCDirector::sharedDirector()->getWinSize();
int calendarNum = 0;
do
{
calendarNum = CCRANDOM_0_1() * 2;
} while(calendarNum == mLastCalendar);
mLastCalendar = calendarNum;
//
std::string name = "Calendar";
std::stringstream ss;
ss << mLastCalendar;
name += ss.str();
name += ".png";
// 加载sprite并置为全屏
CCSprite* sprite = CCSprite::create(name.c_str());
sprite->setScaleX((float)SCREEN_WIDTH / sprite->getContentSize().width);
sprite->setScaleY((float)SCREEN_HEIGHT / sprite->getContentSize().height);
sprite->setPosition(ccp(s.width/2, s.height/2));
//加载掩码图片
CCSprite* maskSprite = CCSprite::create("CalendarMask.png");
maskSprite->setScaleX((float)SCREEN_WIDTH / maskSprite->getContentSize().width);
maskSprite->setScaleY((float)SCREEN_HEIGHT / maskSprite->getContentSize().height);
maskSprite->setPosition(ccp(s.width/2, s.height/2));
CCSprite* maskCal = maskedSpriteWithSprite(sprite, maskSprite);
maskCal->setPosition( ccp(s.width/2, s.height/2) );
addChild(maskCal);
setTouchEnabled(true);
return true;
}
cocos2d::CCSprite* HelloWorld::maskedSpriteWithSprite(cocos2d::CCSprite* textureSprite, cocos2d::CCSprite* maskSprite)
{
// 1
int w = maskSprite->getContentSize().width * maskSprite->getScaleX();
int h = maskSprite->getContentSize().height * maskSprite->getScaleY();
CCRenderTexture* rt = CCRenderTexture::renderTextureWithWidthAndHeight(w, h);
// 2
maskSprite->setPosition( ccp(maskSprite->getContentSize().width * maskSprite->getScaleX()/2,
maskSprite->getContentSize().height * maskSprite->getScaleY()/2));
textureSprite->setPosition( ccp(textureSprite->getContentSize().width * textureSprite->getScaleX() /2,
textureSprite->getContentSize().height * textureSprite->getScaleY()/2));
// 3
ccBlendFunc blendFunc;
blendFunc.src = GL_ONE;
blendFunc.dst = GL_ZERO;
maskSprite->setBlendFunc(blendFunc);
blendFunc.src = GL_DST_ALPHA; // mask图片的当前alpha值是多少,如果是0(完全透明),那么就显示mask的。如果是1(完全不透明)
blendFunc.dst = GL_ZERO; // maskSprite不可见
textureSprite->setBlendFunc(blendFunc);
// 4
rt->begin();
maskSprite->visit();
textureSprite->visit();
rt->end();
// 5
CCSprite* retval = CCSprite::spriteWithTexture(rt->getSprite()->getTexture());
retval->setFlipY(true);
return retval;
}
完整cocos2d-x实现代码下载
免责申明(必读!):本博客提供的所有教程的翻译原稿均来自于互联网,仅供学习交流之用,切勿进行商业传播。同时,转载时不要移除本申明。如产生任何纠纷,均与本博客所有人、发表该翻译稿之人无任何关系。谢谢合作!
原文链接地址:http://www.raywenderlich.com/4421/how-to-mask-a-sprite-with-cocos2d-1-0
同类文章:http://www.cnblogs.com/dingwenjie/archive/2012/04/02/2429576.html
posted on 2012-08-26 23:06
风轻云淡 阅读(15709)
评论(0) 编辑 收藏 引用 所属分类:
cocos2d