SpriteKit中的动画路径绘图 [英] Animate Path Drawing in SpriteKit
问题描述
我正在尝试使用SpriteKit在路径上绘制笔画的动画。我已经使用SKActions实现了一个工作解决方案,并使用CABasicAnimations实现了单独的实现。 SKAction解决方案不是很优雅;它在SKAction.repeatAction(action:count :)调用的每次迭代中创建并描绘一个新路径,每个新路径比前一个稍微完整。
I'm attempting to animate the drawing of a stroke on a path using SpriteKit. I have implemented a working solution using SKActions and a separate implementation using CABasicAnimations. The SKAction solution is not very elegant; it creates and strokes a new path on each iteration of an SKAction.repeatAction(action:count:) call with each new path slightly more complete than the previous.
func start() {
var i:Double = 1.0
let spinAction:SKAction = SKAction.repeatAction(SKAction.sequence([
SKAction.runBlock({
self.drawCirclePercent(i * 0.01)
if (++i > 100.0) {
i = 1.0
}
}),
SKAction.waitForDuration(0.01)
]), count: 100)
runAction(spinAction)
}
func drawCirclePercent(percent:Double) {
UIGraphicsBeginImageContext(self.size)
let ctx:CGContextRef = UIGraphicsGetCurrentContext()
CGContextSaveGState(ctx)
CGContextSetLineWidth(ctx, lineWidth)
CGContextSetRGBStrokeColor(ctx, 1.0, 1.0, 1.0, 1.0)
CGContextAddArc(ctx, CGFloat(self.size.width/2.0), CGFloat(self.size.height/2.0), radius/2.0, CGFloat(M_PI * 1.5), CGFloat(M_PI * (1.5 + 2.0 * percent)), 0)
CGContextStrokePath(ctx)
let textureImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()
texture = SKTexture(image: textureImage)
UIGraphicsEndImageContext()
}
虽然上面的代码有效,但它肯定不是很有效,也可能不是SKActions的用途。 CABasicAnimation解决方案更优雅,效率更高。
While the above code works, it certainly is not pretty or efficient, and is likely not how SKActions were intended to be used. The CABasicAnimation solution is much more elegant and much more efficient.
let path:CGMutablePathRef = CGPathCreateMutable()
CGPathAddArc(path, nil, 0, 0, 40.0, CGFloat(M_PI_2 * 3.0), CGFloat(M_PI_2 * 7.0), false)
let pathLayer:CAShapeLayer = CAShapeLayer()
pathLayer.frame = CGRectMake(100, 100, 80.0, 80.0)
pathLayer.path = path
pathLayer.strokeColor = SKColor.whiteColor().CGColor
pathLayer.fillColor = nil
pathLayer.lineWidth = 3.0
self.view.layer.addSublayer(pathLayer)
let pathAnimation:CABasicAnimation = CABasicAnimation(keyPath: "strokeEnd")
pathAnimation.duration = 2.0
pathAnimation.fromValue = 0.0
pathAnimation.toValue = 1.0
pathLayer.addAnimation(pathAnimation, forKey: "strokeEndAnimation")
我的问题是我真的更喜欢将所有代码都包含在SKSpriteNode的子类中(以至于如果以上两个解决方案都是只有我拥有的选项我会选择第一个)。有没有什么方法可以让我的SKAction实现更接近CoreAnimation实现,而不需要包含CoreAnimation?基本上,我想知道SpriteKit是否具有我不知道哪些可用于改进第一次实现的功能。
My issue is that I would really prefer to have all the code contained within a subclass of SKSpriteNode (so much so that if the above two solutions are the only options I have I would go with the first). Is there any way in which I can improve my SKAction implementation to closer resemble the CoreAnimation implementation, without the need to include CoreAnimation? Essentially, I'm wondering if SpriteKit has functionality that I'm unaware of which could be used to improve the first implementation.
推荐答案
您可以通过提供基于SKShader的属性, v_path_distance
和 u_path_length
。这由上面的rickster暗示,完整代码如下。在着色器中,我们添加了 u_current_percentage
,并引用了我们想要描述的路径中的当前点。由此,场景决定了动画抚摸的速度。另请注意, strokeShader
作为片段着色器,在每一步输出RGB,它允许沿路径进行颜色控制,例如可以使渐变颜色成为可能。
you can animate SKShapeNode's path by supplying a custom strokeShader that outputs based on a few of SKShader's properties, v_path_distance
and u_path_length
. This hinted at by rickster above, full code to do so follows. Within the shader, u_current_percentage
is added by us and refers to the current point within the path we want stroked up to. By that, the scene determines the pace of the animated stroking. Also note that strokeShader
being a fragment shader, outputs an RGB at every step, it allows color control along the path, making a gradient color possible for example.
着色器作为文件添加到Xcode项目animateStroke.fsh中:
The shader is added as a file to the Xcode project "animateStroke.fsh":
void main()
{
if ( u_path_length == 0.0 ) {
gl_FragColor = vec4( 0.0, 0.0, 1.0, 1.0 );
} else if ( v_path_distance / u_path_length <= u_current_percentage ) {
gl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );
} else {
gl_FragColor = vec4( 0.0, 0.0, 0.0, 0.0 );
}
}
使用它的示例SKScene子类:
And the sample SKScene subclass using it:
import SpriteKit
import GameplayKit
func shaderWithFilename( _ filename: String?, fileExtension: String?, uniforms: [SKUniform] ) -> SKShader {
let path = Bundle.main.path( forResource: filename, ofType: fileExtension )
let source = try! NSString( contentsOfFile: path!, encoding: String.Encoding.utf8.rawValue )
let shader = SKShader( source: source as String, uniforms: uniforms )
return shader
}
class GameScene: SKScene {
let strokeSizeFactor = CGFloat( 2.0 )
var strokeShader: SKShader!
var strokeLengthUniform: SKUniform!
var _strokeLengthFloat: Float = 0.0
var strokeLengthKey: String!
var strokeLengthFloat: Float {
get {
return _strokeLengthFloat
}
set( newStrokeLengthFloat ) {
_strokeLengthFloat = newStrokeLengthFloat
strokeLengthUniform.floatValue = newStrokeLengthFloat
}
}
override func didMove(to view: SKView) {
strokeLengthKey = "u_current_percentage"
strokeLengthUniform = SKUniform( name: strokeLengthKey, float: 0.0 )
let uniforms: [SKUniform] = [strokeLengthUniform]
strokeShader = shaderWithFilename( "animateStroke", fileExtension: "fsh", uniforms: uniforms )
strokeLengthFloat = 0.0
let cameraNode = SKCameraNode()
self.camera = cameraNode
let strokeHeight = CGFloat( 200 ) * strokeSizeFactor
let path1 = CGMutablePath()
path1.move( to: CGPoint.zero )
path1.addLine( to: CGPoint( x: 0, y: strokeHeight ) )
// prior to a fix in iOS 10.2, bug #27989113 "SKShader/SKShapeNode: u_path_length is not set unless shouldUseLocalStrokeBuffers() is true"
// add a few points to work around this bug in iOS 10-10.1 ->
// for i in 0...15 {
// path1.addLine( to: CGPoint( x: 0, y: strokeHeight + CGFloat( 0.001 ) * CGFloat( i ) ) )
// }
path1.closeSubpath()
let strokeWidth = 17.0 * strokeSizeFactor
let path2 = CGMutablePath()
path2.move( to: CGPoint.zero )
path2.addLine( to: CGPoint( x: 0, y: strokeHeight ) )
path2.closeSubpath()
let backgroundShapeNode = SKShapeNode( path: path2 )
backgroundShapeNode.lineWidth = strokeWidth
backgroundShapeNode.zPosition = 5.0
backgroundShapeNode.lineCap = .round
backgroundShapeNode.strokeColor = SKColor.darkGray
addChild( backgroundShapeNode )
let shapeNode = SKShapeNode( path: path1 )
shapeNode.lineWidth = strokeWidth
shapeNode.lineCap = .round
backgroundShapeNode.addChild( shapeNode )
shapeNode.addChild( cameraNode )
shapeNode.strokeShader = strokeShader
backgroundShapeNode.calculateAccumulatedFrame()
cameraNode.position = CGPoint( x: backgroundShapeNode.frame.size.width/2.0, y: backgroundShapeNode.frame.size.height/2.0 )
}
override func update(_ currentTime: TimeInterval) {
// the increment chosen determines how fast the path is stroked. Note this maps to "u_current_percentage" within animateStroke.fsh
strokeLengthFloat += 0.01
if strokeLengthFloat > 1.0 {
strokeLengthFloat = 0.0
}
}
}
这篇关于SpriteKit中的动画路径绘图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!