cocos2dx 3.3 tilemap 缩放滑动并且准确点击对象

本文介绍如何在Cocos2d-x中实现TileMap的3倍缩放、鼠标滑动及点击定位功能,并确保角色始终位于屏幕中心。文中详细解析了TileMap坐标转换的原理,包括分辨率转换、缩放系数处理等关键技术点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

之前已经有一篇更基础的tilemap笔记了,这两天用了下3.3的tilemap发现以前有些东西又套用不了。所以又写了一篇札记,顿时感觉自己萌萌哒。

完全没有玩过的童鞋可以看看基础篇


主要实现目标: 

1. 能够3倍缩放tiledMap

2. 能够鼠标滑动 tiledMap

3. 在缩放和滑动的情况下,点击一个tile 能够判断出 实际的格子坐标。

4. 永远让角色保持在屏幕中心


缩放和滑动这里就不再详细解释了,有很多的例子,末尾呈上完整代码。主要说一下第三条的实现。

可以从图中看出,我建了一个points对象组,然后建立了两个对象 startPos 和endPos

startPos 大概覆盖了 4个tiled   [0,24] [1,24] [0,25] [1,25]

并且给startPos设置了一个 自定义属性:id 。我需要的就是点击到 上面4块tiled的时候能够 获取到这个对象的id值。


     





首先写了个函数来读地图对象

void HelloWorld::parseTileMap()
{
    CCLOG("parseTileMap");
    if(_tiledMap == NULL)
        return;
    
    _objectsGroup = _tiledMap->getObjectGroup("points");

    //tileX 0  tileY 24
    ValueMap startPos = _objectsGroup->getObject("startPos");
    std::string name = startPos["name"].asString();
    float pointX = startPos["x"].asFloat();
    float pointY = startPos["y"].asFloat();;
    float PointWidth = startPos["width"].asFloat();
    float PointHeight = startPos["height"].asFloat();

    Size winSize = Director::getInstance()->getWinSize();
    Point mapPoint = _tiledMap->getPosition();
    Point centerPos = covertTiledPointToCenterPoint(Point(0,24));
    Point tilePos = covertPointToTiledPoint(Point(pointX,pointY));
    Point tilePos2 = covertPointToTiledPoint(centerPos);
}

很顺利的拿到startPos对象的 pointX 和  pointY , 但是值却是  (0,296) , 不是期待的(1,768) 忽然有点凌乱 -_- !!了。

直到看了一下源码TMXLayer 的getPositionAt才发现,原来这个(0,296) 是已经经过 “分辨率转换“了。

Vec2 TMXLayer::getPositionAt(const Vec2& pos)
{
    Vec2 ret = Vec2::ZERO;
    switch (_layerOrientation)
    {
    case TMXOrientationOrtho:
        ret = getPositionForOrthoAt(pos);
        break;
    case TMXOrientationIso:
        ret = getPositionForIsoAt(pos);
        break;
    case TMXOrientationHex:
        ret = getPositionForHexAt(pos);
        break;
    case TMXOrientationStaggered:
        ret = getPositionForStaggeredAt(pos);
        break;
    }
    CCLOG("%f,%f,%f",ret.x,ret.y,CC_CONTENT_SCALE_FACTOR());
    
<span style="color:#ff0000;">    ret = CC_POINT_PIXELS_TO_POINTS( ret );</span>
    return ret;
}


看一下宏的定义:

/** @def CC_POINT_PIXELS_TO_POINTS
 Converts a rect in pixels to points
 */
#define CC_POINT_PIXELS_TO_POINTS(__pixels__)                                    \
Vec2( (__pixels__).x / CC_CONTENT_SCALE_FACTOR(), (__pixels__).y / CC_CONTENT_SCALE_FACTOR())

/** @def CC_CONTENT_SCALE_FACTOR
On Mac it returns 1;
On iPhone it returns 2 if RetinaDisplay is On. Otherwise it returns 1
*/
#define CC_CONTENT_SCALE_FACTOR() Director::getInstance()->getContentScaleFactor()


从源码可以看到:

1 . 我们可以确定的是tilemap 上面 pixel : point 的比例是  1 : 1, 而设备上面却并不一定。

mac 设备上面 pixel : point = 1: 1 , iphone Retina 是 2 : 1, 低分辨率就会更大。我们就把这个比值像素尺寸因子吧

2. 通过 CC_POINT_PIXELS_TO_POINTS 能够把 tilemap上面的坐标值转换成 设备上面的坐标值。

3. 这个setScale getScale做的是类似的事情,但是要区分开来计算。


