我需要动态创建这样的轮廓:
不适用于CCSprite,而是适用于在一个CCNode中合并的多个动画CCSprit.我在考虑:
>将CCNode的内容复制到纹理(如AS3中的canvasBitmapData.draw(sourceDisplayObject))
>使用生成的纹理创建CCSprite
>将精灵着色为轮廓颜色并稍微缩放
>将精灵放在节点中的其他精灵后面
我不知道如何执行步骤1.也许在步骤3中在纹理的不透明像素周围绘制“true stroke”而不是色调比例更快?
我完全忘记发布这个问题的答案.这是一个非常平滑的笔画的代码.它并不快,但在第一台iPad上为几个大精灵工作得很好.
我们的想法是在精灵周围画出微小的彩色和模糊的球,并将它们放在自己的纹理上.它既可以用于CCNode,也可以用于CCSprite.该代码还会移动锚点,因为生成的精灵将具有更大的宽度和高度.
得到的轮廓(身体和2只手,在iPad1上约0.3秒):
白球示例:
> 5f:http://i.stack.imgur.com/e9kos.png
> 10f:http://i.stack.imgur.com/S5goU.png
> 20f:http://i.stack.imgur.com/qk7GL.png
CCNode类别,适用于Cocos2d-iPhone 2.1:
@implementation CCNode (Outline) - (CCSprite*) outline { return [self outlineRect:CGRectMake(0,self.contentSize.width,self.contentSize.height)]; } - (CCSprite*) outlineRect:(CGRect)rect { NSInteger gap = dscale(4); CGPoint positionShift = ccp(gap - rect.origin.x,gap - rect.origin.y); CGSize canvasSize = CGSizeMake(rect.size.width + gap * 2,rect.size.height + gap * 2); CCRenderTexture* renderedSpriteTexture = [self renderTextureFrom:self shiftedFor:positionShift onCanvasSized:canvasSize]; CGSize textureSize = renderedSpriteTexture.sprite.contentSize; CGSize textureSizeInPixels = renderedSpriteTexture.sprite.texture.contentSizeInPixels; NSInteger bitsPerComponent = 8; NSInteger bytesPerPixel = (bitsPerComponent * 4) / 8; NSInteger bytesPerRow = bytesPerPixel * textureSizeInPixels.width; NSInteger myDataLength = bytesPerRow * textureSizeInPixels.height; NSMutableData* buffer = [[NSMutableData alloc] initWithCapacity:myDataLength]; Byte* bytes = (Byte*)[buffer mutableBytes]; [renderedSpriteTexture begin]; glReadPixels(0,textureSizeInPixels.width,textureSizeInPixels.height,GL_RGBA,GL_UNSIGNED_BYTE,bytes); [renderedSpriteTexture end]; //SEE ATTACHMENT TO GET THE FILES NSString* spriteFrameName; if (IS_IPAD) spriteFrameName = (CC_CONTENT_SCALE_FACTOR() == 1) ? @"10f.png" : @"20f.png"; else spriteFrameName = (CC_CONTENT_SCALE_FACTOR() == 1) ? @"5f.png" : @"10f.png"; CCSprite* circle = [CCSprite spriteWithSpriteFrameName:spriteFrameName]; circle.anchorPoint = ccp(0.48,0.48); float retinaScale = (CC_CONTENT_SCALE_FACTOR() == 1) ? 1.0 : 0.5; CCRenderTexture* strokeTexture = [CCRenderTexture renderTextureWithWidth:textureSize.width height:textureSize.height pixelFormat:kCCTexture2DPixelFormat_RGBA8888]; [strokeTexture beginWithClear:0 g:0 b:0 a:0]; for (NSInteger x = 0; x < textureSizeInPixels.width; x++) { for (NSInteger y = 0; y < textureSizeInPixels.height; y++) { NSInteger idx = y * bytesPerRow + x * bytesPerPixel + 3; NSInteger w = 1; if (bytes[idx] <= 254) { BOOL shouldBeStroked = NO; for (NSInteger nx = -w; nx <= w; nx++) { for (NSInteger ny = -w; ny <= w; ny++) { if (x + nx < 0 || y + ny < 0 || x + nx >= textureSizeInPixels.width || y + ny >= textureSizeInPixels.height) continue; if (bytes[idx + nx * bytesPerPixel + ny * bytesPerRow] == 255) { shouldBeStroked = YES; break; } } } if (shouldBeStroked == YES) { circle.position = ccp(x * retinaScale,y * retinaScale); [circle visit]; } } } } [strokeTexture end]; CCSprite* resultSprite = [CCSprite spriteWithTexture:strokeTexture.sprite.texture]; [resultSprite.texture setAntiAliasTexParameters]; resultSprite.flipY = YES; if ([self isKindOfClass:[CCSprite class]]) { CGPoint oldAnchorInPixels = ccp(roundf(self.contentSize.width * self.anchorPoint.x),roundf(self.contentSize.height * self.anchorPoint.y)); resultSprite.anchorPoint = ccp((oldAnchorInPixels.x + gap) / resultSprite.contentSize.width,(oldAnchorInPixels.y + gap) / resultSprite.contentSize.height); resultSprite.position = self.position; } else { //CCNode resultSprite.anchorPoint = CGPointZero; resultSprite.position = ccpAdd(self.position,ccp(rect.origin.x - gap,rect.origin.y - gap)); } return resultSprite; } - (CCRenderTexture*) renderTextureFrom:(CCNode*)node shiftedFor:(CGPoint)posShift onCanvasSized:(CGSize)size { SoftAssertion(!CGSizeEqualToSize(size,CGSizeZero),@"node has zero size"); BOOL isSprite = [node isMemberOfClass:[CCSprite class]]; CGPoint apSave = node.anchorPoint; CGPoint posSave = node.position; BOOL wasVisible = node.visible; CCRenderTexture* rtx = [CCRenderTexture renderTextureWithWidth:size.width height:size.height pixelFormat:kCCTexture2DPixelFormat_RGBA8888]; [rtx beginWithClear:0 g:0 b:0 a:0]; node.anchorPoint = CGPointZero; node.position = posShift; node.visible = YES; if (isSprite) [node visit]; else [[self cloneCCNode:node] visit]; node.anchorPoint = apSave; node.position = posSave; node.visible = wasVisible; [rtx end]; return rtx; } - (CCNode*) cloneCCNode:(CCNode*)source { CCNode* clone = [CCNode node]; void (^copyCCNodeProperties)(CCNode*,CCNode*) = ^(CCNode* source,CCNode* clone) { clone.visible = source.visible; clone.rotation = source.rotation; clone.position = source.position; clone.anchorPoint = source.anchorPoint; clone.zOrder = source.zOrder; clone.tag = source.tag; }; for (CCNode* srcSubnode in source.children) { CCNode* subNode; if ([srcSubnode isMemberOfClass:[CCSprite class]]) { CCSprite* srcSprite = (CCSprite*)srcSubnode; subNode = [CCSprite spriteWithTexture:srcSprite.texture]; CCSprite* subSprite = (CCSprite*)subNode; subSprite.flipX = srcSprite.flipX; subSprite.flipY = srcSprite.flipY; subSprite.displayFrame = srcSprite.displayFrame; subSprite.opacity = srcSprite.opacity; } else if ([srcSubnode isMemberOfClass:[CCLabelTTF class]]) { CCLabelTTF* srcLabel = (CCLabelTTF*)srcSubnode; subNode = [CCLabelTTF labelWithString:srcLabel.string fontName:srcLabel.fontName fontSize:srcLabel.fontSize dimensions:srcLabel.dimensions hAlignment:srcLabel.horizontalAlignment vAlignment:srcLabel.verticalAlignment]; CCSprite* subLabel = (CCSprite*)subNode; subLabel.flipX = srcLabel.flipX; subLabel.flipY = srcLabel.flipY; subLabel.color = srcLabel.color; } else { subNode = [self cloneCCNode:srcSubnode]; } copyCCNodeProperties(srcSubnode,subNode); [clone addChild:subNode]; } copyCCNodeProperties(source,clone); return clone; }