初窥cocos2d tileMap

TileMap,瓦片地图,应该算是最简单,最原始的地图了。如果你还不知道什么是瓦片地图,请点这里。假设现在你已经对瓦片地图有了最基本的了解,下面通过cocos2d-x自带的testapp了解TileMapEditTest。(ps:我看的是C++的code)

TestApp跑起来之后选择“TileMapTest”,进入“TileMapTest”之后又有N多相关的demo,一直翻到“TileMapEditTest”。

首先我们来看构造函数:

TileMapEditTest::TileMapEditTest()
{
    CCTileMapAtlas* map = CCTileMapAtlas::create(s_TilesPng, s_LevelMapTga, 16, 16);
    // Create an Aliased Atlas
    map->getTexture()->setAliasTexParameters();
    
    CCSize CC_UNUSED s = map->getContentSize();
    CCLOG("ContentSize: %f, %f", s.width,s.height);
    
    // If you are not going to use the Map, you can free it now
    // [tilemap releaseMap);
    // And if you are going to use, it you can access the data with:
    schedule(schedule_selector(TileMapEditTest::updateMap), 0.2f);
    
    addChild(map, 0, kTagTileMap);
    
    map->setAnchorPoint( ccp(0, 0) );
    map->setPosition( ccp(-20,-200) );	
}

我们看到第一句就创建了一个map,然后向map addchild到当前layer,再就是设置一堆属性。

我们继续跟踪 CCTileMapAtlas::create

/** creates a CCTileMap with a tile file (atlas) with a map file and the width and height of each tile in points.
    The tile file will be loaded using the TextureMgr.
    */
CCTileMapAtlas * CCTileMapAtlas::create(const char *tile, const char *mapFile, int tileWidth, int tileHeight)
{
    CCTileMapAtlas *pRet = new CCTileMapAtlas();
    if (pRet->initWithTileFile(tile, mapFile, tileWidth, tileHeight))
    {
        pRet->autorelease();
        return pRet;
    }
    CC_SAFE_DELETE(pRet);
    return NULL;
}

四个参数,tile是瓦片路径,其实是一张png的图片(位于resource下面的TileMaps/tiles.png),之后我们看到map上的东西都是从此图片上分割出来的。

 mapFile,是tga格式的图片(位于resource下面的TileMaps/testmap.tga),用于构造地图,也就是说mapFile的像素大小决定了最终地图的基础大小。

tileWidth  tileHeight,将tile文件按照从左到右,从上到下的优先级分割成一个一个小tile。

我们继续向下看CCTileMapAtlas的initWithTileFile函数:

bool CCTileMapAtlas::initWithTileFile(const char *tile, const char *mapFile, int tileWidth, int tileHeight)
{
    this->loadTGAfile(mapFile);
    this->calculateItemsToRender();

    if( CCAtlasNode::initWithTileFile(tile, tileWidth, tileHeight, m_nItemsToRender) )
    {
        m_pPosToAtlasIndex = new CCDictionary();
        this->updateAtlasValues();
        this->setContentSize(CCSizeMake((float)(m_pTGAInfo->width*m_uItemWidth),
                                        (float)(m_pTGAInfo->height*m_uItemHeight)));
        return true;
    }
    return false;
}
第一句,loadTGAfile,所以此时传进来的mapFile一定个要是TGA格式的文件。

第二句calculateItemsToRender,我们进去这个函数瞄一眼,

void CCTileMapAtlas::calculateItemsToRender()
{
    CCAssert( m_pTGAInfo != NULL, "tgaInfo must be non-nil");

    m_nItemsToRender = 0;
    for(int x=0;x < m_pTGAInfo->width; x++ ) 
    {
        for( int y=0; y < m_pTGAInfo->height; y++ ) 
        {
            ccColor3B *ptr = (ccColor3B*) m_pTGAInfo->imageData;
            ccColor3B value = ptr[x + y * m_pTGAInfo->width];
            if( value.r )
            {
                ++m_nItemsToRender;
            }
        }
    }
}
一个遍历,乏善可陈。有意思的时这一句 if( value.r ),如果此像素的r值不为0,则 m_nItemsToRender ++,所以我推断map上的图像只跟tga文件像素值得R(红色)分量有关,最后实时证明确实只跟r相关。退出继续向下走。

if( CCAtlasNode::initWithTileFile(tile, tileWidth, tileHeight, m_nItemsToRender) )

只有最后一个参数变了,是在calculateItemsToRender中计算出来的。

