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

4.5 Cocos2d-x坐标系

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

4.5.1 UI坐标

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

图4-22 UI坐标

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

    var touchLocation = touch.getLocationInView();

使用getLocationInView()方法获得触摸点坐标事实上就是UI坐标,它的坐标原点在左上角。

4.5.2 OpenGL坐标

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

图4-23 OpenGL坐标

我们通过一个触摸对象(Touch)获得OpenGL坐标位置,Cocos2d-x JS API代码如下:

    var touchLocation = touch.getLocation();

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

图4-24 三维坐标

4.5.3 世界坐标和模型坐标

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

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

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

图4-25 世界坐标和模型坐标

有时需要将世界坐标与模型坐标互相转换。可以通过Node对象的Cocos2d-x JS API方法实现:


□convertToNodeSpace(worldPoint):将世界坐标转换为模型坐标,参数和返回值都是cc.Point类型。

□convertToNodeSpaceAR(worldPoint):将世界坐标转换为模型坐标,参数和返回值都是cc.Point类型,AR表示相对于锚点。

□convertTouchToNodeSpace(touch):将世界坐标中触摸点转换为模型坐标,touch参数是cc.Touch类型,返回值是cc.Point类型。

□convertTouchToNodeSpaceAR(touch):将世界坐标中的触摸点转换为模型坐标,touch参数是cc.Touch类型,返回值是cc.Point类型,AR表示相对于锚点。

□convertToWorldSpace(nodePoint):将模型坐标转换为世界坐标,参数和返回值都是cc.Point类型。

□convertToWorldSpaceAR(nodePoint):将模型坐标转换为世界坐标,参数和返回值都是cc.Point类型,AR表示相对于锚点。


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

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

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

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

图4-26 世界坐标转换为模型坐标

编写代码如下:

    var HelloWorldLayer = cc.Layer.extend({
        sprite: null,
        ctor: function () {
            this._super();
    
            var size = cc.winSize;
            var closeItem = new cc.MenuItemImage(
                res.CloseNormal_png,
                res.CloseSelected_png,
                function () {
                    cc.log("Menu is clicked!");
                }, this);
            closeItem.attr({
                x: size.width - 20,
                y: 20,
                anchorX: 0.5,
                anchorY: 0.5
            });
            var menu = new cc.Menu(closeItem);
            menu.x = 0;
            menu.y = 0;
            this.addChild(menu, 1);
    
            //创建背景
            var bg = new cc.Sprite(res.bg_png);                                     ①
            bg.setPosition(size.width / 2, size.height / 2);
            this.addChild(bg, 2);                                                   ②
    
            //创建Node1
            var node1 = new cc.Sprite(res.node1_png);                               ③
            node1.setPosition(400, 500);
            node1.setAnchorPoint(1.0, 1.0);
            this.addChild(node1, 2);                                                ④
    
            //创建Node2
            var node2 = new cc.Sprite(res.node2_png);                               ⑤
            node2.setPosition(200, 300);
            node2.setAnchorPoint(0.5, 0.5);
            this.addChild(node2, 2);                                                ⑥
    
            var point1 = node1.convertToNodeSpace(node2.getPosition());             ⑦
            var point3 = node1.convertToNodeSpaceAR(node2.getPosition());           ⑧
    
            cc.log("Node2 NodeSpace = (" + point1.x + "," + point1.y + ")");
            cc.log("Node2 NodeSpaceAR =  (" + point3.x + "," + point3.y + ")");
    
            return true;
        }
    });

代码第①~②行是创建背景精灵对象,这个背景是一个白色900×640像素的图片。代码第③~④行是创建Node1对象,并设置了位置和锚点属性。代码第⑤~⑥行是创建Node2对象,并设置了位置和锚点属性。代码第⑦行将Node2的世界坐标转换为相对于Node1的模型坐标。而代码第⑧行是类似的,它是相对于锚点的位置。

运行结果如下:

    JS: Node2 NodeSpace = (100,-100)
    JS: Node2 NodeSpaceAR =  (-200,-200)

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

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

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

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

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

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

编写代码如下:

    var HelloWorldLayer = cc.Layer.extend({
        sprite: null,
        ctor: function () {
            this._super();
            var size = cc.winSize;
            var closeItem = new cc.MenuItemImage(
                res.CloseNormal_png,
                res.CloseSelected_png,
                function () {
                    cc.log("Menu is clicked!");
                }, this);
            closeItem.attr({
                x: size.width - 20,
                y: 20,
                anchorX: 0.5,
                anchorY: 0.5
            });
            var menu = new cc.Menu(closeItem);
            menu.x = 0;
            menu.y = 0;
            this.addChild(menu, 1);
    
            //创建背景
            var bg = new cc.Sprite(res.bg_png);
            bg.setPosition(size.width / 2, size.height / 2);
            this.addChild(bg, 2);
    
            //创建Node1
            var node1 = new cc.Sprite(res.node1_png);
            node1.setPosition(400, 500);
            node1.setAnchorPoint(0.5, 0.5);
            this.addChild(node1, 2);
    
            //创建Node2
            var node2 = new cc.Sprite(res.node2_png);
            node2.setPosition(0, 0);                                             ①
            node2.setAnchorPoint(0, 0); ;                                        ②
    
            node1.addChild(node2, 2);                                           ③
    
            var point2 = node1.convertToWorldSpace(node2.getPosition());                ④
            var point4 = node1.convertToWorldSpaceAR(node2.getPosition());                ⑤
    
            cc.log("Node2 WorldSpace = (" + point2.x + "," + point2.y + ")");
            cc.log("Node2 WorldSpaceAR =  (" + point4.x + "," + point4.y + ")");
    
            return true;
        }
    });

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

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

运行结果如下:

    JS: Node2 WorldSpace = (250,450)
    JS: Node2 WorldSpaceAR =  (400,500)

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

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