There's 2 approaches I could think of.
1) Bend Approach
You could bend your scene using an SKWarpGeometry. Semantically, you'd envision a 2 x 2 quadrant grid on your game scene, described by a 3 x 3 point data structure. You'd move the left and right edges in a little, and make them a little smaller. And you'd make the centre vertical edge a little longer.
Apple's documentation also mentions that essentially any type of node (including an SKEmitterNode), can be warped by putting it into an SKEffectNode, and applying the warp to that. See: https://developer.apple.com/documentation/spritekit/skwarpgeometrygrid/animate_the_warping_of_a_sprite?language=objc.
All we have to do is make an effect node be the root node of everything, and apply the warp to it. Now, this works, but as soon as there's an emitter in it, the warp starts spazzing in seizure-like convulsions. I regard this as a bug. I'll include the code anyway incase its fixed later on, because it's supposed to work according to the documentation.
let sourcePoints = [
// bottom row: left, center, right
vector_float2(0.0, 0.0),
vector_float2(0.5, 0.0),
vector_float2(1.0, 0.0),
// middle row: left, center, right
vector_float2(0.0, 0.5),
vector_float2(0.5, 0.5),
vector_float2(1.0, 0.5),
// top row: left, center, right
vector_float2(0.0, 1.0),
vector_float2(0.5, 1.0),
vector_float2(1.0, 1.0)
]
var destinationPoints = sourcePoints
// Bring lefts in, and make the edge shorter
destinationPoints[0] = vector_float2(0.1, 0.1)
destinationPoints[3] = vector_float2(0.1, 0.4)
destinationPoints[6] = vector_float2(0.1, 0.9)
// Bring rights in, and make the edge shorter
destinationPoints[2] = vector_float2(0.9, 0.1)
destinationPoints[5] = vector_float2(0.9, 0.4)
destinationPoints[8] = vector_float2(0.9, 0.9)
// Make the center edge longer
destinationPoints[1] = vector_float2(0.5, -0.2)
destinationPoints[4] = vector_float2(0.5, 0.7)
destinationPoints[7] = vector_float2(0.5, 1.2)
// Need to set the no warp geometry first
effectNode.warpGeometry = SKWarpGeometryGrid(columns: 2, rows: 2)
let newGeometry = SKWarpGeometryGrid(columns: 2, rows: 2,
sourcePositions: sourcePoints,
destinationPositions: destinationPoints)
effectNode.run(SKAction.warp(to: newGeometry, duration: 1.0)!)
Sources used:
https://www.hackingwithswift.com/example-code/games/how-to-warp-a-sprite-using-skwarpgeometrygrid
http://sound-of-silence.com/?page=blog&sort=recent&count=0
2) Live Copy Approach
This is simpler, there's far less code, and it's a lot more versatile; you can do things like in this Rayman Legends level https://youtu.be/7m5YQrucis8?t=1226. Also, it works.
Basically, we have our master SKView showing the master scene. We'll add a copy SKView showing a copy scene. The copy scene will have only one sprite node in it.
In the master scene's update loop, we'll grab a flattened texture of the scene and assign it to the sprite node in the copy scene. An object that is the scene's delegate can tap into this update loop, so we'll make the view controller conform to SKSceneDelegate.
class Playground_VC: UIViewController, SKSceneDelegate {
var sprite: SKSpriteNode?
override func loadView() {
self.view = SKView()
}
var skView: SKView {
return self.view as! SKView
}
override func viewWillAppear(_ animated: Bool) {
let scene = SKScene(size: self.skView.bounds.size)
scene.backgroundColor = UIColor.clear
scene.delegate = self
self.skView.presentScene(scene)
let e = newEmitter()
scene.addChild(e)
e.position = self.skView.midPoint
self.createCopy()
}
func update(_ currentTime: TimeInterval, for scene: SKScene) {
// Grab the root texture and assign to the sprite that's in the copy scene
let t = self.skView.texture(from: self.skView.scene!)
self.sprite?.texture = t
}
func createCopy() {
// Make a new SKView that's 4 times smaller
var f = self.skView.bounds
f.size.height *= 0.4
f.size.width *= 0.4
let copy = SKView(frame: f)
copy.presentScene(SKScene(size: f.size))
self.view.addSubview(copy)
let sprite = SKSpriteNode(texture: nil, size: f.size)
copy.scene?.addChild(sprite)
self.sprite = sprite
sprite.position = CGPoint(x: f.size.width / 2, y: f.size.height / 2)
}
func newEmitter() -> SKEmitterNode {
return SKEmitterNode(fileNamed: "Particle.sks")!
}
}
Any manipulations you perform on the master SKView, like tilting it in 3D space, shouldn't affect the appearance of the copy, because the game scene is unaffected by those manipulations.
