Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SpriteKit action like "Skew" or "Distort"

Is it possible to create an SKAction for SKSpriteNode in SpriteKit that generates the same effect as "Photoshop" with the Edit->Transform->Distort option?

Example:

Distort image

like image 886
Maetschl Avatar asked Jan 23 '26 09:01

Maetschl


2 Answers

I solve with this implementation:

Swift 5

extension SKSpriteNode {

    func addSkew(value: CGFloat = -1){

        var effectNode = SKEffectNode()
        effectNode.shouldRasterize = true
        effectNode.shouldEnableEffects = true
        effectNode.addChild(SKSpriteNode(texture: texture))
        effectNode.zPosition = 1
        let transform = CGAffineTransform(a:  1    , b:  0,
                                          c:  value, d:  1,
                                          tx: 0    , ty: 0)
        let transformFilter = CIFilter(name: "CIAffineTransform")!
        transformFilter.setValue(transform, forKey: "inputTransform")
        effectNode.filter = transformFilter
        addChild(effectNode)
        texture = nil

    }

}
like image 169
Maetschl Avatar answered Jan 25 '26 04:01

Maetschl


You can create a skew using a 1x1 warp mesh. This is supported in iOS10.0+.

This extension receives the skew angle in degrees, and distorts around the anchor point of the given sprite.

Swift 4.2

extension SKWarpGeometryGrid {

    public static var skewPosGridZero:[float2] {
        get {
            return [float2(0.0, 0.0), float2(1.0, 0.0),
                    float2(0.0, 1.0), float2(1.0, 1.0)]
        }
    }

    public static func skewXPosGrid(_ skewX: CGFloat, node:SKSpriteNode? = nil) -> [float2] {

        let anchorY:Float = Float(node?.anchorPoint.y ?? 0.5)  
        var skewPosGrid = skewPosGridZero
        let offsetX = Float(tan(skewX.degToRad()) * (node == nil ? 1.0 : (node!.size.height/node!.size.width)) )
        skewPosGrid[2][0] += offsetX * (1.0 - anchorY)
        skewPosGrid[3][0] += offsetX * (1.0 - anchorY)
        skewPosGrid[0][0] -= offsetX * anchorY
        skewPosGrid[1][0] -= offsetX * anchorY

        return skewPosGrid

    }

    public static func skewYPosGrid(_ skewY: CGFloat, node:SKSpriteNode? = nil) -> [float2] {

        let anchorX:Float = Float(node?.anchorPoint.x ?? 0.5)  
        var skewPosGrid = skewPosGridZero
        let offsetY = Float(tan(skewY.degToRad()) * (node == nil ? 1.0 : (node!.size.width/node!.size.height)) )
        skewPosGrid[1][1] += offsetY * (1.0 - anchorX)
        skewPosGrid[3][1] += offsetY * (1.0 - anchorX)
        skewPosGrid[0][1] -= offsetY * anchorX
        skewPosGrid[2][1] -= offsetY * anchorX

        return skewPosGrid

    }

    public static func skewX(_ angle: CGFloat, node:SKSpriteNode? = nil) -> SKWarpGeometryGrid {

        return SKWarpGeometryGrid(columns: 1, rows: 1, sourcePositions: skewPosGridZero, destinationPositions: skewXPosGrid(angle, node:node))

    }
    public static func skewY(_ angle: CGFloat, node:SKSpriteNode? = nil) -> SKWarpGeometryGrid {

        return SKWarpGeometryGrid(columns: 1, rows: 1, sourcePositions: skewPosGridZero, destinationPositions: skewYPosGrid(angle, node:node))

    }

    public static func skewZero() -> SKWarpGeometryGrid {
        return SKWarpGeometryGrid(columns: 1, rows: 1)
    }

}

Example animation:

    let spriteNode = SKSpriteNode(imageNamed: "tex")
    spriteNode.anchorPoint = CGPoint(x:0.25, y:1.0)
    let skewA = SKWarpGeometryGrid.skewX(-45.0, node: spriteNode)
    let skewB = SKWarpGeometryGrid.skewX(45.0, node: spriteNode)
    spriteNode.warpGeometry = skewB
    if let skewActionA = SKAction.warp(to: skewA, duration: 3.0),
        let skewActionB = SKAction.warp(to: skewB, duration: 3.0){
        // Individual easing
        skewActionA.timingMode = .easeInEaseOut
        skewActionB.timingMode = .easeInEaseOut
        spriteNode.run(SKAction.repeatForever(SKAction.sequence([skewActionA,skewActionB])))
    }
like image 34
Loks Avatar answered Jan 25 '26 06:01

Loks