Swiftで遊ぼう! on Hatena

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

Swiftで遊ぼう! - 893 - チュートリアルをアップデイト中

AppleチュートリアルがSwift 3に対応したので、私の翻訳バージョンもSwift 3に対応しています。

今日は2ページ目です。

yataiblue.hatenablog.com

Swiftで遊ぼう! - 892 - 改訂から新年は始まります

2017年、明けましておめでとうございます!

とうとう今年はiOS開発の勉強を初めて3年目になります。まだAppStoreに自分のアプリを公開できていないへたれオヤジです。

「3週間でアプリを作る」とか、「1月で売れるアプリを作る」なんてタイトルが溢れている中、初心者50歳オヤジが3年目に突入してもアプリを公開できません。実際のところこれが普通のオヤジの進捗状況ではないかと思います。今更才能のある50歳なんてあり得ないでしょう。50歳になると、あと延びるか延びないかは「継続」だけだと思っています。何があっても続けていれば必ず進歩すると信じて勉強を続けています。才能が無くても、歳をとっていても続ければ報われることを信じている人に向けて私は頑張ります。

ということで、GameplayKitの勉強も中途半端という自分勝手な展開ですが、AppleチュートリアルをSwift 3向けに改訂することから2017年をスタートさせます。

これからもよろしくお願いいたします。

yataiblue.hatenablog.com

Swiftで遊ぼう! - 891 - 描画ステップ

大晦日ですが、たんたんとプログラミングを続けます。というのも記事は数日分溜めているからです。本当の大晦日はブログも見ていないでしょう。

さあ、移動のためのメソッドを加えます。

func followPath(path: [GKGridGraphNode]) {
    var sequence = [SKAction]()
        
    for node in path {
        let action = 
            SKAction.move(to: 
                scene.pointFor(coordinate: 
                    node.gridPosition), 
                    duration: 1)
        let update = SKAction.run() { [unowned self] in
            self.coordinate = node.gridPosition
        }
            
        sequence += [action, update]
    }
        
    sprite.run(SKAction.sequence(sequence))
}

このコードもちゃんと追って理解できるようになりました。本当にここまで来るのに時間がかかりました。パスデータを持っている[GKFridGraphNode]データの1つずつの項目をSKActionのmoveに変更してsequenceデータにまとめて、一気にrunさせるステップです。

[unowned self]というキャプチャ・リストも先日勉強した*1ので分かりますね。

これがMovementComponentの全てです。次は敵キャラ(Entity)にこのComponentを実装させていくステップです。

2016年が終わります。

ここまで付き合ってくださった読者の皆さん、ありがとうございました!

来年こそアプリをAppStoreに発表していく時期なのでご期待下さい!

Swiftで遊ぼう! - 890 - GCGraphのパワーを見よう

Pathfindingの力をみていこう。

GameplayKitでコントロールされるゲームボードは、「Graph」の管理下に置かれます。Graphは、位置情報をNodeとして扱います。

そしてNodeの位置情報は「int2」型として保持されます。ゲームに登場するオブジェクトはすべてEntity型で、自分の存在する位置情報は、VisualComponentクラスとして持ちました。動くために移動先の目的地の情報が必要で、MovementComponentに持たせたところが前回のまでの流れです。

このゲームボード(Graph)の真骨頂が、次のメソッドです。

func findPath(from startNode: GKGraphNode, 
                to endNode: GKGraphNode) -> [GKGraphNode]

このメソッドで、道順が導かれるんです。どんな計算式でパスを見つけたか知る必要が無いんです。アルゴリズムは分かりません。知らなくても使えるってのが初心者にいいのかどうか分かりませんが、上級プログラマーになるために、アルゴリズムを知るという作業はしないといけないでしょうね。

私はそこまで深入りしません。若い人は必ず取り組みましょう。

このメソッドを使って、カスタムメソッドを組み立てます。

