Cocos2dx 實現擦除 橡皮擦 刮獎 效果的實現

轉載自 http://blog.csdn.net/dionysos_lai/article/details/39030081?utm_source=tuicool

原文章 參考 http://zengrong.net/post/2067.htm  

橡皮擦具體功能要求:

1.      實現擦除效果:具體要求是點擊位置,拖動軌跡路上,均可以擦除。在快速拖動過程中,不能出現斷層和鋸齒現象。

2.      擦除的形狀,最好可以自定義。默認可以提供正方形、圓形兩種,最好能提供自定義圖片形狀。

3.      判斷圖片是否擦除完畢。

4.      如果擦除形狀過小,那麼難免在擦除過程中,會遺留一些細小的、可能難以注意的殘留點。在擦除過程中,要求可以自動擦除這些殘留點。

 

功能分析:

1.      擦除效果實現

A.     所謂“擦除”,就是將要擦除的圖片RGB和alpha值,全部去掉。可以通過兩張圖片的混合實現。這裏簡單介紹OpenGL中的混合原理。

         OpenGL中的混合,就是將原來的原色和將要畫上去的顏色,經過“一些處理”,得到一種新的顏色,然後再次將得到的新顏色畫到畫布上。這裏,我們將要畫上去的顏色,稱爲“源顏色”,把原來的顏色稱爲“目標顏色”。

         上文中的“一些處理”,實際是將源顏色和目標顏色各自取出,乘以一個因數(這裏,對應的因數,我們稱之爲“源因子”和“目標因子”),然後二者相加(當然也可以不是相加,可以是相減、或者取二者最大值等等,新版的OpenGl可以設置運算方式),這樣既可以得到一個新的顏色值。我們假設源顏色的四個分量(指紅色,綠色,藍色,alpha值)是(Rs, Gs, Bs, As),目標顏色的四個分量是(Rd, Gd, Bd, Ad),又設源因子爲(Sr, Sg, Sb, Sa),目標因子爲(Dr, Dg, Db, Da)。則混合產生的新顏色可以表示爲:  


 

         當然,如果某個分量,超過了最大值,會自動截取的。

         源因子和目標因子是可以通過glBlendFunc函數來進行設置的。glBlendFunc有兩個參數,前者表示源因子,後者表示目標因子。這兩個參數可以是多種值,下面介紹比較常用的幾種。

         GL_ZERO:     表示使用0.0作爲因子,實際上相當於不使用這種顏色參與混合運算。

         GL_ONE:      表示使用1.0作爲因子,實際上相當於完全的使用了這種顏色參與混合運算。

         GL_SRC_ALPHA:表示使用源顏色的alpha值來作爲因子。

         GL_DST_ALPHA:表示使用目標顏色的alpha值來作爲因子。

         GL_ONE_MINUS_SRC_ALPHA:表示用1.0減去源顏色的alpha值來作爲因子。GL_ONE_MINUS_DST_ALPHA:表示用1.0減去目標顏色的alpha值來作爲因子。     

                                                                

         利用OpenGl原理,如果我們將源顏色的顏色值設置爲0,並源因子和目標因子分別設置爲GL_OEN,GL_ZERO,則新顏色具體值如下所示:

         注:這裏的Rs、Gs、Bs、As均爲0。

         因此可以很方便的實現的擦除效果了。其詳細代碼如下所示:

  1.        m_pEraser->setPosition(point);  
  2. ccBlendFunc blendFunc = { GL_ONE, GL_ZERO };    ///< 設置混合模式, 源---1, 目標---0  
  3. m_pEraser->setBlendFunc(blendFunc);  
  4. m_pRTex->begin();  
  5. m_pEraser->visit();  
  6.        m_pRTex->end();  

 

         如果是自定義的形狀(這裏我們的討論的自定義形狀,是圖片提供的形狀,而不是自己畫出來的-----因爲自己畫出來的,跟前面沒有區別)。這裏對圖片有比較特殊的要求,即要求圖片中間形狀是鏤空的,外部的alpha通道必須爲255。如下圖所示:

                                                                                                                                       

         (*^__^*) 嘻嘻……,這裏是一張動物圖片(這次是做有關動物繪本遊戲),在其輪廓內部是鏤空的,外部只要alpha最大即可。然後我們將源因子和目標因子分別設置爲GL_ONE_MINUS_SRC_ALPHA、GL_SRC_ALPHA。

         則新顏色如下表示:

         在外部區域:GL_ONE_MINUS_SRC_ALPHA = 0; GL_SRC_ALPHA =1。則新顏色值如下所示:

                                             

         還是原來的值。

         在內部區域:GL_ONE_MINUS_SRC_ALPHA = 1; GL_SRC_ALPHA =0。則新顏色值如下所示:


                                                   

         可以看出,值全部爲0。

         具體代碼如下所示:

  1.        CCSprite* drawSprite = CCSprite::createWithTexture(m_drawTextture);  
  2. drawSprite->setPosition(point);  
  3. ccBlendFunc blendFunc = { GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA };   ///< 設置混合模式, 源---1-alpha, 目標---alpha  
  4. drawSprite->setBlendFunc(blendFunc);  
  5. m_pRTex->begin();  
  6. drawSprite->visit();  
  7. m_pRTex->end();  

