SCNPhysicsWorld Force Simulation Update

问题描述 投票:2回答:1

我正在尝试使客户端预测/对帐适用于客户端/服务器模型。我一直在使用Scenekit渲染3D世界并处理物理计算。

为了使这项工作有效,我需要能够在不渲染这些更新的情况下,强制物理模拟“计算”给定数量的框架(施加力,检查碰撞,更新节点位置/方向)。这样,当我确定客户端对服务器发回的内容有错误的预测时,我可以重新定位物理仿真中的所有节点以使其与服务器发送的内容匹配(包括设置物理属性,例如速度/角速度),然后快速浏览服务器尚未处理的“事件”,以返回到客户端的当前时间点。

我了解SceneKit没有使用确定性的物理引擎,但我希望我能得到看起来“足够好”的东西(我的错误预测系统有一个阈值,可以确定给定的预测是否与服务器的不一致)。

该过程大致如下:

  1. 客户端根据本地输入/当前本地模拟状态在本地运行物理模拟
  2. 每次物理仿真运行时,客户端都会生成自己的主机数据包,将其缓存为当前帧日期。
  3. 客户端从服务器收到一个主机数据包,说明物理模拟的外观。该数据包基于客户端过去发送到服务器的数据包。主机数据包包含一个标识符(日期),客户端与它捆绑在一起的数据包已事先发送到服务器。
  4. 客户端查询该日期以前的缓存版本,以了解该日期在过去的物理世界。如果它与主机发送的内容相差太大,则我们确定我们的预测有误,需要更正。
  5. 客户端重新定位场景中的所有节点以匹配主机数据包状态(设置位置,旋转,速度,角速度等)
  6. 然后,客户端在主机数据包的日期之后(服务器尚未处理的客户端数据包作为此主机数据包的一部分)遍历它已发送到服务器的所有数据包,并将它们重新应用于它们的相应节点。
  7. 我正在苦苦挣扎的部分:每次客户端将一组历史数据包应用于第6部分中的节点时,我都需要强制物理模拟来“处理”这些更改(应用从这些数据包生成的力,对于冲突,请更新节点位置),然后再继续处理服务器尚未处理的下一组数据包。

我已经尝试过使用物理学世界的timeStep,但是似乎增加此属性也会降低每个物理学“周期”的作用力(我们获得了更多的物理学模拟,但最终结果是通过移动物理学物体而进行的“精确”模拟)检查碰撞前的距离较短)。

我已经尝试过物理世界的速度,但是a)增加此值会降低物理模拟的精度,b)由于某些原因,在场景编辑器中创建的场景的默认速度为6而不是1,因此在此处确定合适的值是有点令人困惑,并且c)在下一次模拟尝试运行之前,它似乎没有任何影响。

[我尝试过使用场景视图的sceneTime,每次我处理一组历史输入时都将其递增1,我认为这是行得通的,但仔细研究后,似乎没有任何作用。

我尝试暂停场景,应用更改,然后播放场景,但是暂停场景也会暂停物理模拟。

有没有办法让SCNPhysicsWorld在循环中手动/重复地更新其物理模拟,而不会触发任何渲染调用?

server client scenekit physics prediction
1个回答
0
投票

经过大量的试验和错误后,我无法获得近似于强制仿真更新才能与SceneKit一起使用的任何信息。与Apple联系后,我还得知当前不支持此功能。

我决定使用一个单独的物理引擎来代替SceneKit来实现我的目标,这使我可以将SCNNode定向到外部物理引擎的计算。

为此,我创建了PhysicsKit,这是流行的Bullet物理引擎的开源包装,可以在此处找到:https://github.com/AdamEisfeld/PhysicsKit

该框架可通过Cocoapods获得,将来我将致力于增加对Carthage / SPM的支持。回购和类文档中的README应该有助于启动和运行程序,但是为了在本文中包含一些实际代码,这里简要演示了如何使用框架触发物理模拟步骤,显示充气球与地面碰撞:

import UIKit
import SceneKit
import PhysicsKit

class ViewController: UIViewController {

    let sceneView = SCNView()
    let scene = SCNScene(named: "scenes.scnassets/world.scn")!
    let physicsWorld = PKPhysicsWorld()
    let physicsScene = PKPhysicsScene(isMotionStateEnabled: true)
    var sceneTime: TimeInterval? = nil
    let cameraNode = SCNNode()

    override func viewDidLoad() {

        super.viewDidLoad()

        setupSceneView()

        setupGroundPlane()
        setupBouncyBall()

    }

    private func setupSceneView() {
        // Embed the scene view into our view
        sceneView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(sceneView)
        NSLayoutConstraint.activate([
            sceneView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            sceneView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            sceneView.topAnchor.constraint(equalTo: view.topAnchor),
            sceneView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        ])
        // Play the sceneview so we get constant calls to the delegate's update(atTime...) function
        sceneView.isPlaying = true
        // Set the delegate so we can update our physics world with the scene view
        sceneView.delegate = self
        // Set the scene
        sceneView.scene = scene

        // Configure the camera
        cameraNode.camera = SCNCamera()
        scene.rootNode.addChildNode(cameraNode)
        sceneView.pointOfView = cameraNode
        cameraNode.position = SCNVector3(0, 0, 20)

    }

    private func setupGroundPlane() {

        // Create a visual node representing a ground plane, and add it to our scene
        let groundGeometry = SCNFloor()
        let groundNode = SCNNode(geometry: groundGeometry)
        scene.rootNode.addChildNode(groundNode)

        // Create a physics body for the ground and add it to our physics world
        let groundShape = PKCollisionShapeStaticPlane()
        let groundBody = PKRigidBody(type: .static, shape: groundShape)
        physicsWorld.add(groundBody)

        // Shift the ground down 10 units
        groundBody.position = .vector(0, -10, 0)

        // Make the ground a little bouncy
        groundBody.restitution = 0.6

        // Wire the transform of the ground's rigid body to it's node
        physicsScene.attach(groundBody, to: groundNode)
    }

    private func setupBouncyBall() {

        // Create a visual node representing a bouncy ball, and add it to our scene
        let ballGeometry = SCNSphere(radius: 1.0)
        let ballNode = SCNNode(geometry: ballGeometry)
        scene.rootNode.addChildNode(ballNode)

        // Create a physics body for the bouncy ball and add it to our physics world
        let ballShape = PKCollisionShapeSphere(radius: 1.0)
        let ballBody = PKRigidBody(type: .dynamic(mass: 1.0), shape: ballShape)
        physicsWorld.add(ballBody)

        // Make the ball a little bouncy
        ballBody.restitution = 0.6

        // Wire the transform of the ball's rigid body to it's node
        physicsScene.attach(ballBody, to: ballNode)

    }

}

extension ViewController: SCNSceneRendererDelegate {

    func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
        sceneTime = sceneTime ?? time
        let physicsTime = time - sceneTime!

        // Here we get to the root of the SO question: We can now manually increment the physicsWorld's
        // simulation time, irrespective of the scene's render loop. We could iteratively increment
        // this simulationTime value as many times as we want in a single render cycle of the scene view.
        physicsWorld.simulationTime = physicsTime
        physicsScene.iterativelyOrientAllNodesToAttachedRigidBodies()
    }

}

我不会将其标记为可接受的答案,因为它并不能真正回答原始问题,但是我希望将其包括在此处,以便将来在对任何人有帮助的情况下提供参考。也许将来SceneKit将被更新以支持手动物理步骤。

  • 亚当
© www.soinside.com 2019 - 2024. All rights reserved.