GameplayKitの「Entities and Components」を試してみました。
Entities and Componentsとは
Entities and Componentsとは、コンポーネント指向プログラミングをサポートするものです。
コンポーネント指向プログラミングとは、そのオブジェクトの性質を継承などではcomponentの追加を使って表すものです。
例えばタップ時にジャンプするオブジェクトにはジャンプコンポーネントを追加を、ランダムに移動するオブジェクトにはランダム移動コンポーネントの追加をすると言った具合です。
そうすることで、簡単に1つのオブジェクトが複数の性質を持たせる事ができます。
Entities and Componentsの実装
早速実装してみます。
今回はNodeを2つ作って、片方は横移動+タップで回転、もう片方は縦移動+タップで回転するようにします。
まずは画面にNodeを2つ表示します。
import SpriteKit import GameplayKit class GameScene: SKScene { let node1 = SKSpriteNode(color: UIColor(red: 0.1, green: 0.8, blue: 0.8, alpha: 1), size: CGSize(width: 100, height: 100)) let node2 = SKSpriteNode(color: UIColor(red: 0.8, green: 0.8, blue: 0.1, alpha: 1), size: CGSize(width: 100, height: 100)) override func didMove(to view: SKView) { node1.position.x = -100 node2.position.x = 100 addChild(node1) addChild(node2) } }
次にnode1は右方向、node2は上方向に動くようにします。
まずは上方向に移動する性質を表すHorizontalComponentクラスと横方向に移動する性質を表すVerticalComponentクラスを作ります。
class HorizontalComponent: GKComponent { let node: SKNode init(node: SKNode) { self.node = node super.init() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func update(deltaTime seconds: TimeInterval) { node.position.x += 1 } } class VerticalComponent: GKComponent { let node: SKNode init(node: SKNode) { self.node = node super.init() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func update(deltaTime seconds: TimeInterval) { node.position.y += 1 } }
続けて今作ったクラスをnode1、node2に適用します。
import SpriteKit import GameplayKit class GameScene: SKScene { let horizontalComponentSystem = GKComponentSystem(componentClass: HorizontalComponent.self) let verticalComponentSystem = GKComponentSystem(componentClass: VerticalComponent.self) var lastTime: TimeInterval = 0 let node1 = SKSpriteNode(color: UIColor(red: 0.1, green: 0.8, blue: 0.8, alpha: 1), size: CGSize(width: 100, height: 100)) let node2 = SKSpriteNode(color: UIColor(red: 0.8, green: 0.8, blue: 0.1, alpha: 1), size: CGSize(width: 100, height: 100)) let entity1 = GKEntity() let entity2 = GKEntity() override func didMove(to view: SKView) { node1.position.x = -100 node2.position.x = 100 addChild(node1) addChild(node2) let horizontalComponent = HorizontalComponent(node: node1) entity1.addComponent(horizontalComponent) horizontalComponentSystem.addComponent(horizontalComponent) let verticalComponent = VerticalComponent(node: node2) entity2.addComponent(verticalComponent) verticalComponentSystem.addComponent(verticalComponent) } override func update(_ currentTime: TimeInterval) { if lastTime == 0 { lastTime = currentTime } let deltaTime = currentTime - lastTime horizontalComponentSystem.update(deltaTime: deltaTime) verticalComponentSystem.update(deltaTime: deltaTime) lastTime = currentTime } }
これでnode1が右方向、node2が上方向に移動するようになりました。
entity1とentity2は下のupdateメソッドのように、片方のコンポーネントから別のコンポーネントのメソッドを呼び出す時に便利です。
class HorizontalComponent: GKComponent { let node: SKNode init(node: SKNode) { self.node = node super.init() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func update(deltaTime seconds: TimeInterval) { node.position.x += 1 entity?.components.flatMap { $0 as? VerticalComponent }.forEach { $0.update(deltaTime: seconds) } } }
最後に、それぞれのnodeに「画面タップで回転」という性質を追加します。
「画面タップで回転」という性質を表すRotateComponentクラスを下のように作成します。
class RotateComponent: GKComponent { let node: SKNode init(node: SKNode) { self.node = node super.init() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func rotate() { node.run(SKAction.rotate(byAngle: 2, duration: 1)) } }
今作ったcomponentをnode1とnode2に反映します。
import SpriteKit import GameplayKit class GameScene: SKScene { let horizontalComponentSystem = GKComponentSystem(componentClass: HorizontalComponent.self) let verticalComponentSystem = GKComponentSystem(componentClass: VerticalComponent.self) let rotateComponentSystem = GKComponentSystem(componentClass: RotateComponent.self) var lastTime: TimeInterval = 0 let node1 = SKSpriteNode(color: UIColor(red: 0.1, green: 0.8, blue: 0.8, alpha: 1), size: CGSize(width: 100, height: 100)) let node2 = SKSpriteNode(color: UIColor(red: 0.8, green: 0.8, blue: 0.1, alpha: 1), size: CGSize(width: 100, height: 100)) let entity1 = GKEntity() let entity2 = GKEntity() override func didMove(to view: SKView) { node1.position.x = -100 node2.position.x = 100 addChild(node1) addChild(node2) let horizontalComponent = HorizontalComponent(node: node1) entity1.addComponent(horizontalComponent) horizontalComponentSystem.addComponent(horizontalComponent) let verticalComponent = VerticalComponent(node: node2) entity2.addComponent(verticalComponent) verticalComponentSystem.addComponent(verticalComponent) // ここから今回追加 let rotateComponent1 = RotateComponent(node: node1) let rotateComponent2 = RotateComponent(node: node2) entity1.addComponent(rotateComponent1) entity2.addComponent(rotateComponent2) rotateComponentSystem.addComponent(rotateComponent1) rotateComponentSystem.addComponent(rotateComponent2) } override func update(_ currentTime: TimeInterval) { if lastTime == 0 { lastTime = currentTime } let deltaTime = currentTime - lastTime horizontalComponentSystem.update(deltaTime: deltaTime) verticalComponentSystem.update(deltaTime: deltaTime) lastTime = currentTime } // ここから今回追加 override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { rotateComponentSystem.components.flatMap { $0 as? RotateComponent }.forEach { $0.rotate() } } }
これで画面タップでnodeが回転するようになりました。