Cocos 2.x 坐标系

坐标系和节点变换属性

一、概念
1.锚点Anchor

锚点由 anchorX 和 anchorY 两个值表示,他们是通过节点尺寸计算锚点位置的乘数因子,范围都是 0 ~ 1 之间。(0.5, 0.5) 表示锚点位于节点长度乘 0.5 和宽度乘 0.5 的地方,即节点的中心。


image.png

锚点属性设为 (0, 0) 时,锚点位于节点本地坐标系的初始原点位置,也就是节点约束框的左下角。


image.png
2.坐标轴方向

x轴都是向右的,y轴是向上的。


image.png
3.坐标轴原点

这里比较有特色了。

先说世界坐标系。世界坐标系也叫做绝对坐标系,在 Cocos Creator 游戏开发中表示场景空间内的统一坐标体系,「世界」就用来表示我们的游戏场景。世界坐标系的原点在屏幕的左下角。

然后就是本地坐标系。本地坐标系也叫相对坐标系,是和节点相关联的坐标系。每个节点都有独立的坐标系,当节点移动或改变方向时,和该节点关联的坐标系将随之移动或改变方向。

所有子节点就会以 锚点所在位置 作为坐标系原点,注意这个行为和 cocos2d-x 引擎中的默认行为不同,是 Cocos Creator 坐标系的特色!

二、验证

image.png

创建了一个ppp的scene,然后自动创建了一个Canvas,Canvas是处于世界坐标系当中的。

这时候再拖进去一张图片,设置Anchor为0.5,0.5,坐标为0,0。因为这张图也处于世界坐标系中,所以就会显示在左下角。并且是图片的中心点处于左下角,这也说明Anchor决定了一张图片以哪个像素点做为自己的位置参考点。比如,把Anchor改为0,0。此时这张图片的左下角会和世界坐标系的左下角对齐。

1.canvas
image.png

继续来做验证,Canvas宽高设置为960,640。Anchor设置为0.5,0.5。这表明,当我们设置它的位置时,实际上它的参考点是中心点,也就是480,320那个像素。Canvas本身处于世界坐标系,如果想让它居中,那么Position应该设置多少呢?很明显,相对于左下角的坐标原点,正是480,320。

2.canvas中的子节点

再拖进去一个图片,设置Anchor为0.5,0.5。然后设置Position为0,0。此时,特色系统发挥作用了,canvas的坐标原点在其Anchor位置,所以新拖的图片会被显示在正中心。

这个时候,用肉眼确认一下这张新图片,在世界坐标系中的坐标值,很显然是480,320。然后挂个脚本组件,把这张图片命名为pic1,使用API再验证一下:

cc.log(this.pic1.convertToWorldSpaceAR(cc.v2(0,0)));

convertToWorldSpaceAR的作用是,将节点坐标系下的一个点转换到世界空间坐标系。

如果此时,把pic1的Anchor改为0,0。在显示上确实不一样了,图片会偏向右上方。但是convertToWorldSpaceAR打印的,仍然是480,320。如果此时把pic1的Position改为300,0。图片会向右方移动,convertToWorldSpaceAR打印的,会变成780,320。

3.再嵌入一个子节点

把pic1里面,再嵌入一个小星星图片叫star。当设置pic1的Anchor为0,0。设置star的Anchor为0,0;Postion为0,0时,发现star确实是在pic1的左下角。这验证了,子节点以 锚点所在位置 作为坐标系原点。这时同样可以验证star在世界坐标系的位置:cc.log(this.pic1.getChildByName("star").convertToWorldSpaceAR(cc.v2(0,0)));

三、为什么搞成这样子

参考位置系统对程序员来说极度不友好,摘抄一部分过来:

Q:原来写一个需要居中的节点node.setPosition(parent.width/2,parent.height/2),现在需要写的 node.setPosition(parent.width*(0.5-parent.anchorX),parent.height*(0.5-parent.anchorY))。现在的很容易忘记写现在的这种写法 一旦父节点的锚点不在中心点,位置就不会居中。

