一文搞懂2d/3d坐标系转换!疯狂星期四小游戏免费开源!!

二喵今天分享下2d坐标系和3d坐标之间的转换!

在线试玩

昨天是疯狂星期四!很多同学问老菜喵3D游戏中,Canvas的坐标和3D世界中的坐标如何通过屏幕空间坐标相互转换。

d1

老菜喵请出了我们的老演员坤坤,让我们用一个吃金币的小游戏(小游戏结尾获取下载链接,模型来自真ikun,与老菜喵无关,商用后果自付~),一文搞懂空间坐标转换

坐标的基本概念

在 Cocos 引擎中,屏幕空间坐标、世界坐标和本地坐标是三种不同的坐标系,它们各自有不同的用途和特点:

  • 屏幕空间坐标(Screen Space Coordinates): 这是一个二维坐标系,用于确定元素在屏幕上的位置。它的原点(0,0)通常位于屏幕的左下角,最大值为屏幕的分辨率(例如,如果屏幕分辨率是1920x1080,那么右上角的屏幕空间坐标就是(1920,1080))。这个坐标系主要在处理用户输入(如点击或拖动)或在屏幕上显示UI元素时使用。

  • 世界坐标(World Coordinates): 这是一个三维坐标系,用于确定游戏世界中物体的位置。世界坐标系统中的每个点都有一个唯一的位置,无论摄像机在何处,无论相机如何移动或旋转,其在世界空间中的坐标都是固定的。

  • 本地坐标(Local Coordinates): 这也是一个三维坐标系,但它是相对于特定游戏对象的。在本地坐标系中,(0,0,0)总是表示对象的原点,其他坐标则表示相对于这个原点的位置。当你移动或旋转一个物体时,它的本地坐标将不会改变,但其世界坐标将会改变。编辑器中默认的坐标系就是本地坐标,有个特殊情况当本地坐标的父节点坐标(包括父节点的父节点)也都是(0,0,0),且不存在缩放和旋转,我们也可以使用本地坐标替代世界坐标,这时候本地坐标和世界坐标的值是一样的。

image

总的来说,屏幕空间坐标、世界坐标和本地坐标都是描述物体位置的方式,但它们是在不同的上下文和用途中使用的。

2D UI坐标转 3D 节点坐标

由于2D UI和3D 节点的相机不一样,这里的转换其实指的是视觉上,通过坐标转换,让3D 节点的位置在视觉上看起来一致。

这个Demo中比较有代表性的粒子就是 3D的金币飞到了2D 节点的下,并增加金币数量。

image
我们来简单看下这段代码,先使用2D UI的相机把2D物体坐标转换了了屏幕空间坐标,再把屏幕空间坐标转换了3D相机下的世界坐标,随着3D相机的位移缩放,这个转换过的世界坐标不是一个绝对坐标,会随着3D坐标的位移缩放而发生改变。

        /* 2D UI Node's wordposition to screen space pos*/
        aa.cameras[1].worldToScreen(this._coinView.worldPosition, temp_V3_3);
        const flyingPos = new Vec3();
        /* screen space pos to  3d world position under this camera*/
        aa.cameras[0].screenToWorld(temp_V3_3, flyingPos);

所以当我们进行金币飞行时候,为了节省性能,最好在金币飞行时候暂停镜头的跟随与移动。(这里给角色添加了一个举手的动画,并做了暂停)

image

接下来再飞行中,我们希望3D金币能飞到2D的金币精灵上面,这时候需要改变下观察3D金币的相机,我们额外创建一个金币相机,这个相机只能观察3D_UI场景,且优先级是2,模式是depth_only.
image

(2D相机的优先级是1,这个相机渲染的画面会在2D之上)
最后,由于我们使用的3D主相机是透视相机,和正常人眼观察的情况一样,符合近大远小的规律,我们2D节点在屏幕边缘,转换的屏幕坐标再到3D世界坐标,所处的位置也会比较边缘,为了让视觉上一致,我们给金币添加了一个tween动画,让金币的scale也慢慢变大。

