cocos第二天 坐标系简介

本文详细介绍了Cocos2d-x中的UI坐标系、Cocos2d-x坐标系、世界坐标系及节点坐标系的特点,并解释了坐标系之间的转换原理及API使用方法。
原文章地址:http://www.jellythink.com/archives/717

UI坐标系

在进行iOS或者Android界面开发时,它的坐标系规则如下图所示:

alt

  • 原点坐标(x=0, y=0)位于左上角;
  • X轴从屏幕最左边开始,由左向右渐增;
  • Y轴坐标从屏幕最上方开始,由上向下渐增

Cocos2d-x坐标系

Cocos2d-x坐标系是这里的重点,也是我们开发时考虑的最多的。由于Cocos2d-x是基于OpenGL和OpenGL ES的。该坐标系的规则如下:

  • 原点坐标(x=0, y=0)位于左下角;
  • X轴从屏幕最左边开始,由左向右渐增;
  • Y轴从屏幕最下方开始,由下向上渐增;

在Cocos2d-x中的场景,就是使用的该坐标系。

世界坐标系

世界坐标系也叫绝对坐标系,是游戏开发中建立的概念。它建立了描述其它坐标系所需要的参考标准。我们都可以使用世界坐标系来描述其它坐标系的位置。

Cocos2d-x中元素是有父子关系的层次结构,通过Node设置位置使用的是相对其父节点的本地坐标系,而非世界坐标系,最后在绘制屏幕的时候,Cocos2d-x会把这些元素的本地节点坐标映射成世界坐标系坐标。世界坐标系和OpenGL坐标系方向一致,原点在屏幕左下角,X轴向右,Y轴向上。

节点坐标系

节点坐标系也叫相对坐标系,它是与特定节点相关联的坐标系。每个节点都有独立的坐标系,当节点移动或改变方向时,和该节点关联的坐标系(它的子节点)将随之移动或改变方向。比如一个Layer上面有10个精灵,当移动这个Layer的时候,这些精灵也会跟着一起移动的。

Node节点类中,我们可以对节点进行位置的操作,而这些设置位置使用的就是父节点的节点坐标系。它和OpenGL坐标系方向一致,原点在屏幕左下角,X轴向右,Y轴向上。有的时候,我们需要将节点坐标转换成世界坐标,或者将世界坐标转换成节点坐标。在Node节点类中,就提供了对应的转换函数,下面我们就使用一下这些函数,加深对Cocos2d-x坐标系、世界坐标系和节点坐标系的理解。

转换API

在Cocos2d-x中提供了以下的API用来进行坐标转换。

/**
* 将世界坐标转换成节点坐标,忽略锚点的影响;结果是以点为单位。
*/
Vec2 convertToNodeSpace(const Vec2& worldPoint) const;

/**
* 将节点坐标转换成世界坐标,忽略锚点的影响;结果是以点为单位。
*/
Vec2 convertToWorldSpace(const Vec2& nodePoint) const;

/**
* 将世界坐标转换成节点坐标;结果是以点为单位。
* 会考虑到锚点的影响
*/
Vec2 convertToNodeSpaceAR(const Vec2& worldPoint) const;

/**
* 将节点坐标转换成世界坐标;结果是以点为单位。
* 会考虑到锚点的影响。
*/
Vec2 convertToWorldSpaceAR(const Vec2& nodePoint) const;

/**
* 将Touch对应的点转换成节点坐标,忽略锚点的影响。
*/
Vec2 convertTouchToNodeSpace(Touch * touch) const;

/**
* 将Touch对应的点转换成节点坐标,考虑锚点的影响。
*/
Vec2 convertTouchToNodeSpaceAR(Touch * touch) const;

好了,世界坐标系转节点坐标系,节点坐标系转世界坐标系,就这么几个函数就能搞定了,剩下的就是实际的应用了。对了,在实际中,一定要考虑到锚点的影响,可能你得到的结果,就是因为锚点的影响,而完全不同的。

坐标系变换原理

上面总结了坐标系之间转换的一些API函数,下面就来看看它们之间到底是如何转换的。看了网上很多人的博客,写的转换原理,写的都不错,就是看的云里雾里的,很多人都配上了坐标图,搞笑的是那些坐标图都是“一副”,也不知道谁抄袭的谁的。

