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個字符串代替了原來的 '{'—— 一個字符!天哪這還是懶到骨頭裡的程序員嗎?難道偷懶也能本末倒置?