此外,因为我们的tilemap可以滑动点击,所以在从触摸点转换 tilemap绝对坐标的时候需要额外做两点:

1.  把tilemapLayer的坐标作为偏移量

bool HelloWorld::onTouchBegan(Touch *touch, Event  *event)
{
    CCLOG("HelloWorld::onTouchBegan");
    Point touchPoint = touch->getLocation();
    Point mapPoint = _tiledMap->getPosition();
    <span style="color:#ff0000;">Point realPoint = touchPoint - mapPoint;</span>
    Point tilePoint = covertPointToTiledPoint(realPoint);
    CCLOG("tilePoint x:%f y:%f",tilePoint.x,tilePoint.y);
    
    tryGetObjectPropertyByPosition(realPoint);
    return true;
}



2. 除以缩放系数

注意:在scale的情况下,因为touchPoint 和 mapPoint 经过缩放,已经在scale的作用产生变化了

所以:

Point realPoint = (touchPoint - mapPoint)/_tiledMap->getScale();


bool HelloWorld::onTouchBegan(Touch *touch, Event  *event)
{
    CCLOG("HelloWorld::onTouchBegan================");
    Point touchPoint = touch->getLocation();
    Point mapPoint = _tiledMap->getPosition();

    Point realPoint = (touchPoint - mapPoint)/getTileMapScale();
}



接下来再逆推一下从 触摸点 转换到 tilemapPoint 的方法就非常简单了

Point HelloWorld::covertTiledPointToCenterPoint(Point p)
{
    float scale = _tiledMap->getScale();
    float factor = CC_CONTENT_SCALE_FACTOR();
    
    CCLOG("HelloWorld::covertTiledPointToCenterPoint scale:%f factor:%f",scale,factor);
    
    int offsetX = _tiledMap->getTileSize().width / (2 * factor);
    int offsetY = _tiledMap->getTileSize().height / (2 * factor);
    TMXLayer* layer = _tiledMap->layerNamed("background");
    Point point = layer->getPositionAt(p);
    
    point = Point(point.x  + offsetX,point.y - offsetY);
    point = Point(point.x * scale, point.y * scale);
    return point;
}


测试发现,上述方法还是计算有偏差,补上一个正确的计算方法

Point HelloWorld::tileCoordForPosition(const Point &position) const
{
    float factor = CC_CONTENT_SCALE_FACTOR();
    int x = position.x * factor/ _tiledMap->getTileSize().width;
    int y = ((_tiledMap->getMapSize().height * _tiledMap->getTileSize().height) - position.y * factor) / _tiledMap->getTileSize().height;
    return Point(x, y);
}

Point HelloWorld::positionForTileCoord(const Point &tileCoord) const
{
    float factor = CC_CONTENT_SCALE_FACTOR();

    int x = (tileCoord.x * _tiledMap->getTileSize().width) + _tiledMap->getTileSize().width / 2;
    int y = (_tiledMap->getMapSize().height * _tiledMap->getTileSize().height) -
    (tileCoord.y * _tiledMap->getTileSize().height) - _tiledMap->getTileSize().height / 2;
    return Point(x/factor, y/factor);
}


注意:在进行了scale缩放的情况下, positionForTileCoord 得出来的坐标是在 tilemap上面的相对坐标 relativePoint;

如果用来设置 tileMap 上面的子对象的坐标,那么不需要额外加工了,因为计算子结点相对坐标是不需要考虑scale的。


最后说一下关注角色居中的问题:

先上代码

void HelloWorld::setViewpointCenter2(const Point &RolePoint)
{
    Size winSize = Director::getInstance()->getWinSize();
    Point centerOfView(winSize.width / 2, winSize.height / 2);
    Point tileMapPoint = _tiledMap->getPosition();
    Point centerPoint = (centerOfView - tileMapPoint)/_tiledMap->getScale();
    Point OffsetVec = centerPoint - RolePoint;
    _tiledMap->setPosition(tileMapPoint + OffsetVec);
}



假设图中的黑框就是设备,那么我们现在要做的就是移动地图,让小猫处在设备中心,也就是蓝圈的位置。

1.中心位置,在屏幕坐标系上恒定不变,永远是 Point centerOfView(winSize.width / 2, winSize.height / 2);

2. 地图的坐标是相对屏幕坐标系的坐标(受到scale影响),这样 (centerOfView - tileMapPoint ) / scale 就得到了蓝圈在 地图坐标系的坐标centerPoint

3. 角色坐标(RolePoint)  + 偏移量(offsetVec) = centerPoint

4. tileMap的新位置 = tileMapPoint + 偏移量




代码下载


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值