下面就做一些简单的原理,没有过的图来说明,就是一些简短的文字,按照这些文字说明,你肯定能看的懂的。

  • convertToNodeSpace
    Vec2 newPosition = node1->convertToNodeSpace(node2->getPosition());
    将node2的位置坐标转换成相对于node1左下角顶点的坐标。转换方法:node1和node2位置不变,将坐标轴原点设置为node1的左下角顶点,重新计算node2->getPosition()这个点的坐标即为newPosition。
    当调用以下代码时,返回的是相对于其父节点的节点坐标,当然了,以下代码的实际用处并不大。
    Vec2 newPosition = node1->convertToNodeSpace(node1->getPosition());
  • convertToNodeSpaceAR
    Vec2 newPosition=node1->convertToNodeSpaceAR(node2->getPosition());
    将node2的位置坐标转换成相对于node1锚点的坐标。转换方法:node1和node2位置不变,将坐标轴原点设置为node1的锚点,重新计算node2->getPosition()这个点的坐标即为newPosition。
  • convertToWorldSpace
    Vec2 newPosition=node1->convertToWorldSpace(node2->getPosition());
    将node2的位置坐标转换成世界坐标。转换方法:node1的位置不变,世界坐标的坐标轴也不变,以node1的左下角顶点再建立一个坐标系(其实就是本地坐标),将node2->getPosition()这个点设置到新建的坐标系中,以原来的世界坐标系为参考,重新计算node2->getPosition()这个点的坐标即为newPosition。
  • convertToWorldSpaceAR
    Vec2 newPosition=node1->convertToWorldSpaceAR(node2->getPosition());
    将node2的位置坐标转换成世界坐标。转换方法:node1的位置不变,世界坐标的坐标轴也不变,以node1的锚点再建立一个坐标系,将node2->getPosition()这个点设置到新建的坐标系中,以原来的世界坐标系为参考,重新计算node2->getPosition()这个点的坐标即为newPosition。
