SceneKit中如何判断节点触地并实现isJumping属性?
解决SCNNode子类中
isJumping属性重置问题 核心思路:利用物理碰撞检测触地状态
最可靠的方式是通过SceneKit的物理碰撞系统,检测节点是否与地面/平台接触,以此精准重置isJumping属性。
具体实现步骤
1. 配置物理体碰撞掩码
给玩家节点和地面/平台节点分别设置碰撞类别,确保两者能触发碰撞检测:
class PlayerNode: SCNNode { var isJumping = false let jumpForce: CGFloat = 10.0 override init() { super.init() // 初始化动态物理体 physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil) // 玩家碰撞类别(自定义二进制标识) physicsBody?.categoryBitMask = 0b0001 // 监听与地面/平台的碰撞 physicsBody?.contactTestBitMask = 0b0010 } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func jump() { guard !isJumping else { return } isJumping = true physicsBody?.applyForce(SCNVector3(x: 0, y: jumpForce, z: 0), asImpulse: true) } }
地面节点物理体配置:
let groundNode = SCNNode() groundNode.physicsBody = SCNPhysicsBody(type: .static, shape: nil) // 地面碰撞类别与玩家监听的类别匹配 groundNode.physicsBody?.categoryBitMask = 0b0010
2. 实现碰撞代理方法
在视图控制器或场景代理中实现SCNPhysicsContactDelegate,处理碰撞事件:
extension GameViewController: SCNPhysicsContactDelegate { func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) { // 判断碰撞双方是否为玩家和地面/平台 let isPlayerGroundCollision = (contact.nodeA is PlayerNode && contact.nodeB.physicsBody?.categoryBitMask == 0b0010) || (contact.nodeB is PlayerNode && contact.nodeA.physicsBody?.categoryBitMask == 0b0010) guard isPlayerGroundCollision else { return } // 重置isJumping属性 if let player = contact.nodeA as? PlayerNode { player.isJumping = false } else if let player = contact.nodeB as? PlayerNode { player.isJumping = false } } }
别忘了给场景的物理世界设置代理:
sceneView.scene?.physicsWorld.contactDelegate = self
对你尝试方法的补充说明
- 检查垂直速度:该方法的缺陷在于,跳跃顶点速度为0会误判,且节点旋转时,全局坐标系下的垂直速度无法反映局部"脚部"的实际状态,导致误标记。
- 检查位置:无法区分节点是站在平台上还是处于空中,场景中有多层平台时完全不可靠。
优化你提出的向下力方案
如果不想依赖碰撞代理,可以在PlayerNode子类中实现帧更新方法,通过微小向下力检测位置变化:
extension PlayerNode: SCNSceneRendererDelegate { private var lastYPosition: CGFloat = 0 func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) { guard isJumping else { return } let currentY = position.y // 施加微小向下力(不影响正常物理运动) physicsBody?.applyForce(SCNVector3(x: 0, y: -0.1, z: 0), asImpulse: false) // 下一帧检测位置变化,判断是否触地 DispatchQueue.main.async { if abs(self.position.y - currentY) < 0.001 { self.isJumping = false } self.lastYPosition = self.position.y } } }
注意:需根据场景物理参数调整向下力大小和位置变化阈值,避免误判。
内容的提问来源于stack exchange,提问作者rayaantaneja
相关产品推荐
相关产品推荐

