Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

前回までの記事

この記事は、効率的にゲームをつくるためのフレームワーク②の続きです。GameplayKitの概要を説明して、サンプルプロジェクトの準備をしています。

この記事の概要

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

手順

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

プレイヤーコントロールコンポーネントを定義

メニューバーから「File > New > File... > iOS > Source > Swift File」を選択してファイルを新規作成(PlayerControlComponent.swift)する。
SceneKitGameplayKitをインポートする。
PlayerControlComponentクラスを定義する。

PlayerControlComponent.swift
import SceneKit
import GameplayKit

class PlayerControlComponent: GKComponent {

}

エンティティにプレイヤーコントロールコンポーネントを追加

ファクトリメソッドに引数wantsPlayerControlComponentを定義する。

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の場合だけ、エンティティboxPlayerControlComponentオブジェクトを追加しています。
なお、引数wantsPlayerControlComponentは初期値にfalseを設定することで、省略可能にしています。

ジオメトリコンポーネントにノードを弾く機能を実装

GeometryComponentクラスに、ノードを上方向に弾く機能を実装する。

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()メソッドは、プレイヤーコントロールコンポーネントから呼び出されることになります。

プレイヤーコントロールコンポーネントにジャンプ機能を実装

メンバプロパティgeometryComponentjump()メソッドを宣言する。

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()メソッドを実装する。

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()メソッドを実装して、イニシャライザで呼び出す。

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

エンティティの生成

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

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接続する。

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()
    }
}

ビルド

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

続き

効率的にゲームをつくるためのフレームワーク④

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away