Reputation: 313
I am trying to get a transition effect similar to that in super mario run game by Nintendo in sprite kit
I just want a circle cutout to expand revealing the next scene or just revealing the current scene. This is not available as one of the standard iOS sprite kit transitions.
I have done this with a completely black layer covering the entire scene and using SKCropNode to animate an expanding circle to reveal the scene.
The following is the code which is in didMove(to view: SKView)
let fullScreen = SKSpriteNode(color: .black, size: self.size)
let mask = SKSpriteNode(color: .black, size: self.size)
let circle = SKShapeNode(circleOfRadius: self.size.height / 2)
circle.fillColor = .white
circle.blendMode = .subtract
circle.setScale(0.001)
circle.isHidden = true
circle.name = "circleShape"
mask.addChild(circle)
let crop = SKCropNode()
crop.position = CGPoint(x: self.size.width / 2, y: self.size.height / 2)
crop.maskNode = mask
crop.name = "cropNode"
crop.addChild(fullScreen)
crop.zPosition = 100
self.addChild(crop)
let waitAction = SKAction.wait(forDuration: 2.0)
let callAction = SKAction.run({
let cropNode = self.childNode(withName: "cropNode") as! SKCropNode
let maskNode = cropNode.maskNode as! SKSpriteNode
let circleNode = maskNode.childNode(withName: "circleShape") as! SKShapeNode
circleNode.isHidden = false
let scaleAction = SKAction.scale(to: 2.0, duration: 2.0)
scaleAction.timingFunction = scaleAction.easeInQuartic
circleNode.run(scaleAction, completion: {
cropNode.removeFromParent()
})
})
let seqAction = SKAction.sequence([waitAction,callAction])
self.run(seqAction)
This is working in the simulator but not while running on a device (iPad pro 2016 with the latest iOS 10). On the device, no expanding circle appears and the black overlay layer just disappears after the scale action is finished.
My questions:
1) why isn't this working on the device and only on the simulator?
2) is there a better and more efficient way to achieve this transition in sprite kit?
Thanks in advance.
Upvotes: 0
Views: 1374
Reputation: 313
I ended up using SKSpriteNode with a fragment shader. The following is the code in the main scene:
let fullScreen = SKSpriteNode(color: UIColor.clear, size: CGSize(width: self.size.width, height: self.size.height))
fullScreen.position = CGPoint(x: self.size.width / 2.0, y: self.size.height / 2.0)
fullScreen.zPosition = 200
self.addChild(fullScreen)
let shader = SKShader(fileNamed: "transitionShader.fsh")
shader.attributes = [SKAttribute(name: "a_sprite_size", type: .vectorFloat2),
SKAttribute(name:"a_duration", type: .float)]
fullScreen.shader = shader
let spriteSize = vector_float2(Float(fullScreen.frame.size.width),Float(fullScreen.frame.size.height))
fullScreen.setValue(SKAttributeValue(vectorFloat2: spriteSize),forAttribute: "a_sprite_size")
fullScreen.setValue(SKAttributeValue(float: Float(mainGameTransitionDuration)), forAttribute: "a_duration")
And the following is the shader code which is in a file called transitionShader.fsh
#define M 1000.0 // Multiplier
void main()
{
float aspect = a_sprite_size.y / a_sprite_size.x;
float u = v_tex_coord.x;
float v = v_tex_coord.y * aspect;
vec2 uv = vec2(u,v) * M;
vec2 center = vec2(0.60,0.55 * aspect) * M;
float t = u_time / a_duration;
if ( t < 1.0 )
{
float easeIn = pow(t,5.0);
float radius = easeIn * 2.0 * M;
float d = length(center - uv) - radius;
float a = clamp(d, 0.0, 1.0);
gl_FragColor = vec4(0.0,0.0,0.0, a);
}
else
{
gl_FragColor = vec4(0.0,0.0,0.0,0.0);
}
}
It seems to work with no issues and does not appear to be expensive on the CPU or the memory.
Upvotes: 0
Reputation: 6071
There are probably a 100 different ways to achieve this. Is my way better than yours? probably not, but this works for me on both simulator and on my iPad.
just transition into your scene with a duration of 0.0 and then run this fun from didMove(to view:)
The reason you need a separate image for the circle is so that it keeps its nice crisp edge as it is scaling up
the bgOverlay is just so that it starts from a completely black screen and then applies the seudo transition
func circleTransition() {
let bgMask = SKSpriteNode(texture: SKTexture(imageNamed: "bg_mask"), color: .clear, size: CGSize(width: self.size.width, height: self.size.height))
bgMask.position = CGPoint(x: self.size.width / 2, y: self.size.height / 2)
bgMask.zPosition = 5000
self.addChild(bgMask)
let transitionCircle = SKSpriteNode(texture: SKTexture(imageNamed: "transition_circle"), color: .clear, size: CGSize(width: 13, height: 13))
transitionCircle.position = CGPoint.zero
transitionCircle.zPosition = 1
bgMask.addChild(transitionCircle)
let bgOverlay = SKSpriteNode(texture: SKTexture(imageNamed: "bg_overlay"), color: .clear, size: CGSize(width: self.size.width, height: self.size.height))
bgOverlay.position = CGPoint(x: self.size.width / 2, y: self.size.height / 2)
bgOverlay.zPosition = 5001
self.addChild(bgOverlay)
bgMask.run(SKAction.sequence([SKAction.scale(to: 100.0, duration: 2.0), SKAction.removeFromParent()]))
bgOverlay.run(SKAction.sequence([SKAction.fadeOut(withDuration: 0.1), SKAction.removeFromParent()]))
}
Upvotes: 2