这是一个开源的 iOS/macOS 封装库,用于 Bullet 物理库,并增加了对 SceneKit 的支持。
要求
PhysicsKit 被包装为一个通用的 XCFramework,为 iOS 和 macOS 构建,通过 Swift Package Manager 或 Cocoapods 发布。
通过 Cocoapods 安装
在 podfile 中参考 PhysicsKit
pod 'PhysicsKit'
运行 pod install 或 pod update。然后,将其导入到 Swift 源代码中并开始使用框架
import PhysicsKit
通过 Swift Package Manager 安装
- 在 Xcode 中,选择 File > Swift Packages > Add Package Dependency
- 输入 "https://github.com/AdamEisfeld/PhysicsKit" 作为远程 URL,然后点击 Next
- 输入所需的版本号或分支,然后点击 Next
- 点击 Finish
然后,将其导入到您的 Swift 源代码中并开始使用框架
import PhysicsKit
示例用法
创建一个物理世界来运行你的模拟
PKPhysicsWorld处理在一系列附加的PKRigidBody实例上运行Bullet物理模拟。
let physicsWorld = PKPhysicsWorld()
创建一个动态刚体以添加到物理世界
动态刚体会受到力/碰撞的影响。
// Create a collision shape to describe the rigid body
let dynamicCollisionShape = PKCollisionShapeBox(size: 1.0)
// Create a rigid body from this physics shape, with a type of "dynamic" so forces / collisions affect it
let dynamicBody = PKRigidBody(type: .dynamic, shape: dynamicCollisionShape)
// Customize the rigid body's physical properties
dynamicBody.restitution = 0.8
// Add the rigid body to the physics world
physicsWorld.add(dynamicBody)
配置刚体的变换
修改刚体位置/旋转的方法有很多。以下是一些示例。
// Position the rigid body 10 units above the "ground"
dynamicBody.position = .vector(0, 10, 0)
// You can adjust a rigid body's rotation either via quaternions...
dynamicBody.orientation = .quaternion(0,0,0,1)
// ...or via euler angles (in either radians or degrees)
dynamicBody.orientation = .euler(0, 0, 45, .degrees)
创建一个静态刚体以添加到物理世界
静态刚体不受力/碰撞的影响,但其他动态刚体可以与之碰撞。PhysicsKit还支持运动学刚体,此处未展示。
// Create a collision shape to describe the rigid body
let staticCollisionShape = PKCollisionShapeBox(width: 100, height: 0.01, length: 100)
// Create a rigid body from this physics shape, with a type of "static" so forces / collisions don't affect it. Other dynamic bodies can still collide with this body.
let staticBody = PKRigidBody(type: .static, shape: staticCollisionShape)
// Add the rigid body to the physics world
physicsWorld.add(staticBody)
步骤物理模拟
要运行模拟,必须通过某些时间增量向前推进物理模拟。通常,这个时间将等于上一次步骤模拟以来经过的时间。
您可以使用CADisplayLink或SceneKit的SCNView的渲染循环来执行此调用。
class SomeViewController: UIViewController {
var sceneTime: TimeInterval? = nil
... setup an SCNView, and wire this view controller up to the view's delegate property
}
extension SomeViewController: SCNSceneRendererDelegate {
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
// Determine how much time has elapsed since we last stepped the simulation
sceneTime = sceneTime ?? time
let physicsTime = time - sceneTime!
// Step the simulation
physicsWorld.simulationTime = physicsTime
}
}
添加触发区域
有时检测给定的刚体何时进入物理模拟中的某个区域是有用的。PKTriggers提供了这个功能
let triggerShape = PKCollisionShapeSphere(radius: 1.0)
let trigger = PKTrigger(shape: triggerShape)
physicsWorld.add(trigger)
将PhysicsKit连接到SceneKit
我们需要一种方法来可视化物理模拟。您可以使用您选择的渲染器(例如自定义OpenGL或Metal渲染器),或者使用SceneKit来处理繁重的工作。
PhysicsScene类提供了一个将PKRigidBody连接到SCNNode的机制,使得节点位置/方向会在关联的PKRigidBody在模拟中移动时更新。
创建PhysicsScene
let physicsScene = PKPhysicsScene(isMotionStateEnabled: true)
添加/附加PKRigidBodies和SCNNodes
let node: SCNNode = ...
let rigidBody: PKRigidBody = ...
let physicsWorld: PKPhysicsWorld = ...
let scene: SCNScene = ...
scene.rootNode.addChildNode(node)
physicsWorld.add(rigidBody)
physicsScene.attach(rigidBody, to: node)
运动状态或手动更新
为了使SCNNodes与附加的PKRigidBody实例对齐,PKPhysicsScene有两个选项,根据传递给其初始化器的isMotionStateEnabled值确定
- isMotionStateEnabled = true: 物理场景将自动监听Bullet物理引擎何时有刚体的转换变更,并将这些变更应用到关联的场景节点。
- isMotionStateEnabled = false: 您必须手动强制物理场景更新其场景节点转换,通过在物理场景上调用iterativelyOrientAllNodesToAttachedRigidBodies()实现。
动作
从PhysicsKit 1.0.2版本开始,添加了PKActions。类似于SceneKit中的SCNActions,您可以构建PKActions来随时间修改刚体的属性,用于动画等。
您可以创建自定义的PKAction,或使用提供的辅助构造函数,例如
// Move the rigid body to a new position in 1 second
let moveActionTo = PKAction.move(rigidBody, to: someNewPosition, duration: 1.0)
moveActionTo.run()
// Move the rigid body forwards by 1 unit / second, constantly
let moveActionBy = PKAction.move(rigidBody, by: .vector(0, 0, 1), duration: 1.0)
moveActionBy.repeatCount = -1 // Repeat forever
moveActionBy.run()
// Rotate the rigid body around it's y axis 180 degrees / second, 3 times
let orientAction = PKAction.orient(rigidBody, by: .euler(0, 180, 0, .degrees), duration: 1.0)
orientAction.repeatCount = 3
orientAction.run()
请注意,只有运动刚体支持PKActions。将PKActions添加到静态/动态刚体可能会产生未定义的行为。
光线投射
从PhysicsKit 1.0.3版本开始,添加了光线投射功能。通过PKPhysicsWorld执行光线投射,以确定哪些刚体与光线相交
let start: PKVector3 = .vector(0, 10, 0)
let end: PKVector3 = .vector(0, -10, 0)
let results = physicsWorld.rayCast(from: start, to: end)
for result in results {
let intersectedRigidBody = result.rigidBody
let intersectionWorldPosition = result.worldPosition
let intersectionWorldNormal = result.worldNormal
}
代理
观察模拟变化
通过符合PKPhysicsWorldSimulationDelegate协议,成为PKPhysicsWorld的模拟代理。此代理将在模拟步进之前和之后被通知。
观察碰撞
通过符合PKPhysicsWorldCollisionDelegate协议,成为PKPhysicsWorld的碰撞代理。此代理将在任意两个刚体开始、继续和结束时被通知碰撞。
观察触发区域
通过符合PKPhysicsWorldTriggerDelegate协议,将自身设置为PKPhysicsWorld的触发代理。此代理将在刚体进入、保持和离开触发区域时被通知。
观察物理场景更新
通过符合PKPhysicsSceneUpdateDelegate协议,将自身设置为PKPhysicsScene的代理。该代理将在节点定位到其刚体之前和之后被通知,并且当物理场景的isMotionStateEnabled设置为true时,如果给定刚体的内部转换发生变化,也会被通知。
碰撞形状
碰撞形状是物理模拟的基本构建块。PhysicsKit提供许多选项来构建物理形状以获得正确的行为,包括
- PKCollisionShapeBox
- PKCollisionShapeSphere
- PKCollisionShapeCapsule
- PKCollisionShapeGeometry(允许您从现有的SceneKit SCNGeometry实例构建物理形状,这些实例可以从外部Collada .dae文件、SceneKit .scn文件或从一系列SCNGeometry实例加载)
- PKCollisionShapeStaticPlane(无限平面,对地面/墙壁很有用)
- PKCollisionShapeFromData(允许您从先前序列化的碰撞形状数据构建物理形状)
- PKCollisionShapeCompound(允许您从其他PKCollisionShapes数组构建物理形状)
PKCollisionShapeFromData对于加快模拟设置很有用。生成碰撞网格可能需要一些时间,尤其是在从非标准原形状(例如 PKCollisionShapeGeometry)加载时。所有PKCollisionShapes都具有一个serialize()函数,允许您获得其数据的表示形式,这可以保存到磁盘/与应用程序包一起打包。然后您可以使用PKCollisionShapeFromData加载这些形状。
let originalCollisionShape = PKCollisionShapeGeometry(...)
let data = originalCollisionShape.serialize() // Save this data to disk somewhere, and stop using PKCollisionShapeGeometry in favor of:
let recreatedCollisionShape = PKCollisionShapeFromData(data)
更新Bullet
PhysicsKit当前针对Bullet v2.89
本地安装Bullet
- 确保已安装cmake
- 通过https://cmake.com.cn/download/安装cmake
- 通过PATH="/Applications/CMake.app/Contents/bin":"$PATH"安装cmake命令行工具
- 安装所需的Bullet库
- 从https://github.com/bulletphysics/bullet3安装Bullet
- 从bullet文件夹中运行xcode.command以生成Xcode项目
修改PhysicsKit目标所使用的Bullet源文件
- 找到包含您所需源代码的bullet文件夹。例如,"src/LinearMath"
- 将此文件夹复制到repo中,在PhysicsKit-shared/bullet下
- 将此文件夹拖到PhysicsKit Xcode项目的"bullet"组下
- 确保选择"创建组",不要选择任何目标,并点击完成
- 在添加的目录中搜索任何.cpp文件,并手动将它们添加到PhysicsKit-ios和PhysicsKit-macos目标中
- 通过Target / Build Phases / Compile Sources将"-w"编译器标志添加到所有cpp文件中
更新PhysicsKit
在做出任何所需的更改后,按照以下步骤发布新的PhysicsKit版本
- 在终端中,导航到PhysicsKit的根目录,并运行helper脚本buildPhysicsKit.sh。此脚本将
- 为所有支持的平台创建PhysicsKit的.xcarchives
- 将这些.xcarchives打包成一个 xcframework
- 仍在终端中,运行helper脚本deployPhysicsKit.sh,并传递您想要应用给当前版本的版本号。PhysicsKit使用语义版本化(请参阅https://semver.org)。此脚本将
- 更新podspec中声明的版本
- 使用版本号标记当前git分支
- 运行"pod lib lint"以确保Cocoapods功能没有损坏
- 暂存所有更改以进行提交
- 提交所有更改(将出现一个提交信息对话框)
- 将分支和版本标签推到远程repo上
- 将pod推送到CocoaPods以便公开消费
添加 Objective-C 文件
- 在 “objc” 文件夹下添加所需的 Objective-C 文件
- 确保实现文件支持 objc++,将 ".m" 扩展名替换为 ".mm"
- 将任何 C++ 依赖项添加到单独的 "MyClassName+Internal.h" 文件中,该文件扩展了您的类
- 在 PhysicsKit.h 文件中添加导入。"MyClassName+Internal.h" 文件不应在此处导入/暴露给 Swift
添加平台特定文件
- 将任何 iOS 专用文件添加到 PhysicsKit-ios 目录/将其链接到 PhysicsKit-ios 目标
- 将任何 macOS 专用文件添加到 PhysicsKit-macos 目录/将其链接到 PhysicsKit-macos 目标
作者
Adam Eisfeld,[email protected]
许可证
PhysicsKit 可在 zlib 许可证下使用。有关更多信息,请参阅 LICENSE 文件。