iOS で SceneKit を試す(Swift 3) その76 - パーティクルの障害物判定と新しいエミッターの派生
SceneKit の パーティクルシステムは他のゲームエンジン同様に、パーティクルの障害物判定と新しいパーティクルのエミッター派生させることができる。
パーティクルの処理自体軽いわけではないので多用は禁物。
今回の流れ
- 雨のパーティクルを落とし床に衝突
- 衝突の際にパーティクルの削除
- 削除した場所から新しいパーティクルのエミッター(パーティクルシステム)を派生させる
プロジェクトファイルを作る
いつも通り、Xcode の SceneKit の Game テンプレートを作成。
GameViewController.swift を開き、viewDidLoad() を編集する。
今回 ship.scn を使用しないので、19行目を以下に変更。
let scene = SCNScene()
ship.scn を使用しなくなり、宇宙船のアニメーションはいらないので、44、47行目を削除
let ship = scene.rootNode.childNode(withName: "ship", recursively: true)!
ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))
準備完了。
床のジオメトリを追加する
物理シミュレーションを行うため、PhysicsBody で Static に設定。 viewDidLoad() に以下のコードを書く。
let floor = SCNFloor() floor.firstMaterial?.diffuse.contents = UIColor.darkGray floor.reflectivity = 0.0 let floorNode = SCNNode(geometry: floor) floorNode.physicsBody = SCNPhysicsBody(type: .static, shape: nil) scene.rootNode.addChildNode(floorNode)
SCNP ファイルを作成、編集する
2つの SCNP ファイルを作成する
Command + N から Resource の項目の「SceneKit Particle System File」を選択し「Next」ボタンを押下。
テンプレートの選択が出るので「Rain」を選択し、ここでは Rain.scnp で保存。
さらにサブエミッターとして「Rain」のテンプレートでもうひとつ scnp ファイルを作成。
名前を Splash.scnp で保存。
Splash.scnp 編集する
以下に変更すると5つのパーティクルが Z 軸方向に飛ぶようになる。
| パラメーター名 | 設定値 |
|---|---|
| Birth rate | 5 |
| Direction | X:0 Y:0 Z:1 |
| Speading angle | 50° |
| Shape | Point |
| Life span | 1 |
| Linner velocity | 2 |
| Stretch factor | 0.1 |

コードを書いてパーティクルを表示してみる
行うことは簡単で SCNParticleSystem(named:〜 でパーティクルを2つ設定する。
元となるパーティクルシステムをノードに適応。
SCNParticleSystem のプロパティ、colliderNodes で障害物を設定して、 particleDiesOnCollision を true にし設定した障害物に衝突するとパーティクルを消す。
そして、systemSpawnedOnCollision でぶつかった際に、派生させるパーティクルを設定する。
以下コード。
let emitter1 = SCNParticleSystem(named: "Rain.scnp", inDirectory: "") let emitter2 = SCNParticleSystem(named: "Splash.scnp", inDirectory: "") emitter2?.loops = false let particleNode = SCNNode() particleNode.position = SCNVector3(0,30,0) particleNode.addParticleSystem(emitter1!) emitter1?.colliderNodes = [floorNode] emitter1?.particleDiesOnCollision = true emitter1?.systemSpawnedOnCollision = emitter2 scene.rootNode.addChildNode(particleNode)
emitter2?.loops と emitter1?.particleDiesOnCollision をコードで書いているが、
Rain.scnp や Splash.scnp を Scene Editor で開き Attributes Inspector からでも設定変更が可能。
わかりづらいが地面に落ちると新しいパーティクルが作成されている。

GameViewController.swift の全コード
import UIKit
import QuartzCore
import SceneKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scene = SCNScene()
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 5, z: 15)
scene.rootNode.addChildNode(cameraNode)
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scene.rootNode.addChildNode(lightNode)
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = UIColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
let scnView = self.view as! SCNView
scnView.scene = scene
scnView.allowsCameraControl = true
scnView.showsStatistics = true
scnView.backgroundColor = UIColor.black
// 床
let floor = SCNFloor()
floor.firstMaterial?.diffuse.contents = UIColor.darkGray
floor.reflectivity = 0.0
let floorNode = SCNNode(geometry: floor)
floorNode.physicsBody = SCNPhysicsBody(type: .static, shape: nil)
scene.rootNode.addChildNode(floorNode)
// パーティクルの設定
let emitter1 = SCNParticleSystem(named: "Rain.scnp", inDirectory: "")
let emitter2 = SCNParticleSystem(named: "Splash.scnp", inDirectory: "")
emitter2?.loops = false
let particleNode = SCNNode()
particleNode.position = SCNVector3(0,30,0)
particleNode.addParticleSystem(emitter1!)
emitter1?.colliderNodes = [floorNode]
emitter1?.particleDiesOnCollision = true
emitter1?.systemSpawnedOnCollision = emitter2
scene.rootNode.addChildNode(particleNode)
}
override var shouldAutorotate: Bool {
return true
}
override var prefersStatusBarHidden: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
今回はここまで。