Apple Engine

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

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

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

今回はプレイヤーキャラクター Max の色違いのレッサーパンダの仲間の初期設定と助け出した時の処理。
鍵を開けて助ける処理は次回。

 

助け出した仲間のフレーム毎の処理

friendsAreFree が true になり仲間を助けた際に、SCNSceneRenderer のデリゲートで毎フレーム呼ばれる処理。

全ての仲間に対して、pathCurve と Z 座標を元に X 方向のオフセットを作成。 addFriends 関数で作成される仲間のスピードと経過時間、0.5をかけ Z 座標に設定し全身させる。

ensureNoPenetrationOfIndex 関数で各キャラの重なりを防ぐ。

func updateFriends(deltaTime: CFTimeInterval) {
    let pathCurve: Float = 0.4

    for i in 0..<friendCount {
        let friend = friends[i]

        var pos = friend.simdPosition
        let offsetx = pos.x - sinf(pathCurve * pos.z)

        pos.z += friendsSpeed[i] * Float(deltaTime) * 0.5
        pos.x = sinf(pathCurve * pos.z) + offsetx

        friend.simdPosition = pos

        ensureNoPenetrationOfIndex(i)
    }
}

 

助け出した仲間のアニメーション処理

仲間の Max のアニメーション設定。

シーンファイル max_walk.scn から歩行のアニメーションを取得し、 SCNAnimationPlayer の speed に各 friendsSpeed を入れアニメーションさせる。

func animateFriends() {
    let walkAnimation = Character.loadAnimation(fromSceneNamed: "Art.scnassets/character/max_walk.scn")

    SCNTransaction.begin()
    for i in 0..<friendCount {
        //unsynchronize
        let walk = walkAnimation.copy() as! SCNAnimationPlayer
        walk.speed = CGFloat(friendsSpeed[i])
        friends[i].addAnimationPlayer(walk, forKey: "walk")
        walk.play()
    }
    SCNTransaction.commit()
}

 

画面上に仲間を追加する

addFriends はイニシャライズに呼ばれ、3 が設定されている。 その後 unlockDoor 関数で NumberOfFiends が設定される。

func addFriends(_ count: Int) {
    ...
}

中身が多いなため分けてみてゆく。

 

関数内の変数 count を設定

count に friendCount を足し NumberOfFiends 大きければ処理をする。

初期状態は friendCount は 0 なのでこの命令は通らず、unlockDoor 関数が呼ばれた時初めて動く。

var count = count
if count + friendCount > GameController.NumberOfFiends {
    count = GameController.NumberOfFiends - friendCount
}

関数内の変数 count を設定

3種類の異なる色を持つ仲間の Max の設定。

シーンファイル max.scn から Max を取り出し、friend という名前を設定後、コピーした3つジオメトリを配列に設定。 テクスチャを3つ設定し、元の Max のジオメトリからマテリアルをコピーし割り当てる。

自動で動き、何も接触しないので、ジオメトリを入れた配列の全てノードを調べ物理シミュレーション処理を省く。

let friendScene = SCNScene(named: "Art.scnassets/character/max.scn")
guard let friendModel = friendScene?.rootNode.childNode(withName: "Max_rootNode", recursively: true) else { return }
friendModel.name = "friend"

var textures = [String](repeating: "", count: 3)
textures[0] = "Art.scnassets/character/max_diffuseB.png"
textures[1] = "Art.scnassets/character/max_diffuseC.png"
textures[2] = "Art.scnassets/character/max_diffuseD.png"

var geometries = [SCNGeometry](repeating: SCNGeometry(), count: 3)
guard let geometryNode = friendModel.childNode(withName: "Max", recursively: true) else { return }

geometryNode.geometry!.firstMaterial?.diffuse.intensity = 0.5

geometries[0] = geometryNode.geometry!.copy() as! SCNGeometry
geometries[1] = geometryNode.geometry!.copy() as! SCNGeometry
geometries[2] = geometryNode.geometry!.copy() as! SCNGeometry

geometries[0].firstMaterial = geometries[0].firstMaterial?.copy() as? SCNMaterial
geometryNode.geometry?.firstMaterial?.diffuse.contents = "Art.scnassets/character/max_diffuseB.png"

geometries[1].firstMaterial = geometries[1].firstMaterial?.copy() as? SCNMaterial
geometryNode.geometry?.firstMaterial?.diffuse.contents = "Art.scnassets/character/max_diffuseC.png"

