轉自:cocos2d-x 給Sprite添加Mask(遮罩)
免責申明(必讀!):本部落格提供的所有教程的翻譯原稿均來自於互連網,僅供學習交流之用,切勿進行商業傳播。同時,轉載時不要移除本申明。如產生任何糾紛,均與本部落格所有人、發表該翻譯稿之人無任何關係。謝謝合作!
原文連結地址:http://www.raywenderlich.com/4421/how-to-mask-a-sprite-with-cocos2d-1-0
教程:
有時候,你在做遊戲時,可能需要一種方式來顯示精靈的某一部分(就是添加遮罩啦)。
一種方式就是使用另外一張圖片,叫做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 variableint 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實現代碼下載