B.     利用動態紋理,實現紋理的變化。

         使用擦除效果,紋理必然是發生動態變化的。這裏採用CCRenderTexture實現動態紋理改變。對於CCRenderTexture的具體使用方法,可見引擎裏描述語言:

To render things into it, simply construct arender target, call begin on it, call visit on any cocos scenes or objects torender them, and call end.

其實,就是下面一段話:

  1. 創建一個新的CCRenderTexture. 這裏,你可以指定將要創建的紋理的寬度和高度。.
  2. 調用 CCRenderTexture:begin. 這個方法會啓動OpenGL,並且接下來,任何繪圖的命令都會渲染到CCRenderTexture裏面去,而不是畫到屏幕上。
  3. 繪製紋理. 你可以使用原始的OpenGL調用來繪圖,或者你也可以使用cocos2d對象裏面已經定義好的visit方法。(這個visit方法就會調用一些opengl命令來繪製cocos2d對象)
  4. 調用 CCRenderTexture:end. 這個方法會渲染紋理,並且會關閉渲染至CCRenderTexture的通道。
  5. 從生成的紋理中創建一個sprite. 你現在可以用CCRenderTexture的sprite.texture屬性來輕鬆創建新的精靈了
         這裏,引用的是子龍山人博文《(譯)如何使用CCRenderTexture來創建動態紋理》,具體博文,詳見地址:http://www.cnblogs.com/andyque/archive/2011/07/01/2095479.html。博文中,詳細介紹了CCRenderTexture的動態紋理創建方法。下面給出如何將一個精靈紋理添加進CCRenderTexture中:
  1.          CCSprite* sprite = CCSprite::create(pszFileName);  
  2.     spriteSize = sprite->getContentSize();  
  3.     /// 將精靈加入紋理後,其中心點座標應該設置在(0,0)處, 這是由於紋理的中心點在(0,0),當然,可以通過設置其偏移座標實現;  
  4.     sprite->setAnchorPoint(ccp(0.f, 0.f));  
  5. //  sprite->setPosition(ccp(spriteSize.width/2.f, spriteSize.height/2.f));  
  6.   
  7.     m_pRTex = CCRenderTexture::create(spriteSize.width, spriteSize.height);  
  8.     m_pRTex->setPosition(CCPointZero);  
  9.     this->addChild(m_pRTex);  
  10.   
  11.     m_pRTex->begin();  
  12.     sprite->visit();  
  13.     m_pRTex->end();  

C.     避免出現斷層和鋸齒現象

         之所以出現斷層和鋸齒的原因,是由於在快速擦除過程中,系統接收到的第一個點位置和第二個點位置,可能有很大的位移偏差。如果只是簡單的處理這兩個點的信息,顯而亦然中間很多點就會缺失,而不畫。因此就出現了斷層和鋸齒的現象。

         因此,我們只要簡單的判斷兩點之間的距離是否超過一定程度,就在二者間再次處理。至於二者間,要抽取多少點進行處理,就要看其距離的長度了。這邊,我簡單的判斷距離超過1,就要處理(顯然這樣做,會比較準確,但消耗性能大,可以適當更改)。下面給出具體代碼:

  1. void EraserSprite::ccTouchMoved( cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent )  
  2. {  
  3.     if (m_bEraser)  
  4.     {  
  5.         CCPoint point = pTouch->getLocation();   
  6.         CCPoint normal = ccpNormalize(point-m_touchPoint);  
  7.   
  8.         /// 處理一次移動過多,造成中間有遺漏,或者鋸齒現象;  
  9.         while(1)  
  10.         {  
  11.             if (ccpDistance(point, m_touchPoint) < 1.f)  
  12.             {  
  13.                 /*      m_pEraser->setPosition(-this->getPosition() + point + spriteSize/2.f);*/  
  14.                 eraseByBlend(-this->getPosition() + point + spriteSize/2.f);  
  15.                 break;  
  16.             }  
  17.             m_touchPoint = m_touchPoint + normal*1.f;  
  18.               
  19.             /*      m_pEraser->setPosition(-this->getPosition() + m_touchPoint + spriteSize/2.f);*/  
  20.             eraseByBlend(-this->getPosition() + m_touchPoint + spriteSize/2.f);  
  21.         }  
  22.   
  23.         m_touchPoint = point;  
  24.     }  
  25. }  


