Swiftで遊ぼう! on Hatena

あしたさぬきblogでやってた初心者オヤジのiOSプログラミング奮闘記がHatenaに来ました

Swiftで遊ぼう! - 899 - 過去記事のマイナー調整やってます

いろいろな意味で総復習中。

UIGestureRecognizerの説明ページの内容が古いままだったと思ったら他のページに集約されていました(^_^;)

yataiblue.hatenablog.com

整理をしていってます。

Swiftで遊ぼう! - 898 - Parhfindingのチュートリアル終了

問題も解決して、再びPathfindingのチュートリアルを仕上げます。

実は、年始から非常に忙しく、Swiftの勉強がなおざりになっていました(>_<)

Pathfindingチュートリアルも最期のステップを説明していなかったので簡単に説明して締めくくります。

最期に説明したupdateVisualPathメソッドを次のupdatePathForEntitiesメソッド内でで呼びます。

func updatePathForEntities(entities: [GKEntity]) {
    for entity in entities {
       if let movementComponent = 
           entity.component(ofType: MovementComponent.self) {
           movementComponent.sprite.removeAllActions()
              
            var path = movementComponent.pathToDestination()
            path.remove(at: 0)
            
           // update visual path
            updateVisualPath(path: path)
                
            movementComponent.followPath(path: path)
        }
    }
}

これでシュミレーターをランすると次のように点線が表示されます。凄いですね。タワーを設置するとルートが自動で再計算されて変化していきます。

f:id:yataiblue:20170115095349j:plain


これでいちおうPathfindingのチュートリアルを終了にします。

次はどうしたものか...

Swiftで遊ぼう! - 897 - CGPathの初期化ステップ

昨日の記事のステップ「8」はiOS10で変更になった点線をつくる初期化ステップ...

let dashed = 
       CGPath.init(__byDashing: bezierPath.cgPath, 
                     transform: nil, 
                         phase: 0, 
                       lengths: pattern,
                         count: 2)!

ここで「pattern」と「count」の意味がよく分かりません.... 自分で調べないといけないんですが、なかなか情報に辿りつけないので、どなたかご存じの人がいらっしゃれば教えてくださいm(_ _)m

これだけ。

Swiftで遊ぼう! - 896 - チュートリアルやっと終了に向かって...

やっとGameplayKitの古い*1チュートリアルもやった終盤に入ります。

最後の課題は、Pathfindingを使って得られる道筋の描画用のSKNodeを用意します。GameScene.swiftの冒頭で宣言。

var pathLine: SKNode!

宣言をしたら初期化が必須です。カスタムクラスならinit()イニシャライザーで書くのですが、フレームワークの使用では、多くの場合、初期化メソッドが用意されています*2。SKSceneクラスの場合、didMove(to view: SKView)メソッドなので、次のコードを加えます。

pathLine = SKNode()
addChild(pathLine)

これで受け皿は用意できました。ここにpathLineに描画すれば、画面に出現するわけです。

パスラインを描画しるメソッドを用意します。

func updateVisualPath(path: [GKGridGraphNode]) {
    // 1
    pathLine.removeAllChildren()
    // 2
    var index = 0
    // 3
    for node in path {
        // 4
        let position = 
            pointFor(coordinate: node.gridPosition)
            
        if index + 1 < path.count {
            // 5
            let nextPosition = 
          pointFor(coordinate: path[index + 1].gridPosition)
            // 6
            let bezierPath = UIBezierPath()
            let startPoint = 
                CGPoint(x: position.x, y: position.y)
            let endPoint = 
                CGPoint(x: nextPosition.x, y: nextPosition.y)
            bezierPath.move(to: startPoint)
            bezierPath.addLine(to: endPoint)
            // 7
            let pattern: [CGFloat] = 
                [CGFloat(boxSize / 10), CGFloat(boxSize / 10)]
            // 8
            let dashed = 
                CGPath.init(__byDashing: bezierPath.cgPath, 
                              transform: nil, 
                                  phase: 0, 
                                lengths: pattern, 
                                  count: 2)!
            // 9
            let line = SKShapeNode(path: dashed)
            line.strokeColor = UIColor.black
            pathLine.addChild(line)

        }
        // 10
        index += 1
    }
}