<think>我们正在处理Cocos引擎中TiledMap对象层的坐标转换问题。根据提供的引用,我们可以总结出坐标转换的关键点: 1. TiledMap的坐标系统与Cocos2d的坐标系统不同。TiledMap的坐标原点在左上角,而Cocos2d的坐标原点在左下角(默认情况下)。因此,需要进行坐标转换。 2. 对象层中的对象位置(objPos)是以TiledMap的坐标系统(左上角为原点)给出的。我们需要将其转换为Cocos2d的坐标系统(左下角为原点)或转换为格子坐标(Tile坐标),然后再根据需要进行转换。 3. 引用[1]中给出了将对象位置转换为格子坐标的方法: ```javascript this.cellWidth = tmxMap.getTileSize().width; this.cellHeight = tmxMap.getTileSize().height; var cellX = parseInt(objPos.x / (this.mapObj.cellWidth / 2)); var cellY = parseInt(objPos.y / this.mapObj.cellHeight); ``` 注意:这里除以2的操作可能是由于特定地图类型(如45度角菱形地图)导致的,但对于正交地图通常不需要。因此,我们需要根据地图类型调整转换公式。 4. 引用[3]和[5]提供了将游戏世界坐标转换为格子坐标的示例,同时也体现了坐标原点的转换(Y轴翻转): - 引用[3](C++): ```cpp cocos2d::Vec2 CTiledMapCtrl::getTileByPos(const Vec2& pos) { Size tileSize = m_pTiledMap->getTileSize(); Size mapSize = m_pTiledMap->getMapSize(); int nX = pos.x / tileSize.width; int nY = (mapSize.height * tileSize.height - pos.y) / tileSize.height; return Vec2(nX, nY); } ``` - 引用[5](JavaScript): ```javascript getTilePos: function (posInPixel) { var mapSize = this.map.node.getContentSize(); var tileSize = this.map.getTileSize(); var x = Math.floor(posInPixel.x / tileSize.width); var y = Math.floor((mapSize.height - posInPixel.y) / tileSize.height); return cc.p(x, y); } ``` 5. 对于对象层,我们通常需要将对象的位置(在Tiled编辑器中设置的位置)转换为Cocos中的坐标。由于TiledMap对象层中的对象位置是相对于整个地图的(以像素为单位,原点在左上角),而Cocos节点的位置是相对于父节点的(原点在左下角),因此需要转换。 转换步骤: 步骤1:将对象的位置(objPos)转换为Cocos世界坐标(左下角原点)。 - 设对象在Tiled中的位置为 (objPos.x, objPos.y)(单位:像素,左上角原点)。 - 整个地图的高度为 mapHeight(单位:像素),注意 mapHeight = mapSize.height * tileSize.height(对于正交地图)。 - 则Cocos中的y坐标为:mapHeight - objPos.y(这样就将左上角原点的y坐标转换为了左下角原点的y坐标)。 步骤2:如果需要格子坐标(即瓦片坐标),则用转换后的世界坐标除以每个瓦片的宽和高(取整)。 注意:上述引用[1]中的转换并没有进行y坐标的翻转,而是直接除以格子高度,这是因为objPos.y是Tiled的坐标,而他们可能是在同一个坐标系下处理(如果地图已经做了翻转处理则不需要)。但根据引用[3]和[5],我们通常需要翻转y坐标。 因此,针对对象层中的对象位置,转换为Cocos坐标(世界坐标)的公式如下: ```javascript // 假设 tmxMap 是加载的TiledMap对象 let mapSize = tmxMap.getMapSize(); // 地图的尺寸(单位:格子数) let tileSize = tmxMap.getTileSize(); // 每个格子的尺寸(单位:像素) let mapHeight = mapSize.height * tileSize.height; // 地图总高度(像素) // 对象在Tiled中的位置(objPos是Tiled中的坐标,左上角原点) let objPos = {x: 100, y: 200}; // 示例数据 // 转换为Cocos坐标(左下角原点) let worldX = objPos.x; let worldY = mapHeight - objPos.y; // 翻转Y轴 // 此时,(worldX, worldY) 就是对象在Cocos世界中的位置(像素坐标) ``` 如果我们需要将对象的位置转换为格子坐标(瓦片坐标),可以使用以下方法: ```javascript let tileX = Math.floor(worldX / tileSize.width); let tileY = Math.floor(worldY / tileSize.height); // 或者,也可以直接使用翻转前的坐标计算(但注意,这样计算出的格子坐标是正交地图的,如果是菱形地图则不同) // 引用[5]中给出了翻转后坐标转换为格子坐标的方法,与我们上面的步骤一致:先翻转y再除以格子高度。 // 注意:上面的worldY已经是翻转后的坐标,所以除以格子高度时,得到的是从地图底部开始计数的行数(即第一行是地图底部,最后一行是顶部)。 // 但是,在Tiled中,格子坐标的原点在左上角0,0)表示左上角的格子,而我们这样转换后,tileY=0表示地图底部的第一行。为了与Tiled的格子坐标一致(即左上角为原点),我们可能需要再次翻转行号? // 实际上,在Tiled中,格子坐标(0,0)对应地图左上角,而(0, mapSize.height-1)对应左下角。所以,如果我们希望得到的格子坐标与Tiled中的一致,那么: // tileY = mapSize.height - 1 - Math.floor(worldY / tileSize.height); // 但通常,我们可能不需要翻转行号,因为我们在Cocos中绘制对象时,可以直接使用转换后的世界坐标(像素)来放置对象,而不是格子坐标。 // 引用[3]和[5]的转换函数得到的格子坐标是Tiled中的格子坐标(即左上角为原点),所以引用[5]中翻转了y坐标后,再除以格子高度,得到的是从顶部开始计数的行号(与Tiled相同),但注意引用[5]中: // y = Math.floor((mapSize.height - posInPixel.y) / tileSize.height); // 实际上,这里并没有再次翻转行号,因为mapSize.height - posInPixel.y 就是把Cocos左下角原点的坐标转换成了Tiled左上角原点的坐标(相对于整个地图的顶部),所以除以格子高度后,得到的就是Tiled中从上到下计数的行号(即0表示最上面一行,mapSize.height-1表示最下面一行)。所以这个函数返回的坐标就是Tiled的格子坐标。 // 因此,如果我们想要获取对象在Tiled地图中的格子坐标(与Tiled编辑器中的格子坐标一致),可以这样做: let tileX = Math.floor(objPos.x / tileSize.width); let tileY = Math.floor(objPos.y / tileSize.height); // 注意:这里objPos.y是Tiled中的y坐标(从上到下递增),所以除以格子高度后,tileY就是Tiled中的行号(从上到下递增)。 // 但是,引用[1]中的做法是: // cellX = parseInt(objPos.x / (this.mapObj.cellWidth / 2)); // cellY = parseInt(objPos.y / this.mapObj.cellHeight); // 这可能是针对菱形地图(斜45度)的情况,因为菱形地图中,一个格子的宽和高与像素坐标的转换关系不同,并且通常格子被渲染为菱形,所以x方向可能需要除以半个格子宽度(因为相邻行是错开的)。这里我们主要讨论正交地图。 总结: 1. 如果需要在Cocos中放置对象,使用世界坐标(像素): worldX = objPos.x; worldY = mapHeight - objPos.y; 2. 如果需要获取对象所在的格子坐标(Tiled中的格子坐标): tileX = Math.floor(objPos.x / tileSize.width); tileY = Math.floor(objPos.y / tileSize.height); 3. 如果要将对象放置在某个格子的中心(在Cocos中),则: worldX = tileX * tileSize.width + tileSize.width / 2; worldY = (mapSize.height - tileY - 1) * tileSize.height + tileSize.height / 2; // 注意:这里使用(mapSize.height - tileY - 1)是因为Tiled中第0行在顶部,而在Cocos世界坐标中,我们想要将这个格子转换到世界坐标,需要从底部开始计算行号。但实际上,如果我们已经有了世界坐标的转换公式,也可以直接用第一步的转换得到对象的位置,而不通过格子坐标。 注意:对象在Tiled中设置的位置可以是任意像素位置,不一定在格子中心。所以第一步转换得到的worldX和worldY就是对象在Cocos世界中的精确位置。 因此,在加载对象层时,我们可以这样处理每个对象: ```javascript // 假设对象层为objectLayer,我们遍历其中的所有对象 let objects = objectLayer.getObjects(); for (let obj of objects) { let objPos = obj.getPosition(); // 获取对象在Tiled中的位置(像素,左上角原点) // 转换为Cocos世界坐标(左下角原点) let worldX = objPos.x; let worldY = mapHeight - objPos.y; // 在Cocos中创建一个精灵(或其他节点)代表这个对象 let sprite = new cc.Node(); sprite.setPosition(cc.p(worldX, worldY)); this.node.addChild(sprite); // 如果需要,可以设置精灵的图片(通过obj.getProperty("image")等获取属性) } ``` 以上是针对正交地图的转换。如果是菱形地图,则需要不同的转换方式(引用[1]中除以2的操作可能是用于菱形地图)。由于问题中并未明确地图类型,我们主要讨论正交地图。 另外,引用[2]提到对象层的高级用法,包括如何获取对象层的信息,我们可以参考相关教程。 因此,回答用户的问题:Cocos中TiledMap对象层坐标转换的方法主要是将Tiled中的像素坐标(左上角原点)转换为Cocos世界坐标(左下角原点),通过翻转Y轴实现。 </think>在 Cocos 引擎中处理 TiledMap 对象层坐标转换时,需要特别注意两个坐标系差异:**Tiled 编辑器坐标系**(左上角原点)和 **Cocos 世界坐标系**(左下角原点)。以下是完整的坐标转换方法: ### 核心转换方法 1. **对象层坐标获取**(Tiled 坐标系Cocos 世界坐标系): ```javascript // 获取对象在Tiled中的原始位置 (objPos.x, objPos.y) const objPos = object.getPosition(); // 计算地图总高度(像素) const mapHeight = tiledMap.getMapSize().height * tiledMap.getTileSize().height; // 转换为Cocos世界坐标(左下角原点) const worldX = objPos.x; const worldY = mapHeight - objPos.y; // Y轴翻转 // 创建节点并设置位置 const node = new cc.Node(); node.setPosition(cc.v2(worldX, worldY)); ``` 2. **世界坐标转瓦片坐标**(Cocos 世界坐标系 → 瓦片索引): ```javascript getTileCoord(worldPos) { const tileSize = tiledMap.getTileSize(); const mapSize = tiledMap.getMapSize(); const mapHeight = mapSize.height * tileSize.height; const tileX = Math.floor(worldPos.x / tileSize.width); const tileY = Math.floor((mapHeight - worldPos.y) / tileSize.height); return cc.v2(tileX, tileY); } ``` 3. **瓦片坐标转世界坐标**(瓦片索引 → Cocos 世界坐标系): ```javascript getWorldCoord(tileX, tileY) { const tileSize = tiledMap.getTileSize(); const mapHeight = tiledMap.getMapSize().height * tileSize.height; const worldX = tileX * tileSize.width + tileSize.width / 2; // 瓦片中心 const worldY = mapHeight - (tileY * tileSize.height) - tileSize.height / 2; return cc.v2(worldX, worldY); } ``` ### 关键说明 1. **Y 轴翻转原理**: - Tiled 坐标系:$(0,0)$ 在左上角,Y 值向下增加 - Cocos 坐标系:$(0,0)$ 在左下角,Y 值向上增加 - 转换公式:$y_{\text{cocos}} = H_{\text{map}} - y_{\text{tiled}}$[^1][^3][^5] 2. **对象层处理流程**: ```mermaid graph LR A[加载对象层] --> B[遍历对象] B --> C[获取Tiled坐标objPos] C --> D[转换为Cocos世界坐标] D --> E[创建游戏节点] ``` 3. **菱形地图特殊处理**: 如果是 45 度菱形地图(isometric),需额外处理: ```javascript // 菱形地图转换示例 const isoX = (cellX - cellY) * (tileWidth / 2); const isoY = (cellX + cellY) * (tileHeight / 2); ``` ### 实际应用场景 ```javascript // 示例:在对象层放置宝箱 const objectLayer = tiledMap.getObjectGroup('objects'); const chests = objectLayer.getObjects(); chests.forEach(chest => { const pos = chest.getProperty('position'); const worldPos = this.convertTiledToCocos(pos); const chestNode = cc.instantiate(this.chestPrefab); chestNode.position = worldPos; this.node.addChild(chestNode); }); ``` ### 常见问题排查 - **对象位置偏移**:检查是否忘记 Y 轴翻转或未计算地图总高度 - **坐标不对齐**:确认使用 `Math.floor()` 而非 `parseInt()` 避免小数误差[^1] - **菱形地图错位**:需使用等角投影转换公式[^2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值