Open16

【Swift】visionOSアプリ開発スクラップ

Raisuke ShirabeRaisuke Shirabe

壁のすり抜けもisContinuousCollisionDetectionEnabledという継続的判定をすればいい

これやってみてなんとなく通り抜けなくはなったけど
それでも速すぎるとやっぱりすり抜ける
Unityみたいに4段階あってほしい

こちらの記事がわかりやすかった

Raisuke ShirabeRaisuke Shirabe

ParticleはParticleEmitterComponentを使えばできるが、RealityComposerProを使ったが柔軟性あるのかな

entity.components.set(particleComponent())

func particleComponent() -> ParticleEmitterComponent {
  var particles = ParticleEmitterComponent()
  particles.emitterShape = .sphere
  particles.emitterShapeSize = [1,1,1] * 0.05
        
  particles.mainEmitter.birthRate = 2000
  particles.mainEmitter.size = 0.05
  particles.mainEmitter.lifeSpan = 0.5
  particles.mainEmitter.color = .evolving(start: .single(.white), end: .single(.cyan))

  return particles
}
Raisuke ShirabeRaisuke Shirabe

左右のAnchorを保存して手首の距離を計算

  // HandTrackingのAnchorを更新
    func processHandUpdates() async {
        for await update in handTracking.anchorUpdates {
            switch update.event {
            case .updated:
                let anchor = update.anchor
                guard anchor.isTracked else { continue }
                
                if anchor.chirality == .left {
                     self.leftHandAnchor = anchor // 左手のアンカーを保存
                 } else if anchor.chirality == .right {
                     self.rightHandAnchor = anchor // 右手のアンカーを保存
                 }
                
                detectGunGestureTransform(handAnchor: anchor)
                updateTranslationAndShootEnergyBall(handAnchor: anchor)
            default:
                break
            }
        }
    }

    func calculateLeftHandWristToRightHandWristDistance(leftHandAnchor: HandAnchor?, rightHandAnchor: HandAnchor?) -> Float? {
        guard let leftHandAnchor = leftHandAnchor else { return 0 }
        guard let rightHandAnchor = rightHandAnchor else { return 0 }
        let leftWristPosition = leftHandAnchor.originFromAnchorTransform.columns.3.xyz
          let rightWristPosition = rightHandAnchor.originFromAnchorTransform.columns.3.xyz
          
          return distance(leftWristPosition, rightWristPosition)
    }
Raisuke ShirabeRaisuke Shirabe

Particleはこれ

func particleComponent() -> ParticleEmitterComponent {
        var particles = ParticleEmitterComponent()
        
        particles.spawnOccasion = .onDeath
        particles.emitterShape = .sphere
        particles.emitterShapeSize = [0.2, 0.2, 0.2]
        
        particles.speed = -0.1
        particles.birthLocation = .surface
        particles.mainEmitter.birthRate = 1000
        particles.mainEmitter.size = 0.2
        particles.mainEmitter.billboardMode = .billboard
        particles.mainEmitter.color = .evolving(start: .single(.white), end: .single(.cyan))
        
        return particles
    }
Raisuke ShirabeRaisuke Shirabe

カメラの方を向かせるにはDioramaのBilboardSystemがわかりやすい
cameraのpositionはAnchorEntity(.head)だと連続的に取れないっぽいので、WorldTrackingからqueryDeviceAnchorを取得する必要がある

Raisuke ShirabeRaisuke Shirabe

カメラの方に向かってくるのできた

func update(context: SceneUpdateContext) {
    let entities = context.scene.performQuery(Self.query)
    guard let deviceAnchor = worldTrackingProvider.queryDeviceAnchor(atTimestamp: CACurrentMediaTime()) else { return }

    let cameraTransform = Transform(matrix: deviceAnchor.originFromAnchorTransform)
    let cameraPosition = SIMD3(cameraTransform.translation.x, cameraTransform.translation.y - 1.5, cameraTransform.translation.z)

    for entity in entities {
        guard let movingComponent = entity.components[MovingComponent.self] else { return }

        // カメラの方を向く
        entity.look(at: cameraPosition,
                   from: entity.position(relativeTo: nil),
                   relativeTo: nil,
                   forward: .positiveZ)

        // 移動速度を設定
        let moveSpeed: Float = 0.1

        // entityの前進方向を計算
        let forwardDirection = entity.transform.matrix.columns.2.xyz
        let newPosition = entity.position(relativeTo: nil) + (forwardDirection * moveSpeed)

        // 移動処理(毎フレームの更新でスムーズに移動)
        entity.position = newPosition

        entity.components[MovingComponent.self] = movingComponent
    }
}
Raisuke ShirabeRaisuke Shirabe