このメソッドを、読んでコードがスラスラと分かるようなら、あなたのプログラミングレベルは中級です。もし、理解できなければまだまだ初級の域を脱していないってことで、コードの説明に入ります。

  1. まずpathLineに加えた全てのノードを除去します。
  2. ここのpathは[GKGridGraphNode]のことで、このクラス出持っているプロパティ「graph」のことで、nodeのパス情報を持っていることになります。そして、indexを「0」にしているのは、このアレー型のパス情報にいくつのnodeがあるかカウントするために使用します。
  3. そしてpathにある個々のnodeで作業するためにforループを使って要素を抜き出します。
  4. nodeの位置情報はint2型なので、描画のためのCGPoint型に変換。
  5. 次に移動すべきnodeの位置をCGPont型に変換してnextPositionに保持させます。
  6. UIBezierPathを使って*3今いるnodeの中点から次のnodeを中点へ線を書きます。
  7. 実践を点線に変更 <- これが理解できない
  8. この現在のnodeから次のnodeに引くBezierPathを使って、SKShapeNodeのインスタンスにして、pathLineに加えます。
  9. pathにあるすべてのnodeを繋げていく点線をpathLineに保持させます。

とこういう流れです。

ちょっと分からない「8」のステップは明日のエントリーに。

*1:iOS 9で最初に紹介されたPathfindingの説明なんで...

*2:初期化メソッドの使用が推奨されている理由は次 -> Swiftで遊ぼう! - 248 - UIViewの初期化ステップ - Swiftで遊ぼう! on Hatena

*3:UIBezierPathの基本事項 -> Swiftで遊ぼう! - 251 - draw: UIBezierPath - Swiftで遊ぼう! on Hatena

Swiftで遊ぼう! - 895 - GameplayKitは結構おもしろい

GameplayKitはiOS9で導入された新しいフレームワークですが、やっぱりゲームの世界は売れる市場なんで、変化と工夫が多岐に渡っていますね。iOS10でもその流れは変わらず、ネットに溢れるチュートリアルが、どれもこれもあっという間に時代遅れの情報になっています。

私がトライしている「GameplayKit with Swift for Beginners — Tower Defense Game — Part 4: Dynamic Pathfinding」も例外ではなく、少々古い内容です。GameplayKitの「Pathfinding」もiOS10でアップデイトされています。

残念なことに、私には新しいAPIの説明をする実力が備わっていないので、このままチュートリアルを進めます。

敵のキャラ(赤い四角)が画面左端に出現して右動いていきます。やぐらをタップして出現させても動きを遮ることができません。次はやぐらで行く手を遮るコードを加えていきます。自動的にルートを再計算して敵キャラの進行が変化するところがPathfindingの真骨頂です。

さて、SpriteKitとGameplayKitのPathfindingを扱う時に混乱しそうになるのが、それぞれの座標系が異なるため、自分で用意したメソッド、coordinateFor()とpointFor()を使って、int2にしたり、CGPointにしなければならないのが億劫です。

まず、createEnemies()メソッドにタワーを設置したときにPathfindingをアップデイトする仕組みを組み込む必要があります。

タワーの設置に関係なく突き進む次のコードを消去します。

// 一時的に以下のコードを加えて動作チェック
let path = movementComponent.pathToDestination()
MovementComponent.followPath(path: path)

そして、アップデイト様のメソッド「 updatePathForEntities(entities: [GKEntity])」を「createTower(atCoordinate: int2) 」の下に加えます。

func updatePathForEntities(entities: [GKEntity]) {
    for entity in entities {
        if let movementComponent = 
            entity.component(ofType: MovementComponent.self) {
            movementComponent.sprite.removeAllActions()
                
            var path = movementComponent.pathToDestination()
            path.remove(at: 0)
                
            // update visual path
                
            movementComponent.followPath(path: path)
        }
    }
}

entityがボード上に加わる度にパスを再計算させるメソッドです。アレー型のエンティティを引数として受け取ります。

エンティティにMovementComponentがあるかどうか判断して、あれば全てのActionを消去します。そして新たにpathTODestination()メソッドを使ってパスの再設定をしますが、スタート地点は現在エンティティが存在する場所になるので、インデックス「0」の位置は消去します。そしてfollowPath()メソッドを使って描画するって話しです。

// update visual path

これは明日説明します。

このメソッドをcreateEnemies()メソッド内で呼びます。

let action = SKAction.run { [unowned self] in
    let movementComponent = 
        enemy.component(ofType: MovementComponent.self)!
    self.addChild(movementComponent.sprite)
                
    self.updatePathForEntities(entities: [enemy])          
}

そしてもう1箇所、 createTower(atCoordinate: int2)メソッドの最期で呼びます。

updatePathForEntities(entities: enemies)

これで敵キャラはタワーを避けながら目的地に進みます。

f:id:yataiblue:20170105173701j:plain

次はパスを描画するステップです。