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 のヴァーチャルパッドについて見てゆく。