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 }
次回は鍵を開けるなどゲームでのアクションの関数を見てゆく。