I'm trying to apply a warp/distortion effect to all SKSpriteNodes that pass through a fixed rectangle sized area on the screen. Shown in the image below, the same SKSpriteNode will start at the top of the screen and work its way down allowing the rectangle distortion filter to warp the node as it passes through.
I've tried using a SKEffectNode shown in the code below. However, I couldn't set a fixed height and width value to the SKEffectNode which later gave me inconsistent warp effects due the SKEffectNode constantly changing its height to accommodate all the SKSpritNode children.
I'm wondering if there is another way to achieve this effect or if I'm missing something with the SKEffectNode. Ideally I'd like the the warp to effect any SKNode that passes under it without the need to add and remove children.
Any information would be much appreciated.
Warp effect I'm trying to achieve above and current SKEffectNode code below.
func warpToUpEffectNode(effectNode:SKEffectNode, view:SKView){
effectNode.zPosition = priorityPos.upEffectNodeZ
let destinationPositions: [vector_float2] = [
vector_float2(-0.1, 1), vector_float2(0.5, 1.3), vector_float2(1.1, 1),
vector_float2(0.1, 0.5), vector_float2(0.5, 0.5), vector_float2(0.9, 0.5),
vector_float2(-0.1, 0), vector_float2(0.5, -0.3), vector_float2(1.1, 0)
]
let warpGeometryGrid = SKWarpGeometryGrid(columns: 10,rows: 1)
effectNode.warpGeometry = warpGeometryGrid.replacingByDestinationPositions(positions: destinationPositions)
}
CodePudding user response:
you can accomplish this using render-to-texture.
first, put all of your scene elements into a single large node, i'm calling it container
then set up your viewport area, the part you want to warp. it's a bit tricky because you also have to crop it (otherwise you'll see the fringe of the warped shape)
/*
create a crop node with
- mask
- a visible frame
- a warpable spritenode
*/
viewport_warp = SKSpriteNode(color: .white, size: CGSize(width: 150, height: 150))
let viewport_frame = SKShapeNode(rectOf: viewport_warp.size, cornerRadius: 15)
viewport_frame.strokeColor = .black
viewport_frame.zPosition = 3
viewport_warp.addChild(viewport_frame)
let viewport_mask = SKShapeNode(rectOf: viewport_warp.size, cornerRadius: 15)
viewport_mask.fillColor = .black
let cropNode = SKCropNode()
cropNode.zPosition = 2
cropNode.maskNode = viewport_mask
cropNode.addChild(viewport_warp)
addChild(cropNode)
then set up your warp geometry
//warp the geometry of the spritenode
let PINCH_OFFSET:Float = 0.1
let dst = [
// bottom row: left, center, right
vector_float2(0.0, 0.0),
vector_float2(0.5, 0.0 - PINCH_OFFSET),
vector_float2(1.0, 0.0),
// middle row: left, center, right
vector_float2(0.0 - PINCH_OFFSET, 0.5),
vector_float2(0.5, 0.5),
vector_float2(1.0 PINCH_OFFSET, 0.5),
// top row: left, center, right
vector_float2(0.0, 1.0),
vector_float2(0.5, 1.0 PINCH_OFFSET),
vector_float2(1.0, 1.0)
]
let warpGeometryGrid = SKWarpGeometryGrid(columns: 2,rows: 2)
viewport_warp.warpGeometry = warpGeometryGrid.replacingByDestinationPositions(positions: dst)
and finally, do render-to-texture on the container
and update the texture of your spritenode
override func update(_ currentTime: TimeInterval) {
let cropped_viewport = viewport_warp.frame.insetBy(dx: 10, dy: 10) //optional: adds magnification effect
let texture:SKTexture? = self.view?.texture(from:container, crop:cropped_viewport)
viewport_warp.texture = texture
}