【GameplayKit】効率的にゲームをつくるためのフレームワーク③

#前回までの記事
この記事は、[効率的にゲームをつくるためのフレームワーク②](http://qiita.com/chino_tweet/items/bb1b28e3910d275bd479)の続きです。GameplayKitの概要を説明して、サンプルプロジェクトの準備をしています。

#この記事の概要
プレイヤーコントロールコンポーネントを定義して、いくつかのエンティティに実装していきます。
プレイヤーコントロールコンポーネントを実装したエンティティのみが、プレイヤーからのインタラクション（画面のタップなど）に反応することができます。

#手順
プレイヤーコントロールコンポーネントを定義
エンティティにプレイヤーコントロールコンポーネントを追加
ジオメトリコンポーネントにノードを弾く機能を実装
プレイヤーコントロールコンポーネントにジャンプ機能を実装
シーンからコンポーネントのジャンプ機能を呼び出せるようにする
コンポーネントシステムにコンポーネントを追加
エンティティの生成を修正
ビューコントローラにIBActionメソッドを実装

##プレイヤーコントロールコンポーネントを定義
メニューバーから「File > New > File... > iOS > Source > Swift File」を選択してファイルを新規作成（PlayerControlComponent.swift）する。
`SceneKit`と`GameplayKit`をインポートする。
`PlayerControlComponent`クラスを定義する。

```ruby:PlayerControlComponent.swift
import SceneKit
import GameplayKit

class PlayerControlComponent: GKComponent {
    
}
```

##エンティティにプレイヤーコントロールコンポーネントを追加
ファクトリメソッドに引数`wantsPlayerControlComponent`を定義する。

```ruby:Game.swift
    func makeBoxEntity(forNodeWithName name: String, wantsPlayerControlComponent: Bool = false) -> GKEntity {
        let node = scene.rootNode.childNode(withName: name, recursively: false)
        guard let boxNode = node else { fatalError("no exist \(name) node.") }
        
        let box = GKEntity()
        let geometryComponent = GeometryComponent(geometryNode: boxNode)
        box.addComponent(geometryComponent)
        
        if wantsPlayerControlComponent {
            let playerControlComponent = PlayerControlComponent()
            box.addComponent(playerControlComponent)
        }
        print("box is added \(box.components)")
        return box
    }
```

Bool型`wantsPlayerControlComponent`がtrueの場合だけ、エンティティ`box`に`PlayerControlComponent`オブジェクトを追加しています。
なお、引数`wantsPlayerControlComponent`は初期値にfalseを設定することで、省略可能にしています。

##ジオメトリコンポーネントにノードを弾く機能を実装
`GeometryComponent`クラスに、ノードを上方向に弾く機能を実装する。

```ruby:GeometryComponent.swift
class GeometryComponent: GKComponent {
    
    let geometryNode: SCNNode
    
    init(geometryNode: SCNNode) {
        self.geometryNode = geometryNode
        super.init()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func applyImpulse(_ vector: SCNVector3) {
        geometryNode.physicsBody?.applyForce(vector, asImpulse: true)
    }    
}
```

ジオメトリコンポーネントが参照しているシーンノードを`vector`方向に弾くことで、ジャンプしているような動作を表現しています。`applyImpulse()`メソッドは、プレイヤーコントロールコンポーネントから呼び出されることになります。

##プレイヤーコントロールコンポーネントにジャンプ機能を実装
メンバプロパティ`geometryComponent`と`jump()`メソッドを宣言する。

```ruby:PlayerControlComponent.swift
class PlayerControlComponent: GKComponent {
    
    var geometryComponent: GeometryComponent? {
        return self.entity?.component(ofType: GeometryComponent.self)
    }
    
    func jump() {
        let jumpVector = SCNVector3(0, 2, 0)
        geometryComponent?.applyImpulse(jumpVector)
    }    
}
```

`geometryComponent`プロパティは、自身が現時点で所属しているエンティティから、そのエンティティが所有しているジオメトリコンポーネントを格納している。
`jump()`メソッドは、そのジオメトリコンポーネントから`applyImpulse()`メソッドを呼び出して、ジャンプさせる。この`jump()`メソッドは、シーンから呼ばれることになる。

##シーンからコンポーネントのジャンプ機能を呼び出せるようにする
Gameクラスに`GKComponentSystem`型の`playerControlComponentSystem`プロパティを宣言する。
`jumpBoxes()`メソッドを実装する。

```ruby:Game.swift
class Game: NSObject {
    
    let scene = SCNScene(named: "GameScene.scn")!

    let playerControlComponentSystem = GKComponentSystem(componentClass: PlayerControlComponent.self)
    
    var entities = [GKEntity]()
    
    override init() {
        super.init()
        setUpEntities()
    }
    
    func setUpEntities() {
        let redBoxEntity    = makeBoxEntity(forNodeWithName: "redBox")
        let yellowBoxEntity = makeBoxEntity(forNodeWithName: "yellowBox")
        let greenBoxEntity  = makeBoxEntity(forNodeWithName: "greenBox")
        let blueBoxEntity   = makeBoxEntity(forNodeWithName: "blueBox")
        let purpleBoxEntity = makeBoxEntity(forNodeWithName: "purpleBox")
        
        entities = [redBoxEntity, yellowBoxEntity, greenBoxEntity, blueBoxEntity, purpleBoxEntity]
    }
    
    func jumpBoxes() {
        let playerControlComponents = playerControlComponentSystem.components
        for case let component as PlayerControlComponent in playerControlComponents {
            component.jump()
        }
    }
    
    func makeBoxEntity(forNodeWithName name: String, wantsPlayerControlComponent: Bool = false) -> GKEntity {
        let node = scene.rootNode.childNode(withName: name, recursively: false)
        guard let boxNode = node else { fatalError("no exist \(name) node.") }
        
        let box = GKEntity()
        let geometryComponent = GeometryComponent(geometryNode: boxNode)
        box.addComponent(geometryComponent)
        
        if wantsPlayerControlComponent {
            let playerControlComponent = PlayerControlComponent()
            box.addComponent(playerControlComponent)
        }
        
        return box
    }
}
```

`playerControlComponentSystem`プロパティは、各色エンティティが所有しているプレイヤーコントロールコンポーネントを保持する配列になる。格納するべき値は、Gameクラスの初期化時に設定される。
`jumpBoxes()`メソッドは、すべてのプレイヤーコントロールコンポーネントの`jump()`メソッドを呼び出している。


###コンポーネントシステムにコンポーネントを追加
`addComponentsToComponentSystems()`メソッドを実装して、イニシャライザで呼び出す。

```ruby:Game.swift
class Game: NSObject, SCNSceneRendererDelegate {
    
    let scene = SCNScene(named: "GameScene.scn")!

    let playerControlComponentSystem = GKComponentSystem(componentClass: PlayerControlComponent.self)
    
    var entities = [GKEntity]()
    
    override init() {
        super.init()
        
        setUpEntities()
        addComponentsToComponentSystems()
    }
    
    func setUpEntities() {
        let redBoxEntity    = makeBoxEntity(forNodeWithName: "redBox")
        let yellowBoxEntity = makeBoxEntity(forNodeWithName: "yellowBox")
        let greenBoxEntity  = makeBoxEntity(forNodeWithName: "greenBox",
                                            wantsPlayerControlComponent: true)
        let blueBoxEntity   = makeBoxEntity(forNodeWithName: "blueBox",
                                            wantsPlayerControlComponent: true)
        let purpleBoxEntity = makeBoxEntity(forNodeWithName: "purpleBox")
        
        entities = [redBoxEntity, yellowBoxEntity, greenBoxEntity, blueBoxEntity, purpleBoxEntity]
        
    }
    
    func addComponentsToComponentSystems() {
        for box in entities {
            playerControlComponentSystem.addComponent(foundIn: box)
        }
    }
    
    func jumpBoxes() {
        print("Called jumpBoxes Method.")
        let playerControlComponents = playerControlComponentSystem.components
        print("Components are \(playerControlComponents)")
        for case let component as PlayerControlComponent in playerControlComponents {
            print("jump Method is Calling!")
            component.jump()
        }
    }
    
    func makeBoxEntity(forNodeWithName name: String, wantsPlayerControlComponent: Bool = false) -> GKEntity {
        let node = scene.rootNode.childNode(withName: name, recursively: false)
        guard let boxNode = node else { fatalError("no exist \(name) node.") }
        
        let box = GKEntity()
        let geometryComponent = GeometryComponent(geometryNode: boxNode)
        box.addComponent(geometryComponent)
        
        if wantsPlayerControlComponent {
            let playerControlComponent = PlayerControlComponent()
            box.addComponent(playerControlComponent)
        }
        print("box is added \(box.components)")
        return box
    }    
}
```

Gameクラスの初期化時に、各色エンティティが所有しているコンポーネントをコンポーネントシステムに追加しておく。
こうしておけば、コンポーネントシステムからすべてのコンポーネントの機能を呼び出すことができる。

##エンティティの生成
ファクトリメソッドの実行時に、グリーンとブルーのボックスだけプレイヤーからのインタラクションを受け付けるように変更する。

```ruby:Game.swift
    func setUpEntities() {
        let redBoxEntity    = makeBoxEntity(forNodeWithName: "redBox")
        let yellowBoxEntity = makeBoxEntity(forNodeWithName: "yellowBox")
        let greenBoxEntity  = makeBoxEntity(forNodeWithName: "greenBox",
                                            wantsPlayerControlComponent: true)
        let blueBoxEntity   = makeBoxEntity(forNodeWithName: "blueBox",
                                            wantsPlayerControlComponent: true)
        let purpleBoxEntity = makeBoxEntity(forNodeWithName: "purpleBox")
        
        entities = [redBoxEntity, yellowBoxEntity, greenBoxEntity, blueBoxEntity, purpleBoxEntity]
    }
```

`wantsPlayerControlComponent`引数を`true'にしたエンティティにだけ、エンティティにプレイヤーコントロールコンポーネントが追加される。（グリーンとブルー）

##ビューコントローラにIBActionメソッドを実装
ビューコントローラに`UITapGestureRecognizer`を配置する。
`UITapGestureRecognizer`オブジェクトを`handleTap()`メソッドとIBAction接続する。

```ruby:GameViewController.swift
class GameViewController: UIViewController {

    let game = Game()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        guard let scnView = view as? SCNView else { fatalError("シーンビュー生成失敗") }
        scnView.backgroundColor = UIColor.lightGray
        scnView.scene = game.scene
    }

    @IBAction func handleTap(_ sender: UITapGestureRecognizer) {
        game.jumpBoxes()
    }
}
```

##ビルド
画面をタップして、グリーンとブルーのボックスだけが跳ね上がることを確認する。

#続き
[効率的にゲームをつくるためのフレームワーク④](http://qiita.com/chino_tweet/items/361cb6e7e1043767d776)