1. 程式人生 > >cocos2dx 常用函式和巨集定義

cocos2dx 常用函式和巨集定義

最近我們的cocos2d-x遊戲專案已經進入了正式開發的階段了,幾個dev都辛苦碼 程式碼。cocos2d-x還是一套比較方便的api的,什麼action啊、director啊、ccpoint啊都蠻便捷的。但是我看到幾個dev有時 候會很不知道用它們,還是首先自己去寫函式……

用一些比較原始、低效率的方法……

甚至是copy / paste……

這不科學啊!你不能這麼勤勞啊!你這麼勤勞要出事的啊!每年有多少程式設計師過勞死啊!程式設計師一定要是懶骨頭才是正道啊!

首先第一個,看到有問題,要寫很多程式碼處理問題,自己動手,豐衣足食——不是一 條好路,是一條革命的老路。我們前面有那麼多前任程式設計師的屍體,要學會翻爛它們……然後本文也是菜筆寫的,僅簡整理一下自己用的比較多一些 cocos2d-x的util,幫助大家提高效率,要變懶,會偷懶,沒有最懶,只有更懶。

 

1.數學類

cocos2d-x 裡使用最多的數學型別是CCPoint,一個點,本質上也是一個向量,對於向量和向量之間有很多的數學操作要做,oh我知道要幹什麼,也許我知道怎麼求一個值但是不知道怎麼求得高效(或者不知道),怎麼辦我能偷懶嗎?那當然可以。這其實並不是一個懶的標準,因為有一些方法寫多了也可能確實稍微有那麼點麻煩,所以自然cocos2d提供了一套ccp系列來幫助我們完成很多的工作,也顯示一下庫程式設計師照顧開發程式設計師的懶惰精神(當然他們自己也用,他們也很懶)。

那我們首先建立向量

ccp(x, y); // 以座標x,y建立一個向量這個大家都知道。
 ccpFromSize(s); // 以size s的width為x,height為y建立一個向量

 

有了ccp很多人就覺得自己已經夠懶了,因為C++是可以用CCPoint()建立臨時變數的,就是喜歡少打幾個字吧。寫個ccp(v1.x + v2.x, v1.y + v2.y)也不長……但是,有沒有稍微再懶一點的?

——這個可以有。

基本的加法、減法、取負、數乘

複製程式碼
ccpAdd(v1v2); // 等價 ccp(v1.x+v2.x, v1.y+v2.y); ccpSub(v1, v2); // 等價 ccp(v1.x-v2.x, v1.y-v2.y); ccpNeg(v) // 等價 ccp(-v.x, -v.y);

ccpMult(v, s); //等價 ccp(v.x * s, v.y * s); s是個浮點數嘛
複製程式碼

不錯,但是這個寫法不是那麼符合我們原生C++程式設計師的習慣,向量運算子呢?可惜cocos2d原本是一套objc的API,沒有操作符過載,cocos2d-x也沒有像一些原生的C++數學庫一樣直接過載向量運算子。不過過載一下還是很方便的,我們的專案裡聲明瞭一個數學標頭檔案,也就幾行程式碼就好了:

複製程式碼
inline cocos2d::CCPoint operator + (const cocos2d::CCPoint& v1, const cocos2d::CCPoint v2) {return ccp(v1.x + v2.x, v1.y + v2.y); } inline cocos2d::CCPoint operator - (constcocos2d::CCPoint& v1, const cocos2d::CCPoint v2) { return ccp(v1.x - v2.x, v1.y - v2.y); } inlinecocos2d::CCPoint operator - (const cocos2d::CCPoint& v) { return ccp(-v.x, -v.y); } inlinecocos2d::CCPoint operator * (const cocos2d::CCPoint& v1, float scale) { return ccp(v1.x * scale, v1.y * scale); } inline cocos2d::CCPoint operator * (float scale, const cocos2d::CCPoint& v1) {return ccp(v1.x * scale, v1.y * scale); } inline cocos2d::CCPoint operator / (constcocos2d::CCPoint& v1, float scale) { return ccp(v1.x / scale, v1.y / scale); } inline booloperator == (const cocos2d::CCPoint& v1, const cocos2d::CCPoint& v2) { return (v1.x == v2.x) && (v1.y == v2.y); } inline bool operator != (const cocos2d::CCPoint& v1, const cocos2d::CCPoint& v2) { return (v1.x != v2.x) || (v1.y != v2.y); }
複製程式碼

