Apple Engine

Apple, iPhone, iOS, その周辺のことについて

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