カメラの位置はSystemを別途作ったほうが各Systemからアクセスできる
↓でSceneにあるEntityにpositionを追加すればいい

func update(context: SceneUpdateContext) {
    guard let device = context.scene.findEntity(named: "Device") else { return }

    guard let deviceAnchor = worldTrackingProvider.queryDeviceAnchor(atTimestamp: CACurrentMediaTime()) else { return }

    device.transform = Transform(matrix: deviceAnchor.originFromAnchorTransform)
}

↓で各Systemから呼び出すときはこれ

guard let device = context.scene.findEntity(named: "Device") else { return }
let deviceTransform = device.transform
Raisuke ShirabeRaisuke Shirabe

SceneReconstuctionにOcclusionを追加するときは↓

    @MainActor func generateModelEntity(geometry: MeshAnchor.Geometry) async throws -> ModelEntity {
        // generate mesh
        var desc = MeshDescriptor()
        let posValues = geometry.vertices.asSIMD3(ofType: Float.self)
        desc.positions = .init(posValues)
        let normalValues = geometry.normals.asSIMD3(ofType: Float.self)
        desc.normals = .init(normalValues)
        do {
            desc.primitives = .polygons(
                (0..<geometry.faces.count).map { _ in UInt8(3) },
                (0..<geometry.faces.count * 3).map {
                    geometry.faces.buffer.contents()
                        .advanced(by: $0 * geometry.faces.bytesPerIndex)
                        .assumingMemoryBound(to: UInt32.self).pointee
                }
            )
        }
        let meshResource = try MeshResource.generate(from: [desc])
        let material = OcclusionMaterial()
        let modelEntity = ModelEntity(mesh: meshResource, materials: [material])
        return modelEntity
    }
Raisuke ShirabeRaisuke Shirabe

影をつけるにはまずHierarchy配下のEntityを全て取得するExtensionを追加

extension Entity {
    func enumerateHierarchy(_ body: (Entity, UnsafeMutablePointer<Bool>) -> Void) {
        var stop = false

        func enumerate(_ body: (Entity, UnsafeMutablePointer<Bool>) -> Void) {
            guard !stop else {
                return
            }

            body(self, &stop)

            for child in children {
                guard !stop else {
                    break
                }
                child.enumerateHierarchy(body)
            }
        }

        enumerate(body)
    }
}

影をつけるにはHierarchy配下を取得してGroundingShadowComponentをset

entity.enumerateHierarchy { entity, stop in
    if entity is ModelEntity {
        entity.components.set(GroundingShadowComponent(castsShadow: true))
    }
}
Raisuke ShirabeRaisuke Shirabe

blenderでメッシュとボーンをリンクさせたいとき

  1. 編集モードにする
  2. リンクさせたいBoneを選択し、アニメーションがあるモデルを選択
  3. poseモードにする
  4. ショートカットキーの「Ctrl + P」を押して、ペアレントメニューを開く
  5. 「Bone」を選択

これにより、モデルBはモデルAの動きに追従するようになります。

Raisuke ShirabeRaisuke Shirabe

entity.components[ModelComponent.self]?.mesh.contents.skeletonsでボーンを取得できるっぽいけど
それらのTransformを変更するのは難しそう
さて、Hitしたときのリアクションをどうするか

Raisuke ShirabeRaisuke Shirabe

AppPreviewの動画を作成するときはこちらの方法で撮影後、iMovieで編集できる。
編集するときは1080Pではなく4KでExportしないとアップロードできない。