Box2d中使用开源的PRKit库来制作任意形状的多边形刚体的纹理

news/2024/7/20 20:23:27 标签: IOS, box2d, 刚体, 纹理, 贴图


Box2d中刚体纹理可以有很多种实现方式(参考Box2d中刚体纹理的几种实现方式),但是这几种实现方式都是在我们已知刚体形状并且保证刚体形状不变的情况下,通过提前将刚体纹理绘制好并“附加”到刚体上来完成的。

因此如果遇到下面这些情况:

1.    刚体形状不确定,例如随机生成的形状或者某些游戏中玩家可以手工绘制物体等等。

2.    刚体形状可能会发生变化,例如切割游戏中,一个大的刚体被切割成若干个小的刚体,或者游戏中一些能够变形的物体或软体受力发生形变等等。

这时,我们通过提前绘制好纹理的那种解决方案就失效了。

那么这种情况下,我们的思路是根据任意路径去创建一个多边形,然后为多边形填充一个纹理,让多边形外边的区域不显示纹理呢。Box2d和Cocos2d中都没有为我们提供这样的类或者方法,因此我们使用开源库PRKit来实现。PRKit由PrecognitiveReserch的开发人员实现和维护,可以处理纹理映射和纹理填充。

PRKit的下载地址:https://github.com/asinesio/cocos2d-PRKit,点击右侧的“downloadzip”下载即可。

下载后解压,将子文件夹“PRKit”中的文件全部添加到项目中即可。

我们利用PRKit来制作一个简单的小例子:通过绘制来制作刚体,并为其填充纹理

看来一下最终的效果:


 

首先创建cocos2d iOS withBox2d模板的工程(Box2d的版本是2.3.1),创建完成后,把我们不需要的默认创建出来的东东都给注释或者删掉,例如菜单啊,默认创建的box啊,点击创建box的事件响应方法呀等等这些,就剩下一个空白的场景就好了。

然后我们准备一张用于平铺的纹理贴图(注意纹理贴图在制作的时候左右和上下要能对接上,不然会不连续),将其添加到工程中,stone_texture.png:


 

接着我们在HelloWorldLayer中添加下面两个成员:

intvertex_min_distance;

NSMutableArray*pathVertexes;

vertex_min_distance定义了两个端点之间的取样最小距离,我们的思路是,当用户开始绘制刚体时,时时地获取绘制点的位置并记录,但是有一个问题是,如果取样过于密集,会导致最终得到的多边形边数过多(虽然这样看起来非常细腻,但是对于效率和模拟效果反而不利),因此通过定义一个端点之间的取样最小距离,当绘制点和上一个顶点的距离大于取样最小距离时,才进行记录,这样就能很好的限制顶点的数量(当然越大的图形顶点数量自然越多)。

另一个成员pathVertexes用来记录用户所绘制出的所有节点。

接下来我们让HelloWorldLayer继承CCTouchOneByOneDelegate接口,并重写下面的方法来注册touch事件:

-(void)onEnterTransitionDidFinish {

   [[[CCDirector sharedDirector]touchDispatcher] addTargetedDelegate:self priority:1 swallowsTouches:true];

}

 

-(void) onExit{

   [[[CCDirector sharedDirector]touchDispatcher] removeDelegate:self];

}

在初始化方法中初始化两个成员变量(对于取样的最小距离可以根据自己喜好来设置):

pathVertexes =[[NSMutableArray alloc] init];

vertex_min_distance= 30;

另外说明一点,Box2d中多边形的最大顶点数是通过b2Settings.h中的b2_maxPolygonVertices定义的,默认为8,如果在添加多边形的时候顶点数超过了这个上线,会抛错,因此,我们需要把这个值调大一些,这里我设置成50,如果觉得不够,可以再调大一些(对于这里例子来说,也不用太大了吧~~)。

接着我们来实现下面的三个touch事件响应函数:

