cocos2d中,涉及到4種座標系:
- GL座標系Cocos2D以OpenglES爲圖形庫,所以它使用OpenglES座標系。GL座標系原點在屏幕左下角,x軸向右,y軸向上。
- 屏幕座標系蘋果的Quarze2D使用的是不同的座標系統,原點在屏幕左上角,x軸向右,y軸向下。
ios的屏幕觸摸事件CCTouch傳入的位置信息使用的是該座標系。因此在cocos2d中對觸摸事件做出響應前需要首先把觸摸點轉化到GL座標系。可以使用CCDirector的convertToGL來完成這一轉化。
- 世界座標系也叫做絕對座標系。世界座標系和GL座標系一致,原點在屏幕左下角。
cocos2d中的元素是有父子關係的層級結構,我們通過CCNode的position設定元素的位置使用的是相對與其父節點的本地座標系而非世界座標系。最後在繪製屏幕的時候cocos2d會把這些元素的本地座標映射成世界座標系座標。
- 本地座標系 相對於父對象的座標。
[obj.parent convertToWorldSpace:[obj position]]; //獲得obj的世界座標 [obj.parent convertToNodeSpace:[obj position]]; //獲得obj的本地座標 [[CCDirector sharedDirector] convertToGL:*****(0,0)]; //獲得GL座標 [[CCDirector sharedDirector] convertToUI:*****(0,0)]; //獲得屏幕座標
無論是搞2d還是3d開發,最需要搞清楚的就是座標系,這部分混亂的話就沒啥奔頭了。所以玩cocos2d,一上來就先把各種與座標有關的東西搞清楚。
基本的兩個座標系:屏幕座標系和GL座標系。
屏幕座標系x軸朝右,y軸朝下。默認原點在左上角。
GL座標系x軸朝右,y軸朝上。默認原點在左下角。
在調用任何需要設置位置的函數,或從函數獲取位置信息前,必須要明確這個函數使用哪個座標系。比如調用CCNode類的setPosition函數,它使用的就是GL座標系。比如在處理觸摸事件時CCTouch對象中的座標就是屏幕座標系。
比較用的多的座標系是和Node相關的本地座標系。這個結構和一般做3D用的場景樹的概念是一樣的。所以從Node拿到的位置是該節點的本地座標,需要通過特定的函數才能把本地座標轉換爲世界座標。而且這裏的座標都用的是GL座標系。在CCNode對象中有幾個方便的函數可以做座標轉換。convertToWorldSpace方法可以把基於當前node的本地座標系下的座標轉換到世界座標系中。convertToNodeSpace方法可以把世界座標轉換到當前node的本地座標系中。
另外順便說下點擊命中問題,這會涉及到一些座標系的相互轉換,同時這裏也不得不稍微提下cocos2d裏面各種對象的大小問題。因爲在cocos2d裏CCNode對象有縮放的方法setScaleX和setScaleY。所以在獲取對象大小的時候就必須根據情況明確指定獲取對象原始大小,還是縮放後的大小。當然cocos2d裏提供了對應的函數來完成這些操作。
getContentSize 函數用來獲得節點原始的大小。
boundingBox 函數用來獲得經過縮放和旋轉之後的外框盒大小。
舉個簡單的例子:
bool ret = CCRect::CCRectContainsPoint(
this->boundingBox() , this->getParent()->convertTouchToNodeSpace( pTouch ));
這個例子的功能是來判定當前的觸摸操作是否發生在自己的node對象上。其中pTouch是CCTouch對象的指針,包含了當前觸摸事件發生點的座標。
CCRectContainsPoint這個函數用來判斷一個點是否在一個矩形範圍內。我們就想用這個函數來判斷當前觸摸操作的這個點是否在當前node的範圍內。
this->boundingBox() 方法獲得了當前節點對象在父節點對象下的縮放之後的本地座標大小,並且是用GL座標系表示的。
pTouch對象中的座標是屏幕座標系,所以必須轉換到GL座標系,再轉換到父節點的本地座標下。好在convertTouchToNodeSpace這個函數一次完成了這兩個轉換,可以參考該庫的源碼,其中有具體的計算過程。
所有數據都轉換到同一個座標系下了以後,就可以通過CCRectContainsPoint函數完成最終的判定操作。
最後想說的一點是,儘可能用相對座標。換句話說,程序中所有對象在設置大小和位置時,都應該以父對象的大小和位置爲依據。 這樣程序發佈在以各種不同的分辨率發佈時,只需要調整根對象的大小就可以了。
結尾順便說下cocos2dx判斷點擊命中的幾種方法:
- //重載
- virtual bool ccTouchBegan(CCTouch *touch, CCEvent *pEvent);
- virtual void ccTouchMoved(CCTouch *touch, CCEvent *pEvent);
- virtual void ccTouchEnded(CCTouch *touch, CCEvent *pEvent);
- virtual void onEnter();
- virtual void onExit();
- //添加支持觸摸事件
- void CTestLayer::onEnter()
- {
- CCLayer::onEnter();
- this->setTouchEnabled(true);
- CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, 0, true);
- }
- void CTestLayer::onExit()
- {
- CCLayer::onExit();
- CCDirector::sharedDirector()->getTouchDispatcher()->removeDelegate(this);
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////
- //用自己的座標系相對於原點進行判斷
- bool checkTouchInSelf(CCTouch *touch);
- //用自己的座標系相對於錨點進行判斷
- bool checkTouchInSelf_AR(CCTouch *touch);
- //用父元素座標系及自己在父座標中的位置進行判斷
- bool checkTouchInSelf_Parent(CCTouch *touch);
- //______________________________________________________________________________________________
- //用自己的座標系相對於原點進行判斷
- bool CTestLayer::checkTouchInSelf(CCTouch *touch)
- {
- //方案一
- //將點擊點轉換成自己座標系中的座標,相對於0,0點
- CCPoint pt = convertTouchToNodeSpace(touch);
- printf("pt.x=%.1f pt.y=%.1f\n", pt.x, pt.y);
- int nw = getContentSize().width;
- int nh = getContentSize().height;
- CCRect rc(0, 0, nw, nh);
- if(rc.containsPoint(pt))
- {
- //獲得點擊的OpenGL的世界座標值
- CCPoint touchPoint = touch->getLocation();
- printf("ccTouchBegan x=%.1f y=%.1f\n", touchPoint.x, touchPoint.y);
- return true;
- }
- return false;
- }
- //______________________________________________________________________________________________
- //用自己的座標系相對於錨點進行判斷
- bool CTestLayer::checkTouchInSelf_AR(CCTouch *touch)
- {
- //方案二
- //將點擊點轉換成自己座標系中的座標,相對於錨點
- CCPoint ptAR = convertTouchToNodeSpaceAR(touch);
- printf("ptAR.x=%.1f ptAR.y=%.1f\n", ptAR.x, ptAR.y);
- CCPoint pp = this->getAnchorPoint();
- int nw = getContentSize().width;
- int nh = getContentSize().height;
- int nx = -(nw * pp.x);
- int ny = -(nh * pp.y);
- CCRect rcar(nx, ny, nw, nh);
- if(rcar.containsPoint(ptAR))
- {
- //獲得點擊的OpenGL的世界座標值
- CCPoint touchPoint = touch->getLocation();
- printf("ccTouchBegan x=%.1f y=%.1f\n", touchPoint.x, touchPoint.y);
- return true;
- }
- return false;
- }
- //______________________________________________________________________________________________
- //用父元素座標系及自己在父座標中的位置進行判斷
- bool CTestLayer::checkTouchInSelf_Parent(CCTouch *touch)
- {
- //方案三
- //獲得點擊的OpenGL的世界座標值
- CCPoint touchPoint = touch->getLocation();
- //將點擊的位置轉換成父元素座標系中的相對座標
- CCPoint pt=getParent()->convertToNodeSpace(touchPoint);
- printf("pt.x=%.1f, pt.y=%.1f\n", pt.x, pt.y);
- //得到自己在父元素座標系中的位置範圍
- CCRect rect=boundingBox();
- printf("rect.l=%.1f, rect.b=%.1f, rect.r=%.1f, rect.t=%.1f\n",\
- rect.getMinX(), rect.getMinY(), rect.getMaxX(), rect.getMaxY());
- //判斷是否點擊落在自己的範圍當中, 以上判斷全是在父元素座標系中進行計算
- if(rect.containsPoint(pt))
- {
- printf("ccTouchBegan x=%.1f y=%.1f\n", touchPoint.x, touchPoint.y);
- return true;
- }
- return false;
- }