0

I am trying to get a transition effect similar to that in super mario run game by Nintendo in sprite kit

enter image description here

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.

Alessandro Ornano
  • 34,887
  • 11
  • 106
  • 133
plawres
  • 313
  • 4
  • 19

2 Answers2

2

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()]))
}

bg

transition_circle

bg_mask

Ron Myschuk
  • 6,011
  • 2
  • 20
  • 32
  • I do not see how your approach achieves the desired outcome. You are creating bgMask, transitionCircle and bgOverlay sprite nodes. Then you are adding the transitionCircle as a child of bgMask. Where is the circle cut out? how are you making a circular hole in the bgMask? I do not see any use of blend modes or SKCropNode. What are the image contents of bgMask, transitionCircle and bgOverlay? – plawres Feb 03 '17 at 15:51
  • 1
    it's 3 images, 16 lines of code, 1 function. It probably would've been easier to try this code than write that comment. – Ron Myschuk Feb 03 '17 at 16:19
  • 1
    No Blend Modes, no CropNodes. Just simple eye candy. The transition circle image has a transparent circle (not white as would appear on this page). As far as image contents...you can see the images, correct? – Ron Myschuk Feb 03 '17 at 16:22
  • Yes, I can see the images. but I couldn't understand which is which. So from what I understand bgmask is a black image with a transparent square of 13px x 13px, and the transition circle is a transparent circle with black around it and the bgOverlay is just a black box which is for fading out. Am I correct? I wish there was a more a more memory efficient way. maybe I can do this with SKShapeNodes instead of SKSpriteNodes. SKSpriteNodes means that it will take unnecessary memory for textures which are the size of the device screen resolution. – plawres Feb 03 '17 at 16:37
  • 1
    Do you know what the memory footprint of using shape nodes is? (assuming you can get it to work with them). I ran the images through tinyPNG and they ended up at 38.6kb for the 3 images. That's a pretty low footprint. I spent over an hour on your setup yesterday trying to use SKCropNode and and blend modes unsuccessfully. Therefore in my opinion; to answer your question of is there a better way. Yes this is better, because it works. – Ron Myschuk Feb 03 '17 at 16:44
  • Please notes that the memory footprint is different to the hard disk size. While the hard disk file size is 38.6kb, in memory it is different. for iPad Pro which has a resolution of 1242 x 2208, that equals to (1242 * 2208 * 2) / 1000,000 (assuming 16bits per pixel) which is around 5MB of memory. As SKShapeNodes do not store individual pixel values (R,G,B,A), I am hoping this will be much more memory efficient (However, I know that shape nodes might be more CPU intensive). – plawres Feb 03 '17 at 17:07
0

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.

plawres
  • 313
  • 4
  • 19