|
前两篇文章分别讨论了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(&#34;object OT_POLYLINE, points len %d \n&#34;,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图形对象)完全对齐是很费劲(几乎办不到)的事,这是机器该干的事!解决这个问题,只需要在菜单中把 &#39;View&#39; ->&#39;Snaping &#39;-> &#39;Snap To grid&#39; 勾选上。这时候你无论如何拉线,创建的对象都会与格子边缘对齐,这对于创建类马里奥的平台游戏特别好用!
第二个,关于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。 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|