Cocos2d-x学习笔记:完全掌握C++ API与游戏项目开发 (未来书库)
上QQ阅读APP看书,第一时间看更新

3.6 Cocos2d-x坐标系

在图形、图像和游戏应用开发中,坐标系是非常重要的,我们在Android和iOS等平台应用开发的时候使用的二维坐标系的原点是在左上角的。而在Cocos2d-x坐标系中的原点是在左下角,而且Cocos2d-x坐标系又可以分为:世界坐标和模型坐标。

3.6.1 UI坐标

UI坐标就是Android和iOS等应用开发的时候使用的二维坐标系。它的原点是在左上角的(见图3-23)。

图3-23 UI坐标

UI坐标原点是在左上角,x轴向右为正,y轴向下为正。我们在Android和iOS等平台使用的视图、控件等都是遵守这个坐标系。然而Cocos2d-x默认不是采用UI坐标,但是有的时候也会用到UI坐标,例如在触摸事件发生的时候,我们会获得一个触摸对象(Touch),触摸对象(Touch)提供了很多获得位置信息的函数,如下面的代码所示:

Vec2 touchLocation = touch->getLocationInView();

使用getLocationInView()函数获得触摸点坐标事实上就是UI坐标,它的坐标原点在左上角,而不是Cocos2d-x默认坐标,我们可以采用下面的语句进行转换:

Vec2 touchLocation2 = Director::getInstance()->convertToGL(touchLocation);

通过上面的语句就可以将触摸点位置从UI坐标转换为OpenGL坐标,OpenGL坐标就是Cocos2d-x默认坐标。

3.6.2 OpenGL坐标

我们在前面提到了OpenGL坐标,OpenGL坐标是种三维坐标。由于Cocos2d-x底层采用OpenGL渲染,因此默认坐标就是OpenGL坐标,只不过采用两维(x和y轴)。如果不考虑z轴,OpenGL坐标的原点在左下角(见图3-24)。

图3-24 OpenGL坐标

提示

三维坐标根据z轴的指向不同可分为:左手坐标和右手坐标。右手坐标是z轴指向屏幕外,如图3-25(a)所示。左手坐标是z轴指向屏幕里,如图3-25(b)所示。OpenGL坐标是右手坐标,而微软平台的Direct3DDirect3D(简称D3D)是微软公司在Microsoft Windows操作系统上所开发的一套3D绘图编程接口,是DirectX的一部分,目前为各家的显卡支持。它与OpenGL同为计算机绘图软件和计算机游戏最常使用的两套绘图编程接口之一。——引自于维基百科http://zh.wikipedia.org/wiki/Direct3D是左手坐标。

图3-25 三维坐标

3.6.3 世界坐标和模型坐标

由于OpenGL坐标又可以分为世界坐标和模型坐标,所以Cocos2d-x的坐标也有世界坐标和模型坐标。

你是否有过这样的问路经历:张三会告诉你向南走1000米,再向东走500米,即到目的地。而李四会告诉你向右走1000米,再向左走500米,即到目的地。这里两种说法或许都可以找到你要寻找的地点。张三采用的坐标是世界坐标,他把地球作为参照物,表述位置使用地理的东、南、西和北。而李四采用的坐标是模型坐标,他让你自己作为参照物,表述位置使用你的左边、你的前边、你的右边和你的后边。

我们看看图3-26,从图中可以看到A的坐标是(5,5),B的坐标是(6,4),事实上这些坐标值就是世界坐标。如果采用A的模型坐标来描述B的位置,则B的坐标是(1,-1)。

图3-26 世界坐标和模型坐标

有的时候,我们需要将世界坐标与模型坐标互相转换。我们可以通过Node对象函数实现:

●Vec2 convertToNodeSpace(const Vec2& worldPoint):将世界坐标转换为模型坐标。

●Vec2 convertToNodeSpaceAR(const Vec2& worldPoint):将世界坐标转换为模型坐标,AR表示相对于锚点。

●Vec2 convertTouchToNodeSpace(Touch * touch):将世界坐标中触摸点转换为模型坐标。

●Vec2 convertTouchToNodeSpaceAR (Touch * touch):将世界坐标中触摸点转换为模型坐标,AR表示相对于锚点。

●Vec2 convertToWorldSpace (const Vec2& nodePoint):将模型坐标转换为世界坐标。

●Vec2 convertToWorldSpaceAR (const Vec2& nodePoint):将模型坐标转换为世界坐标,AR表示相对于锚点。

下面通过两个例子了解一下世界坐标与模型坐标互相转换。

1. 世界坐标转换为模型坐标

图3-27是世界坐标转换为模型坐标实例运行结果。

