🕶️

visionOS Tips: FaceOut Component & System

2025/02/08に公開

completionHandler付きのFadeOutComponentとSystem。
フェイドアウトは多用するのでこういう物を用意しておくと便利。

画像


左側の球体タップ前


タップ後、1秒掛けて消える

実装

import Foundation
import RealityKit

struct FadeOutComponent: Component {
    var duration: Float
    var currentProgress: Float = 0.0
    var initialOpacity: Float = 1.0
    var completionHandler: (() -> Void)?
    var isFading: Bool = false
}

@MainActor
class FadeOutSystem: System {
    private static let query = EntityQuery(where: .has(FadeOutComponent.self))
    
    required init(scene: Scene) { }
        
    func update(context: SceneUpdateContext) {
        for entity in context.entities(matching: Self.query, updatingSystemWhen: .rendering) {
            updateAnimation(on: entity, deltaTime: context.deltaTime)
        }
    }
    
    private func updateAnimation(on entity: Entity, deltaTime: TimeInterval) {
        guard var fadeOutComponent = entity.components[FadeOutComponent.self], fadeOutComponent.isFading else { return }
        fadeOutComponent.currentProgress += Float(deltaTime)
        let progressRatio = fadeOutComponent.currentProgress / fadeOutComponent.duration
        let newOpacity = fadeOutComponent.initialOpacity * (1 - progressRatio)
        entity.components.set(OpacityComponent(opacity: newOpacity))
        if fadeOutComponent.currentProgress >= fadeOutComponent.duration {
            fadeOutComponent.isFading = false
            fadeOutComponent.completionHandler?()
        }
        entity.components.set(fadeOutComponent)
    }
}

使い方

import SwiftUI
import RealityKit
import RealityKitContent

struct ImmersiveView: View {

    @State private var rootEntity: Entity!
    @State private var targetEntity: Entity!
    @State private var sphereLeft: Entity!

    var body: some View {
        RealityView { content in
            if let immersiveContentEntity = try? await Entity(named: "Immersive", in: realityKitContentBundle) {
                content.add(immersiveContentEntity)
                sphereLeft = immersiveContentEntity.findEntity(named: "Sphere_Left")
                sphereLeft.setOpacity(1.0)
            }
        }
        .gesture(
            TapGesture().targetedToAnyEntity().onEnded({ event in
                if let fadeOutComponent = sphereLeft.components[FadeOutComponent.self], fadeOutComponent.isFading {
                    return
                }
                var fadeOutComponent = FadeOutComponent(duration: 1.0, completionHandler: {
                    print("done")
                })
                fadeOutComponent.isFading = true
                sphereLeft.components.set(fadeOutComponent)
            })
        )
        .task {
            FadeOutSystem.registerSystem()
        }
    }
}

extension Entity {
    func setOpacity(_ opacity: Float) {
        if var opacityComponent = components[OpacityComponent.self] {
            opacityComponent.opacity = opacity
            components.set(opacityComponent)
        } else {
            components.set(OpacityComponent(opacity: opacity))
        }
    }
}

Discussion