【iOS ARKit】触发器与触发域

news/2024/7/20 21:20:48 标签: ios, AR

触发器

     在上节的示例中,所有可见的物体都参与了物理模拟,但在一些应用中,我们物理模拟,同时又需要了解是否有物体与它们发生了碰撞。如在 AR场景中,当角色靠近一散门时,我们并不希望因为角色与门发生碰撞而导致门移动,但又需要了解是否有角色与门发生了碰撞并以此为依据决定是否打开门。在这种应用场合中,使用触发器是最好的选择。

     在 RealityKit 中使用触发器非常简单,具体在使用时,只需要将物体的 physicsBody. mode设置为static,并将 collision. mode 设置为 trigger即可,这样既能防止物体产生运动又能捕获到碰撞相关信息。

     修改后,运行应用,操作球体与长方体发生碰撞,在碰撞发生后,可以看到相应的碰撞信息依然会打印出来,但由于长方体 physicsBody. mode 属性设置 static,长方体不会参与物理模拟,也不会发生移动。

 触发域

     使用触发器的方式适合于对可见物体进行碰撞检测,在实际应用开发中,还有一种情况,对不可见物体的碰撞检测,如在 AR 游戏中,当角色进入某一空间后触发新的机关或者激活 AI Agent(NPC,Non-PlayerCharacter,智能体)。对于这种情况,我们可以建一个 ModeIEntity,但是不渲染相应网格,就像上节代码四周围栏所做的那样。但在 RealityKit 中提供了另一种更简单易用的应对这种情况的实体类,它就是 TriggerVolume。Trigger Volume(触发区域体)实体类包含 Transform component、 Synchronizationcomponent、Collision component3 个组件。

     触发域实体包含 Collision component 组件,能够与其他碰撞体发生碰撞,因此,我们可以将触发域实体作为一个 传感器使用,当有其他碰撞体进入或者离开触发域实体所占空间时实时地获取相应消息。与其他带碰撞器的实体一样,当有其他碰撞体进入或者离开触发域实体时也会触发 CollisionEvents,我们可以通过订阅这些事件进行相应处理。

     触发域实体也是一个实体,但它非常简单,因为不带有网格信息,因此无法对它的触发域实体进行渲染。触发域实体也不参与物理模拟,但将其作为碰撞检测非常高效。

     触发域实体的使用与其他实体的使用一样,我们对代码进行改造,将 boxEntity 换成TriggerVolume,关键代码如下所示。

//
//  TriggerVolumeView.swift
//  ARKitDeamo
//
//  Created by zhaoquan du on 2024/3/18.
//

import SwiftUI
import ARKit
import RealityKit

struct TriggerVolumeView: View {
    var body: some View {
        TriggerVolumeContentView().navigationTitle("触发器与触发域").edgesIgnoringSafeArea(.all)
    }
}

struct TriggerVolumeContentView: UIViewRepresentable{
    func makeCoordinator() -> Coordinator {
        Coordinator()
    }
    
    
    
    func makeUIView(context: Context) -> some ARView {
        let arView = ARView(frame: .zero)
        let config = ARWorldTrackingConfiguration()
        config.planeDetection = .horizontal
        
        context.coordinator.arView = arView
        arView.session.delegate  = context.coordinator
        arView.session.run(config)
        
        return arView
    }
    
    func updateUIView(_ uiView: UIViewType, context: Context) {
        
    }
    
    
    class Coordinator: NSObject, ARSessionDelegate{
        var sphereEntity : ModelEntity!
        var arView:ARView? = nil
        let gameController = GameController()
        
        @MainActor func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
            guard let anchor = anchors.first as? ARPlaneAnchor,
                  
                  let arView = arView else{
                return
            }
            let planeAnchor = AnchorEntity(anchor:anchor)
            
            
            let triggerShape: ShapeResource = .generateBox(size: [01,0.2,0.3])
            let triggerVolume = TriggerVolume(shape: triggerShape)
            triggerVolume.name = "TriggerVolum"
            triggerVolume.transform.translation = [0.2,planeAnchor.transform.translation.y + 0.15,0]
            
            let sphereMaterial = SimpleMaterial(color:.red,isMetallic: true)
            sphereEntity = ModelEntity(mesh: .generateSphere(radius: 0.05),materials: [sphereMaterial], collisionShape: .generateSphere(radius: 0.05), mass: 0.04)
            sphereEntity.physicsBody?.mode =  .dynamic
            sphereEntity.name = "Sphere"
            sphereEntity.transform.translation = [-0.3,planeAnchor.transform.translation.y+0.15,0]
            sphereEntity.physicsBody?.material = .generate(friction: 0.001,restitution: 0.01)
            
