我试图使用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, 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, 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是否具有我不知道可用于改进第一次实现的功能。
答案 0 :(得分:8)
您可以通过提供基于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, 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子类:
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
}
}
}
答案 1 :(得分:4)
基于Bobjt的answer,我的着色器使用了SKShapeNode的glowWidth ......
所以当我使用它时,我可以使用shapeNode.glowWidth = 5.0;
等!
void main()
{
vec4 def = SKDefaultShading();
if ( u_path_length == 0.0 ) {
gl_FragColor = vec4( 0xff / 255.0f, 0 / 255.0f, 0 / 255.0f, 1.0f );
} else if ( v_path_distance / u_path_length <= u_current_percentage ) {
gl_FragColor = vec4(
//0x1e / 255.0f, 0xb8 / 255.0f, 0xca / 255.0f, <-- original boring color :-P
0x1e / 255.0f * def.z, 0xb8 / 255.0f * def.z, 0xca / 255.0f * def.z, def.z
);
} else {
gl_FragColor = vec4( 0.0f, 0.0f, 0.0f, 0.0f );
}
}