在SpriteKit

时间:2016-05-20 17:39:15

标签: ios sprite-kit core-graphics

我正在研究一个SpriteKit项目,需要创建一个褪色尾部的彗星,在整个屏幕上显示动画效果。在这方面我对SpriteKit存在严重问题。

尝试1 。它:

  1. 绘制CGPath并从路径
  2. 创建SKShapeNode
  3. 使用渐变
  4. 创建正方形SKShapeNode
  5. 创建一个SKCropNode并将其maskNode指定为line,并将square添加为子项
  6. 在屏幕上设置正方形,同时被行/ SKCropNode剪切

    func makeCometInPosition(from: CGPoint, to: CGPoint, color: UIColor, timeInterval: NSTimeInterval) {
            ... (...s are (definitely) irrelevant lines of code)
            let path = CGPathCreateMutable()
            ...
            let line = SKShapeNode(path:path)
            line.lineWidth = 1.0
            line.glowWidth = 1.0
    
            var squareFrame = line.frame
            ...
            let square = SKShapeNode(rect: squareFrame)
            //Custom SKTexture Extension. I've tried adding a normal image and the leak happens either way. The extension is not the problem
            square.fillTexture = SKTexture(color1: UIColor.clearColor(), color2: color, from: from, to: to, frame: line.frame)
    
            square.fillColor = color
            square.strokeColor = UIColor.clearColor()
            square.zPosition = 1.0
    
            let maskNode = SKCropNode()
            maskNode.zPosition = 1.0
            maskNode.maskNode = line
            maskNode.addChild(square)
    
            //self is an SKScene, background is an SKSpriteNode
            self.background?.addChild(maskNode)
    
            let lineSequence = SKAction.sequence([SKAction.waitForDuration(timeInterval), SKAction.removeFromParent()])
    
            let squareSequence = SKAction.sequence([SKAction.waitForDuration(1), SKAction.moveBy(CoreGraphics.CGVectorMake(deltaX * 2, deltaY * 2), duration: timeInterval), SKAction.removeFromParent()])
    
            square.runAction(SKAction.repeatActionForever(squareSequence))
            maskNode.runAction(lineSequence)
            line.runAction(lineSequence)
        }
    

    有效,如下所示。 enter image description here

  7. 问题是,在屏幕上出现20-40个其他节点后,会发生奇怪的事情。屏幕上的一些节点消失,一些停留。此外,fps和节点数(在SKView中切换,从不更改)

    self.showsFPS = true
    self.showsNodeCount = true
    
    屏幕上的

    消失。这让我觉得它是SpriteKit的一个错误。 SKShapeNode has been known to cause issues

    尝试2。我尝试将广场从SKShapeNode更改为SKSpriteNode(根据需要添加和删除与这两者相关的行)

    let tex = SKTexture(color1: UIColor.clearColor(), color2: color, from:    from, to: to, frame: line.frame)
    let square = SKSpriteNode(texture: tex)
    

    其余代码基本相同。这产生了类似的效果,没有错误性能/内存。但是,SKCropNode发生了一些奇怪的事情,它看起来像这样 enter image description here

    没有抗锯齿,线条较粗。我尝试过更改抗锯齿,光晕宽度和线宽。由于某种原因,有一个最小宽度不能改变,并且将发光宽度设置得更大就是这样 enter image description here 。根据其他stackoverflow问题,maskNodes在alpha中为1或0。这是令人困惑的,因为SKShapeNode可以具有不同的线/发光宽度。

    尝试3。经过一些研究,我发现我可以使用剪贴效果并使用SKEffectNode而不是SKCropNode来保留线宽/发光。

        //Not the exact code to what I tried, but very similar
        let maskNode = SKEffectNode()
        maskNode.filter = customLinearImageFilter
        maskNode.addChild(line)
    

    这产生了(字面上)与尝试1完全相同的效果。它创建了相同的线条和动画,但发生了与其他节点/ fps / nodeCount相同的错误。所以它似乎是SKEffectNode的一个错误,而不是SKShapeNode。

    我不知道如何通过尝试1/3或2来绕过这些错误。

    是否有人知道我做错了什么,是否有绕过这个问题,或者我的问题完全不同的解决方案?

    编辑:我考虑过发射器,但可能会有几百个彗星/其他节点在几秒钟内进入,并且认为它们在性能方面不可行。我没有在这个项目之前使用SpriteKit所以如果我错了就纠正我。

