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() } }
今回はここまで。