WWDC 2017 の SceneKit サンプル Fox 2 を調べる その36
2D の UI である Overlay クラスを見てゆく。
SceneKit の overlay の機能を使い SpriteKit を SKScene を表示している。
Overlay クラスの中身
表示される要素
- 右上のプレイヤーキャラクター Max のアイコン
- 宝石や鍵のアイコン
- クリア時の Congratulations の表示部分
- iOS 用のヴァーチャルパッド
- デモ用のメニュー
以下、コードを見てゆく。
import
SpriteKit が追加でインポートされている。
import Foundation import SceneKit import SpriteKit
クラスの宣言
class Overlay: SKScene { ... }
以下、クラスの中身を見てゆく。
メンバー定数 / 変数
定数 / 変数 | 説明 |
---|---|
overlayNode | このシーンのルートになるノード |
congratulationsGroupNode | クリア時に表示されるものをひとまとめにしたノード |
collectedKeySprite | 取得した鍵のノード |
collectedGemsSprites | 取得した宝石のノード |
demoMenu | Menu表示用の関数 |
controlOverlay | ヴァーチャルパッドを表示するためのノード |
private var overlayNode: SKNode private var congratulationsGroupNode: SKNode? private var collectedKeySprite: SKSpriteNode! private var collectedGemsSprites = [SKSpriteNode]() private var demoMenu: Menu? #if os( iOS ) public var controlOverlay: ControlOverlay? #endif
イニシャライズ
宣言
画面サイズと GameController 受け取って初期化される。
init(size: CGSize, controller: GameController) { ... }
初期設定
- 全体で使用する overlayNode に SKNode を設定
- w、h に引数 CGSize から weight、height を渡す。
- collectedGemsSprites に空の配列を設定
- シーンを scaleMode を resizeFill を使用し画面にフィットさせる。
- シーンに overlayNode を適応する
- overlayNode は X 軸 0、Y 軸 画面の高さにして左下を支点にする
overlayNode = SKNode() super.init(size: size) let w: CGFloat = size.width let h: CGFloat = size.height collectedGemsSprites = [] // Setup the game overlays using SpriteKit. scaleMode = .resizeFill addChild(overlayNode) overlayNode.position = CGPoint(x: 0.0, y: h)
右上のプレイヤーキャラクター Max のアイコン
MaxIcon.png を SKSpriteNode の characterNode に入れ、別で設定されている Button クラスの引数を menuButton に入れ、overlayNode に配置している。
setClickedTarget で toggleMenu 関数を呼びデモ用のメニューを開く
let characterNode = SKSpriteNode(imageNamed: "MaxIcon.png") let menuButton = Button(skNode: characterNode) menuButton.position = CGPoint(x: 50, y: -50) menuButton.xScale = 0.5 menuButton.yScale = 0.5 overlayNode.addChild(menuButton) menuButton.setClickedTarget(self, action: #selector(self.toggleMenu))
宝石
collectableBIG_empty.png を SKSpriteNode の gemNode に入れ、overlayNode に配置している。
collectedGemsSprites の配列に gemNode に入れている。 for 分を使っているが今回使用しているのは1つしかなく1回しかループがまわらない。
for i in 0..<1 { let gemNode = SKSpriteNode(imageNamed: "collectableBIG_empty.png") gemNode.position = CGPoint(x: 125 + i * 80, y: -50) gemNode.xScale = 0.25 gemNode.yScale = 0.25 overlayNode.addChild(gemNode) collectedGemsSprites.append(gemNode) }
鍵
key_empty.png を SKSpriteNode の collectedKeySprite に入れ、overlayNode に配置している。
collectedKeySprite = SKSpriteNode(imageNamed: "key_empty.png") collectedKeySprite.position = CGPoint(x: CGFloat(195), y: CGFloat(-50)) collectedKeySprite.xScale = 0.4 collectedKeySprite.yScale = 0.4 overlayNode.addChild(collectedKeySprite)
iOS の場合の処理
controlOverlay からヴァーチャルパッド ControlOverlay クラスを設定しデリゲートを 引数の controller を渡して、シーンに追加している。
#if os( iOS ) controlOverlay = ControlOverlay(frame: CGRect(x: CGFloat(0), y: CGFloat(0), width: w, height: h)) controlOverlay!.leftPad.delegate = controller controlOverlay!.rightPad.delegate = controller controlOverlay!.buttonA.delegate = controller controlOverlay!.buttonB.delegate = controller addChild(controlOverlay!) #endif
デモ用の UI
デモ用の UI を設定している。 isHidden 指定しているため表示されない。
右上の Max アイコンをタップすると toggleMenu 関数が呼ばれて、このデモ用の UI が表示されるのだが、この UI は画面外に位置しているため表示する際は Menu クラスの設定変更する必要がある。
demoMenu = Menu(size: size) demoMenu!.delegate = controller demoMenu!.isHidden = true overlayNode.addChild(demoMenu!)
デモ用の UI
SceneKit 上で操作するのでインタラクションを切る。
isUserInteractionEnabled = false
layout2DOverlay 関数
クリア時の Congratulations 表示設定を congratulationsGroupNode で設定する。
func layout2DOverlay() { overlayNode.position = CGPoint(x: 0.0, y: size.height) guard let congratulationsGroupNode = self.congratulationsGroupNode else { return } congratulationsGroupNode.position = CGPoint(x: CGFloat(size.width * 0.5), y: CGFloat(size.height * 0.5)) congratulationsGroupNode.xScale = 1.0 congratulationsGroupNode.yScale = 1.0 let currentBbox: CGRect = congratulationsGroupNode.calculateAccumulatedFrame() let margin: CGFloat = 25.0 let bounds = CGRect(x: 0, y: 0, width: size.width, height: size.height) let maximumAllowedBbox: CGRect = bounds.insetBy(dx: margin, dy: margin) let top: CGFloat = currentBbox.maxY - congratulationsGroupNode.position.y let bottom: CGFloat = congratulationsGroupNode.position.y - currentBbox.minY let maxTopAllowed: CGFloat = maximumAllowedBbox.maxY - congratulationsGroupNode.position.y let maxBottomAllowed: CGFloat = congratulationsGroupNode.position.y - maximumAllowedBbox.minY let `left`: CGFloat = congratulationsGroupNode.position.x - currentBbox.minX let `right`: CGFloat = currentBbox.maxX - congratulationsGroupNode.position.x let maxLeftAllowed: CGFloat = congratulationsGroupNode.position.x - maximumAllowedBbox.minX let maxRightAllowed: CGFloat = maximumAllowedBbox.maxX - congratulationsGroupNode.position.x let topScale: CGFloat = top > maxTopAllowed ? maxTopAllowed / top: 1 let bottomScale: CGFloat = bottom > maxBottomAllowed ? maxBottomAllowed / bottom: 1 let leftScale: CGFloat = `left` > maxLeftAllowed ? maxLeftAllowed / `left`: 1 let rightScale: CGFloat = `right` > maxRightAllowed ? maxRightAllowed / `right`: 1 let scale: CGFloat = min(topScale, min(bottomScale, min(leftScale, rightScale))) congratulationsGroupNode.xScale = scale congratulationsGroupNode.yScale = scale }
宝石と鍵の取得処理
宝石の collectedGemsCount は Int の値を入れ、鍵は didCollectKey 関数を実行すると、各取得時の画像に変更され、アニメーションが行われる。
var collectedGemsCount: Int = 0 { didSet { collectedGemsSprites[collectedGemsCount - 1].texture = SKTexture(imageNamed:"collectableBIG_full.png") collectedGemsSprites[collectedGemsCount - 1].run(SKAction.sequence([ SKAction.wait(forDuration: 0.5), SKAction.scale(by: 1.5, duration: 0.2), SKAction.scale(by: 1 / 1.5, duration: 0.2) ])) } } func didCollectKey() { collectedKeySprite.texture = SKTexture(imageNamed:"key_full.png") collectedKeySprite.run(SKAction.sequence([ SKAction.wait(forDuration: 0.5), SKAction.scale(by: 1.5, duration: 0.2), SKAction.scale(by: 1 / 1.5, duration: 0.2) ])) }
iOS での分岐
ヴァーチャルパッドの表示 / 非表示を行う。
主に GameController クラスで呼び出される。
#if os( iOS ) func showVirtualPad() { controlOverlay!.isHidden = false } func hideVirtualPad() { controlOverlay!.isHidden = true } #endif
クリア時の表示
congratulationsNode を設定しつつ、アニメーション設定を行い再生する。
func showEndScreen() { let congratulationsNode = SKSpriteNode(imageNamed: "congratulations.png") let characterNode = SKSpriteNode(imageNamed: "congratulations_pandaMax.png") characterNode.position = CGPoint(x: CGFloat(0.0), y: CGFloat(-220.0)) characterNode.anchorPoint = CGPoint(x: CGFloat(0.5), y: CGFloat(0.0)) congratulationsGroupNode = SKNode() congratulationsGroupNode!.addChild(characterNode) congratulationsGroupNode!.addChild(congratulationsNode) addChild(congratulationsGroupNode!) layout2DOverlay() congratulationsNode.alpha = 0.0 congratulationsNode.xScale = 0.0 congratulationsNode.yScale = 0.0 congratulationsNode.run( SKAction.group([SKAction.fadeIn(withDuration: 0.25), SKAction.sequence([SKAction.scale(to: 1.22, duration: 0.25), SKAction.scale(to: 1.0, duration: 0.1)])])) characterNode.alpha = 0.0 characterNode.xScale = 0.0 characterNode.yScale = 0.0 characterNode.run(SKAction.sequence([SKAction.wait(forDuration: 0.5), SKAction.group([SKAction.fadeIn(withDuration: 0.5), SKAction.sequence([SKAction.scale(to: 1.22, duration: 0.25), SKAction.scale(to: 1.0, duration: 0.1)])])])) }
その他
トグルで toggleMenu 関数から demoMenu の isHidden 呼び非表示にする。
@objc func toggleMenu(_ sender: Button) { demoMenu!.isHidden = !demoMenu!.isHidden }
次回は iOS のヴァーチャルパッドについて見てゆく。