1 个答案:

答案 0 :(得分:3)

对于附加到彗星路径的自定义着色器,这看起来很像问题。如果您不熟悉SpriteKit中的OpenGL Shading Language (GLSL),则可以直接跳转到GPU片段着色器,以控制它附加到via SKShader的节点的绘制行为。

方便地, SKShapeNode 具有 strokeShader 属性,用于连接 SKShader 以绘制路径。当连接到此属性时,除了该点的颜色值之外,着色器还会传递路径的长度和当前正在绘制的路径上的点。*

controlFadePath.fsh

void main() {

  //uniforms and varyings
  vec4  inColor = v_color_mix;
  float length = u_path_length;
  float distance = v_path_distance;
  float start = u_start;
  float end = u_end;

  float mult;

  mult = smoothstep(end,start,distance/length);
  if(distance/length > start) {discard;}

  gl_FragColor = vec4(inColor.r, inColor.g, inColor.b, inColor.a) * mult;
}

要控制沿路径的淡入淡出,请使用名为u_startu_end的两个SKUniform对象将起点和终点传递到自定义着色器中。这些对象在初始化自定义SKShapeNode类时会添加到自定义着色器中CometPathShape并通过自定义Action动画。

类CometPathShape:SKShapeNode

class CometPathShape:SKShapeNode {

  //custom shader for fading
  let pathShader:SKShader
  let fadeStartU = SKUniform(name: "u_start",float:0.0)
  let fadeEndU = SKUniform(name: "u_end",float: 0.0)
  let fadeAction:SKAction

  override init() {
    pathShader = SKShader(fileNamed: "controlFadePath.fsh")

    let fadeDuration:NSTimeInterval = 1.52
    fadeAction = SKAction.customActionWithDuration(fadeDuration, actionBlock:
      { (node:SKNode, time:CGFloat)->Void in
            let D = CGFloat(fadeDuration)
            let t = time/D
            var Ps:CGFloat = 0.0
            var Pe:CGFloat = 0.0

            Ps = 0.25 + (t*1.55)
            Pe = (t*1.5)-0.25

            let comet:CometPathShape = node as! CometPathShape
            comet.fadeRange(Ps,to: Pe) })

    super.init()

    path = makeComet...(...)   //custom method that creates path for comet shape 

    strokeShader = pathShader
    pathShader.addUniform(fadeStartU)
    pathShader.addUniform(fadeEndU)
    hidden = true
    //set up for path shape, eg. strokeColor, strokeWidth...
    ...
  }

  func fadeRange(from:CGFloat, to:CGFloat) {
    fadeStartU.floatValue = Float(from)
    fadeEndU.floatValue = Float(to)     
  }

  func launch() {
    hidden = false
    runAction(fadeAction, completion: { ()->Void in self.hidden = true;})
  }

...

SKScene初始化CometPathShape对象,缓存并将它们添加到场景中。在update:期间,场景只需在所选的CometPathShapes上调用.launch()

类GameScene:SKScene

...
  override func didMoveToView(view: SKView) {
    /* Setup your scene here */
    self.name = "theScene"
    ...

    //create a big bunch of paths with custom shaders
    print("making cache of path shape nodes")
    for i in 0...shapeCount {
      let shape = CometPathShape()
      let ext = String(i)
      shape.name = "comet_".stringByAppendingString(ext)
      comets.append(shape)
      shape.position.y = CGFloat(i * 3)
      print(shape.name)
      self.addChild(shape)
    }

  override func update(currentTime: CFTimeInterval) {
    //pull from cache and launch comets, skip busy ones
    for _ in 1...launchCount {
        let shape = self.comets[Int(arc4random_uniform(UInt32(shapeCount)))]
        if shape.hasActions() { continue }
        shape.launch()
    }
}

这将每个彗星的SKNodes数量从3减少到1,简化了代码和运行时环境,并通过着色器为更复杂的效果打开了大门。我能看到的唯一缺点是必须学习一些GLSL。**

*在设备模拟器中并不总是正确的。模拟器未将距离和长度值传递给自定义着色器。

**和CGPath glsl行为中的一些特性。路径构造正在影响淡入淡出的执行方式。看起来v_path_distance不能在曲线段之间平滑混合。仍然,小心地构建曲线,这应该有效。