iOS で SceneKit を試す(Swift 3) その82 - キューブマップを設定する
立方体6面の内側に指定された画像を内側に貼り付ける360度の背景画像をキューブマップと呼び、
SCNMaterial の親玉である SCNMaterialProperty から使用する。
(内部的には Model I/O の機能だったはず)
SceneKit ではシーンの background や lightingEnvironment、または Blinn や Phong マテリアルの Reflective プロパティ で使用可能。 lightingEnvironment は Physically Based のシェーダーで効果を表す。
シーンの background や lightingEnvironment にテクスチャを適応したもの。
キューブマップ使用できる画像配置と画像サイズ
ドキュメントには +X, -X, +Y, -Y, +Z, -Z で配置させると書いているが、ワールド座標軸的には +X, -X, +Y, -Y, -Z, +Z にしないと Z 軸に貼られる画像がおかしくなる。
以下4つの画像はどれを適応しても同じにため、状況によって最適なものを選べば良いと思われる。
Vertical strip (縦並びの1枚画像)
高さは幅の6倍の画像。
Vertical strip が SceneKit で使用する全てのキューブマップ処理の中で一番パフォーマンスが良いとのこと。
Horizontal strip (横並びの1枚画像)
幅は高さの6倍の画像。
Spherical projection (1枚のパノラマ画像)
幅は高さの2倍の画像。
Array of six images (配列に6枚の画像を入れたもの)
幅と高さが同じの画像。
コードで書く
Array of six images がコードのみでしか実現できないので、 以下のもの
scene.background.contents = [ UIImage(named:"x_p.png"), UIImage(named:"x_m.png"), UIImage(named:"y_p.png"), UIImage(named:"y_m.png"), UIImage(named:"z_m.png"), UIImage(named:"z_p.png"), ]
参照元
contents - SCNMaterialProperty | Apple Developer Documentation
今回はこれまで。
iOS で SceneKit を試す(Swift 3) その81 - シーンに音楽や効果音をつける。
正直なところ、Core Audio など iOS 標準機能が使用できるので、 SceneKit のオーディオ再生機能を使用する必要はないけどご紹介。
SceneKit でのオーディオ再生の特徴としては、VR や HoloLens と同様に 3D 空間の位置に対して音源を再生(ミキシング)させることができる。
SceneKit では音を鳴らす方法は2つ。
- ノードの addAudioPlayer に SCNAudioSource (AVAudioNode) を設定した SCNAudioPlayer で再生する
- SCNAction の playAudio で SCNAudioSource を指定して再生する
使用するクラス
- SCNAudioSource
- SCNAudioPlayer
SCNAudioSource
プロパティ名 | デフォルト値 | 説明 |
---|---|---|
init?(named: String) | なし | オーディオファイル名の指定 |
init?(fileNamed: String) | なし | main bundle からのオーディオファイル名の指定 |
init?(url: URL) | なし | URL からのオーディオファイル名の指定 |
isPositional | true | オーディオの音量やリバーブなど距離によって自動的に変化させる。false にすると場所に関係なく一定で再生される |
load() | なし | 事前読み込み用メソッド。shouldStream プロパティの値が true の場合は無効 |
volume | 1.0 | 音量を設定する |
rate | 1.0 | 再生スピード。値を大きくすると再生速度が速くなる |
reverbBlend | 0.0 | リバーブ (お風呂場のような音響効果)を付加する |
loops | false | 繰り返し再生するか否か。デフォルトは false で1回のみの再生 |
shouldStream | false | true の場合はソースファイルから直接読み込み、オーディオバッファデータにロードを行わない。 |
SCNAudioPlayer
プロパティ名 | 説明 |
---|---|
init(source: SCNAudioSource) | SCNAudioSource を設定し初期化する |
init(avAudioNode: AVAudioNode) | AVAudioNode を設定し初期化する |
audioSource | SCNAudioSource を設定する |
audioNode | AVAudioNode を設定する |
willStartPlayback | SCNAudioPlayer 再生前に呼ばれる。ループの場合は最初に戻った際毎回呼ばれるので注意 |
didFinishPlayback | 再生が止まった時呼ばれる |
コードを書いてみる
いつも通り、Xcode の Game テンプレートで SceneKit 選択して、GameViewController.swift を開き、viewDidLoad() の下のところに以下のコードを書く。
あと、適当なオーディオファイルをプロジェクトに追加して、ファイル名を変更すること。
m4a ファイルなどインポート時にターゲットのチェックが外れるものがある。ターゲットが外れていると設定していてもファイルが見つからず音が鳴らないため注意。
let audioNode = SCNNode() audioNode.name = "audioNode" audioNode.position = SCNVector3(0,0,0) scene.rootNode.addChildNode(audioNode) let audioSouce = SCNAudioSource(named: "audio.wav") audioSouce?.loops = true let audioPlayer = SCNAudioPlayer(source: audioSouce!) audioNode.addAudioPlayer(audioPlayer) audioPlayer.willStartPlayback = { print("willStartPlayback") } audioPlayer.didFinishPlayback = { print("didFinishPlayback") }
画面タップで音を止める
いつも通り、func handleTap(_ gestureRecognize: UIGestureRecognizer) の中身を変える。
func handleTap(_ gestureRecognize: UIGestureRecognizer) { let scnView = self.view as! SCNView let audio = scnView.scene?.rootNode.childNode(withName: "audioNode", recursively: true)! audio?.removeAllAudioPlayers() }
シーンから名前をつけた audioNode のノードを探し出し、全ての SCNAudioPlayer を消す。
今回、設定していないが SCNAudioPlayer は複数設定でき、個別で SCNAudioPlayer を消すことができる。
SCNAction で設定する
使用するのは playAudio。
waitForCompletion が true の場合は再生持続時間が SCNAction の長さになる。
false の場合は再生後すぐ完了され、他のアクションがあれば再生中でも移行される。
let audioSouce = SCNAudioSource(named: "a.wav") node.runAction(SCNAction.playAudio(audioSouce!, waitForCompletion: true))
Action なので、他の Action との併用、
順番に行う sequence や全て同時に実行する group が使用できる。
今回はここまで。
iOS で SceneKit を試す(Swift 3) その80 - ジオメトリにフィルター効果(Core Image Filter)をつける
SceneKit ではジオメトリなどノードに Core Image Filter を使用してエフェクトをつけることができ、Metal Shader で行うより簡単にできる。
多分、他のゲームエンジンではこれほど簡単にエフェクトをかけるのは難しいと思われる。
とりあえず、試した感じだと、Metal Shader の方がパフォーマンスが良さそう。
コードを書いてみる
いつも通り、Xcode で Game のテンプレートで SceneKit を選択。
ship の下らへんに以下のコードを書いてみる。
let ship = scene.rootNode.childNode(withName: "ship", recursively: true)! let filter = CIFilter.init(name: "CIPixellate") ship.filters = [filter!]
ビルドすると宇宙船にモザイクがかかる。
コードを見るとわかるように SCNNode のプロパティ filters に CIFilter を入れるだけ。
今回はパラメーターは入れていないが設定可能かつ、複数のフィルターを適応可能。
背景にジオメトリを置くと手前だけフィルターがかかっているのがわかる。
CICMYKHalftone を適応するとカラー印刷の網点のような効果が出たりする。
Core Image Filter の一覧。
2つ画像が必要なものやジェネレーターなどはあるが、ある程度のものは使用できる。
今回はここまで。
iOS で SceneKit を試す(Swift 3) その79 - 画面操作などからオブジェクトを探し出すヒットテストについて
今回は、特定のオブジェクトを SCNView から探し出すヒットテストをみていく。
Xcode の Game テンプレートではレンダリングされた画像から調べ、
宇宙船をタップするとマテリアルの色が赤く変わるアニメーションが実装されている。
(ドキュメントではレンダリングされた画像と書いてあるが、カメラからレイを飛ばして調べているのだと思われる)
ヒットテストで検索が終わり、ぶつかった対象が見つかると、ノードの情報などを持った SCNHitTestResult を返す。
SceneKit で用意されているヒットテスト
ヒットテストの検索を実行できるメソッドは3つ。 レンダリング画像 (SCNSceneRenderer)、SCNNode、物理シミュレーションを使用した PhysicsWorld 内で調べることができる。
- hitTest(_:options :)
- hitTestWithSegment(from:to:options :)
- rayTestWithSegment(from:to:options :)
hitTest
シーンをレンダリングした画像内でタップされたポイントに対応するオブジェクトを検索する。 画面操作からオブジェクトを探す場合など。
hitTestWithSegment
指定された2点間の線分を交差するオブジェクトのノードのチルドノード内を検索する。 キャラクタがある線を超えた時など。
rayTestWithSegment
PhysicsWorld 内で2点間の線分を交差する PhysicsBody を検索する。 視線のようなものができるため、敵キャラの領域にプレイヤーが侵入した際の処理など。
ヒットテストのオプションとは?
オブジェクトを調べる際に、チルドノードを無視するなど、 どのように調べるかその方法を決める。
hitTest、hitTestWithSegment と rayTestWithSegment では設定する値が異なるので注意。
hitTest、hitTestWithSegment
rootNode 以外の値は Bool を含む NSNumber。
オプション名 | デフォルト値 | 説明 |
---|---|---|
backFaceCulling | true | カメラに向いてない面を無視する。ジオメトリの裏面など |
boundingBoxOnly | false | バウンディングボックスのみでオブジェクトを検索。検索範囲が荒くなるがパフォーマンスが向上する |
categoryBitMask | categoryBitMask に適合するものを検索する。デフォルト値は全て適応する | |
clipToZRange | true | カメラの zNear と zFardistances の間のオブジェクトのみを検索 |
firstFoundOnly | false | 見つかった最初のオブジェクトのみを返す。一番近くにあるものではないので注意 |
ignoreChildNodes | false | チルトノードを無視する |
ignoreHiddenNodes | true | 表示していないノードを無視する。カメラからジオメトリの重なりで見えなくなっているものではなく、ノードの hidden のプロパティを ture しているもの |
rootNode | ルートノード及び設定したノード | 検索開始するルートのノードを決める。渡す値は SCNNode。hitTestWithSegment は設定したノードが検索対象になる |
sortResults | true | ヒットテストの結果をソートする。false の場合は結果を任意の順序で返される |
SCNPhysicsWorld.TestOption
オプション名 | デフォルト値 | 説明 |
---|---|---|
backfaceCulling | false | 裏側の衝突対象にするか否か。 |
collisionBitMask | categoriesBitMask に適合するものだけ検索する。デフォルト値は全て適応する | |
searchMode | any | どのように検索するかを決める。パラメーターは以下参照 |
SearchMode
- all - パラメータに一致するすべてを返す
- any - 位置に関係なく最初のものを返す
- closest - 最も近いものを返す
ヒットテスト後 SCNHitTestResult が返す値
node
検索に適合したジオメトリのノードを返す。
geometryIndex
SCNGeometryElement のインデックスを返す。
ノード内のジオメトリ要素が複数のない場合は初めの 0 を返す。
チルドノードではないので注意。
faceIndex
ジオメトリの面のインデックスを返す。
localCoordinates
検索時にヒットした場所のローカル座標を返す。
worldCoordinates
検索時にヒットした場所のワールド座標を返す。
localNormal
検索時にヒットした面の法線ベクトルをローカル座標で返す。
worldNormal
検索時にヒットした面の法線ベクトルをワールド座標で返す。
modelTransform
検索時にヒットしたノードのワールド座標を行列 (SCNMatrix4) で返す。
textureCoordinates(withMappingChannel:)
テクスチャマッピングの UV 座標を返す。
UV を使用している チャネル を指定する必要があるので注意。
例えば銃を撃たれた際、表面に銃痕をつけるなどで使用する。
Game テンプレートの hitTest はどのように動いているのか
Xcode の SceneKit の Game テンプレートは以下のような流れ。
GameViewController.swift の viewDidLoad() で、UITapGestureRecognizer からタップのジェスチャーを呼び、SCNView がタップされるとアクションから handleTap(_:) が呼ばれる
// add a tap gesture recognizer let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:))) scnView.addGestureRecognizer(tapGesture)
handleTap では GestureRecognize の location から画面の位置を渡し、SCNView から hitTest の関数を読んでいる。
func handleTap(_ gestureRecognize: UIGestureRecognizer) { // retrieve the SCNView let scnView = self.view as! SCNView // check what nodes are tapped let p = gestureRecognize.location(in: scnView) let hitResults = scnView.hitTest(p, options: [:]) ・ ・ ・ }
検索が成功すると hitResults に SCNHitTestResult が配列で渡されるので、最初のものを呼び出して、ノードを取得し、マテリアル情報を取得
・ ・ ・ if hitResults.count > 0 { // retrieved the first clicked object let result: AnyObject = hitResults[0] // get its material let material = result.node!.geometry!.firstMaterial! ・ ・ ・
SCNTransaction でマテリアルの色を変更して、completionBlock で色を戻しているいる。
・ ・ ・ // highlight it SCNTransaction.begin() SCNTransaction.animationDuration = 0.5 // on completion - unhighlight SCNTransaction.completionBlock = { SCNTransaction.begin() SCNTransaction.animationDuration = 0.5 material.emission.contents = UIColor.black SCNTransaction.commit() } material.emission.contents = UIColor.red SCNTransaction.commit() } }
SCNHitTestResult の他のパラメータを見てみる
Game テンプレートを使用して見ていく。
宇宙船が回転しているとタップしづらいので GameViewController.swift の 以下の部分(44行目あたり)を消す。
// retrieve the ship node let ship = scene.rootNode.childNode(withName: "ship", recursively: true)! // animate the 3d object ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))
func handleTap(_ gestureRecognize: UIGestureRecognizer) { 〜 の let material = result.node!.geometry!.firstMaterial! の下に以下のものを書くと、ビルド後に宇宙船をタップすると、デバッガーコンソール、情報が出力される。
// get its material let material = result.node!.geometry!.firstMaterial! print("\n----") print("name: \(String(describing: result.node!.name))") print("geometryIndex: \(result.geometryIndex)") print("faceIndex: \(result.faceIndex)") print("localCoordinates: \(result.localCoordinates)") print("worldCoordinates: \(result.worldCoordinates)") print("localNormal: \(result.localNormal)") print("worldNormal: \(result.worldNormal)") print("modelTransform: \(result.modelTransform)") print("textureCoordinates: \(result.textureCoordinates(withMappingChannel:0))")
試しに SCNHitTestResult のパラメーターを使ってみる
先ほどの print 文の下に以下のものを書いて実行すると、宇宙船をタップすると SCNSphere が付着する。
とりあえず、今回はワールド座標に設置していて、球の法線に合わせて向きは変えてない。
// 球の半径 let radius:Float = 0.2 // 球のノード作成 let node = SCNNode(geometry: SCNSphere(radius: CGFloat(radius))) // 球のマテリアルの色を設定 node.geometry?.firstMaterial?.diffuse.contents = UIColor(red: 1.0, green: 0.0, blue: 0.501960784313725, alpha: 1.0) // result.worldCoordinates でジオメトリをタップした場所を取得し SCNMatrix4 へ let pos = SCNMatrix4MakeTranslation( result.worldCoordinates.x, result.worldCoordinates.y, result.worldCoordinates.z ) // サーフェイスの法線から球の半径分移動したベクトルを SCNMatrix4 へ let normal = SCNMatrix4MakeTranslation( result.worldNormal.x * radius, result.worldNormal.y * radius, result.worldNormal.z * radius ) // 球のピボットが中心にあるため、ジオメトリの場所に半径分移動させている node.transform = SCNMatrix4Mult(pos, normal) scnView.scene?.rootNode.addChildNode(node)
今回はここまで。
iOS で SceneKit を試す(Swift 3) その78 - パーティクルシステムのパラメーターをみてみる
パーティクルシステムのパラメーターをみてゆく。
ちなみに、画像を設定しないと Scene Editor 上で、何も表示されない場合があるので注意。
Scene Editor の Attributes Inspector での表示
Δ=0 となっているものは、コードでは Variation と呼ばれているもの。
ここで設定した値は、時間に応じてその値の幅をプラスマイナスの値でランダムに加算する。
Image Size を 5、Birth rate の Variation 8 にした場合、 Variation が ±4 されるためフレーム毎 1〜9 の値で生成される。
Variation のデフォルト値は 0。
Emitter
Birth rate (birthRate / birthRateVariation)
emissionDuration の設定時間中にパーティクルをどのぐらい発生させるかを決める値。
デフォルト値は 100。Variation あり。
birthRateVariation を指定しない場合で、birthRate を 0 にするとパーティクルが放出されなくなるので注意。
Warmup duration (warmupDuration)
パーティクルが生成開始される時間。
デフォルト値は 0。
例えば 10 に設定するとパーティクルアニメーション再生 10 後から表示する。 雨とか雪とか降り始めではなく、降っている状態から、表示するなど。
Location (birthLocation)
パーティクルを放出する場所の指定する。
ジオメトリの表面か、ジオメトリの体積となる部分か、頂点かを決める。
デフォルト値は surface で表面から放出される。
Emission space
Scene Editor のみ。
World space、Local space が設定でき、エミッターの位置をワールドかローカル座標で決める。
デフォルト値は World space。
Direction mode (birthDirection)
パーティクルを放出する方向。
コードで設定する場合、emitterShape が設定されていないと動作しないものがある。
設定は一定方向に放出する constant、emitterShape で設定されたジオメトリの表面を使用する surfaceNormal、 ランダム方向から放出する random。
デフォルト値は constant。 Scene Editor では random。
Spreading angle (spreadingAngl)
birthDirection を constant か surfaceNormal に設定した場合、個々のパーティクルを放射する角度を設定することがで、 値を設定するとその角度ないでラインダムな方向で放出される。
デフォルト値は 0
random に設定している場合、この設定は無視される。
Direction (emittingDirection)
birthDirection を constant に設定した場合は、パーティクルを放射する方向を設定することができる。
デフォルト値は (x:0.0, y:0.0, z:1.0)
Scene Editor では (x:0.0, y:1.0, z:0.0)
Initial angle (particleAngle / particleAngleVariation)
放出するパーティクル画像の回転角度。
Variation あり。
デフォルト値は 0 で画像は回転されずに放出される。
Shape (emitterShape)
放出するパーティクルのエミッターの形状をジオメトリで指定する。
コードの場合は、前回行ったように SCNGeometry として代入する。
Scene Editor での値は以下のもの。
パラメーター名 | 設定値 | 説明 |
---|---|---|
Box | width, height, lenght | SCNBox のジオメトリを使用する |
Cylinder | radius, height | SCNCylinder のジオメトリを使用する |
Plane | width, height | SCNPlane のジオメトリを使用する |
Point | なし | 1つの頂点から放出する。デフォルト値 |
Sphere | radius | SCNSphere のジオメトリを使用する |
Other | 不明 | 設定不能。やり方があったら教えて欲しい |
Simulation
Life Span (particleLifeSpan / particleLifeSpanVariation)
各パーティクルが生成され、シーンから削除されるまでの表示時間を設定。
Variation あり。
デフォルト値は 1.0。
Linear velocity (particleVelocity / particleVelocityVariation)
各パーティクルの生成時の速度。
Variation あり。
デフォルト値は 0.0。
Angular velocity (particleAngularVelocity / particleAngularVelocityVariation)
各パーティクルの回転角度を決める。
Variation あり。
orientationMode で回転方法変更可能。
Acceleration (acceleration)
加速度ベクトルを付加し簡易的な物理シミュレーションを施す。
単位は毎秒/秒。
デフォルト値は (x:0.0, y:0.0, z:0.0)
複雑な物理シミュレーションを行いたい場合は Affected by gravity、または Affected by physics fields をオン。
もしくは、コードで isAffectedByGravity、または isAffectedByPhysicsFields、を true にする。
Speed factor (speedFactor)
アニメーションスピードを変更する。
デフォルト値は 1。
パラメーターは乗数となっているため 2 にするとアニメーションスピードは倍になる。
Stretch factor (stretchFactor)
パーティクルの画像を動きの方向によって引き伸ばす設定。
デフォルト値は 0.0 値。
Image
Image (particleImage)
パーティクル画像を設定する。
同サイズで左〜右、上〜下に並べた画像をつくりこちらに適応。
Image Sequence で設定をすることで、アニメーションするパーティクルを作成することができる。
particleImage - SCNParticleSystem | Apple Developer Documentation
Color (particleColor)
パーティクル画像に色をつける。
Animate Color
Scene Editor のみの値。
チェックをオンにするとパーティクルが発生して消滅する間の色を設定しそれに沿ってアニメーションさせる。
Color variation (particleColorVariation)
指定した色の間でランダムに変化させる。
SCNVector4 となってをり HSB(HSV) 色設定と アルファとなっている。 よって、4つのパラメーターは色相、彩度、明度、アルファ値となる。
Size (particleSize / particleSizeVariation)
パーティクル画像の大きさを変更できる。 Variation あり。
デフォルト値は 1.0。
Custom Animation
Scene Editor のみの機能。
歯車のボタンをクリックすることで消滅するまでのアニメーションタイミングを設定できる。
Image Sequence
Initial frame (imageSequenceInitialFrame / imageSequenceInitialFrameVariation)
連続した画像のアニメーションをどのフレームから始めるかを決める。
Variation あり。
デフォルト値は 0 で画像左上から表示する。
Frame rate (imageSequenceFrameRate / imageSequenceFrameRateVariation)
連続した画像のアニメーションのフレームレートを決める。
Variation あり。
デフォルト値は 0 でアニメーションを行わない。
そのため、このパラメータを設定しないとアニメーションが再生されない。
Aniamtion (imageSequenceAnimationMode)
アニメーションがどうのように行われるか設定する。
設定値は以下のもの。
パラメーター名 | 説明 |
---|---|
repeat | 繰り返し再生する。デフォルト値 |
clamp | 再生後停止 |
autoReverse | 再生・逆再生を繰り返す |
Dimentions - Row (imageSequenceRowCount)
アニメーション画像の行数。
デフォルト値は 0。
Dimentions - Columns (imageSequenceColumnCount)
アニメーション画像の列数。
デフォルト値は 0。
Rendering
Blending (blendMode)
パーティクルが重なり合った時、画像のブレンドモードを設定する。
設定値はマテリアルと同じ、additive、subtract、multiply、screen、alpha、replace。
デフォルト値は additive。
Enable black pass (isBlackPassEnabled)
パーティクルの裏に黒色を重ねコントラストを高くする。
画面上に白身が多いと ブレンドモードなどによってはパーティクルが見えなくなるため。
デフォルト値は false。
こちらを使用すると、パーティクルのレンダリング処理が 2 回行われるのでパフォーマンスを優先したい場合など注意して使う。
Orientation (orientationMode)
パーティクル画像がどうのような向き出表示されるか設定する。
設定値は以下のもの。
パラメーター名 | 説明 |
---|---|
billboardScreenAligned | 画像が必ずカメラに向く。デフォルト値 |
billboardViewAligned | 画像がカメラに向くが平行な軸で回転する |
free | 自由に回転する。向きによってはパーティクル画像が見えなくなるので注意 |
billboardYAligned | Y 軸方向のみ、カメラに対して常に固定される |
Sorting (sortingMode)
blendMode とともに、レンダリング時に重なり合ったパーティクルイメージの見た目を決める。
設定値は以下のもの。
パラメーター名 | 説明 |
---|---|
none | ソートされず任意で表示される。デフォルト値 |
projectedDepth | 深度情報から近いパーティクルを前にレンダリングする |
distance | 遠いものが近いものより前にレンダリングされる |
oldestFirst | 先に放出したパーティクルが最前面でレンダリングされる |
youngestFirst | 新しいく放出されたパーティクルが最前面でレンダリングされる |
Enable lighting (isLightingEnabled)
パーティクルにライトを適応するか決める。
スモークなどヴォリュームエフェクトなどで使用する。
ライト、1つにのみ適応されるので、複数ライトがある場合は categoryBitMask で振り分ける。
デフォルト値は false。
Physics
Affected by gravity (isAffectedByGravity)
PhysicsWorld などの重力の影響を受けるか否かを設定する。
true にすると質量などの影響で重力の物理シミュレーションが行われる。
デフォルト値は false。
Affected by physics fields (isAffectedByPhysicsFields)
PhysicsField の影響を受けるか否かを設定する。
true にすると PhysicsField による質量やコードでしか設定できないが電荷に影響して物理シミュレーションが行われる。
デフォルト値は false。
Die on collision (particleDiesOnCollision)
true にすると設定した障害物(コリジョン)にぶつかった場合、パーティクルが消滅する。
デフォルト値は false。
Mass (particleMass / particleMassVariation)
物理シミュレーション同様に質量を設定する。 単位は kg で、Variation あり。
デフォルト値は 1。
Bounce (particleBounce / particleBounceVariation)
物理シミュレーション同様に衝突で得られたエネルギーの量を決める。
Variation あり。
デフォルト値は 0.7。
Friction (particleFriction / particleFrictionVariation)
物理シミュレーション同様に摩擦を決める。
デフォルト値は 1.0。
Damping (dampingFactor)
物理シミュレーション同様に流体摩擦や空気抵抗などの効果を付加する。
デフォルト値は 0.0 で、 acceleration の設定、重力や PhysicsField の影響を受けない場合は減速しない。
Life cycle
Emission Duration (emissionDuration / emissionDurationVariation)
パーティクルの再生する時間を指定する。単位は秒。
Variation あり。
デフォルト値は 1.0。
Idle Duration (idleDuration / idleDurationVariation)
パーティクルの再生するまでの時間を指定する。単位は秒。
Variation あり。
デフォルト値は 0.0。
emissionDuration を 1 にして、idleDuration を 1 にすると再生されなくなるので注意。
Loopting (loops)
パーティクルシステムのアニメーションをループさせるか決める。 false (Play Once) に設定すると設定されたもの1回分だけのパーティクルを放出する。
デフォルト値は true。 (Loops continuously)
コードで設定するもの
fresnelExponent
キューブマップのリフレクトマップが使用されている場合、パーティクルにフレネル効果を適応する度合いを決める。
デフォルト値は 1.0。
particleCharge / particleChargeVariation
プラス、またはマイナスの値入れることで、
パーティクルに電荷を付加し PhysicsField の影響を受けるようにする。
デフォルト値は 0.0 で効果はない。
colliderNodes
障害物となるノードを配列で設定する。
その他
パーティクルをさらに細かく制御するものがあるが今回は割愛。
また、iOS 11 では以下のものが増えている。
- orientationDirection
- particleIntensity / particleIntensityVariation
今回はここまで。
iOS で SceneKit を試す(Swift 3) その77 - パーティクルシステムを scn ファイルの Scene Editor で確認してみる
パーティクルシステムを scn ファイルの Scene Editor で確認してみようと思うが、 カメラ、ライト、ジオメトリ、アクションなどと特に変わりはない。
scn ファイルを開いた状態で Object Library (Command + Option + Control + 3) を開き、 Particle System をドラッグ&ドロップするだけ。
Scene Graph View にもパーティクルシステムが適応されたノードが配置され、
Attributes Inspector でパーティクル用の画像と各種設定を行って調整することとなる。
コードでも設定可能だが設定値が多いため、scn や scnp ファイルで編集した方が楽だと思われる
次回は設定値をみていく。
今回はここまで。
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() } }
今回はここまで。