順便還過載了等號和不等號,這樣就可以直接用+、-來進行向量加減法,*、 / 進行數乘,==、!=判斷是否相等了。程式設計師,這樣才夠懶!

什麼,你說還有 +=、 -=、 /=、 *= 沒過載?哦,改那些必須得修改到cocos2d-x的原始碼了,改完還得重新編譯一遍,略微有點懶得改吧,至少CCPoint還是能用 = 賦值的。我們還是看看cocos2d-x還提供了什麼數學方法吧。

 

取中點!本來也就一 ccpMult(ccpAdd(v1,v2), 0.5f) 的事,開發者說不要,我就是要少打幾個字,好吧庫程式設計師就給了一個方法

ccpMidpoint(v1, v2); // 等價 ccp( (v1.x + v2.x)/2, (v1.y + v2.y)/2 );

 

點乘、叉乘、投影

ccpDot(v1, v2); // 等價 v1.x * v2.x + v1.y * v2.y; ccpCross(v1, v2); // 等價 v1.x * v2.y - v1.y * v2.x;

ccpProject(v1, v2) // 返回的是向量v1在向量v2上的投影向量

 

喜聞樂見求長度、距離和各自的平方值(在僅需要比較兩個長度大小時使用長度平方,因為省去了開方這一步,效率要高不少,這就不光是程式設計師的懶了,懶得要有效率)

複製程式碼
ccpLength(v) // 返回向量v的長度,即點v到原點的距離 ccpLengthSQ(v) // 返回向量v的長度的平方,即點v到原點的距離的平方 ccpDistance(v1, v2) // 返回點v1到點v2的距離 ccpDistanceSQ(v1, v2) // 返回點v1到點v2的距離的平方

ccpNormalize(v) // 返回v的標準化向量,就是長度為1
複製程式碼

 

旋轉、逆時針90度、順時針90度(90度的效率當然是更快的。。。同樣懶得有效率)

複製程式碼
ccpRotate(v1, v2); // 向量v1旋轉過向量v2的角度並且乘上向量v2的長度。當v2是一個長度為1的標準向量時就是正常的旋轉了,可以配套地用ccpForAngle ccpPerp(v); // 等價於 ccp(-v.y, v.x); (因為opengl座標系是左下角為原點,所以向量v是逆時針旋轉90度) ccpRPerp(v); // 等價於 ccp(v.y, -v.x); 順時針旋轉90度
複製程式碼

 

上面說到ccpRotate,配套的有向量和弧度的轉換向量,還有一些角度相關的

複製程式碼
ccpForAngle(a); // 返回一個角度為弧度a的標準向量 ccpToAngle(v); // 返回向量v的弧度 

ccpAngle(a, b); // 返回a,b向量指示角度的差的弧度值

ccpRotateByAngle(v, pivot, angle) // 返回向量v以pivot為旋轉軸點,按逆時針方向旋轉angle弧度
複製程式碼

 

線段相交的檢測,哦天哪原來庫程式設計師把這些事情都幹了!我還在傻傻地想線段相交演算法!實在是太勤奮了!

複製程式碼
ccpLineIntersect(p1, p2, p3, p4, &s, &t); // 返回p1為起點p2為終點線段1所在直線和p3為起點p4為終點線段2所在的直線是否相交,如果相交,引數s和t將返回交點線上段1、線段2上的比例 // 得到s和t可以通過 p1 + s * (p2 - p1) 或 p3 + t * (p4 - p3) 求得交點。 ccpSegmentIntersect(A, B C, D) // 返回線段A-B和線段C-D是否相交 ccpIntersectPoint(A, B, C, D) // 返回線段A-B和線段C-D的交點
複製程式碼

數學方法沒有列全,更多請直接查標頭檔案CCPointExtension.h。基本該有的都有了。

 

當然數學不只有向量,還有一些其他的……這些也很經常用到。小懶一下。

 

CC_RADIANS_TO_DEGREES(a); // 弧度轉角度 CC_DEGREES_TO_RADIANS(a); // 角度轉弧度 CCRANDOM_0_1(); //產生0到1之間的隨機浮點數 CCRANDOM_MINUS1_1(); // 產生-1到1之間的隨機浮點數

 

2.語句巨集

常用的,首先第一個,斷言。

CCAssert(cond, msg); // 斷言表示式cond為真,如果不為真,則顯示字串msg資訊

 

在這之後,也非常常用的,有遍歷CCARRAY、CCDICTIONARY的巨集。

