找回密码
 立即注册
查看: 174|回复: 0

游戏开发笔记(九):tile map对象转Box2D物体

[复制链接]
发表于 2022-10-19 10:26 | 显示全部楼层 |阅读模式
前两篇文章分别讨论了tile map在游戏中的批渲染和“编辑”处理功能,这里继续讨论tile map的第三个主题:如何将tile map对象转成Box2D物体。
Tiled软件并不提供某种标记功能,标注某个图形层瓦片(tile)为某个特定物体,相反它另起一个对象层,通过独立对象的方式提供非图像信息,实现渲染与逻辑分离。从识别的角度来看,不同的对象可以用不同的层进行分类,比如砖块,可以起一个Brick对象层,金币可以起一个Coin对象层。从现实需要的角度看,对象可以创建为点(point)、矩形(rectangle)、椭圆(ellipse)和多边形(polygon)。
除了点(point)一般作为提供位置信息用,其他形状对象都可以转变为对应的b2shape:矩形(rectangle)转变为b2PolygonShape(通过SetAsBox方法设为矩形),椭圆(ellipse)转变为b2CircleShape,多边形(polygon)转变为b2PolygonShape(闭合的情况下)或b2EdgeShape(不闭合的情况下)。
幸运的是,libtmx提供了这个对应关系的识别功能,标识了4类对象(obj_type):OT_SQUARE,OT_POLYGON,OT_POLYLINE,OT_ELLIPSE,由此我们可以轻松创建自己的映射类Map2BodyBuilder。
首先我们根据libtmx提供的解析接口,把最后的对象数据分类存放了起来:
void TilemapHelper::get_tilemap_objects(const char *name, tmx_object_group *objgr) {
   
    tmx_object *head = objgr->head;
   
    while (head) {
        if (head->visible) {
            if (head->obj_type == OT_SQUARE) {
                printf("object square(%f,%f) order %d \n",head->x,head->y,objgr->draworder);
                Color clr = PINK;
                clr.a *= 0.5f;
                EnvItem item = {{head->x,head->y},{head->width, head->height},4,clr};
                strcpy(item.name, name);
                gameItems.push_back(item);
            }
            else if (head->obj_type  == OT_POLYGON) {
                printf("object OT_POLYGON,len %d\n",head->content.shape->points_len);
                MultPointsItem pItem;
                int i;
                for (i=0; i<head->content.shape->points_len; i++) {
                    auto px = head->x+head->content.shape->points[0];
                    auto py = head->y+head->content.shape->points[1];
                    pItem.vertices.push_back({px,py});
                }
                polygonItems.push_back(pItem);
            }
            else if (head->obj_type == OT_POLYLINE) {
                printf("object OT_POLYLINE, points len %d \n",head->content.shape->points_len);
                MultPointsItem plItem;
                int i;
                for (i=0; i<head->content.shape->points_len; i++) {
                    auto px = head->x+head->content.shape->points[0];
                    auto py = head->y+head->content.shape->points[1];
                    plItem.vertices.push_back({px,py});
                }
                polyLineItems.push_back(plItem);
            }
            else if (head->obj_type == OT_ELLIPSE) {
                //to do
            }
        }
        head = head->next;
    }
}
映射类Map2BodyBuilder,直接根据上面的数据创建Box2D世界里的物体:
//创建单个的Rectangle物体
void Map2BodyBuilder::BuildEnvItemObject(tmx_map *map, b2World *world, EnvItem *item)
{
    glm::vec2 pos = {item->position.x,item->position.y};
    glm::vec2 size = {item->size.x,item->size.y};
    b2BodyDef bd;
    int map_y = map->height-(pos.y+size.y)/map->tile_height;
    int py = map_y * map->tile_height + size.y/2.0f;
    float base = Global::ScreenHeight - (map->height)*map->tile_height;
    bd.position.Set((pos.x+size.x/2.0f)/PPM, (py+base)/PPM);
    b2Body* body = world->CreateBody(&bd);

    b2PolygonShape shape;
    shape.SetAsBox(size.x/2.0f/PPM, size.y/2.0f/PPM);
   
    b2FixtureDef fdef;
    fdef.shape = &shape;
    body->CreateFixture(&fdef);
}