CCAtlasNode::initWithTileFile我们就不继续深入看这个函数了,有兴趣的朋友自己去八一八。这个函数里面做三件事:

1.将tile处理成OpenGL的一张纹理;

2.创建m_nItemsToRender个宽solid模型;

3.将tilewidth和tileHeight分别赋值给m_uItemWidth和m_uItemHeight,这两个东西后面会用到。

如果initWithTileFile没问题,下载就会进入if,

首先通过m_pPosToAtlasIndex = new CCDictionary();创建一个字典,通过hash表对资源的管理,方便资源查找,点击这里深入了解。

下一步会调用CCTileMapAtlas的updateAtlasValues,进去看看。

void CCTileMapAtlas::updateAtlasValues()
{
    CCAssert( m_pTGAInfo != NULL, "tgaInfo must be non-nil");

    int total = 0;

    for(int x=0;x < m_pTGAInfo->width; x++ ) 
    {
        for( int y=0; y < m_pTGAInfo->height; y++ ) 
        {
            if( total < m_nItemsToRender ) 
            {
                ccColor3B *ptr = (ccColor3B*) m_pTGAInfo->imageData;
                ccColor3B value = ptr[x + y * m_pTGAInfo->width];

                if( value.r != 0 )
                {
                    this->updateAtlasValueAt(ccp(x,y), value, total);

                    CCString *key = CCString::createWithFormat("%d,%d", x,y);
                    CCInteger *num = CCInteger::create(total);
                    m_pPosToAtlasIndex->setObject(num, key->getCString());

                    total++;
                }
            }
        }
    }
}
同样先是遍历,遍历到一个r不为0的value之后会调用
this->updateAtlasValueAt(ccp(x,y), value, total);

先看下面的,一会回过头再看此函数。

   CCString *key = CCString::createWithFormat("%d,%d", x,y);
    CCInteger *num = CCInteger::create(total);
     m_pPosToAtlasIndex->setObject(num, key->getCString());

这三句是将渲染的index和渲染的坐标组成一个hash键值对,存入上一步创建的字典中。

好,我们回头看updateAtlasValueAt

void CCTileMapAtlas::updateAtlasValueAt(const CCPoint& pos, const ccColor3B& value, unsigned int index)
{
    ccV3F_C4B_T2F_Quad quad;

    int x = pos.x;
    int y = pos.y;
    float row = (float) (value.r % m_uItemsPerRow);
    float col = (float) (value.r / m_uItemsPerRow);

    float textureWide = (float) (m_pTextureAtlas->getTexture()->getPixelsWide());
    float textureHigh = (float) (m_pTextureAtlas->getTexture()->getPixelsHigh());

    float itemWidthInPixels = m_uItemWidth * CC_CONTENT_SCALE_FACTOR();
    float itemHeightInPixels = m_uItemHeight * CC_CONTENT_SCALE_FACTOR();

#if CC_FIX_ARTIFACTS_BY_STRECHING_TEXEL
    float left        = (2 * row * itemWidthInPixels + 1) / (2 * textureWide);
    float right       = left + (itemWidthInPixels * 2 - 2) / (2 * textureWide);
    float top         = (2 * col * itemHeightInPixels + 1) / (2 * textureHigh);
    float bottom      = top + (itemHeightInPixels * 2 - 2) / (2 * textureHigh);
#else
    float left        = (row * itemWidthInPixels) / textureWide;
    float right       = left + itemWidthInPixels / textureWide;
    float top         = (col * itemHeightInPixels) / textureHigh;
    float bottom      = top + itemHeightInPixels / textureHigh;
#endif

    quad.tl.texCoords.u = left;
    quad.tl.texCoords.v = top;
    quad.tr.texCoords.u = right;
    quad.tr.texCoords.v = top;
    quad.bl.texCoords.u = left;
    quad.bl.texCoords.v = bottom;
    quad.br.texCoords.u = right;
    quad.br.texCoords.v = bottom;

    quad.bl.vertices.x = (float) (x * m_uItemWidth);
    quad.bl.vertices.y = (float) (y * m_uItemHeight);
    quad.bl.vertices.z = 0.0f;
    quad.br.vertices.x = (float)(x * m_uItemWidth + m_uItemWidth);
    quad.br.vertices.y = (float)(y * m_uItemHeight);
    quad.br.vertices.z = 0.0f;
    quad.tl.vertices.x = (float)(x * m_uItemWidth);
    quad.tl.vertices.y = (float)(y * m_uItemHeight + m_uItemHeight);
    quad.tl.vertices.z = 0.0f;
    quad.tr.vertices.x = (float)(x * m_uItemWidth + m_uItemWidth);
    quad.tr.vertices.y = (float)(y * m_uItemHeight + m_uItemHeight);
    quad.tr.vertices.z = 0.0f;

    ccColor4B color = { _displayedColor.r, _displayedColor.g, _displayedColor.b, _displayedOpacity };
    quad.tr.colors = color;
    quad.tl.colors = color;
    quad.br.colors = color;
    quad.bl.colors = color;

    m_pTextureAtlas->updateQuad(&quad, index);
}