複製程式碼
CCArray* _array; CCObject* _object; // 用來遍歷陣列的臨時變數 CCARRAY_FOREACH(_array, _object) //正向遍歷 // todo with _object.... CCARRAY_FOREACH_REVERSE(_array, _object) // 反向遍歷 //todo with _object.... } CCDictionary* _dict; CCDictElement* _elmt; // 遍歷表的臨時變數CCDICT_FOREACH(_dict, _elmt) {   // todo with elmt; }
複製程式碼

CCArray和CCDictionary都沒有實現模版,取得的遍歷元素之後還需要強制轉換,假如說,嗯,通常數組裡的元素都是同一型別的,比如這樣

複製程式碼
CCArray* _array; CCObject* _object; // 用來遍歷陣列的臨時變數 CCARRAY_FOREACH(_array, _object) //正向遍歷 { CCSprite* _bullet = (CCSprite*)_object; // todo with _bullet.... }
複製程式碼

總覺得我好像多定義了一個CCObject* _object,因為它沒什麼用似的?而且我也懶得多寫一句強制轉換,可以嗎?首先看看CCARRAY_FOREACH怎麼定義的

複製程式碼
#define CCARRAY_FOREACH(__array__, __object__) \ if ((__array__) && (__array__)->data->num > 0) \for(CCObject** arr = (__array__)->data->arr, **end = (__array__)->data->arr + (__array__)->data->num-1; \ arr <= end && (((__object__) = *arr) != NULL); \ arr++)
複製程式碼

看到那句 (__object__) = *arr 了嗎?好,要直接強制轉換,就提供一個型別,在這裡開刀!

複製程式碼
#define CCARRAY_TFOREACH(__array__, __object__, __type__) \ if ((__array__) && (__array__)->data->num > 0) \ for(CCObject** arr = (__array__)->data->arr, **end = (__array__)->data->arr + (__array__)->data->num-1; \ arr <= end && (((__object__) = (__type__)*arr) != NULL); \ arr++)
複製程式碼

然後用這個CCARRAY_TFOREACH巨集,這樣我們的遍歷就可以做得更懶一點

複製程式碼
CCArray* _array; CCSprite* _bullet; // 用來遍歷陣列的臨時變數 CCARRAY_TFOREACH(_array, _bullet, CCSprite*) // 正向遍歷 // todo with _bullet.... }
複製程式碼

舒坦,偷懶改造,完。

 

在定義型別的時候,經常需要定義一些getter setter,有cocos2d從objc帶來的CC_PROPERTY 和 CC_SYNTHESIZE。