func pathToDestination() -> [GKGridGraphNode] {
    let current = 
        scene.graph.node(atGridPosition: coordinate)!
    let goal = 
        scene.graph.node(atGridPosition: destination)!
    return scene.graph.findPath(from: current, 
                                  to: goal) as! [GKGridGraphNode]
}

これでルート情報をゲットできます。ルート情報を得たら、これに基づいてオブジェyクト(Entity)を動かす(描画)メソッドを加えていきます。

今日はここまで。

Swiftで遊ぼう! - 889 - 敵を加えていく段階です

VisualComponentクラスを使って画面に表示する機能を付け加えた... とは思わない自分もいるけど、表示機能を加えたとしよう。

次はEntityオブジェクトに動きを加えていきます。

新しいComponentクラスを作ります。またメニューから「File -> New -> File...」を選び、「Cocoa Touch Class」にしてから、「Subclass of:」に「VisualComponent」を入力してから名前を「MovementComponent」にしてプロジェクト内に保存します。

VisualComponentクラスを継承しているところが解せないんですが、「動き」は、表示されているから機能するわけで、描画機能を継承してもいいという説明があります。ゲームに使用される全てのオブジェクトに共通機能なので、これでいいのでしょう。

MovementComponentクラスに必要なプロパティは、たった一つだけです。動いていくゴールの座標情報だけです。

var destination: int2!

では、初期化ステップをコードします。

init(scene: GameScene, 
         sprite: SKSpriteNode, 
     coordinate: int2,
    destination: int2) {
        
    self.destination = destination
        
    super.init(scene: scene, 
              sprite: sprite, 
          coordinate: coordinate)
}
    
required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

ここまでのコーディングはVisualComponentクラスと同じですね。ここから移動のためのメソッドを導入していきます。

これだけ(^^;)

Swiftで遊ぼう! - 888 - やっとTower(turret)をグリッドに並べるよ

次は、やぐら(turret)を画面上に並べていくところです。

コーディングの舞台は、「GameScene.swift」です。ゲームはこの上で展開されます。画面上のタップされた場所にやぐらを出現させるためにイメージを用意します。私は次のイメージを作りました。勝手にコピーして使って構いませんよ。

f:id:yataiblue:20161220182302p:plain

ファイル形式は当然、PNGで、名前を「turrt.png」にして、プロジェクト・ナビゲータにある「Assets.xcassets」にドラッグ&ドロップします。

GameSceneクラス、そのスーパークラスにあたるUIViewクラスが持っている「touchesBegan()」メソッドをオーバライドして実装します。

override func touchesBegan(_ touches: Set<UITouch>, 
                          with event: UIEvent?) {
        
    let touch = touches.first!
    let location = touch.location(in: self)
        
    let coordinate = coordinateFor(point: location)
    // この時点でエラーです

    createTower(atCoordinate: coordinate)
    // この時点でエラーです

}

まず最初の2行で、タッチした位置をCGPoint型の「location」として取り出します。そして次にGameplayKitの座標システム「int2」型に変換するメソッドを用意します。このコードを入力したところで、赤いエラーマークが出ています。

update()メソッドの下に次のヘルパーメソッドを作ります。

// MARK: Unit Changing
    
func coordinateFor(point: CGPoint) -> int2 {
    return int2(Int32((point.x - gridStart.x) / boxSize), 
                Int32((point.y - gridStart.y) / boxSize))
}

この説明はいらないですね。これでどのグリッドが選択されたのか分かります。この情報を使って、createTower()メソッドを発動して、やぐらオブジェクト(Entity)を作って、VisualComponentも使用して画面に表示させるんです。まだ赤いエラーコードが表示されているんで、「// MARK: Unit Changing」のcoordinatFor()メソッドの下に次のコードを加えます。

// MARK: Tower
    