图3-27 世界坐标转换为模型坐标

在游戏场景中有两个Node对象,其中Node1的坐标是(400, 500),大小是300×100像素。Node2的坐标是(200, 300),大小也是300×100像素。这里的坐标事实上就是世界坐标,它的坐标原点是屏幕的左下角。

编写代码如下:

bool HelloWorld::init()
{
  
  if ( !Layer::init() )
  {
    return false;
  }

  …
  //创建背景
  auto bg = LayerColor::create(Color4B(255, 255, 255, 255));            ①
  this->addChild(bg, 0);                                      ②

  //创建Node1
  auto node1 = Sprite::create("node1.png");                         ③
  node1->setPosition(Vec2(400,500));
  node1->setAnchorPoint(Vec2(1.0, 1.0));
  this->addChild(node1, 0);                                     ④

  //创建Node2
  auto node2 = Sprite::create("node2.png");                          ⑤
  node2->setPosition(Vec2(200,300));  
  node2->setAnchorPoint(Vec2(0.5, 0.5));
  this->addChild(node2, 0);                                      ⑥

  Vec2point1 = node1->convertToNodeSpace(node2->getPosition());             ⑦
  Vec2point3 = node1->convertToNodeSpaceAR(node2->getPosition());             ⑧
  
  log("Node2 NodeSpace = (%f,%f)",point1.x,point1.y);
  log("Node2 NodeSpaceAR = (%f,%f)", point3.x, point3.y);

  return true;
}

代码第①~②行是创建背景颜色层对象,它是一个白色的900×640大小的层,其中Color4B(255, 255, 255, 255)是创建一个白颜色对象,四个参数分别表示颜色的RGBA值。代码第③~④行是创建Node1对象,并设置了位置和锚点属性。代码第⑤~⑥行是创建Node2对象,并设置了位置和锚点属性。第⑦行代码将Node2的世界坐标转换为相对于Node1的模型坐标。而第⑧行代码是类似的,它是相对于锚点的位置。

运行结果如下:

Node2 NodeSpace = (100.000000,-100.000000)
Node2 NodeSpaceAR = (-200.000000,-200.000000)

结合图3-27,我们解释一下:Node2的世界坐标转换为相对于Node1的模型坐标,就是将Node1的左下角作为坐标原点(图3-27中的A点),不难计算出A点的世界坐标是(100, 400),那么convertToNodeSpace函数就是C点坐标减去A点坐标,结果是(-100,100)。

而convertToNodeSpaceAR函数要考虑锚点,因此坐标原点是B点,C点坐标减去B点坐标,结果是(-200,-200)。

2. 模型坐标转换为世界坐标

图3-28是模型坐标转换为世界坐标的实例运行结果。

图3-28 模型坐标转换为世界坐标

在游戏场景中有两个Node对象,其中Node1的坐标是(400, 500),大小是300×100像素。Node2是放置在Node1中的,大小是150×50像素,Node2相对于Node1的模型坐标是(0, 0)。

编写代码如下:

bool HelloWorld::init()
{
  if ( !Layer::init() )
  {
    return false;
  }

  …

  //创建背景
  auto bg = LayerColor::create(Color4B(255, 255, 255, 255));
  this->addChild(bg, 0);

  //创建Node1
  auto node1 = Sprite::create("node1.png");
  node1->setPosition(Vec2(400,500));
  this->addChild(node1, 0);

  //创建Node2
  auto node2 = Sprite::create("node2.png");
  node2->setPosition(Vec2(0.0, 0.0));                                  ①
  node2->setAnchorPoint(Vec2(0.0, 0.0));                               ②
  node1->addChild(node2, 0);                                           ③

  Vec2point2 = node1->convertToWorldSpace(node2->getPosition());       ④
  Vec2point4 = node1->convertToWorldSpaceAR(node2->getPosition());     ⑤


    log("Node2 WorldSpace = (%f,%f)",point2.x,point2.y);
    log("Node2 WorldSpaceAR = (%f,%f)",point4.x,point4.y);

    return true;
}

上述代码中第③行是将Node2放到Node1中,这是与之前的代码的区别。这样,第①行代码设置的坐标就变成了相对于Node1的模型坐标了。

代码第④行将Node2的模型坐标转换为世界坐标。而代码第⑤行是类似的,它是相对于锚点的位置。

运行结果如下:

Node2 WorldSpace = (250.000000,450.000000)
Node2 WorldSpaceAR = (400.000000,500.000000)

图3-26所示的位置,可以用世界坐标描述。代码第①~③行修改如下:

node2->setPosition(Vec2(250, 450));
node2->setAnchorPoint(Vec2(0.0, 0.0));
this->addChild(node2, 0);