You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 11:25:43