【Swift】SpriteKitの使い方。Warp Transformationを使って画像をねじ曲げる。
Warp Transformationとは
本記事では、iOS10で登場したWarp Transformation(以下、歪曲変換)について説明する。歪曲変換とは、ノードの一部分をつまんで引き伸ばしたり、縮めたりしたかのように歪曲させる機能である。
これを使えば、木が風に吹かれて曲がったり、衝撃を受けてひん曲がるような表現が可能になる。
歪曲変換を試す
実際に歪曲変換を使ってノードの画像を曲げてみよう。以降の手順を行う前のXcodeプロジェクトをGitHubに置いたので、試してみる方はご利用下さい。⇒「テスト用プロジェクト」
事前準備ではパンダを1匹シーンに追加しておいた。このパンダをこれから歪曲させる。
画像を歪曲させるために「つまみ点」を指定する必要がある。つまみ点を指でつまんで画像を引っ張ったり、縮めたりするイメージだ。
歪曲前のつまみ点をSource Position(以下、歪曲前ポジション)、歪曲先のつまみ点をDestination Position(以下、歪曲先ポジション)といい、この設定値をもとにノードが歪曲される。
つまみ点は任意の数だけ追加できるので、本記事では基本の9個のつまみ点を指定する。画像の左下角を(0,0)、右上角を(1.0, 1.0)としてつまみ点を指定する。
歪曲前ポジションには下図の値を指定する。
歪曲先ポジションには下図の値を指定する。パンダがくの字に歪曲するのがイメージできる。
TestScene.swiftを以下のコードに変更する。つまみ点の配列は上記の⓪地点から順番に格納するので、実際のつまみ点の並び方とイメージが異なる。注意しよう。
歪曲グリッドを作成するときの引数には、つまみ点によって作成されるグリッドの縦横のマス数を指定する。今回の例では2×2マスのグリッドになるので、colums「2」、rows「2」を指定している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
// // TestScene.swift // import Foundation import SpriteKit class TestScene:SKScene { //歪曲前つまみ点 let source: [vector_float2] = [ vector_float2(0, 0), vector_float2(0.5, 0), vector_float2(1, 0), vector_float2(0, 0.5), vector_float2(0.5, 0.5), vector_float2(1, 0.5), vector_float2(0, 1), vector_float2(0.5, 1), vector_float2(1, 1) ] //歪曲先つまみ点 let distination: [vector_float2] = [ vector_float2(0.25, 0), vector_float2(0.75, 0), vector_float2(1.25, 0), vector_float2(-0.25, 0.5), vector_float2(0.25, 0.5), vector_float2(0.75, 0.5), vector_float2(0.25, 1), vector_float2(0.75, 1), vector_float2(1.25, 1) ] //現在シーン設定時の呼び出しメソッド override func didMove(to view: SKView) { //歪曲グリッッドを作成する。 let warp = SKWarpGeometryGrid(columns: 2, rows: 2, sourcePositions: source, destinationPositions: distination) //パンダノードを取得する。 let pandaNode = self.childNode(withName: "panda") as? SKSpriteNode //パンダノードに歪曲グリッドを設定する。 pandaNode!.warpGeometry = warp //歪曲のアクションを作成する。 let action = SKAction.warp(to: warp, duration: 0) //アクションを実行する。 pandaNode!.run(action!) } } |
下図は実行結果。パンダがくの字になった。
歪曲をループさせる
複数の歪曲をつなげてループさせたらどうのようになるか試してみよう。TestScene.swiftを以下のコードに変更する。
左にくの字、右にくの字、顔を長くするの3つの歪曲アクションを無限ループさせている。歪曲をアニメーションさせるにはノードのwarpGeometryプロパティにつまみ点の無い歪曲グリッドを設定する必要があることを覚えておくこと。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
// // TestScene.swift // import Foundation import SpriteKit class TestScene:SKScene { //歪曲前つまみ点 let source: [vector_float2] = [ vector_float2(0, 0), vector_float2(0.5, 0), vector_float2(1, 0), vector_float2(0, 0.5), vector_float2(0.5, 0.5), vector_float2(1, 0.5), vector_float2(0, 1), vector_float2(0.5, 1), vector_float2(1, 1) ] //歪曲先つまみ点1(左にくの字) let distination: [vector_float2] = [ vector_float2(0, 0), vector_float2(0.5, 0), vector_float2(1, 0), vector_float2(-0.25, 0.5), vector_float2(0.25, 0.5), vector_float2(0.75, 0.5), vector_float2(0.25, 1), vector_float2(0.75, 1), vector_float2(1.25, 1) ] //歪曲先つまみ点2(右にくの字) let distination2: [vector_float2] = [ vector_float2(0, 0), vector_float2(0.5, 0), vector_float2(1, 0), vector_float2(0.25, 0.5), vector_float2(0.75, 0.5), vector_float2(1.25, 0.5), vector_float2(-0.25, 1), vector_float2(0.25, 1), vector_float2(0.75, 1) ] //歪曲前つまみ点(顔を長くする) let distination3: [vector_float2] = [ vector_float2(0, 0), vector_float2(0.5, 0), vector_float2(1, 0), vector_float2(0, 0.5), vector_float2(0.5, 0.5), vector_float2(1, 0.5), vector_float2(0, 1.25), vector_float2(0.5, 1.25), vector_float2(1, 1.25) ] //画面タッチ開始時の呼び出しメソッド override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { //パンダノードを取得する。 let pandaNode = self.childNode(withName: "panda") as? SKSpriteNode //歪曲グリッッドを作成する。 let warp1 = SKWarpGeometryGrid(columns: 2, rows: 2, sourcePositions: source, destinationPositions: distination) let warp2 = SKWarpGeometryGrid(columns: 2, rows: 2, sourcePositions: source, destinationPositions: distination2) let warp3 = SKWarpGeometryGrid(columns: 2, rows: 2, sourcePositions: source, destinationPositions: distination3) //つまみ点の無い歪曲グリッドをパンダノードに設定する。 let noWarp = SKWarpGeometryGrid(columns: 2, rows: 2) pandaNode!.warpGeometry = noWarp //アクションを作成する。 let action1 = SKAction.warp(to: warp1,duration: 1.0) let action2 = SKAction.warp(to: warp2,duration: 1.0) let action3 = SKAction.warp(to: warp3,duration: 1.0) //アクションを実行順に並べる。 let allAction = SKAction.sequence([action1!,action2!, action3!]) //無限ループのアクションを作成する。 let actionLoop = SKAction.repeatForever(allAction) //アクションを実行する。 pandaNode!.run(actionLoop) } } |
以下は実際のプレイ動画。画面をタップするとパンダがダンスしているように歪曲がループする。
以下のコードのようにしてアクションインスタンスを1つにしたほうがスマートに実装できそうだが、うまくアニメーションしてくれないので上記コードのような実装にしている。
何か感づいた人は教えて頂けると幸いです。
1 2 |
let action = SKAction.animate(withWarps:[warp1, noWarp, warp2, noWarp, warp3], times: [1, 1, 1, 1, 1]) |
ドラッグで歪曲させる
つまみ点を指でドラッグして自分好みの形に歪曲させてみよう。
下図赤枠の「TestScene.sks」を選択してシーンエディタを開く。黄緑枠のオブジェクトライブラリボタンを押して部品一覧を表示し、一覧にある「Color Sprite」をドラッグ&ドロップでパンダ左下のつまみ点まで運ぶ(紫矢印)。
水色枠のアトリビュートインスペクタボタンを押して設定画面を表示する。Nameに「point0」、SizeにW「20」、H「20」を入力する。User Dataにx(Float)、y(Float)を追加する(黄枠)。
残り7個のつまみ点も同じように追加する。コピペで配置してNameだけ変更したほうが作業が楽だ。
TestScene.swiftを以下のコードに変更する。
画面に触れた指を移動するたびに、パンダノードを歪曲させるアクションとつまみノードを移動させるアクションを実行している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
// // TestScene.swift // import Foundation import SpriteKit class TestScene:SKScene { var pandaNode:SKSpriteNode! var source:[vector_float2] = [] var targetNode:SKSpriteNode! //歪曲前のつまみ点 var list = [(0,0),(0.5,0),(1.0,0),(0,0.5),(0.5,0.5),(1.0,0.5),(0.0,1.0),(0.5,1.0),(1.0,1.0)] //パンダのサイズ var panda_height:Float! var panda_width:Float! //現在シーン設定時の呼び出しメソッド override func didMove(to view: SKView) { //パンダノードを取得する。 pandaNode = self.childNode(withName: "panda") as? SKSpriteNode //パンダノードのサイズを保存する。 panda_height = Float(pandaNode!.frame.height) panda_width = Float(pandaNode!.frame.width) for i in 0 ..< list.count { //つまみ点をノードに設定する。 let node = self.childNode(withName: "point\(i)") as! SKSpriteNode node.userData?.setValue(list[i].0, forKey: "x") node.userData?.setValue(list[i].1, forKey: "y") //歪曲前のつまみ点を作成する。 source.append(vector_float2(node.userData!.value(forKey: "x") as! Float, node.userData!.value(forKey: "y") as! Float)) } } //画面タッチ開始時の呼び出しメソッド override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { //タッチ座標を取得する。 let location = touches.first!.location(in: self) //タッチしたのがつまみノードの場合はターゲットに設定する。 if let node = atPoint(location) as? SKSpriteNode { if(node.name!.contains("point") == true){ targetNode = node } } } //画面タッチ移動時の呼び出しメソッド override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { //タッチ座標を取得する。 let location = touches.first!.location(in: self) //タッチ座標をタイルマップノードを基準にした座標に変換する。 let position = convert(location, to: pandaNode) //ターゲットノードにつまみ点を設定する。 targetNode.userData?.setValue(Float(position.x)/panda_width, forKey: "x") targetNode.userData?.setValue(Float(position.y)/panda_height, forKey: "y") //歪曲先のつまみ点を作成する var dst: [vector_float2] = [] for i in 0 ..< list.count { let dstNode = self.childNode(withName: "point\(i)") as! SKSpriteNode dst.append(vector_float2(dstNode.userData!.value(forKey: "x") as! Float, dstNode.userData!.value(forKey: "y") as! Float)) } //歪曲グリッドを作成する。 let warpGrid = SKWarpGeometryGrid(columns: 2, rows: 2, sourcePositions: source, destinationPositions: dst) //歪曲のアクションを作成する。 let warpAction = SKAction.warp(to: warpGrid, duration: 0) //タッチした位置までつまみノードを移動するアクションを作成する。 let moveAction = SKAction.move(to: CGPoint(x:location.x, y:location.y), duration:0) //アクションを実行する。 pandaNode!.run(warpAction!) targetNode.run(moveAction) } } |
以下は実際のプレイ動画。