先說下世界座標跟本地:
// 把世界座標轉換到當前節點的本地座標系中
Point convertToNodeSpace(constPoint& worldPoint) const;
// 把基於當前節點的本地座標系下的座標轉換到世界座標系中
Point convertToWorldSpace(constPoint& nodePoint) const;
// 基於Anchor Point把基於當前節點的本地座標系下的座標轉換到世界座標系中
Point convertToNodeSpaceAR(constPoint& worldPoint) const;
// 基於Anchor Point把世界座標轉換到當前節點的本地座標系中
Point convertToWorldSpaceAR(constPoint& nodePoint) const;
下面通過一個例子來說明這四個方法的理解和作用:
auto *sprite1 = Sprite::create("HelloWorld.png");
sprite1->setPosition(ccp(20,40));
sprite1->setAnchorPoint(ccp(0,0));
this->addChild(sprite1); //此時添加到的是世界座標系,也就是OpenGL座標系
auto *sprite2 = Sprite::create("HelloWorld.png");
sprite2->setPosition(ccp(-5,-20));
sprite2->setAnchorPoint(ccp(1,1));
this->addChild(sprite2);//此時添加到的是世界座標系,也就是OpenGL座標系
//將 sprite2 這個節點的座標ccp(-5,-20) 轉換爲 sprite1節點 下的本地(節點)座標系統的 位置座標
Point point1 = sprite1->convertToNodeSpace(sprite2->getPosition());
//將 sprite2 這個節點的座標ccp(-5,-20) 轉換爲 sprite1節點 下的世界座標系統的 位置座標
Point point2 = sprite1->convertToWorldSpace(sprite2->getPosition());
log("position = (%f,%f)",point1.x,point1.y);
log("position = (%f,%f)",point2.x,point2.y);
運行結果:
Cocos2d: position = (-25.000000,-60.000000)
Cocos2d: position = (15.000000,20.000000)
其中:Point point1 = sprite1->convertToNodeSpace(sprite2->getPosition());
相當於sprite2
這個節點添加到(實際沒有添加,只是這樣理解)sprite1
這個節點上,那麼就需要使用sprite1
這個節點的節點座標系統,這個節點的節點座標系統的原點在(20,40),而sprite1
的座標是(-5,-20),那麼經過變換之後,sprite1
的座標就是(-25,-60)。
其中:Point point2 = sprite1->convertToWorldSpace(sprite2->getPosition());
此時的變換是將sprite2
的座標轉換到sprite1
的世界座標系下,而其中世界座標系是沒有變化的,始終都是和OpenGL等同,只不過sprite2
在變換的時候將sprite1
作爲了”參照“而已。所以變換之後sprite2
的座標爲:(15,20)。
以上只是泛泛的簡單的轉換了下,但是工作中往往會稍微複雜些。
比如說:
判斷子彈是否飛出了屏幕,但是子彈是在地圖層的,地圖又在gameLayer層上,gameLayer上又有層,這一層套一層到底該怎麼判斷子彈的絕對位置呢?
bullet->getPosition() 查了下資料得到的位置是相對父結點的,即地圖層下的局部座標,我希望得到是子彈相對於屏幕左下角的位置,這也就是著名的絕對座標。顯然bullet->getPosition()在地圖一移動的情況下就不再是世界座標了,那可怎麼辦呢?
網上很多網友給出的解決辦法是座標系轉換,即本地座標轉爲世界座標系,就一句代碼就行:
posBullet = (bullet->getParent())->convertToWorldSpace(bullet->getPosition());
經試驗是可行的,但我對此深深的懷疑:bullet的父結點是一層套一層的,那convertToWorldSpace裏的參數是什麼意思,(bullet->getParent())又是什麼,爲什麼不能是(bullet->getParent()->getParent())
看了很多官方說法,再查源代碼:終於理解了這方法的含義:我們是要求的是bullet的世界座標系,即相對於屏幕左下角的座標,所以convertToWorldSpace的參數是 bullet->getPosition(),那什麼執行主體是bullet->getParent()而不用考慮更深層次的父結點呢,原因很簡單,bullet的position是相對於它的父結點的,也就是bullet->getParent(),所以執行主體也就是bullet->getParent(),那怎麼保證此函數求到的是最上層的世界座標而不是相對於他的父結點的座標呢,看源代碼:
Vec2 Node::convertToWorldSpace(const Vec2& nodePoint) const
{
Mat4 tmp = getNodeToWorldTransform();
Vec3 vec3(nodePoint.x, nodePoint.y, 0);
Vec3 ret;
tmp.transformPoint(vec3,&ret);
return Vec2(ret.x, ret.y);
}
裏面有個重要的矩陣變換 getNodeToWorldTransform() ,估且不要理解其中複雜的數學變換,它的源碼:
Mat4 Node::getNodeToWorldTransform() const
{
Mat4 t = this->getNodeToParentTransform();
for (Node *p = _parent; p != nullptr; p = p->getParent())
{
t = p->getNodeToParentTransform() * t;
}
return t;
}
可看出它是一層一層向上回溯找其父結點的變換矩陣的,所以最後能求到最上層父結點的變換矩陣,也就求得了世界座標系。所以
posBullet = (bullet->getParent())->convertToWorldSpace(bullet->getPosition()); 此方法是正確的,就是子彈的世界座標。然後要是要是再將子彈的這個座標轉換成相對於其他節點的本地座標,就要用到剛纔的posBullet ,比如說轉換成非該子彈父節點的node的相對座標:pos = node->convertToNodeSpace(posBullet);這樣就可以將不在同一層級中的節點轉換成其他層級的本地座標了,用到最多的比如每個傳奇項目中的 每個主角總是在屏幕的中心處。