比如有一组卡牌,有规律排列, 原来通过获取坐标很容易得出是第几张, 现在这是不容易的, 如果不考虑父节点锚点就会出错. 而锚点除了运动外是常被忽略的, 如果后期不经意变动,那就会出问题.

A:居中的节点请让美术直接在子节点上加 Widget,设置对齐到 horizontal center 或 vertical center 就可以了,不需要再写代码了(而且你的代码只能对齐一次,Widget 可以在运行时保持对齐状态)

忘记需要考虑锚点位置是改变规则以后的正常现象,请多一点耐心,转换一下思路,相信很快就能看到新规则带来的各种好处(尤其是编辑器里拼场景时)。

关于你说的通过位置获取索引的问题,我理解你从以前经验中产生的需求是「随手设置一个可以让父节点旋转的点作为锚点,子节点排列不受锚点影响」,但现在整个工作流都重新设计过了,比如卡牌索引的问题,应该是动态创建的时候就保存好索引,这样根本不用去算位置;而需要旋转父节点时,只要知道子节点会围绕父节点的锚点旋转,再根据需要重新设置好锚点位置就可以了吧?

以编辑器为核心搭建 UI 的工作流程和以前用代码写 UI 的流程是完全不同的,所以需要修改一些系统来更好的匹配现在的工作流程。对于旧项目如何移植,我们之后会推出更多案例给大家参考。

我再总结一下锚点的作用。
锚点的作用无非两个,标识美术关键元素和方便策划布局。

  • 标识美术关键点:
    这种锚点是由美术在场景里设置的,就像我前面举例的,十字准心和箭头。
    此时父节点旋转,子物体必须绕着父物体锚点动 (现在满足,原先满足)。
    父节点锚点位置如果变化,子物体应该自动跟着移动 (现在满足,原先不满足)。

  • 方便策划布局:
    如果是卡牌背景图,按钮,图标这些 UI 元素,美术是无所谓锚点在哪的,一般都是策划在编辑器里根据布局需要设置好的。
    我们现在鼓励的是策划使用 widget 来做定位,策划其实不再需要通过 anchor 来做布局了。
    就算要用 anchor 布局,如果 anchor 需要变,一般是由于 UI 有了新的布局需求,本来就不是改一个 anchor 能解决的事情,更不会有策划手贱去改一个本来设得好好的 anchor。
    就算改错了,策划也不可能不预览结果,有错误他自己会及时在编辑器下处理,不需要经过程序解决。

所以你帖子标题说的“位置系统对程序员来说极度不友好”应该是不成立的。

四、converToWorldSpaceAR和converToWorldSpace区别

参考 使用convertToNodeSpace()的一个问题

node.convertToWorldSpace(cc.p(0,0))是将node的0,0(忽略锚点,也就是node的左下角)这个位置转换为世界坐标,所以得到的位置应该是node的左下角相对于世界坐标系原点的偏移量。如果你要获取node在世界坐标系的位置,应该使用Canvas.converToWorldSpaceAR(node),这样才能获取node在世界坐标系的position

关于这一点,看一下源码,更清楚:

//CCNode.js:

 * !#zh 将一个相对于节点左下角的坐标位置转换到世界空间坐标系。
 * 这个 API 的设计是为了和 cocos2d-x 中行为一致,
 * 更多情况下你可能需要使用 convertToWorldSpaceAR
 * @method convertToWorldSpace
 * @param {Vec2} nodePoint
 * @return {Vec2}
 * @example
 * var newVec2 = node.convertToWorldSpace(cc.v2(100, 100));
 */
convertToWorldSpace (nodePoint) {
    this._updateWorldMatrix();
    let out = new cc.Vec2(
        nodePoint.x - this._anchorPoint.x * this._contentSize.width,
        nodePoint.y - this._anchorPoint.y * this._contentSize.height
    );
    return vec2.transformMat4(out, out, this._worldMatrix);
},

 * !#zh
 * 将节点坐标系下的一个点转换到世界空间坐标系。
 * @method convertToWorldSpaceAR
 * @param {Vec2} nodePoint
 * @return {Vec2}
 * @example
 * var newVec2 = node.convertToWorldSpaceAR(cc.v2(100, 100));
 */
