Apple Engine

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

WWDC 2017 の SceneKit サンプル Fox 2 を調べる その31

引き続き GameController クラスの残りの関数を見てゆく。

今回は鍵を開けるなどゲームでのアクションについて。

 

鍵を開ける

unlockDoor 関数も長いので中身を別で見てゆく。

func unlockDoor() {
    ...
}

 

扉が開いているか調べる

friendsAreFree が true の場合はこの関数を実行しない。 開いても プレイヤーキャラクター Max が少し操作でき、この関数を呼ぶトリガーを弾いてしまうため。

if friendsAreFree {
    return
}

 

シーンを止める

宝石のアイテム取得時の演出と同様に startCinematic() を使ってシーンを止める

startCinematic()

 

鍵を開ける音を鳴らす

playSound(AudioSourceKind.unlockDoor)

 

扉が開く演出

SCNTransaction に対して completionBlock が2つあるため、3段階でアニメーションされる。
以下、アニメーション内容。

 

1段階目

扉が開くトリガーが引かれると即時で setActiveCamera でカメラ移動のアニメーションが行われる

 

2段階目
  • ドアのノード取得
  • ドアのパーティクル配置用のノードを探しパーティクルを置く
  • 扉が開いた際の音を鳴らす
  • ドアを半透明にする

 

3段階目
  • animateFriends() 呼び仲間を初期状態アニメーションさせる
  • friendsAreFree を ture にして SceneRenderer のデリゲートで設定している歩行アニメーションの実行を許可にする
  • DispatchQueue から showEndScreen() を呼び、クリア時の画面を表示する。

 

SCNTransaction.begin()
SCNTransaction.animationDuration = 0.0
SCNTransaction.completionBlock = {() -> Void in
    let door: SCNNode? = self.scene!.rootNode.childNode(withName: "door", recursively: true)
    let particle_door: SCNNode? = self.scene!.rootNode.childNode(withName: "particles_door", recursively: true)
    self.addParticles(with: .unlockDoor, withTransform: particle_door!.worldTransform)

    self.playSound(.collectBig)

    //add friends
    SCNTransaction.begin()
    SCNTransaction.animationDuration = 0.0
    self.addFriends(GameController.NumberOfFiends)
    SCNTransaction.commit()

    //open the door
    SCNTransaction.begin()
    SCNTransaction.animationDuration = 1.0
    SCNTransaction.completionBlock = {() -> Void in
        self.animateFriends()

        self.friendsAreFree = true

        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() +
            Double(Int64(1.0 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: {() -> Void in
            self.showEndScreen()
        })
    }
    door!.opacity = 0.0
    SCNTransaction.commit()
}

setActiveCamera("CameraCinematic02", animationDuration: 1.0)
SCNTransaction.commit()

 

鍵のアイテムを表示する

鍵のアイテムを表示する処理をする関数。

  • 鍵表示用のフラグを true にする
  • 鍵のノードを探し、key に渡す
  • 鍵を表示した時の音を鳴らす
  • 鍵を表示する前のパーティクルを設定し再生
  • 鍵のノードの透明度を 1 にして出現させる
  • completionBlock から DispatchQueue で keyDidAppear() 呼び処理を終える。
func showKey() {
    keyIsVisible = true

    let key: SCNNode? = scene!.rootNode.childNode(withName: "key", recursively: true)

    playSound(AudioSourceKind.collectBig)

    addParticles(with: .keyApparition, withTransform: key!.worldTransform)

    SCNTransaction.begin()
    SCNTransaction.animationDuration = 1.0
    SCNTransaction.completionBlock = {() -> Void in
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() +
            Double(Int64(2.5 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: {() -> Void in
            self.keyDidAppear()
        })
    }

    key!.opacity = 1.0
    SCNTransaction.commit()
}

 

鍵のアイテムを表示後の処理

鍵のアイテムを表示後、execTrigger でカメラを元の位置に戻し、 stopCinematic() を一時停止しているシーンを再生する。

func keyDidAppear() {
    execTrigger(lastTrigger!, animationDuration: 0.75)
    stopCinematic()
}

 

鍵のアイテムが出現する事前処理

先ほどの showKey 関数が呼ばれる前に keyShouldAppear が呼ばれる。

startCinematic() を呼び、画面を一時停止。
SCNTransaction のアニメーションから setActiveCamera を呼び CameraCinematic01 のカメラに移動。 completionBlock で showKey 関数が呼ぶ。

func keyShouldAppear() {
    startCinematic()

    SCNTransaction.begin()
    SCNTransaction.animationDuration = 0.0
    SCNTransaction.completionBlock = {() -> Void in
        self.showKey()
    }
    setActiveCamera("CameraCinematic01", animationDuration: 3.0)
    SCNTransaction.commit()
}

 

アイテムを取る処理

physicsWorld のデリゲートで Max がアイテムに接触した際呼ばれる関数。

このノードの physicsBody がある場合処理開始。
引数で渡されるノード collectable の名前が "key" なら鍵、"CollectableBig" なら宝石。

 

鍵の取得

  • keyIsVisible が true でなく表示されていない場合は処理を抜ける。(透明度 0 にしているだけなので物理判定は通るため)
  • アイテム取得のサウンドを再生
  • UI である overlay の didCollectKey 関数を呼び画面左上に鍵を表示する。
  • collectedKeys を1増やす

 

宝石の取得

  • collectedGems を1増やす
  • アイテム取得のサウンドを再生
  • UI である overlay の collectedGemsCount 関数を呼び画面左上に宝石を表示する。
  • collectedGems が 1 の場合 DispatchQueue から keyShouldAppear() を呼ぶ。

 

その後の処理

collectable の physicsBody を nil にして物理判定を削除し、keyApparition のパーディクルを表示して、collectable ノードをシーンから消す。

 

func collect(_ collectable: SCNNode) {
    if collectable.physicsBody != nil {

        if collectable.name == "key" {
            if !self.keyIsVisible {
                return
            }

            // play sound
            playSound(AudioSourceKind.collect)
            self.overlay?.didCollectKey()

            self.collectedKeys += 1
        }

        //the gems
        else if collectable.name == "CollectableBig" {
            self.collectedGems += 1

            // play sound
            playSound(AudioSourceKind.collect)

            // update the overlay
            self.overlay?.collectedGemsCount = self.collectedGems

            if self.collectedGems == 1 {
                //we collect a gem, show the key after 1 second
                DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() +
                    Double(Int64(0.5 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: {() -> Void in
                    self.keyShouldAppear()
                })
            }
        }

        collectable.physicsBody = nil //not collectable anymore

        // particles
        addParticles(with: .keyApparition, withTransform: collectable.worldTransform)

        collectable.removeFromParentNode()
    }
}

 

次回はプレイヤーキャラクターのコントロールをする関数を見てゆく。