            let plane :MeshResource = .generatePlane(width: 1.2, depth: 1.2)
            let planeCollider : ShapeResource = .generateBox(width: 1.2, height: 0.01, depth: 1.2)
            let planeMaterial = SimpleMaterial(color:.gray,isMetallic: false)
            let planeEntity = ModelEntity(mesh: plane, materials: [planeMaterial], collisionShape: planeCollider, mass: 0.01)
            planeEntity.physicsBody?.mode = .static//静态平面
            planeEntity.physicsBody?.material = .generate(friction: 0.001, restitution: 0.1)
            
            planeAnchor.addChild(planeEntity)
            planeAnchor.addChild(triggerVolume)
            planeAnchor.addChild(sphereEntity)
            
            let subscription = arView.scene.subscribe(to: CollisionEvents.Began.self,on: triggerVolume) { event in
                print("trigger volume发生碰撞")
                print("EntityA name : \(event.entityA.name)")
                print("EntityB name : \(event.entityB.name)")
                print("Force : \(event.impulse)")
                print("Collision Position: \(event.position)")
            }
            gameController.gameAnchor = try! Ball.loadBallGame()
            gameController.collisionEventStreams.append(subscription)
            arView.scene.addAnchor(planeAnchor)
            let gestureRecognizers = arView.installGestures(.translation, for: sphereEntity)
            if let gestureRecognizer = gestureRecognizers.first as? EntityTranslationGestureRecognizer {
                gameController.gestureRecognizer = gestureRecognizer
                gestureRecognizer.removeTarget(nil, action: nil)
                gestureRecognizer.addTarget(self, action: #selector(self.handleTranslation))
            }
            arView.session.delegate = nil
            arView.session.run(ARWorldTrackingConfiguration())
        }
       
       @objc
       func handleTranslation(_ recognizer: EntityTranslationGestureRecognizer) {
           guard let ball = sphereEntity else { return }
           let settings = gameController.settings
           if recognizer.state == .ended || recognizer.state == .cancelled {
               gameController.gestureStartLocation = nil
               ball.physicsBody?.mode = .dynamic
               return
           }
           guard let gestureCurrentLocation = recognizer.translation(in: nil) else { return }
           guard let gestureStartLocation = gameController.gestureStartLocation else {
               gameController.gestureStartLocation = gestureCurrentLocation
               return
           }
           let delta = gestureStartLocation - gestureCurrentLocation
           let distance = ((delta.x * delta.x) + (delta.y * delta.y) + (delta.z * delta.z)).squareRoot()
           if distance > settings.ballPlayDistanceThreshold {
               gameController.gestureStartLocation = nil
               ball.physicsBody?.mode = .dynamic
               return
           }
           ball.physicsBody?.mode = .kinematic
           let realVelocity = recognizer.velocity(in: nil)
           let ballParentVelocity = ball.parent!.convert(direction: realVelocity, from: nil)
           var clampedX = ballParentVelocity.x
           var clampedZ = ballParentVelocity.z
           // 夹断
           if clampedX > settings.ballVelocityMaxX {
               clampedX = settings.ballVelocityMaxX
           } else if clampedX < settings.ballVelocityMinX {
               clampedX = settings.ballVelocityMinX
           }
           // 夹断
           if clampedZ > settings.ballVelocityMaxZ {
               clampedZ = settings.ballVelocityMaxZ
           } else if clampedZ < settings.ballVelocityMinZ {
               clampedZ = settings.ballVelocityMinZ
           }
           
           let clampedVelocity: SIMD3<Float> = [clampedX, 0.0, clampedZ]
           ball.physicsMotion?.linearVelocity = clampedVelocity
       }
    }
}

#Preview {
    TriggerVolumeView()
}

    运行上述代码,在加载后的场景中无法看到触发域实体对象,使用移动手势操作球体,当球体经过触发域实体所在区域时,碰撞被检测到,CollisionEvents.Began 事件被触发,相应信息也被打印出来。在本章所有演示示例中,我们只对碰撞发生的Began事件进行了处理,CollisionEvents 事件其实包括3个事件,具体如下表。

事件名称

描述

CollisionEvents. Began

结构体,当两个碰撞体开始接触时触发,这个事件在每次碰撞中只触发一次

CollisionEvents. Updated

结构体,当两个碰撞体保持接触时,这个事件在每一帧都会触发

CollisionEvents. Ended

结构体,当两个碰撞体脱离接触时触发,这个事件在每次碰撞中只触发一次

    通过这3个事件,就能方便地处理所有与碰撞相关的事务,关于 RealityKit 中事件的处理,可参阅之前相关章节。

