cocos2d-x 數學函數、常用宏粗整理

      廢話不多說,總之這是一篇好文章,它會教會我們很多“偷懶”的方法!


【轉自Shine Team】:http://www.cnblogs.com/buaashine/archive/2012/11/12/2765691.html


最近我們的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(v1, v2); // 等價 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 - (const cocos2d::CCPoint& v1, const cocos2d::CCPoint v2)
    {
        return ccp(v1.x - v2.x, v1.y - v2.y);
    }
    inline cocos2d::CCPoint operator - (const cocos2d::CCPoint& v)
    {
        return ccp(-v.x, -v.y);
    }
    inline cocos2d::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 / (const cocos2d::CCPoint& v1, float scale)
    {
        return ccp(v1.x / scale, v1.y / scale);      
    }
    inline bool operator == (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/* || true*/);                                                \
    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/* || true*/);                                                \
    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 <typename t>
inline void swap<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 = false;
    do
    {
        // 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; 這可以是常用宏。


哦,什麼?你看到程序員用'NS_CC_END' —— 9個字符串代替了原來的 '{'—— 一個字符!天哪這還是懶到骨頭裏的程序員嗎?難道偷懶也能本末倒置?

其實,嗯,不是這樣的,程序員是需要懶惰的,但是有時候,也還是要有節操的,只有一個BEGIN沒有END,怎麼說,也太看不過去了,懶也要懶得優雅、整潔、高端……

所以懶可以沒有極限,但是不能沒有節操……

……所以有沒有覺得懶一點還是不錯的?

沒有……?

那我懶得接着寫了。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章