13

我正在Sprite Kit 中制作“ Achtung die kurve ”克隆。对于不断移动的线/玩家,我使用 CGMutablePathRef 和 SKShapeNode。在更新方法中,我正在这样做

// _lineNode is an instance of SKShapeNode and path is CGMutablePathRef
CGPathAddLineToPoint(path, NULL, _xPos, _yPos);
_lineNode.path = path;

添加到该行。update 方法也是不断更新 _xPos 和 _yPos 以使其增长。

我想我真正要问的是是否有另一种更有效的画线方式,因为我现在这样做的方式会在一段时间后(大约 15-20 秒)大幅降低帧速率。此时 FPS 会不断下降,直到游戏无法玩。Time Profiler 告诉我这一行:_lineNode.path = path 是导致 FPS 下降的原因。

谢谢你的帮助!非常感谢。

PS。我试图根本不使用 SKShapeNode,因为它们似乎无法很好地绘制线条(曲线中的小孔/伪影等)

截屏: 不断绘制的线条

4

3 回答 3

23

不幸的是, SKShapeNode 对于您正在尝试做的事情并不是那么好。然而,有一种方法可以优化这一点,尽管有一些警告。

fps 的第一个最大问题是绘制计数变得非常高,因为您添加的每个线段都是另一个绘制。如果你设置showsDrawCount你的SKView实例,你会明白我的意思。

在这个答案中,一次绘制多个 skshapenode?,如果您绘制一次,您可以获得有关如何使用shouldRasterizea 的属性来解决问题的更多信息。SKEffectNode如果您不这样做,您将花费处理器时间在每帧进行多次绘制。

所以你可以看到平局是你没有得到你想要的表现的主要问题。但是,您似乎希望随着时间的推移始终如一地绘制,所以我要建议的可能对您来说是一个可行的解决方案。

我建议的解决方案的逻辑是这样的:

1 - 创建一个SKSpriteNode我们可以用作画布的。

2 - 创建一个SKShapeNode仅用于绘制当前线段的线段。

3 - 使其SKShapeNode成为画布的子项。

4 - 通过绘制一条新线段SKShapeNode