convertToWorldSpaceAR (nodePoint) {
    this._updateWorldMatrix();
    let out = new cc.Vec2();
    return vec2.transformMat4(out, nodePoint, this._worldMatrix);
},
五、实例

参考快速上手:制作第一个游戏 摘星星,这里可以下载最终完成的项目

1.Game.js
//Game.js

onLoad: function () {
    // 获取地平面的 y 轴坐标
    this.groundY = this.ground.y + this.ground.height/2;
    ...

spawnNewStar: function() {
    var newStar = null;
    // 使用给定的模板在场景中生成一个新节点
    if (this.starPool.size() > 0) {
    // this will be passed to Star's reuse method
        newStar = this.starPool.get(this);
    } else {
        newStar = cc.instantiate(this.starPrefab);
    }
    // 将新增的节点添加到 Canvas 节点下面
    this.node.addChild(newStar);
    // 为星星设置一个随机位置
    newStar.setPosition(this.getNewStarPosition());
    ...

getNewStarPosition: function () {
    // if there's no star, set a random x pos
    if (!this.currentStar) {
        this.currentStarX = (Math.random() - 0.5) * 2 * this.node.width/2;
    }
    var randX = 0;
    // 根据地平面位置和主角跳跃高度,随机得到一个星星的 y 坐标
    var randY = this.groundY + Math.random() * this.player.jumpHeight + 50;
    // 根据屏幕宽度和上一个星星的 x 坐标,随机得到一个新生成星星 x 坐标
    var maxX = this.node.width/2;
    if (this.currentStarX >= 0) {
        randX = -Math.random() * maxX;
    } else {
        randX = Math.random() * maxX;
    }
    this.currentStarX = randX;
    // 返回星星坐标
    return cc.v2(randX, randY);
},

这个Game.js脚本,是挂在Canvas上的。也就是说this.node.addChild添加星星时,实际上添加到了Canvas坐标系中。

  • 地平面groundY计算时,也就是找地面图片最顶端的坐标,因为上面说了,Anchor决定了一张图片以哪个像素点做为自己的位置参考点,这里ground的Anchor设置了0.5,0.5。所以this.groundY = this.ground.y + this.ground.height/2

  • randY就是地平面加上,跳跃高度最大值,中间的一个随机值。后面还有个+50是为什么呢?可以测试一下,添加randY = this.groundY;,也就是Math.random()取0时,如果不加50,randY就是地平面的位置。此时因为添加的星星star的Anchor设置了0.5,0.5,将会出现星星有一半埋在地下线下面,显然不合理。

  • maxX 这里Canvas的坐标原点就在屏幕中心,所以randX是分正负的,也就是var maxX = this.node.width/2;randX = -Math.random() * maxX;当然,为了游戏更合理,randX总是会出现在上一次的位置坐标完全相反的半轴,这是通过currentStarX 这个变量来标记的。

2.Player.js
// called every frame
update: function (dt) {
    // 根据当前加速度方向每帧更新速度
    if (this.accLeft) {
        this.xSpeed -= this.accel * dt;
    } else if (this.accRight) {
        this.xSpeed += this.accel * dt;
    }
    // 限制主角的速度不能超过最大值
    if ( Math.abs(this.xSpeed) > this.maxMoveSpeed ) {
        // if speed reach limit, use max speed with current direction
        this.xSpeed = this.maxMoveSpeed * this.xSpeed / Math.abs(this.xSpeed);
    }

    // 根据当前速度更新主角的位置
    this.node.x += this.xSpeed * dt;

    // limit player position inside screen
    if ( this.node.x > this.node.parent.width/2) {
        this.node.x = this.node.parent.width/2;
        this.xSpeed = 0;
    } else if (this.node.x < -this.node.parent.width/2) {
        this.node.x = -this.node.parent.width/2;
        this.xSpeed = 0;
    }
},

Player.js也是挂在Canvas上的。这里有边缘检测,如何判断横向移动出界:this.node.x > this.node.parent.width/2

你可能感兴趣的:(Cocos 2.x 坐标系)