//创建所有的Polyline物体
void Map2BodyBuilder::BuildAllPolylineObjects(b2World *world)
{
    for (int i = 0; i < TilemapHelper::polyLineItems.size(); i++)
    {
        MultPointsItem *item = &TilemapHelper::polyLineItems;
        if(item->vertices.size()<3){
            b2BodyDef bd;
            b2Body* edge = world->CreateBody(&bd);
            b2EdgeShape shape;
            for(int j = 1; j < item->vertices.size();j++ ){
                auto prePosY = (Global::ScreenHeight-item->vertices[j-1].y)/PPM;
                auto currPosY = (Global::ScreenHeight-item->vertices[j].y)/PPM;
                auto width = item->vertices[j].x - item->vertices[j-1].x;
                b2Vec2 v0((item->vertices[j-1].x-width)/PPM, prePosY);
                b2Vec2 v1( item->vertices[j-1].x/PPM, prePosY);
                b2Vec2 v2( item->vertices[j].x/PPM, currPosY);
                b2Vec2 v3((item->vertices[j].x+width)/PPM, currPosY);
                shape.SetOneSided(v0, v1, v2, v3);
            }
            b2FixtureDef fdef;
            fdef.filter.categoryBits = Player::OBJECT_BIT;
            fdef.friction = 1.0f;
            fdef.density = 0.0f;
            fdef.shape = &shape;
            edge->CreateFixture(&fdef);
        }
    }
}

//创建所有的Polygon物体
void Map2BodyBuilder::BuildAllPolygonObjects(b2World *world)
{
    for (int i = 0; i < TilemapHelper::polygonItems.size(); i++)
    {
        MultPointsItem *item = &TilemapHelper::polygonItems;
        b2BodyDef bd;
        b2Body* polygon = world->CreateBody(&bd);
        b2Vec2 vertices[8];
        int size = (int)item->vertices.size();
        for(int j = 0; j < size;j++ ){
            vertices[j].Set(item->vertices[j].x/PPM, (Global::ScreenHeight-item->vertices[j].y)/PPM);
        }
        b2PolygonShape shape;
        shape.Set(vertices, size);
        b2FixtureDef fdef;
        fdef.filter.categoryBits = Player::OBJECT_BIT;
        fdef.friction = 1.0f;
        fdef.density = 0.0f;
        fdef.shape = &shape;
        polygon->CreateFixture(&fdef);
    }
}
OK,映射转换工作完成!
记录一些要注意的问题:
在Tiled软件创建对象层物体,默认情况下你可以拖拽鼠标在任意位置创建任意大小的物体,但这种“自由”我们往往不需要,毕竟手工拉线创建一个Rectangle对象与一排砖块(tile图形对象)完全对齐是很费劲(几乎办不到)的事,这是机器该干的事!解决这个问题,只需要在菜单中把 'View' ->'Snaping '-> 'Snap To grid' 勾选上。这时候你无论如何拉线,创建的对象都会与格子边缘对齐,这对于创建类马里奥的平台游戏特别好用!


第二个,关于b2EdgeShape类,经常用于类似下图的情况:


人物落在上面会被托住,人物往上跳跃不会被挡住,即所谓的collision one-sided(单边碰撞)。
b2EdgeShape提供了这样的设置函数:
void b2EdgeShape::SetOneSided        (const b2Vec2 &         v0,
                                 const b2Vec2 &         v1,
                                 const b2Vec2 &         v2,
                                 const b2Vec2 &         v3
)       
为什么需要四个位置点b2Vec2,一段线段不是两个位置点就可以了吗?实际上,v1,v2才是真正的edge vertices,v0,v3是用来辅助碰撞计算的,用来实现collision one-sided。v0,v3往往需要自己计算出来。见Map2BodyBuilder::BuildAllPolylineObjects中的代码:
auto width = item->vertices[j].x - item->vertices[j-1].x;
b2Vec2 v0((item->vertices[j-1].x-width)/PPM, prePosY);
b2Vec2 v1( item->vertices[j-1].x/PPM, prePosY);
b2Vec2 v2( item->vertices[j].x/PPM, currPosY);
b2Vec2 v3((item->vertices[j].x+width)/PPM, currPosY);
shape.SetOneSided(v0, v1, v2, v3);
全部代码见SuperMarioEx。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-25 00:53 , Processed in 0.093807 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表