代码如下:

        for (var i = bl - 1; i >= 0; i--) {
            const coin = backCoins[i];
            /* change layer to be visible to 3D coins' camera in order to be viewed upon the 2D camera*/
            coin.parent = aa.layer3D[4];
            coin.layer = Layers.Enum.UI_3D;
            coin.setRotationFromEuler(90, 0, 0)
            const delay = i * dt;
            /* move and scale the coin */
            tween(coin).delay(delay).to(0.5 + delay, { position: flyingPos, scale: coinScaled }, { easing: 'fade' }).call(() => {
                aa.res.putNode(coin);
            }).start();
        }

整个2D UI坐标转 3D 节点坐标的流程就完成了。

重点如下

  • 2D相机负责把2D物体的世界坐标转换成屏幕空间坐标
  • 3D相机负责把屏幕空间坐标转换成3D相机观察中的世界坐标
  • 3D相机是透视相机时候需要放大3D节点,是正交相机时候则无需放大

3D节点 坐标转 2D 节点坐标

这个需要也是很常用的,常用于2D节点跟随3D角色,如

  • 角色血条
  • 伤害文字
  • 角色状态

这里我们给坤坤添加一个金币数量的状态,并切可以跟随金币的数量而改变跟随高度。

    updateCoinBar(backCoins: Node[]) {
        if (this._coinBarViewComp) {
            /* backcoins length */
            const bl = backCoins.length;
            Vec3.copy(temp_V3_1, this._playerPos);
            temp_V3_1.y += 1.5 + (bl + 1) * 0.075;
            aa.cameras[0].convertToUINode(temp_V3_1, aa.layer2D[4], temp_V3_1);
            this._coinBarView.position = temp_V3_1;
            if (this._currentCoins != bl) {
                this._currentCoins = bl;
                this._coinBarViewComp.coin = "Coins " + bl;
            }
        }
    }

在上述代码中我们首先获取角色的位置和金币的数量,并动态计算出状态栏在3D空间内相对应的位置,然后通过3D相机的convertToUINode 转换成2D节点下的本地坐标

引擎convertToUINode的方法也是先把3D坐标转换成改相机相对于的屏幕空间坐标,再把屏幕空间坐标转换成2D节点下的本地坐标

public convertToUINode (wpos: Vec3 | Readonly<Vec3>, uiNode: Node, out?: Vec3): Vec3 {
        if (!out) {
            out = new Vec3();
        }
        if (!this._camera) { return out; }

        this.worldToScreen(wpos, _temp_vec3_1);
        const cmp = uiNode.getComponent('cc.UITransform') as UITransform;
        const designSize = cclegacy.view.getVisibleSize();
        const xoffset = _temp_vec3_1.x - this._camera.width * 0.5;
        const yoffset = _temp_vec3_1.y - this._camera.height * 0.5;
        _temp_vec3_1.x = xoffset / cclegacy.view.getScaleX() + designSize.width * 0.5;
        _temp_vec3_1.y = yoffset / cclegacy.view.getScaleY() + designSize.height * 0.5;

        if (cmp) {
            cmp.convertToNodeSpaceAR(_temp_vec3_1, out);
        }

        return out;
    }

当角色移动时候,金币状态也会跟随角色移动。

image

获取Demo

关注公众号老菜喵,回复 金币 即可获得

qrcode_for_gh_81e0ce008ab3_258 (1)

8赞

666666 :100: :100: :100: :+1:

第一名 点赞

marked

真是黄昏见证虔诚的信徒,对坤哥真爱啊

二喵出品,必属精品 :grinning:

终于搞明白了我晚上回家自己测试一下 原来 这几个坐标我转化我一直很懵逼。

大佬666666666666666666666666666666666666666666666666

微信文章已上线,同时分享了gameplay的优化
关注公众号老菜喵,回复 金币 即可获得

image

mark 66