👐
【Swift】visionOSでかめはめ波を撃つ
はじめに
前回、【Swift】visionOSのカスタムジェスチャーで玉を飛ばすでカスタムジェスチャーを使って手からオブジェクトを生成することができました。
今回はそれを応用してかめはめ波を撃ってみたいと思います。
つくったもの
環境
- Xcode Version 15.3
- visionOS 1.1
前提条件
- アプリは
FullSpace
に入る必要があります。 - 一部のARKitデータにはアクセス権限が必要です。
かめはめ波のポーズを検出する
前回作成したprocessHandUpdates
でグローバル変数に左右それぞれのAnchorを保存。
ViewModel.swift
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 // 右手のアンカーを保存
}
detectGestureTransform(handAnchor: anchor)
updateTranslationAndShootEnergyBall(handAnchor: anchor)
default:
break
}
}
}
左右の手首の距離を測る。
ViewModel.swift
func calculateHandWristDistance(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)
}
「かーめーはーめー」時にエネルギーボールを生成する
左右の手首が0.03cm以下になったらエネルギーボールを生成する。
ViewModel.swift
func detectGestureTransform(handAnchor: HandAnchor?) {
guard let handAnchor = handAnchor,
(handAnchor.chirality == .left ? leftHandEnergyBall : rightHandEnergyBall) == nil,
let handDistance = calculateHandWristDistance(handAnchor: handAnchor),
handDistance < 0.03
else {
return
}
spawnEnergyBall(handAnchor: handAnchor)
}
エネルギーボールは発光させてParticleを出してかめはめ波っぽくしたい。
エネルギーボールを発光させるMaterialを追加。
ViewModel.swift
var material = PhysicallyBasedMaterial()
material.emissiveColor = PhysicallyBasedMaterial.EmissiveColor(color: .cyan)
material.emissiveIntensity = 2.0
エネルギーボールにParticleを追加。
ViewModel.swift
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
}
また、重力の影響を受けないようにする。
ViewModel.swift
energyBall.physicsBody?.isAffectedByGravity = false
spawnEnergyBall
の全体のコード。
ViewModel.swift
// エネルギーボールを生成
func spawnEnergyBall(handAnchor: HandAnchor?) {
guard let handAnchor = handAnchor else { return }
let radius: Float = 0.1
// enegyBallを発光させる
var material = PhysicallyBasedMaterial()
material.emissiveColor = PhysicallyBasedMaterial.EmissiveColor(color: .cyan)
material.emissiveIntensity = 2.0
let energyBall = ModelEntity(
mesh: .generateSphere(radius: radius),
materials: [material]
)
// エネルギーボールの位置を計算
energyBall.transform.translation = Transform(matrix: handAnchor.originFromAnchorTransform).translation
energyBall.name = "EnergyBall"
// エネルギーボールの衝突形状を設定
energyBall.collision = CollisionComponent(
shapes: [.generateSphere(radius: radius)],
mode: .trigger
)
// Particleをセット
energyBall.components.set(particleComponent())
// エネルギーボールの物理演算処理を設定
energyBall.physicsBody = PhysicsBodyComponent(
shapes: [ShapeResource.generateSphere(radius: radius)],
mass: 0.1,
material: nil,
mode: .dynamic
)
// 重力の影響を受けないようにする
energyBall.physicsBody?.isAffectedByGravity = false
// 継続的な衝突判定をする
energyBall.physicsBody?.isContinuousCollisionDetectionEnabled = true
// エネルギーボールへの参照を左手に保持
handEntity.addChild(energyBall)
handEnergyBall = energyBall
}
エネルギーボールを手の中に保持し「波ー!」で発射する
「かーめーはーめー」とやってるときはエネルギーボールは発射せず、手の中で保持し、「波ー!」のときに発射したい。
ViewModel.swift
// エネルギーボールの位置を更新し手を離したときは発射する
func updateTranslationAndShootEnergyBall(handAnchor: HandAnchor?) {
guard let handAnchor = handAnchor,
let energyBall = handEnergyBall,
let handDistance = calculateHandWristDistance(handAnchor: handAnchor)
else {
return
}
// 手のアンカーに基づいてエネルギーボールの位置を更新
energyBall.transform.translation = Transform(matrix: handAnchor.originFromAnchorTransform).translation
+ calculateHandTranslationOffset(handAnchor: handAnchor)
// 両手が離れた場合(閾値以上の距離)にエネルギーボールを発射
if handDistance > 0.03 {
let forceDirection = calculateForceDirection(handAnchor: handAnchor)
energyBall.addForce(forceDirection * 150, relativeTo: nil)
// 発射後はエネルギーボールをnilに設定
handEnergyBall = nil
}
}
contentEntity.addChild(handEntity)
するのを忘れないように!
おわりに
以上、「visionOSでかめはめ波を撃つ」でした。
基本的なHandGestureProvider
の使い方やその他の関数は前回の記事で紹介しています。
動作確認は行っていますが、多少コードが変わってる可能性もあり、動かない場合はコメント等でご教示いただけると嬉しいです。
今後もvisionOSについて発信していきますので、この記事が参考になったと思ったらぜひ♡をお願いします。
Discussion