SpriteKit是否支持精灵/纹理/形状的密集镶嵌,以便可以自由扭曲?

时间:2013-11-04 23:31:28

标签: ios iphone ipad opengl-es sprite-kit

例如,如果您有蹦床的图像,并且有一个跳跃的角色。然后你想要制作蹦床在中心弯曲的动画。

要做到这一点,我必须采用位图并将其应用于密集镶嵌的OpenGL ES网格。然后将纹理应用于它。然后变形网格。

SpriteKit是否支持此功能,还是只能按原样显示纹理?

2 个答案:

答案 0 :(得分:4)

使用iOS 10,添加了SKWarpGeometry,允许您变形精灵。使用包含八个控制点的源和目标网格定义扭曲,这些点定义扭曲变换; Sprite Kit将处理曲面细分和其他低级细节。

您可以直接为精灵设置包裹几何体,或使用SKActions设置变形动画。

SKAction.animate(withWarps: [SKWarpGeometry], times: [NSNumber]) ->     SKAction?
SKAction.animate(withWarps: [SKWarpGeometry], times: [NSNumber], restore: Bool) -> SKAction?
SKAction.warp(to: SKWarpGeometry, duration: TimeInterval) -> SKAction?

2016年11月更新

在Xcode 8+上,可以在游乐场中使用以下代码片段:拖动控制点,看看生成的SKSpriteNode会相应地变形。

Example

// SpriteKit warp geometry example
// Copy the following in an Xcode 8+ playground
// and make sure your Assistant View is open
// 


import UIKit
import PlaygroundSupport
import SpriteKit


let view = SKView(frame: CGRect(x: 0, y: 0, width: 480, height: 320))

let scene = SKScene(size: view.frame.size)


view.showsFPS = true
view.presentScene(scene)

PlaygroundSupport.PlaygroundPage.current.liveView = view


func drawCanvas1(frame: CGRect = CGRect(x: 0, y: 0, width: 200, height: 200)) -> UIImage {

    UIGraphicsBeginImageContext(frame.size)

    //// Star Drawing
    let starPath = UIBezierPath()
    starPath.move(to: CGPoint(x: frame.minX + 100.5, y: frame.minY + 24))
    starPath.addLine(to: CGPoint(x: frame.minX + 130.48, y: frame.minY + 67.74))
    starPath.addLine(to: CGPoint(x: frame.minX + 181.34, y: frame.minY + 82.73))
    starPath.addLine(to: CGPoint(x: frame.minX + 149, y: frame.minY + 124.76))
    starPath.addLine(to: CGPoint(x: frame.minX + 150.46, y: frame.minY + 177.77))
    starPath.addLine(to: CGPoint(x: frame.minX + 100.5, y: frame.minY + 160))
    starPath.addLine(to: CGPoint(x: frame.minX + 50.54, y: frame.minY + 177.77))
    starPath.addLine(to: CGPoint(x: frame.minX + 52, y: frame.minY + 124.76))
    starPath.addLine(to: CGPoint(x: frame.minX + 19.66, y: frame.minY + 82.73))
    starPath.addLine(to: CGPoint(x: frame.minX + 70.52, y: frame.minY + 67.74))
    starPath.close()
    UIColor.red.setFill()
    starPath.fill()
    UIColor.green.setStroke()
    starPath.lineWidth = 10
    starPath.lineCapStyle = .round
    starPath.lineJoinStyle = .round
    starPath.stroke()

    let image = UIGraphicsGetImageFromCurrentImageContext()!
    UIGraphicsEndImageContext()
    return image
}

class ControlNode : SKShapeNode {

    override init() {
        super.init()
        self.path = CGPath(ellipseIn: CGRect.init(x: 0, y: 0, width: 10, height: 10), transform: nil)
        self.fillColor = UIColor.red
        self.isUserInteractionEnabled = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    typealias UpdateHandler = (ControlNode) -> ()
    var positionUpdated: UpdateHandler? = nil

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.fillColor = UIColor.yellow
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let touch = touches.first, let parent = self.parent {
            let pos = touch.location(in: parent).applying(CGAffineTransform(translationX: -5, y: -5))
            self.position = pos
            positionUpdated?(self)
        }
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.fillColor = UIColor.red
    }

    var warpPosition: vector_float2 {
        if let parent = self.parent {
            let size = parent.frame.size
            let pos = self.position.applying(CGAffineTransform(translationX: -size.width/2 + 5, y: -size.height/2 + 5))
            return vector_float2(
                x: Float(1 + pos.x / size.width),
                y: Float(1 + pos.y / size.height)
            )
        }
        return vector_float2(x: 0, y: 0)
    }
}

extension SKSpriteNode {
    func addControlNode(x: CGFloat, y: CGFloat) -> ControlNode {
        let node = ControlNode()
        node.position = CGPoint(
            x: x * frame.width - frame.width / 2 - 5,
            y: y * frame.height - frame.height / 2 - 5
        )
        self.addChild(node)
        return node
    }
}




let texture = SKTexture(image: drawCanvas1())
let image = SKSpriteNode(texture: texture)
image.position = CGPoint(x: 200, y: 160)
scene.addChild(image)

let controlPoints: [(x:CGFloat, y:CGFloat)] = [
    (0, 0),
    (0.5, 0),
    (1.0, 0),
    (0, 0.5),
    (0.5, 0.5),
    (1.0, 0.5),
    (0, 1.0),
    (0.5, 1.0),
    (1.0, 1.0),
]

let sourcePositions = controlPoints.map {
    vector_float2.init(Float($0.x), Float($0.y))
}

var controlNodes: [ControlNode] = []

controlPoints.forEach { controlNodes.append(image.addControlNode(x: $0.x, y: $0.y)) }


for node in controlNodes {
    node.positionUpdated = {
        [weak image]
        _ in
        let destinationPoints = controlNodes.map {
            $0.warpPosition
        }
        image?.warpGeometry = SKWarpGeometryGrid(columns: 2, rows: 2, sourcePositions: sourcePositions, destinationPositions: destinationPoints)
    }
}

PS:我提到了SKWarpGeometry文档。

答案 1 :(得分:1)

修改:使用iOS 10 / tvOS 10 / macOS 10.12中提供的SKWarpGeometry API,查看@Benzi's answer以获得更好的选项。在这些版本发布之前,Sprite Kit只能将纹理显示为广告牌 - 此答案的其余部分解决了2016年之前的情况,如果您的目标是旧设备,这可能仍然相关。

但是,您可以使用SKEffectNode和其中一个几何失真Core Image滤镜来获得您想要的失真效果。 (例如,CIBumpDistortion。)在游戏逻辑代码中保存对效果节点或其filter属性的引用(在SKScene子类中或您放置的任何其他位置),并且可以调整过滤器的每个动画帧使用setValue:forKey:的参数。