我正在尝试使用SpriteKit在路径上绘制笔画的动画.我已经使用SKActions实现了一个工作解决方案,并使用CABasicAnimations实现了单独的实现. SKAction解决方案不是很优雅;它在SKAction.repeatAction(action:count :)调用的每次迭代中创建并描绘一个新路径,每个新路径比前一个略微更完整.
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)
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解决方案更优雅,更高效.
let path:CGMutablePathRef = CGPathCreateMutable() CGPathAddArc(path,nil,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是否具有我不知道哪些功能可用于改进第一个实现的功能.
解决方法
您可以通过提供基于SKShader的
properties,v_path_distance和u_path_length的一些输出的自定义strokeShader来为SKShapeNode的路径设置动画.这由上面的rickster暗示,完整的代码如下.在着色器中,u_current_percentage由我们添加,并引用我们想要描绘的路径中的当前点.由此,场景决定了动画抚摸的速度.另请注意,strokeShader是片段着色器,在每一步输出RGB,它允许沿路径进行颜色控制,例如,可以使渐变颜色成为可能.
着色器作为文件添加到Xcode项目“animatestroke.fsh”:
void main()
{
if ( u_path_length == 0.0 ) {
gl_FragColor = vec4( 0.0,0.0,1.0 );
} else if ( v_path_distance / u_path_length <= u_current_percentage ) {
gl_FragColor = vec4( 1.0,1.0 );
} else {
gl_FragColor = vec4( 0.0,0.0 );
}
}
和样本SKScene子类使用它:
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
}
}
}