我正在尝试使客户端预测/对帐适用于客户端/服务器模型。我一直在使用Scenekit渲染3D世界并处理物理计算。
为了使这项工作有效,我需要能够在不渲染这些更新的情况下,强制物理模拟“计算”给定数量的框架(施加力,检查碰撞,更新节点位置/方向)。这样,当我确定客户端对服务器发回的内容有错误的预测时,我可以重新定位物理仿真中的所有节点以使其与服务器发送的内容匹配(包括设置物理属性,例如速度/角速度),然后快速浏览服务器尚未处理的“事件”,以返回到客户端的当前时间点。
我了解SceneKit没有使用确定性的物理引擎,但我希望我能得到看起来“足够好”的东西(我的错误预测系统有一个阈值,可以确定给定的预测是否与服务器的不一致)。
该过程大致如下:
我已经尝试过使用物理学世界的timeStep,但是似乎增加此属性也会降低每个物理学“周期”的作用力(我们获得了更多的物理学模拟,但最终结果是通过移动物理学物体而进行的“精确”模拟)检查碰撞前的距离较短)。
我已经尝试过物理世界的速度,但是a)增加此值会降低物理模拟的精度,b)由于某些原因,在场景编辑器中创建的场景的默认速度为6而不是1,因此在此处确定合适的值是有点令人困惑,并且c)在下一次模拟尝试运行之前,它似乎没有任何影响。
[我尝试过使用场景视图的sceneTime,每次我处理一组历史输入时都将其递增1,我认为这是行得通的,但仔细研究后,似乎没有任何作用。
我尝试暂停场景,应用更改,然后播放场景,但是暂停场景也会暂停物理模拟。
有没有办法让SCNPhysicsWorld在循环中手动/重复地更新其物理模拟,而不会触发任何渲染调用?
经过大量的试验和错误后,我无法获得近似于强制仿真更新才能与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将被更新以支持手动物理步骤。