I'm building a RealityKit application where I want to spawn a floating object into my scene, in dynamic mode, which is not affected by gravity. It needs to collide with other objects, have a mass of 1, and be affected by an impulse, which is why I've made it dynamic.
Currently the object spawns into the scene at the correct point, but immedately falls to the floor due to a gravitational force being applied. I've been stuck on this for a while now, and can't find anything on how to remove this gravitational force.
Here's how I'm creating and spawning the object;
// Create the object entity
let object = ModelEntity(mesh: MeshResource.generateBox(size: [0.07, 0.01, 0.14]), materials: [SimpleMaterial(color: .white, isMetallic: false)])
// Define object shape
let objectShape = [ShapeResource.generateBox(size: [0.07, 0.01, 0.14]]
// Create the object PhysicsBody
var objectPhysicsBody = PhysicsBodyComponent(shapes: objectShape, mass: 1, material: .default, mode: .dynamic)
// Create the object CollisionComponent
let objectCollision = CollisionComponent(shapes: objectShape)
// Set components to the object
object.components.set(objectPhysicsBody)
object.components.set(objectCollision)
// Attach the object to the anchor
anchor.addChild(object)
One thing I have tried is applying a constant negating force to the standard gravitational force, however this didn't work as intended. It does apply an opposite force, but it is increasing so gradually ends up sending the object flying upwards
This is what I tried;
var displayLink: CADisplayLink?
// Main view loaded function
override func viewDidLoad() {
super.viewDidLoad()
// Repeatedly call the update function
displayLink = CADisplayLink(target: self, selector: #selector(update))
displayLink?.add(to: .current, forMode: .common)
...
}
…
// Repeatedly call applyAntiGravity
@objc private func update() {
applyAntiGravityForce()
}
private func applyAntiGravityForce() {
guard let ballPhysicsBody = object.physicsBody else { return }
let gravity: SIMD3<Float> = [0, -9.8, 0]
let antiGravityForce = -gravity
ball.addForce(antiGravityForce, relativeTo: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
displayLink?.invalidate()
}
To add custom gravity to entities in a RealityKit scene, you can utilize the Entity Component System (ECS) to create a custom gravity Component
. The following approach allow you to create custom gravity behavior for entities in your scene by applying linear impulse to an Entity
with physicsBody
based on mass and gravity properties.
To spawn an entity in zero gravity, you can add this component to the entity and set its gravity property to 0. Here's an example implementation of a simple GravityComponent
:
class GravityComponent: Component {
var gravity: Float
var mass: Float
init(gravity:Float = 9.81, mass:Float) {
self.gravity = gravity
self.mass = mass
}
func update(entity: Entity, with deltaTime: TimeInterval, at physicsOrigin: Entity) {
// Check that gravity is not default
guard gravity != 9.81 else { return }
// Check that entity indeed has physicsBody
guard let physicsBody = entity as? HasPhysicsBody else { return }
// Calculate gravity correction
let newG: Float = 9.81 - gravity
// Apply linear impulse to the physicsBody
let impulse = mass * newG * Float(deltaTime)
physicsBody.applyLinearImpulse([0.0, impulse, 0.0], relativeTo: physicsOrigin)
}
}
The update
method applies a linear impulse to the entity's physics body based on the mass and gravity properties of the GravityComponent
. It also compensates for FPS changes by considering the time interval (deltaTime) between frames in the update loop. Note that we use applyLinearImpulse
to apply the gravity effect, which is preferred over addForce
, as explained by Apple in this article on designing scene hierarchies for efficient physics simulation.
You can add the component to your entity like this:
let gravity = GravityComponent(gravity: 0.0, mass: 1.0)
entity.components.set(gravity)
To use the GravityComponent
you can call the update method inside your update-loop of choice on entities with the component. Here's an example of how to do this with SceneEvents
:
func updateScene(on event: SceneEvents.Update) {
for entity in sceneEntities {
if let gravity = entity.components[GravityComponent.self] as? GravityComponent {
gravity.update(entity: entity, with: event.deltaTime, at: physicsOrigin)
}
}
}
In this example, we call the update
method on each entity that has a GravityComponent
. Note that we pass in the delta time and a physicsOrigin
entity, which is also recommended by Apple for improved physics simulation in scenes with entities of varying sizes. Apply the impulse relative to nil
to skip this recommendation.
Finally, remember to register your component (once!) before using it:
GravityComponent.registerComponent()
Even though the GravityComponent
compensates for delta time, the implementation is still sensitive to some FPS changes. If the frame rate drops significantly, the impulse becomes unpredictable and might add some disturbance to the entities -- which is especially visible in the case of simulating zero-g.
This solution might not work well for use-cases that requires high precision and predicability, but should be sufficient for simple cases and as a starting point for further improvements.
Starting with iOS 18, you can set custom gravity in RealityKit natively by modifying the gravity property of a PhysicsSimulationComponent.
Gravity
The gravity
vector of PhysicsSimulationComponent
defines the gravitational force applied to entities within the simulation. You can customize this vector to simulate different gravitational conditions or orientations. The default value is [0.0, -9.81, 0.0]
To apply a custom gravity vector to a specific part of your scene, you can add a PhysicsSimulationComponent to an entity and set its gravity property:
let entity = Entity()
var physicsSimulation = PhysicsSimulationComponent()
physicsSimulation.gravity = [0.0, 0.0, 0.0] // Zero gravity
entity.components.set(physicsSimulation)
This setup applies the specified gravity to the entity and its descendants.