您的位置:首页 > 移动开发 > IOS开发

教你使用Box2d制作用蜡笔手绘物体的效果(二)

2017-01-03 20:58 435 查看

我们继续来制作蜡笔手绘物体的效果。上一篇中我们完成了刚体绘制,这一部分我们来完成蜡笔纹理。
先来看一下最终的效果:



(如果觉得蜡笔的纹理不够好的话,可以精加工一个自己的蜡笔纹理)
下面是我用PS简单加工的两个纹理贴图:

crayonBrush.png(128px×16px):



smallWhiteRoundShape.png(6px×6px):



要注意的是,crayonBrush.png的长宽必须是2的指数幂,因为我们在制作的时候要使用ccTexParams来设置将纹理屏幕于多边形中,如果不是2的指数幂会报错。
我们在HelloWorldLayer的init函数中添加初始化纹理的语句:

CCSpriteBatchNode *parent = [CCSpriteBatchNodebatchNodeWithFile:@"crayonBrush.png" capacity:100]

spriteTexture_ = [parent texture];

[self addChild:parent z:0 tag:kTagParentNode];
接下来我们先来说一下制作纹理的思路。
对于单个fixture的多边形刚体来说,纹理的处理比较简单,可以直接通过制作和多边形形状相同的纹理贴图;对于动态创建的多边形可以使用PRKit库来帮助我们创建;而对于多个fixture的刚体的纹理,我在网上还没有找到相关的例子(如果有的话欢迎留言指导),使用CCPhysicsSprite也仅仅对单个fixture有效,因此这里我们利用一些数学知识来帮助我们完成纹理和刚体运动的同步。
首先我们来看一下创建刚体的时候,平铺纹理的代码:

ccTexParams texParams = {

    GL_LINEAR,

    GL_LINEAR,

    GL_REPEAT,

    GL_REPEAT

};

CCNode *parent = [self getChildByTag:kTagParentNode];

CCSprite *sprite = [CCSprite spriteWithTexture:spriteTexture_rect:CGRectMake(0, 0, segLength * 2 - 1.0f, 6.0f)];

sprite.color = color;

[sprite.texture setTexParameters:&texParams];

sprite.position = center;

sprite.rotation = -CC_RADIANS_TO_DEGREES(angle);

[parent addChild:sprite];
这段代码添加到createPathPolygon最后创建线段的循环中,每创建完成一个矩形fixture之后,就创建一个精灵对应于这个矩形,精灵的纹理就是蜡笔的纹理。使用fixture的位置和旋转角度来设置精灵的位置和旋转角度(别忘记将Box2d的弧度转换为角度),上面代码中出现的color的定义和初始化如下:

srand(time(NULL));

float32 colorIndex = CCRANDOM_0_1();

ccColor3B color;

float32 brightness = 160 + random() % 32;

if (colorIndex > 0.6666666f) {

    color = ccc3(brightness,255, 255);

} else if (colorIndex < 0.3333333f) {

    color = ccc3(255,brightness, 255);

} else {

    color = ccc3(255, 255,brightness);

}
使用随机数来生成一个灰度在160-192之间的红色/蓝色/绿色。将该颜色赋给精灵对象(因为我们的纹理颜色是白色,所以叠加之后就是纹理的最终颜色)。上面代码同样放到createPathPolygon方法中(不要放到循环体内,因为我们希望同一个刚体的颜色是统一的)。
接着看一下运行效果:



我们看到,创建好刚体之后确实生成了和刚体匹配的纹理,但是纹理并没有随着刚体的运动而运动。那么我们如何让这些带有纹理的精灵对象随着刚体上每一个fixture来运动呢?
看一下下面的图片:



我们假设现在物体在位置1,物体的绘制起始点为A,物体上的某一个线段为P(P具有一般性,因此结论应用于物体上的所有线段)。物体在模拟的过程从位置1变换到了位置3,我们可以把这个过程分解为移动和旋转两个步骤(顺序无关紧要),我们设物体的移动距离为(dx,dy),即水平移动dx距离,垂直移动dy距离,旋转角度为α,则我们看到线段P也旋转了角度α,但是线段P的移动距离却并不是(dx,dy),这是因为旋转的时候线段仍然在移动(只有一种情况下线段的移动和物体的移动相同,即物体的旋转轴和线段的旋转轴重合的时候),那么我们怎么来计算线段的新坐标呢?
关于坐标变换,请参考详解游戏中的旋转坐标变换
即我们通过物体的旋转角,物体的原位置和当前位置,还有线段的原位置,可以计算出线段的当前位置。线段当前的旋转角度为线段的原旋转角度加上物体的旋转角度的变化量(可正可负)。这样我们能够得到物体上每一个线段的新的位置和旋转角,用这两个值来更新物体的所有fixture对应的精灵对象即可保证纹理随着物体的运动而运动。
我们从这个思路出发,那么必须要有一个地方来存储物体的原位置、原旋转角以及物体对应的所有精灵对象,显然b2Body的UserData属性是一个可以利用的地方,但是UserData只能定义一种对象或者数组,而我们的位置属性的类型是CGPoint,角度属性的类型是float,精灵对象是NSMutableArray数组,那只能定义一个新的类来将这几种属性组装到一起了,下面是类的声明和定义:

