Sprite Kit中的SKShapeNode性能不佳 [英] Poor performance with SKShapeNode in Sprite Kit

查看:60
本文介绍了Sprite Kit中的SKShapeNode性能不佳的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在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;

添加到该行.更新方法还将不断更新_xPos和_yPos以使其增长.

我想我真正要问的是还有另一种更有效的画线方法,因为现在我做的方式会在一段时间(约15至20秒)后将帧速率降低太多.此时,FPS会不断下降,直到无法玩游戏为止.时间分析器告诉我这条线:_lineNode.path = path是FPS下降的原因.

感谢您的帮助!非常感谢.

PS. 我试图完全不使用SKShapeNode,因为它们似乎无法很好地绘制线条(曲线上的小孔/伪像等)

截屏:

解决方案

不幸的是,SKShapeNode对于您要尝试的操作并没有那么出色.但是,尽管有一些警告,但仍有一种方法可以对此进行优化.

fps的最大问题之一是,由于您添加的每个线段都是另一次绘制,因此绘制计数变得非常高.如果在SKView实例上设置showsDrawCount,您将明白我的意思.

在此答案中一次抽奖中有多个skshapenode?,您可以获取有关如何使用SKEffectNodeshouldRasterize属性解决问题的更多信息(如果您绘制一次).如果不这样做,您将在每帧的多次绘制上花费处理器时间.

因此您可以看到平局是主要问题,您没有获得想要的性能.但是,您似乎希望随着时间的流逝不断画画,所以我建议的可能是您可行的解决方案.

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

1-创建一个可用作画布的SKSpriteNode.

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

3-将SKShapeNode设为画布的子代.

4-通过SKShapeNode

绘制新的线段

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

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

循环回到#4,并为下一个线段的SKShapeNode创建新路径.

根据需要重复.

结果应该是您的开奖次数永远不会超过2次开奖,这将解决高开奖次数的问题.

基本上,您将保留先前在纹理中绘制的内容,因此,对于最新的线段,只需一个SKShapeNode绘制,而对于SKTexture,则仅需要绘制一个.

同样,我还没有尝试过此过程,如果有任何滞后,则在textureFromNode中调用每个帧.如果有什么成为您的瓶颈,那就好了!

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

更新

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

基本节点元素是:

容器-> SKNode,其中包含所有需要缓存的元素

画布-> 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];
}

从池中获取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];
    }
}

正如我所说,这不是全部的代码,我假设您对可以在应用程序中实现的概念了解得足够多.这些只是我的准系统实施的示例.

I'm making a "Achtung die kurve"-clone in Sprite Kit. For the constantly moving lines/players I'm using A CGMutablePathRef along with an SKShapeNode. In the update method I'm doing this

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

to add to the line. The update method is also updating the _xPos and _yPos constantly to make it grow.

I guess what I'm really asking is is there another, more efficient way of drawing the lines, since the way I'm doing it now drops the frame rate way too much after a while (about 15-20 seconds). At this point the FPS just drops constantly until the game is unplayable. The Time Profiler tells me that this line: _lineNode.path = path is the cause of the FPS drop.

Thanks for any help! It is greatly appreciated.

PS. I'm trying to not use SKShapeNode at all since they seem to not being able to draw the lines too good (Small holes/artifacts in the curves etc.)

Screenshot:

解决方案

Unfortunately, SKShapeNode is not that great for what you are trying to do. However, there is a way to optimize this, albeit with some caveats.

First one of the largest problems with the fps is that the draw count gets extremely high because each line segment you add is another draw. If you set showsDrawCount on your SKView instance, you will see what I mean.

In this answer Multiple skshapenode in one draw?, you can get more information about how you can use shouldRasterize property of a SKEffectNode to solve the problem if you are drawing something once. If you don't do this, you will have processor time spent on numerous draws each frame.

So you can see that the draws is the main issue with you not getting the performance you desire. However, you seem to want to be drawing consistently over time, so what I am going to suggest might be a viable solution for you.

The logic of the solution I am suggesting is as such :

1 - Create a SKSpriteNode that we can use as a canvas.

2 - Create one SKShapeNode that will be used to draw ONLY the current line segment.

3 - Make that SKShapeNode a child of the canvas.

4 - Draw a new line segment via SKShapeNode

5 - Use the SKView method `textureFromNode to save what has currently been drawn on the canvas.

6 - set the texture of the canvas to that texture.

Loop back to #4 and make a new path for your SKShapeNode for the next line segment.

Repeat as needed.

The result should be that your draw count will never be higher than 2 draws, which would solve the problem of a high draw count.

Basically, you are preserving what has previously been drawn in a texture, therefore only ever needing one SKShapeNode draw for the latest line segment and one draw for the SKTexture.

Again, I have not tried this process yet, and if there is any lag it would be in that textureFromNode call each frame. If anything would be your bottleneck, that would be it!

I might try this theory out some time today, as I need textureFromNode for another problem I am trying to solve, and so I'll definitely find out how fast/slow that method is! haha

UPDATE

This is not complete code, but is the important parts to achieve the desired drawing performance (60fps) :

The basic node elements are :

container -> SKNode that contains all elements that need to be cached

canvas -> SKSpriteNode that will display the cached version of drawn segments

pool of segments -> used to draw segments initially, and get reused as needed

First create a pool of 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];
}

Next create method for getting a SKShapeNode from pool :

-(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;
}

Next create a method for getting a segment from pool and drawing the line :

-(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];
}

Next is a method for creating a texture and returning existing segments to the pool :

-(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];
     }];

}

Lastly the touch handlers :

-(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];
    }
}

As I said, this is not all inclusive code, I assume you understand enough about the concept that you can implement into your application. These are just examples of my barebones implementation.

这篇关于Sprite Kit中的SKShapeNode性能不佳的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