func createTower(atCoordinate: int2) {
        
    if let node = graph.node(atGridPosition: atCoordinate) {
            
        let tower = GKEntity()
            
        let towerSprite = SKSpriteNode(imageNamed: "turret")
        towerSprite.position = pointFor(coordinate: atCoordinate)
        // この時点でエラーです
 
        towerSprite.size = CGSize(width: boxSize * 0.9, 
                                 height: boxSize * 0.9)
            
        let visualComponent = VisualComponent(scene: self, 
                                             sprite: towerSprite, 
                                         coordinate: atCoordinate)
        tower.addComponent(visualComponent)
            
        addChild(towerSprite)
            
        graph.remove([node])
        // updateコードが加わります
    }
}

まず、タップされたgraph領域の座標の位置にnodeがあれば、それを返して、nodeを作ります。SpriteNodeクラスのtowerSpriteをAssets.xcassetsから作って、int2座標システムからCGPoint画面座標システムに変換するpointFor()メソッド(今はエラーになっています)を使って、グリッドの真ん中の情報を持たせて、イメージのサイズも90%にします。VisualComponentクラスのvisualComponentインスタンスを作って、sceneとtowerSpriteと位置情報を結びつけます。これをGKEntityクラスのtowerに組み込んで準備完了です。

しかし、微妙に疑問が残ります。実は、VisualComponentクラスをEntityクラスに持たせても、実際の画面に表示はされません。SpriteKitが絡むと、少し話が複雑になるのかもしれません。結局描画するためにtowerSpriteをSceneに加える作業の「addChild(towerSprite)」が必要になるんです。

2つの座標システムを交互に扱わせているこのチュートリアルはコーディングが悪いのだろうか?私が最初に考えたイメージは、VisualComponentクラスをEntityクラスに組み込んだところで画面表示がされると思いました。プログラマーの皆さん、意見をお願いします。

取りあえず、int2型座標システムからCGPoint型座標システムに変換するメソッドを「// MARK: Unit Changing」に加えます。

func pointFor(coordinate: int2) -> CGPoint {
    return CGPoint(x: CGFloat(coordinate.x) * boxSize + 
                          gridStart.x + boxSize / 2, 
                   y: CGFloat(coordinate.y) * boxSize + 
                          gridStart.y + boxSize / 2)
}

曲がりなりにもこれでタップしたところに「やぐら」が現れます。

f:id:yataiblue:20161220192426j:plain

今日はここまで。

Swiftで遊ぼう! - 887 - Pathfindingを実際実装していきます

GameplayKitを使ったチュートリアルの理解をしようとしています。ゲームはあまりしませんが、タワーディフェンスタイプのゲームは知っています。iPhoneで遊んだこともあります。只今シンプルなタワーディフェンスタイプのチュートリアルに取り組んでいるところです。

今までにやったこと、SpriteKitを使って、sceneの上に碁盤の目状のnodeを並べました。そして、ゲームオブジェクト(Entity)に与える「描画」という機能を持ったComponentクラス、「VisulaComponent」を用意しました。

今日は、この碁盤の目状のゲームボードで、タップしたところにタワー(やぐら)を出現させるメカニズムを組み込みます。そして、敵キャラが、ゲームボードの端から出現して、反対側に向かって動くところに「やぐら」を置いても、Pathifinding機能を使って、新しいルートを再計算させるという仕組みです。

まずルート計算をするために、領域が必要です。当然、GameSceneクラスがその領域情報を持っています。次のプロパティをGameScene.swiftに加えます。

var graph: GKGridGraph<GKGridGraphNode>!

これの初期化は、「didMove()」メソッドを使います。createGrid()メソッドの下に次のコードを加えます。

graph = GKGridGraph(fromGridStartingAt: int2(0, 0), 
                                 width: Int32(width), 
                                height: Int32(height), 
                      diagonalsAllowed: false)

このメソッドをcreateGrid()メソッドの後で呼ぶため、sceneにnodeを加えた情報を使います。最後の「diagonalsAllowed」は、グリッド間を交差して動けるかどうか判断させています。falseにすると交差して動けないということです。

これで、Pathfinding機能が有効な領域となるgraphの初期化ができました。

これだけです。