#前回までの記事
この記事は、効率的にゲームをつくるためのフレームワーク②の続きです。GameplayKitの概要を説明して、サンプルプロジェクトの準備をしています。
#この記事の概要
プレイヤーコントロールコンポーネントを定義して、いくつかのエンティティに実装していきます。
プレイヤーコントロールコンポーネントを実装したエンティティのみが、プレイヤーからのインタラクション(画面のタップなど)に反応することができます。
#手順
プレイヤーコントロールコンポーネントを定義
エンティティにプレイヤーコントロールコンポーネントを追加
ジオメトリコンポーネントにノードを弾く機能を実装
プレイヤーコントロールコンポーネントにジャンプ機能を実装
シーンからコンポーネントのジャンプ機能を呼び出せるようにする
コンポーネントシステムにコンポーネントを追加
エンティティの生成を修正
ビューコントローラにIBActionメソッドを実装
##プレイヤーコントロールコンポーネントを定義
メニューバーから「File > New > File... > iOS > Source > Swift File」を選択してファイルを新規作成(PlayerControlComponent.swift)する。
SceneKit
とGameplayKit
をインポートする。
PlayerControlComponent
クラスを定義する。
import SceneKit
import GameplayKit
class PlayerControlComponent: GKComponent {
}
##エンティティにプレイヤーコントロールコンポーネントを追加
ファクトリメソッドに引数wantsPlayerControlComponent
を定義する。
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
クラスに、ノードを上方向に弾く機能を実装する。
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()
メソッドを宣言する。
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()
メソッドを実装する。
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()
メソッドを実装して、イニシャライザで呼び出す。
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クラスの初期化時に、各色エンティティが所有しているコンポーネントをコンポーネントシステムに追加しておく。
こうしておけば、コンポーネントシステムからすべてのコンポーネントの機能を呼び出すことができる。
##エンティティの生成
ファクトリメソッドの実行時に、グリーンとブルーのボックスだけプレイヤーからのインタラクションを受け付けるように変更する。
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接続する。
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()
}
}
##ビルド
画面をタップして、グリーンとブルーのボックスだけが跳ね上がることを確認する。