🕶️

visionOS Tips: 透明なタップ対象Entity

2025/02/02に公開

表示しているEntityではなく、もっと広い範囲をタップ対象領域にしたい時や、Attachmentなど他の表示と被ってしまう際に任意の場所をタップ対象にしたい事があります。

そんな場合のために空間に透明なタップ対象Entityを配置する実装です。

画像

タップ領域が透過していてタップ対象ではないCubeが見えている状態

タップ領域(仮で青く色付け)が非透過でCubeが隠れている状態

実装

import SwiftUI
import RealityKit

struct ImmersiveView: View {

    @Environment(AppModel.self) private var appModel

    var body: some View {
        RealityView { content in
            let cube: Entity = {
                let entity = Entity()
                entity.name = "cubeA"

                let model = ModelComponent(mesh: .generateBox(size: 0.2), materials: [SimpleMaterial(color: .orange, isMetallic: false)])
                entity.components.set(model)

                entity.position = [0.0, 1.2, -1.0]
                return entity
            }()
            content.add(cube)

            let tapTarget: Entity = {
                let entity = Entity()
                entity.name = "tapTarget"

                let model = ModelComponent(mesh: .generateSphere(radius: 0.2), materials: [makeMaterial(isTransparent: true)])
                entity.components.set(model)
                entity.components.set(InputTargetComponent())
                entity.components.set(CollisionComponent(shapes: [.generateSphere(radius: 0.2)]))

                entity.position = [0.0, 1.2, -1.0]
                return entity
            }()
            content.add(tapTarget)

        } update: { content in
            if let tapTarget = content.entities.first(where: { $0.name == "tapTarget" }),
               var modelComponent = tapTarget.components[ModelComponent.self] {

                modelComponent.materials = [makeMaterial(isTransparent: appModel.isTransparent)]
                tapTarget.components.set(modelComponent)
            }
        }
        .gesture(
            SpatialEventGesture()
                .targetedToAnyEntity()
                .onEnded { value in
                    print("Tap called.")
                }
        )
        .padding()
    }

    private func makeMaterial(isTransparent: Bool) -> UnlitMaterial {
        if isTransparent {
            var material = UnlitMaterial(color: .clear)
            material.blending = .transparent(opacity: PhysicallyBasedMaterial.Opacity(floatLiteral: 1.0))
            material.faceCulling = .front
            return material
        } else {
            var material = UnlitMaterial(color: .blue)
            material.blending = .opaque
            material.faceCulling = .none
            return material
        }
    }
}

Discussion