geometries[2].firstMaterial = geometries[2].firstMaterial?.copy() as? SCNMaterial
geometryNode.geometry?.firstMaterial?.diffuse.contents = "Art.scnassets/character/max_diffuseD.png"

friendModel.enumerateHierarchy({(_ node: SCNNode, _ _: UnsafeMutablePointer<ObjCBool>) -> Void in
    node.physicsBody = nil
})

 

初期設定

friendPosition で初期位置、 FRIEND_AREA_LENGTH で各仲間の Z 軸方向の配置を設定。

上の方で friend とつけたジオメトリをシーン内から探し friendsNode へ渡す。 friendsNode がない場合は空の SCNNode を設定する

let friendPosition = simd_make_float3(-5.84, -0.75, 3.354)
let FRIEND_AREA_LENGTH: Float = 5.0

var friendsNode: SCNNode? = scene!.rootNode.childNode(withName: "friends", recursively: false)
if friendsNode == nil {
    friendsNode = SCNNode()
    friendsNode!.name = "friends"
    scene!.rootNode.addChildNode(friendsNode!)
}

 

仲間の設定

シーンファイル max_idle.scn からアイドル状態(立った状態)のアニメーションを取得。

for を count 分だけ以下の処理をする。

  • friend に friendModel をコピー。
  • geometryIndex に 0〜2 の幅でランダムに値を入れる
  • このループの geometryNode に friend のチルドノード Max を渡し friend の中身を編集する。
  • この関数の中でつくられた geometries の配列からランダムの数を入れている geometryIndex を添字にして、先ほどの geometryNode.geometry に入れる
  • ランダムで friend ノードの位置を設定。
  • idle にアイドル状態のアニメーションを複製し、speed をランダム値を入れ再生。
  • friendsSpeed 配列に idle の speed を入れる
  • friendsNode 配列に friend を入れる
  • friendCount をインクリメントして数を増やす
let idleAnimation = Character.loadAnimation(fromSceneNamed: "Art.scnassets/character/max_idle.scn")

for _ in 0..<count {
    let friend = friendModel.clone()

    let geometryIndex = Int(arc4random_uniform(UInt32(3)))
    guard let geometryNode = friend.childNode(withName: "Max", recursively: true) else { return }
    geometryNode.geometry = geometries[geometryIndex]

    //place our friend
    friend.simdPosition = simd_make_float3(
        friendPosition.x + (1.4 * (Float(arc4random_uniform(UInt32(RAND_MAX))) / Float(RAND_MAX)) - 0.5),
        friendPosition.y,
        friendPosition.z - (FRIEND_AREA_LENGTH * (Float(arc4random_uniform(UInt32(RAND_MAX))) / Float(RAND_MAX))))

    //unsynchronize
    let idle = (idleAnimation.copy() as! SCNAnimationPlayer)
    idle.speed = CGFloat(Float(1.5) + Float(1.5) * Float(arc4random_uniform(UInt32(RAND_MAX))) / Float(RAND_MAX))

    friend.addAnimationPlayer(idle, forKey: "idle")
    idle.play()
    friendsNode?.addChildNode(friend)

    self.friendsSpeed[friendCount] = Float(idle.speed)
    self.friends[friendCount] = friend
    self.friendCount += 1
}

 

ぶつからない様に仲間を並べる

friendCount の数まで ensureNoPenetrationOfIndex を呼び、 ぶつからない様に仲間を並べる。

for i in 0..<friendCount {
    ensureNoPenetrationOfIndex(i)
}

 

addFriends はここでおしまい。

 

ぶつからない様に位置を変更する関数

引数の index 元に friends の配列からノードの位置を設定する。 for 文から friends の配列を指定し、Max のジオメトリの有効範囲の直径を元に、全ての friends 配列から位置を比較する。 Z 舳 が 3.354 より少ない場合は pos.x 軸を調整する。

func ensureNoPenetrationOfIndex(_ index: Int) {
    var pos = friends[index].simdPosition

    let pandaRadius: Float = 0.15
    let pandaDiameter = pandaRadius * 2.0
    for j in 0..<friendCount {
        if j == index {
            continue
        }

        let otherPos = float3(friends[j].position)
        let v = otherPos - pos
        let dist = simd_length(v)
        if dist < pandaDiameter {
            let pen = pandaDiameter - dist
            pos -= simd_normalize(v) * pen
        }
    }

    if friends[index].position.z <= 3.354 {
        pos.x = max(pos.x, -6.662)
        pos.x = min(pos.x, -4.8)
    }
    friends[index].simdPosition = pos
}

次回は鍵を開けるなどゲームでのアクションの関数を見てゆく。