実装メモ:visionOS でパーティクルのプロパティをランタイムで変更する

Reality Composer Pro で定義したパーティクル(前回記事参照)を、Entity に対し ParticleEmitterComponent で設定したあと、その属性をアプリ動作中に動的に変更することができる。

単純に、ParticleEmitterComponent を取り出して値を変更し、再度入れ直せば良い。

struct ImmersiveView: View {
    @State var model: Entity?
    @ObservedObject var someTracking: SomeTracking

    var body: some View {
        RealityView { content in
            model = ModelEntity(...
            await attachParticleEffect(to: model!) // https://p0dee.com/blog/2025/11/03/define-particle-on-reality-composer-pro-and-attach-to-entity/
        } update: { content in
            guard let model else { return }
            model.position = someTracking.location
            updateParticleEmitterProperties(for: model, value: someTracking.strength)
        }
    }

    func updateParticleEmitterProperties(for model: Entity, value: Float) {
        guard var particleEmitterComp = centerMarker.components[ParticleEmitterComponent.self] else {
            return
        }
        let birthRate = Float(value * 100)
        particleEmitterComp.mainEmitter.birthRate = birthRate
        model.components[ParticleEmitterComponent.self] = particleEmitterComp
    }
}

他にも、RealityKit.System を使って、シーンの毎更新でパーティクルを自動的に更新する方法もありそう(出来た)。この手法は、例えば Apple のサンプルだと惑星や飛行機などの物体を周回軌道に乗せて動かし続ける、といった表現に用いられている。以下のコードだと、ParticleEmitterComponent を持ったすべての Entity を更新対象としいる。雑なので、Entity Component System に乗っ取り、カスタム Component を定義して、属性変更に必要な値もこれを経由して受け渡す設計にすると良いかもしれない。

// https://developer.apple.com/videos/play/wwdc2023/10081/
public class ParticleTransitionSystem: RealityKit.System {
    public required init(scene: RealityKit.Scene) { }
    
    private static let query = EntityQuery(where: .has(ParticleEmitterComponent.self))

    public func update(context: SceneUpdateContext) {
        let entities = context.scene.performQuery(Self.query)
        for entity in entities {
            updateParticles(entity: entity)
        }
    }
    
    public func updateParticles(entity: Entity) {
        guard var particleEmitterComp = entity.components[ParticleEmitterComponent.self] else {
            return
        }
        // スケールに応じてパーティクルの放出量を変化させる
        let scale = entity.scale(relativeTo: nil).x
        let birthRate = Float(scale * 100)
        particleEmitterComp.mainEmitter.birthRate = birthRate
        entity.components[ParticleEmitterComponent.self] = particleEmitterComp
    }
}