【Swift】visionOSアプリ開発スクラップ
重力受けないようにするにはisAffectedByGravity
をfalseに
するだけでいいの簡単
これで玉が指先で保持できてまっすぐ飛ぶようになった
entity.physicsBody?.isAffectedByGravity = false
壁のすり抜けもisContinuousCollisionDetectionEnabled
という継続的判定をすればいい
entity.physicsBody?.isContinuousCollisionDetectionEnabled = true
衝突判定はEventSubscription
で取得する
let subscription = content.subscribe(to: CollisionEvents.Began.self, on: modelEntity) { collisionEvent in
print("💥 Collision between \(http://collisionEvent.entityA.name) and \(http://collisionEvent.entityB.name)")
}
こないだの記事に上記を追加して指離すときに玉を飛ばすようにしたらこうなった
壁のすり抜けもisContinuousCollisionDetectionEnabledという継続的判定をすればいい
これやってみてなんとなく通り抜けなくはなったけど
それでも速すぎるとやっぱりすり抜ける
Unityみたいに4段階あってほしい
こちらの記事がわかりやすかった
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
}
左右の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)
}
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
}
カメラの方を向かせるにはDioramaのBilboardSystemがわかりやすい
cameraのpositionはAnchorEntity(.head)
だと連続的に取れないっぽいので、WorldTrackingからqueryDeviceAnchorを取得する必要がある
カメラの方に向かってくるのできた
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
}
}
カメラの位置は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
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
}
影をつけるにはまず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))
}
}
blenderでメッシュとボーンをリンクさせたいとき
- 編集モードにする
- リンクさせたいBoneを選択し、アニメーションがあるモデルを選択
- poseモードにする
- ショートカットキーの「Ctrl + P」を押して、ペアレントメニューを開く
- 「Bone」を選択
これにより、モデルBはモデルAの動きに追従するようになります。
entity.components[ModelComponent.self]?.mesh.contents.skeletonsでボーンを取得できるっぽいけど
それらのTransformを変更するのは難しそう
さて、Hitしたときのリアクションをどうするか
AppPreviewの動画を作成するときはこちらの方法で撮影後、iMovieで編集できる。
編集するときは1080Pではなく4KでExportしないとアップロードできない。