読者です 読者をやめる 読者になる 読者になる

Swiftで遊ぼう! on Hatena

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

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

今日はここまで。