ObjectData.h:

@interface ObjectData : NSObject

@property float lastAngle;

@property CGPoint lastPos;

@property NSMutableArray* sprites;

@end

 

ObjectData.mm:

#import "ObjectData.h"

 

@implementation ObjectData

@synthesize lastAngle;

@synthesize lastPos;

@synthesize sprites;

 

-(id) init {

    if (self = [super init]) {

        sprites =[[NSMutableArray alloc] init];

    }

    return self;

}

@end
比较简单,就是把我们前面讨论的属性给封装了一下。
接着,我们就需要在创建物体的时候将这些属性进行存储了。
在createPathPolygon方法中添加如下代码(添加到for循环的前面,在完成物体的创建之后):

ObjectData* bodyData = [[ObjectData alloc] init];

bodyData.lastPos = [self toCGPoint:body->GetPosition()];

bodyData.lastAngle = body->GetAngle();
使用创建好的物体来初始化lastPos和lastAngle属性。
接着在for循环中创建好精灵之后,将精灵添加到bodyData的sprites数组中:

[bodyData.sprites addObject:sprite];
在createPathPolygon方法的最后,将bodyData赋给body的UserData属性:

body->SetUserData(bodyData);
完成之后,物体的纹理仍然不动,我们需要继续在update方法中做文章。先定义下面的方法:

- (CGPoint)calculateTransformedPosition:(CGPoint) oldPos

                                sinVal:(float) sinVal

                                cosVal:(float) cosVal

                     rotationOldCenter:(CGPoint) oldCenter

                     rotationNewCenter:(CGPoint) newCenter {

    float x = oldPos.x -oldCenter.x;

    float y = oldPos.y -oldCenter.y;

    return ccp(x * cosVal + y* sinVal + newCenter.x, y * cosVal - x * sinVal + newCenter.y);

}
该方法用于计算线段的新位置,我们在前面的讨论中已经做过解释了。
接着我们在update方法的最后添加下面的代码更新刚体对应的精灵对象的位置和旋转:

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

    ObjectData* data =(ObjectData*)body->GetUserData();

    if (data == nil) {

        continue;

    }

    CGPoint newCenter = [selftoCGPoint:body->GetPosition()];

    float rotation =data.lastAngle - body->GetAngle();

    if (rotation == 0&& newCenter.x == data.lastPos.x && newCenter.y ==data.lastPos.y) {

        continue;

    }

    float rotationInDegree =CC_RADIANS_TO_DEGREES(rotation);

    float sinRotation =sinf(rotation);

    float cosRotation =cosf(rotation);

    int spriteCount =[data.sprites count];

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

        CCSprite* sprite =data.sprites[i];

        sprite.rotation +=rotationInDegree;

        CGPoint newPos = [selfcalculateTransformedPosition:sprite.position sinVal:sinRotationcosVal:cosRotation rotationOldCenter:data.lastPos rotationNewCenter:newCenter];

        sprite.position =newPos;

    }

    data.lastPos = newCenter;

    data.lastAngle =body->GetAngle();

}
这个循环遍历了场景中所有的刚体对象,对于UserData是ObjectData类型的刚体,从ObjectData中取出其对应的精灵数组,然后使用物体ObjectData中存储的上一次更新时的位置和角度,以及物体当前的位置和角度来计算出所有精灵对象的新位置和角度,对其进行更新,最后再将物体的ObjectData中的lastPos和lastAngle进行更新以用于下一次update调用。
完成之后,我们在initPhysics方法中将e_shapeBit所在行注释掉,这样在调试的时候就不显示物体的外边框了,这时我们就能够看到蜡笔的纹理效果了。
对于圆形的固定点,我们也为其添加纹理,在createSmallCircleAtPosition方法最后添加下面的代码:

CCSprite* sprite = [CCSpritespriteWithFile:@"smallWhiteRoundShape.png"];

sprite.position = position;

[self addChild:sprite];
现在运行一下,我们可以看到,在点击屏幕的时候可以创建固定点,在绘制路径的时候可以创建物体了。
教程就到这里,如果有问题,欢迎在下方留言讨论~

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  IOS box2d 刚体 手绘