5 - 使用SKView方法 `textureFromNode 保存当前在画布上绘制的内容。

6 - 将画布的纹理设置为该纹理。

循环回到#4 并为您SKShapeNode的下一条线段创建一条新路径。

根据需要重复。

结果应该是您的平局次数永远不会高于 2 次平局,这将解决高平局次数的问题。

基本上,您保留了以前在纹理中绘制的内容,因此只需要SKShapeNode为最新的线段绘制一次,为SKTexture.

同样,我还没有尝试过这个过程,如果有任何延迟,它会在textureFromNode每个帧的调用中。如果有什么是你的瓶颈,那就是它!

今天我可能会尝试这个理论,因为我需要textureFromNode解决另一个我正在尝试解决的问题,所以我肯定会发现这种方法有多快/多慢!哈哈

更新

这不是完整的代码,而是实现所需绘图性能(60fps)的重要部分:

基本节点元素是:

container -> SKNode 包含所有需要缓存的元素

canvas -> SKSpriteNode 将显示绘制段的缓存版本

段池 -> 最初用于绘制段,并根据需要重复使用

首先创建一个 SKShapeNodes 池:

pool = [[NSMutableArray alloc]init];

//populate the SKShapeNode pool
// the amount of segments in pool, dictates how many segments
// will be drawn before caching occurs.
for (int index = 0; index < 5; index++)
{
    SKShapeNode *segment = [[SKShapeNode alloc]init];
    segment.strokeColor = [SKColor whiteColor];
    segment.glowWidth = 1;
    [pool addObject:segment];
}

接下来创建从 pool 获取 SKShapeNode 的方法:

-(SKShapeNode *)getShapeNode
{
    if (pool.count == 0)
    {
        // if pool is empty, 
        // cache the current segment draws and return segments to pool
        [self cacheSegments];
    }

    SKShapeNode *segment = pool[0];
    [pool removeObjectAtIndex:0];

    return segment;
}

接下来创建一个从池中获取段并绘制线的方法:

-(void)drawSegmentFromPoint:(CGPoint)fromPoint toPoint:(CGPoint)toPoint
{
    SKShapeNode *curSegment = [self getShapeNode];
    CGMutablePathRef path = CGPathCreateMutable();
    curSegment.lineWidth = 3;
    curSegment.strokeColor = [SKColor whiteColor];
    curSegment.glowWidth = 1;
    curSegment.name = @"segment";

    CGPathMoveToPoint(path, NULL, fromPoint.x, fromPoint.y);
    CGPathAddLineToPoint(path, NULL, toPoint.x, toPoint.y);
    curSegment.path = path;
    lastPoint = toPoint;
    [canvas addChild:curSegment];
}

接下来是创建纹理并将现有段返回到池中的方法:

-(void)cacheSegments
{
    SKTexture *cacheTexture =[ self.view textureFromNode:container];
    canvas.texture = cacheTexture;
    [canvas setSize:CGSizeMake(canvas.texture.size.width, canvas.texture.size.height)];
    canvas.anchorPoint = CGPointMake(0, 0);
    [canvas enumerateChildNodesWithName:@"segment" usingBlock:^(SKNode *node, BOOL *stop)
     {
         [node removeFromParent];
         [pool addObject:node];
     }];

}

最后是触摸处理程序:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self cacheSegments];
    for (UITouch *touch in touches)
    {
        CGPoint location = [touch locationInNode:self];
        lastPoint = location;
        [self drawSegmentFromPoint:lastPoint toPoint:location];
    }
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    for (UITouch *touch in touches)
    {
        CGPoint location = [touch locationInNode:self];
        [self drawSegmentFromPoint:lastPoint toPoint:location];
    }
}

正如我所说,这不是包含所有代码的代码,我假设您对可以在应用程序中实现的概念有足够的了解。这些只是我的准系统实现的示例。

于 2014-07-03T15:22:43.990 回答
2

要修复曲线中的“孔”,只需将lineCap设置为非零值:

curSegment.lineCap = 1;
于 2015-12-07T04:48:43.850 回答
2

我尝试将原型提出的好的答案翻译成Swift 2.2 :

自定义节点:

class AOShapeNode: SKNode {
    var currentScene: SKScene!
    var canvas = SKShapeNode()
    var segment = SKShapeNode()
    var pool:[SKShapeNode]!
    var poolSize: Int = 50 // big number to improve performance
    var segmentLineWidth: CGFloat = 3

    init(currentScene scene:SKScene,nodeSize size: CGSize) {

        super.init()
        print("---");
        print("∙ \(self.dynamicType)")
        print("---")
        self.userInteractionEnabled = true
        self.currentScene = scene
        self.addChild(canvas)
        pool = [SKShapeNode]()
        for _ in 0..<poolSize
        {
            let segment = SKShapeNode()
            segment.strokeColor = UIColor.blackColor()
            segment.glowWidth = 1
            segment.lineCap = CGLineCap(rawValue: 1)!
            pool.append(segment)
        }
    }

    func getShapeNode() -> SKShapeNode {
        if(pool.count == 0)
        {
            self.cacheSegments()
        }
        let segment = pool.first
        pool.removeFirst()
        return segment!
    }

    func drawSegmentFromPoint(fromPoint:CGPoint, toPoint:CGPoint)->CGPoint {
        let curSegment = self.getShapeNode()
        let path = CGPathCreateMutable()
        curSegment.lineWidth = segmentLineWidth
        curSegment.strokeColor = SKColor.blackColor()
        curSegment.glowWidth = 1
        curSegment.lineCap = CGLineCap(rawValue: 1)!
        curSegment.name = "segment"
        CGPathMoveToPoint(path, nil, fromPoint.x, fromPoint.y)
        CGPathAddLineToPoint(path, nil, toPoint.x, toPoint.y)
        curSegment.path = path
        canvas.addChild(curSegment)
        return toPoint
    }

    func cacheSegments() {
        if let cacheTexture = self.currentScene.view?.textureFromNode(self) {
            let resizeAction = SKAction.setTexture(cacheTexture, resize: true)
            canvas.runAction(resizeAction)
        }
        canvas.enumerateChildNodesWithName("segment", usingBlock: {
            (node: SKNode!, stop: UnsafeMutablePointer <ObjCBool>) -> Void in
            self.pool.append(node as! SKShapeNode)
            node.removeFromParent()
        })

    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

游戏场景:

class GameScene: SKScene {
    var line : AOShapeNode!
    var lastPoint :CGPoint = CGPointZero

    override func didMoveToView(view: SKView) {
        /* Setup your scene here */
        line = AOShapeNode.init(currentScene: self, nodeSize: self.size)
        self.addChild(line)
    }

    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
       /* Called when a touch begins */
        line.cacheSegments()

        for touch in touches {
            let location = touch.locationInNode(self)
            lastPoint = location
            lastPoint = line.drawSegmentFromPoint(lastPoint, toPoint: location)
        }
    }

    override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
        /* Called when a touch moved */
        for touch in touches {
            let location = touch.locationInNode(self)
            lastPoint = line.drawSegmentFromPoint(lastPoint, toPoint: location)
        }
    }
}

以上是所有代码,但如果您想尝试一下,这是github repo的链接。


阅读了一些关于改进更好的SKShapeNode替代方案或强大重构的可能性的文章,我发现这个名为SKUShapeNode的项目实际上并入了SKUtilities 2项目,这是一个使用(一些文档SKSpriteNode)制作渲染子类的想法,有一些错误,并且总是使用 node.js 将转换点从实际的 Sprite Kit 转换为实际的 Sprite Kit。CAShapeLayerUIKit CALayer

于 2016-06-24T16:58:08.023 回答