-(BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {

   CGPoint location = [[CCDirectorsharedDirector] convertToGL:[touch locationInView:[touch view]]];

   [pathVertexes addObject:[NSValuevalueWithCGPoint:location]];

   return YES;

}

 

-(void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event {

   int vertexCount = [pathVertexes count];

   CGPoint location = [[CCDirectorsharedDirector] convertToGL:[touch locationInView:[touch view]]];

   CGPoint lastVertex =[pathVertexes[vertexCount - 1] CGPointValue];

   float distance = ccpDistance(location,lastVertex);

   if (distance > vertex_min_distance) {

       [pathVertexes addObject:[NSValuevalueWithCGPoint:location]];

   }

}

 

-(void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event {

   CGPoint location = [[CCDirectorsharedDirector] convertToGL:[touch locationInView:[touch view]]];

   CGPoint firstVertex = [pathVertexes[0]CGPointValue];

   if (ccpDistance(location, firstVertex) <vertex_min_distance) {

       [self createPolygon];

   }

   [pathVertexes removeAllObjects];

}

ccTouchBegan方法比较简单,获取用户绘制的第一个点,然后添加到节点数组中。ccTouchMoved方法,首先获取当前绘制点的位置,通过ccpDistance计算这个接触点与上一个被记录的绘制点之间的距离,如果这个距离大于接触点取样的最小距离,则记录这个点。

最后在ccTouchEnded方法中,判断如果最后一个绘制点的位置与起始点之间的距离小于取样的最小距离,则认为我们绘制了一个“闭合的”多边形,然后调用createPolygon方法(后面会给出实现,这里编译出错可以先把这句注释掉)来进行多边形的创建。最后把用来存储节点的数组元素清空以便用于下一次绘制。

这里我们完成了基本的逻辑框架,但是我们在屏幕上拖动鼠标的时候看不到任何效果,我们希望在绘制的过程中能够看到我们拖拽出来的路径才对,在HelloWorldLayer的draw方法中添加下面的代码:

ccDrawColor4F(255,255, 255, 255);

intvertexCount = [pathVertexes count];

for (int i =0; i < vertexCount - 1; i++) {

   CGPoint startPoint = [pathVertexes[i]CGPointValue];

   CGPoint endPoint = [pathVertexes[i+1]CGPointValue];

   ccDrawLine(startPoint, endPoint);

}

这段代码首先使用ccDrawColor4F方法设置用于绘制图形的颜色(255,255,255,255),即白色,不透明。接着遍历我们所有得到的取样点,然后使用ccDrawLine方法在相邻的取样点之间绘制直线。由于draw方法在每次刷新的时候都会调用,因此在我们绘制过程中,绘制的路径会一直存在,当绘制结束的时候,即ccTouchEnded方法被调用的时候,由于pathVertexes会被清空,此时路径会自动消失。效果下如图所示:


 

最后,我们来看一下createPolygon方法的实现:

-(void)createPolygon {

   int vertexCount = [pathVertexes count];

   

   //如果绘制的定点数不足三个,无法构成多边形

   if (vertexCount < 3) {

       return;

   }

   

   //由于绘制的方向可能是顺时针也可能是逆时针的,而Box2d中顶点需要按照逆时针方向,因此通过下面的循环来判断

   //之所以使用循环,是因为3个取样点有可能共线,导致行列式的计算结果为0

   BOOL isAntiClockwise = false;

   for (int i = 0; i < vertexCount - 2;i++) {

       float det = [selfcalculateDet:[pathVertexes[i] CGPointValue] pointB:[pathVertexes[i + 1]CGPointValue] pointC:[pathVertexes[i + 2] CGPointValue]];

       if (det == 0) {

           continue;

       }

       isAntiClockwise = det > 0 ? true :false;

       break;

   }

   //如果是顺时针方向,则将所有节点反向排列

   if (!isAntiClockwise) {

       NSMutableArray* reversedArray =[[NSMutableArray alloc] init];

       for (int i = vertexCount - 1; i >-1; i--) {

           [reversedArrayaddObject:pathVertexes[i]];

       }

       pathVertexes = reversedArray;

   }

   

   //获取起始点的位置(多边形的位置也基于这个点的位置)

   CGPoint startVertex = [pathVertexes[0]CGPointValue];

   

   //定义刚体

   b2BodyDef bodyDef;

   bodyDef.type = b2_dynamicBody;

   bodyDef.position = [selftoVec2:startVertex];

   b2Body* body =world->CreateBody(&bodyDef);

   

   //定义装置

   b2FixtureDef fixtureDef;

   fixtureDef.density = 2.0;

   fixtureDef.friction = 0.2f;

   

   //定义一个用于创建多边形形状的b2Vec2数组,需要将全局坐标系的点转换为本地坐标系的点

   b2Vec2 vertexes[b2_maxPolygonVertices];

   int j = 0;

   for (int i = 0; i < vertexCount; i++) {

       //通过与起始点坐标相减,将点坐标转换为本地坐标系

       CGPoint ver = ccpSub([pathVertexes[i]CGPointValue], startVertex);

       

       //下面这行语句非常重要,pathVertexes我们要用来初始化PRFilledPolygon,必须

       //要使用本地坐标,而pathVertexes中的节点是全局坐标系

       pathVertexes[i] = [NSValuevalueWithCGPoint:ver];

       

       vertexes[j++] = [self toVec2:ver];

   }

   

   //定义形状

   b2PolygonShape* shape = newb2PolygonShape();

   shape->Set(vertexes, vertexCount);

   

   fixtureDef.shape = shape;

   body->CreateFixture(&fixtureDef);

   

   //载入纹理

   CCTexture2D* texture = [[CCTextureCachesharedTextureCache] addImage:@"stone_texture.png"];

   //使用纹理和多边形顶点对填充纹理多边形进行初始化

   PRFilledPolygon* prPolygon =[[PRFilledPolygon alloc] initWithPoints:pathVertexes andTexture:texture];

   //纹理多边形的位置和body的位置相同,使用像素作为单位

   prPolygon.position = [selftoCGPoint:body->GetPosition()];

   //将纹理多边形添加到场景中

   [self addChild:prPolygon];

   

   //设置body的UserData指向填充多边形(用于在update方法中同步更新其位置)

   body->SetUserData(prPolygon);

}

代码中做了详细的注释,这里关于顺时针和逆时针的判断,请参考游戏中两个常用的数学运算推导即算法推论。calculateDet方法的实现如下:

-(float)calculateDet:(CGPoint)p1 pointB:(CGPoint) p2 pointC:(CGPoint) p3 {

   return (p1.x * p2.y + p2.x * p3.y + p3.x *p1.y - p1.y * p2.x - p2.y * p3.x - p3.y * p1.x);

}

此外用到的CGPoint和b2Vec2的转换函数如下:

-(CGPoint)toCGPoint:(b2Vec2) vec {

   return ccp(vec.x * (float)PTM_RATIO, vec.y* (float)PTM_RATIO);

}

 

-(b2Vec2)toVec2:(CGPoint) point {

   b2Vec2 vec(point.x / (float)PTM_RATIO,point.y / (float)PTM_RATIO);

   return vec;

}

完成了,我们运行一下,发现绘制完成后,确实创建了纹理多边形,但是它并没有随着物体运动。我们需要在update方法最后添加如下的代码:

for (b2Body*body = world->GetBodyList(); body; body = body->GetNext()) {

   if (body != NULL) {

       PRFilledPolygon* polygon =(PRFilledPolygon*)body->GetUserData();

       polygon.position = [selftoCGPoint:body->GetPosition()];

       polygon.rotation =-CC_RADIANS_TO_DEGREES(body->GetAngle());

   }

}

这样每次更新所有刚体的位置之后,会遍历所有的刚体更新其对应的纹理多边形。

另外要说明的一点是,在Box2d中正常只支持凸多边形,因此在绘制的时候,尽量避免绘制出凹多边形,否则凹面的碰撞会有问题。凹多边形可以通过多个凸多边形组合而成,这部分内容不在本文范围内。

 

好了,就说到这里,如果有不清楚的地方,欢迎留言讨论。



http://www.niftyadmin.cn/n/878391.html

相关文章

积跬步以至千里_听专家前瞻引领,积跬步以至千里

教育&#xff0c;需要教师具有一种虔诚而温馨的情怀和追求完美人生的信念&#xff0c;需要教师不断学习&#xff0c;以促进教师人生的发展和专业生命的成长。开学季&#xff0c;杭州市实验外国语学校小学部邀请教育专家进校指导&#xff0c;给全体教师带来教育教学理念的饕鬄盛…

适用于Chrome类浏览器的喜马拉雅音频下载插件

之前用WordPress做博客程序的时候&#xff0c;分享过不少喜马拉雅下载工具&#xff0c;但是都陆续失效了&#xff0c;今天先是看到一个爱奇艺会员免费领取喜马拉雅会员的帖子&#xff0c;然后看了一下自己并不是爱奇艺会员&#xff0c;就又想起下载喜马拉雅音频这档子事&#x…

聊聊浏览器

之前在分享喜马拉雅下载插件的时候&#xff0c;就像展开说说浏览器&#xff0c;但是毕竟主题所限&#xff0c;避免跑题&#xff0c;今天专门开个帖。 如果你问我在使用电脑的时候什么应用使用的时长最长&#xff0c;我想毫无疑问是浏览器。浏览器作为桌面端获取信息的主要应用…

gdb mysql_使用GDB 修改MySQL参数不重启

使用GDB 修改MySQL参数不重启mysql很多参数都需要重启才能生效&#xff0c;有时候条件不允许&#xff0c;可以使用gdb作为最后的手段先看看修改之前mysql> show global variables like %connection%;---------------------------------------------| Variable_name …

可能是比原版Firefox更好用的个人定制版Firefox

前天吐槽了一下各家的浏览器&#xff0c;其中提到了Firefox&#xff0c;并不是说Firefox不好&#xff0c;而是其定制化的程度太高了&#xff0c;而我们往往需要一款又简单又快速又有自己需要的各种功能的Firefox浏览器&#xff0c;最好能登陆Firefox账号进行同步&#xff0c;而…

mysql (errcode 13)_MySQL Errcode:13 – 权限被拒绝

我正在使用MySQL 5.7.10和Flyway来处理我的数据库迁移.在Linux和Mac上一切正常,但在Windows 10上我收到此错误&#xff1a;Error on rename of .\mydb\#sql-1da0_a.frm to .\mydb\proc_error_table.frm (Errcode: 13 - Permission denied)这是导致错误的SQL的一部分&#xff1a…

Box2d中刚体的纹理的几种实现方式

&#xfeff;&#xfeff;Box2d中创建完刚体并将装置&#xff08;fixture&#xff09;附加到刚体上以后&#xff0c;还需要对刚体应用纹理才能够让刚体看起来像一个真正的物体&#xff0c;而不是一个个多边形或者圆形线框。因此&#xff0c;总结了下面几种为刚体添加纹理的思路…

mysql 5.6 同步_Mysql 5.6主从同步配置与解决方案

主库IP&#xff1a;192.168.1.10从库IP&#xff1a;192.168.1.11centos的mysql配置文件在:/etc/my.cnf1、主库配置编辑my.cnf&#xff1a;# 启用二进制日志log_bin mysql-binserver-id 111 //唯一编号&#xff0c;默认1&#xff0c;一般写IP最后3位log-bin-indexmysql-bin.in…