稍微有点长,最关键的是对quad的赋值,

先看下quad这个结构体:

typedef struct _ccV3F_C4B_T2F_Quad
{
    //! top left
    ccV3F_C4B_T2F    tl;
    //! bottom left
    ccV3F_C4B_T2F    bl;
    //! top right
    ccV3F_C4B_T2F    tr;
    //! bottom right
    ccV3F_C4B_T2F    br;
} ccV3F_C4B_T2F_Quad;
原来是一个矩形的四个顶点。回头继续

先是对quad四个顶点纹理坐标texCoords进行赋值;

再对四个顶点的的顶点坐标赋值,可以看到此矩形大小刚好是m_uItemWidth*m_uItemHeight,和CCTileMapAtlas::create最后两个参数保持一致。

最后设置顶点的默认颜色(没有贴图时候的颜色,如果有贴图这个颜色可能起作用,也可能不起,具体看给OpenGL设置的混合方式)。

到此,我们既有了模型,也有了纹理,刚刚又设置了纹理坐标,我们的工作全完成!只等OpenGL给我们渲染了。

向后退,一直到CCTileMapAtlas::initWithTileFile

updateAtlasValues完之后会设置map的大小,

his->setContentSize(CCSizeMake((float)(m_pTGAInfo->width*m_uItemWidth),
                                        (float)(m_pTGAInfo->height*m_uItemHeight)));

在前面我讲了,mapFile只是决定map的基础像素大小,“基础”二字还特意标红。map实际的大小是tga和tileWidth/tileHeight共同决定的。

好,再继续探栈,一直到TileMapEditTest的构造函数

 CCTileMapAtlas* map = CCTileMapAtlas::create(s_TilesPng, s_LevelMapTga, 16, 16);
    // Create an Aliased Atlas
    map->getTexture()->setAliasTexParameters();
    
    CCSize CC_UNUSED s = map->getContentSize();
    CCLOG("ContentSize: %f, %f", s.width,s.height);
    
    // If you are not going to use the Map, you can free it now
    // [tilemap releaseMap);
    // And if you are going to use, it you can access the data with:
    schedule(schedule_selector(TileMapEditTest::updateMap), 0.2f);
create完之后会调用map的setAliasTexParameters,此方法用来消除tile之间的缝隙。

然后创建了一个schedule,进去updataMap看看,

void TileMapEditTest::updateMap(float dt)
{
    // IMPORTANT
    //   The only limitation is that you cannot change an empty, or assign an empty tile to a tile
    //   The value 0 not rendered so don't assign or change a tile with value 0

    CCTileMapAtlas* tilemap = (CCTileMapAtlas*) getChildByTag(kTagTileMap);

    
    // NEW since v0.7
    ccColor3B c = tilemap->tileAt(ccp(1,0));        
    c.r++;
    c.r %= 50;
    if( c.r==0)
        c.r=1;
    
    // NEW since v0.7
    tilemap->setTile(c, ccp(1,0) );             
}

首先是回去map对象,在通过map的方法tileat取回(1,0)位置的color,再把color的r值进行处理之后通过setTile设置会(1,0)的位置。

这一段就完成了map的(1,0)图像不断变化的一个动画。


至此,我们的初窥之路基本上走完了,总结起来就是:

1.根据tga创建map;

2.将tile文件按照一定大小进行分割

3.将分割好的tile按照map的各个像素的r值贴上去。


注意两点:

1.map pix 的value的r一定大于0才会贴图,所以在制造tile文件的时候第一个单位的tile(index为0)是不会贴到map上的,一般我们留空

2.如果分割的tile不够,比如我们需要第100个tile,但是tile文件按照tileWidth和tileHeight最多只能分割50个,那么,超过50的我们就用index为50的图片。


下一篇我们会写一个基于tilemap的小游戏。












  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值