2.        擦除形狀

         對於擦除形狀,其實上文已經提到了。這裏簡單提一下。如果是採用點或者圓形,可以使用自定義畫節點,即CCDrawNode實現。對於CCDrawNode的擴展使用,也可以用到CCClippingNode中,即實現自定義裁剪模板。下面給出正方形和圓形擦除形狀代碼:

         正方形形狀:

  1. m_pEraser = CCDrawNode::create();  
  2. float width = 10.f;  
  3.        m_pEraser->drawDot(CCPointZero, width, ccc4f(0,0,0,0));  

         圓形形狀:

  1. m_pEraser = CCDrawNode::create();  
  2. /// 繪製圓形區域  
  3. float fRadius       = 30.0f;                            ///< 圓的半徑  
  4. const int nCount    = 100;                          ///< 用正100邊型來模擬園  
  5. const float coef    = 2.0f * (float)M_PI/nCount;    ///< 計算每兩個相鄰頂點與中心的夾角  
  6. static CCPoint circle[nCount];                      ///< 頂點數組  
  7. for(unsigned int i = 0;i <nCount; i++) {  
  8.         float rads = i*coef;                            ///< 弧度  
  9.         circle[i].x = fRadius * cosf(rads);             ///< 對應頂點的x  
  10.         circle[i].y = fRadius * sinf(rads);             ///< 對應頂點的y  
  11. }  
  12.        m_pEraser->drawPolygon(circle, nCount, ccc4f(0, 0, 0, 0), 0, ccc4f(0, 0, 0, 0));//繪製這個多邊形!  

         對於自定義的圖片形狀,其實就是一個精靈對象而已。


3.       判斷圖片是否擦除完畢

         判斷是否擦除完畢,基本思路就是對紋理像素值逐點判斷,當所有像素值均爲0時,則代表圖片已經擦除完畢了。

         首先,獲取紋理的圖片信息。關鍵函數是newCCImage。具體代碼如下:


  1. CCImage* image = new CCImage();  
  2.  m_pRTex->newCCImage(true);  

         這裏要注意一點就是,最後要手動刪除image。

         其次,獲取各個位置的像素值。代碼如下所示:

  1. unsigned char *pixel = data_ + (x + y * image->getWidth()) * m;  
  2.   
  3. // You can see/change pixels' RGBA value(0-255) here !  
  4. unsigned int r = (unsigned int)*pixel;  
  5. unsigned int g = (unsigned int)*(pixel + 1);  
  6. unsigned int b = (unsigned int)*(pixel + 2) ;  
  7.        unsigned int a = (unsigned int)*(pixel + 3);  

         其中,x、y代表位置。

 

         完整代碼如下所示:

  1. bool EraserSprite::getEraserOk()  
  2. {  
  3.     m_bEraserOk = false;  
  4.   
  5.     CCImage* image = new CCImage();  
  6.     image = m_pRTex->newCCImage(true);  
  7.   
  8.     int m = 3;  
  9.     if (image->hasAlpha())  
  10.     {  
  11.         m = 4;  
  12.     }  
  13.   
  14.     unsigned char *data_= image->getData();  
  15.     int x = 0, y = 0;  
  16.     /// 這裏要一點,即Opengl下,其中心點座標在左上角  
  17.     for (x = 0; x < spriteSize.width; ++x)  
  18.     {  
  19.         for (y = 0 ; y < spriteSize.height; ++y)  
  20.         {  
  21.   
  22.             unsigned char *pixel = data_ + (x + y * image->getWidth()) * m;  
  23.   
  24.             // You can see/change pixels' RGBA value(0-255) here !  
  25.             unsigned int r = (unsigned int)*pixel;  
  26.             unsigned int g = (unsigned int)*(pixel + 1);  
  27.             unsigned int b = (unsigned int)*(pixel + 2) ;  
  28.             unsigned int a = (unsigned int)*(pixel + 3);  
  29.   
  30.             if (r != 0 && g != 0 && b != 0 && a != 0)  
  31.             {  
  32.                 m_bEraserOk = false;  
  33.                 break;  
  34.             }  
  35.         }  
  36.         if (spriteSize.height != y)  
  37.         {  
  38.             break;  
  39.         }  
  40.     }  
  41.     if (x == spriteSize.width && y == spriteSize.height)  
  42.     {  
  43.         m_bEraserOk = true;  
  44.     }  
  45.   
  46.     delete image;  
  47.   
  48.     return this->m_bEraserOk;  
  49. }  

         這裏,參考了文章:《Getting andsetting the RGB / RGBA value of a pixel in a CCSprite (cocos2d-x)》,詳細地址:http://stackoverflow.com/questions/9665700/getting-and-setting-the-rgb-rgba-value-of-a-pixel-in-a-ccsprite-cocos2d-x

         好囧啊,這個部分,花了我整整一個上午時間,沒想到就這樣的過去,一點都沒有前面高大善的趕腳。

4.        殘留點清除問題

對於這個問題,還沒有很好的思路

最後,附錄上代碼地址:https://github.com/DionysosLai/EraserSprite
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章