複製程式碼
class Ship: public cocos2d::CCNode { // 定義一個int類的屬性m_energy變數,該變數訪問許可權是protected。 //後面的方法名Energy,即聲明瞭一個int getEnergy() 和一個 void setEnergy(int value)的方法,具體實現需要自己在cpp中定義 CC_PROPERTY(int, m_energy, Energy); // 基本與上相同,但是get方法傳引用,即聲明瞭一個 int& getEnergy(); CC_PROPERTY_PASS_BY_REF(int, m_energy, Energy); // 同樣定義變數,但是隻發聲明 get 方法,具體實現需要自己在cpp中定義 CC_PROPERTY_READONLY(int, m_energy, Energy); CC_PROPERTY_READONLY_PASS_BY_REF(int, m_energy, Energy); // 同樣定義變數,並且直接定義預設的get/set方法。相似的也有前4類 CC_SYNTHESIZE(cocos2d::CCObject*, m_weapon, Weapon);CC_SYNTHESIZE_PASS_BY_REF(cocos2d::CCObject*, m_weapon, Weapon);CC_SYNTHESIZE_READONLY(cocos2d::CCObject*, m_weapon, Weapon);CC_SYNTHESIZE_READONLY_PASS_BY_REF(cocos2d::CCObject*, m_weapon, Weapon); // 在setWeapon的時候,呼叫原有m_weapon的release,並且呼叫新值的的retain。當然已經排除了意外情況(相等或者NULL之類的)。CC_SYNTHESIZE_RETAIN(cocos2d::CCObject*, m_weapon, Weapon); };
複製程式碼

需要注意的是

1.CC_PROPERTY更適用於快速宣告一個值屬性,而CC_SYNTHESIZE更適用於宣告一個物件。因為CC_SYNTHESIZE提供的預設set沒有任何合法性檢查對於值屬性來說太不實用。

2.這些方法的宣告全部都是virtual的,即便是內聯,宣告為virtual的方法也不會產生行內函數,所以不管是CC_PROPERTY還是CC_SYNTHESIZE,他們的效率都是不高的。

3.CC_PROPERTY的get方法都沒有對函式體宣告const修飾符,這意味著對const物件,並不能呼叫CC_PROPERTY宣告的get方法(我怎麼覺得這是個cocos2d-x的BUG……)。

4.在CC_SYNTHESIZE方法之後直接宣告函式或者變數都會變成public:……注意,嗯。

不好用?跳過去看下定義,自己去定義一個唄……懶得看那就算了。

 

然後還有快捷的CREATE_FUNC,自動生成一個預設的靜態create方法。這實在方便了

複製程式碼
class Class: public cocos2d::CCNode {
public: CREATE_FUNC(Class); // 自動生成一個不帶引數的 create 靜態方法,返回一個Class*型別指標。自動呼叫了init和autorelease方法 //CREATE_FUNC(Class) 等價於與以下 static Class* create() { Class* pRet = new Class(); if (pRet && pRet->init()) { pRet->autorelease(); return pRet; } else { delete pRet; pRet = NULL; return NULL; } }
複製程式碼

而且這也是建議的C++建構函式和init方法的使用規範,先分配空間之後立刻初始化,並且由初始化結果確定能否返回一個可用的物件。在定義特定引數的create方法時也應當這樣。

 

說到初始化,就不得不說到析構,還有一些析構相關的巨集。我要release一堆物件,挨個都得判斷物件是不是NULL?還要把release後的東西賦值NULL?程式設計師懶得寫這麼多行程式碼……

複製程式碼
//所謂的safe邏輯都是這樣的,先檢查指標p是否為NULL,不為NULL,則執行delete p或者p->release等等。CC_SAFE_DELETE(p); // 當p不為NULL,delete p 並且將 p 賦為 NULL CC_SAFE_DELETE_ARRAY(p); //...delete[] p.. CC_SAFE_FREE(p); // ...free p ... CC_SAFE_RELEASE(p); // 當p不為NULL,p->release()CC_SAFE_RELEASE_NULL(p); // 當p不為NULL,p->release() 並且將 p 賦為 NULL CC_SAFE_RETAIN(p); // 當p不為NULL,p->retain()
複製程式碼

 

順便還有交換兩個變數的時候,可以都喜歡懶,寫個 void swap(int& a, int &b)什麼的、再寫void swap(float& a, float& b)什麼的,再寫個 void swap(string& a, string& b)什麼的……總感覺你懶都沒人家庫程式設計師懶的懶……這裡有個CC_SWAP的巨集……

複製程式碼
CC_SWAP(x, y, type); // 等價于于以下 { type temp = (x); x = y; y = temp; } // 至少x 和 y 不是表示式的時候這個巨集都能工作正常,也不用擔心temp變數重複
複製程式碼

什麼?你說你不服?你說你連type都不想宣告……?你居然這麼懶那你怎麼辦你怎麼能做到這麼懶的啊!你說你用模版?

template <<span style="color: #0000ff;">typename t> inline void swap<<span style="color: #0000ff;">typename t>(t& a, t& b);

好吧你贏了……

 

還有cocos2d庫開發人員很喜歡用的CC_BREAK_IF,這個巨集有什麼特別的含義嗎?難道其實不就是一行的 if(???) break; ?嗯,就是……沒區別。但是你不覺得CC_BREAK_IF( ??? );懶地比人家高階嗎?現在的IDE都能自動tab出巨集耶!還有可以用下面的while(0)迴圈寫還能代替一些if(???) return false;耶!

複製程式碼
bool Class::init() { bool bRet = falsedo { // do some initialization 1 CC_BREAK_IF(cond); // 當表示式cond為真時候跳出。 // do some more initialization bRet true; } while(0); return bRet; }
複製程式碼

……積小懶,成大懶啊!可見有一些人,是真的真的很懶很懶……

 

還能更懶一點嗎?答案是肯定的。每當寫一個.h時,cocos2d的庫程式設計師都要寫一個 namespace cocos2d {...} 吧;每當寫一個cpp的時候,你也總是要用到using namespace吧?。。他們都懶得多打這幾個字母。。

NS_CC_BEGIN // 這是 namespace cocos2d { NS_CC_END // 這是 } !!!! USING_NS_CC// 這是 using namespace cocos2d; 這可以是常用巨集。