自定义物理实体类

     我们通过实体的 PhysicsBodyComponent 组件和 PhysicsMotionComponent 组件实现了物理模拟,在 RealityKit 中,ModelEntity实体类默认带有这两个组件,使用ModelEntity 类创建的实体都可以与物理模拟。在 RealityKit 中,使用物理引擎进行物理模拟的类必须遵循相应的物理协议,根据物理模拟类协议的层级结构,我们可以通过遵循HasPhysicsBody、HasPhysicsMotion协议或直接通过遵循HasPhysics协议,自定义物理实体类。自定义物理实体类后,可以更方便灵活地进行物理模拟,并简化代码。

物理引擎总结

      物理引擎突破了按照预定脚本执行物体运动计算的方式,通过设置物体的物理参数来运行。使用物理引擎后,虚拟物体之间、虚拟物体与现实环境之间的相互作用不需要进行硬编码,而是按照牛顿运动定律实时计算模拟,由于牛顿运动定律的客观性,这种模拟出来的效果与真实物体间相互作用效果可以做到完全一致,从而大大增强虚拟物体的可信度。


http://www.niftyadmin.cn/n/5438816.html

相关文章

ESSBAE 数据挖掘

essbase数据挖掘框架 1.算法&#xff1a;用来分析数据的方法 2.模型&#xff1a;系列的算法集合 3.任务&#xff1a;数据挖掘的步骤 4.任务模板&#xff0c;可以重复执行的任务 数据挖掘任务&#xff1a; 明确任务 建立及训练模型 测试模型 执行任务 为模型打分 ess…

算法体系-11 第十一节:二叉树基本算法(上)

一 两链表相交 1.1 题目描述 给定两个可能有环也可能无环的单链表&#xff0c;头节点head1和head2。请实现一个函数&#xff0c;如果两个链表相交&#xff0c;请返回相交的 第一个节点。如果不相交&#xff0c;返回null 【要求】 如果两个链表长度之和为N&#xff0c;时间复杂…

UGUI源码分析与研究3-扩展UGUI实现自定义UI组件

扩展UGUI是指在Unity中使用UGUI&#xff08;Unity GUI&#xff09;系统来创建自定义UI组件。UGUI是Unity提供的一套用于创建用户界面的工具&#xff0c;它提供了一系列的UI元素和交互件&#xff0c;可以用于构建游戏中的各种用户界面。 要扩展UGUI实现自定义UI组件&#xff0c…

从零开始搭建游戏服务器 第四节 MongoDB引入并实现注册登录

这里写目录标题 前言正文添加依赖安装MongoDB添加MongoDB相关配置创建MongoContext类尝试初始化DB连接实现注册功能测试注册功能实现登录逻辑测试登录流程 结语下节预告 前言 游戏服务器中, 很重要的一点就是如何保存玩家的游戏数据. 当一个服务端架构趋于稳定且功能全面, 开发…

uniapp 云开发省钱之调整函数执行内存大小

我这个5块钱一个月的服务空间配置&#xff1a; 现在还只有少量的用户和自己测试之用&#xff0c;目前消耗的情况&#xff1a; 云函数的使用量还是挺高的&#xff0c;目前还是正好能覆盖一个月的使用量&#xff0c;等用户量上来肯定是不行的&#xff0c;所以得想想办法压榨一下云…

此站点的连接不安全,怎么解决?

有部分的网站用户在打开的时候会被提示“此站点的连接不安全”这种现象为什么会出现&#xff0c;大概率是因为没有安装SSL证书或者SSL证书出现了错误&#xff0c;小编在这里面将展开讲解为大家分析其中的原因以及解决方法。 一&#xff1a;遇到该情况的时候该怎么办&#xff1…

分布式搜索引擎elasticsearch专栏三

1.数据聚合 聚合&#xff08;aggregations&#xff09;可以让我们极其方便的实现对数据的统计、分析、运算。例如&#xff1a; 什么品牌的手机最受欢迎&#xff1f; 这些手机的平均价格、最高价格、最低价格&#xff1f; 这些手机每月的销售情况如何&#xff1f; 实现这些…

什么是物联网嵌入式硬件?有哪些特点和优势?

【前言】本篇为物联网硬件系列学习笔记&#xff0c;分享学习&#xff0c;欢迎评论区交流~ 物联网嵌入式硬件是专为物联网应用而设计的硬件设备。这些设备通常小型化、低功耗&#xff0c;集成了处理器、存储器、传感器、通信模块等功能&#xff0c;使其能够连接并与其他设备进行…