moveCharacter reports collision with itself

I'm running into an issue with collisions between two entities with a character controller component. In the collision handler for moveCharacter the collision has both hitEntity and characterEntity set to the same object. This object is the entity that was moved with moveCharacter()

The below example configures 3 objects.

  • stationary sphere with character controller
  • falling sphere with character controller
  • a stationary cube with a collision component

if the falling sphere hits the stationary sphere then the collision handler reports both hitEntity and characterEntity to be the falling sphere. I would expect that the hitEntity would be the stationary sphere and the character entity would be the falling sphere.

if the falling sphere hits the cube with a collision component the the hit entity is the cube and the characterEntity is the falling sphere as expected.

Is this the expected behavior? The entities act as expected visually however if I want the spheres to react differently depending on what character they collided with then I am not getting the expected results. IE: If a player controlled character collides with a NPC then exchange resource with NPC. if player collides with enemy then take damage.

import SwiftUI
import RealityKit

struct ContentView: View {
    @State var root: Entity = Entity()
    @State var stationary: Entity = createCharacter(named: "stationary", radius: 0.05, color: .blue)
    @State var falling: Entity = createCharacter(named: "falling", radius: 0.05, color: .red)
    @State var collisionCube: Entity = createCollisionCube(named: "cube", size: 0.1, color: .green)
    //relative to root
    @State var fallFrom: SIMD3<Float> = [0,0.5,0]
    
    var body: some View {
        RealityView { content in
            content.add(root)
            root.position = [0,-0.5,0.0]
            
            root.addChild(stationary)
            stationary.position = [0,0.05,0]
            
            root.addChild(falling)
            falling.position = fallFrom
            
            root.addChild(collisionCube)
            collisionCube.position = [0.2,0,0]
            collisionCube.components.set(InputTargetComponent())
        }
        .gesture(SpatialTapGesture().targetedToAnyEntity().onEnded { tap in
            let tapPosition = tap.entity.position(relativeTo: root)
            falling.components.remove(FallComponent.self)
            falling.teleportCharacter(to: tapPosition + fallFrom, relativeTo: root)
        })
        .toolbar {
            ToolbarItemGroup(placement: .bottomOrnament) {
                HStack {
                    Button("Drop") {
                        falling.components.set(FallComponent(speed: 0.4))
                    }
                    Button("Reset") {
                        falling.components.remove(FallComponent.self)
                        falling.teleportCharacter(to: fallFrom, relativeTo: root)
                    }
                }
            }
        }
    }
}

@MainActor
func createCharacter(named name: String, radius: Float, color: UIColor) -> Entity {
    let character = ModelEntity(mesh: .generateSphere(radius: radius), materials: [SimpleMaterial(color: color, isMetallic: false)])
    character.name = name
    character.components.set(CharacterControllerComponent(radius: radius, height: radius))
    
    return character
}

@MainActor
func createCollisionCube(named name: String, size: Float, color: UIColor) -> Entity {
    let cube = ModelEntity(mesh: .generateBox(size: size), materials: [SimpleMaterial(color: color, isMetallic: false)])
    cube.name = name
    cube.generateCollisionShapes(recursive: true)
    return cube
}

struct FallComponent: Component {
    let speed: Float
}
struct FallSystem: System{
    static let predicate: QueryPredicate<Entity> = .has(FallComponent.self) && .has(CharacterControllerComponent.self)
    static let query: EntityQuery = .init(where: predicate)
    let down: SIMD3<Float> = [0,-1,0]
    
    init(scene: RealityKit.Scene) {
    }
    
    func update(context: SceneUpdateContext) {
        let deltaTime = Float(context.deltaTime)
        for entity in context.entities(matching: Self.query, updatingSystemWhen: .rendering) {
            let speed = entity.components[FallComponent.self]?.speed ?? 0.5
            entity.moveCharacter(by: down * speed * deltaTime, deltaTime: deltaTime, relativeTo: nil) { collision in
                if collision.hitEntity == collision.characterEntity {
                    print("hit entity has collided with itself")
                }
                
                print("\(collision.characterEntity.name) collided with \(collision.hitEntity.name) ")
            }
        }
    }
}

#Preview(windowStyle: .volumetric) {
    ContentView()
}

This does appear to be unexpected behavior. Could you please file a bug report using Feedback Assistant and share the number here? That'll help us track this down on our end.

For the scenarios you described, I recommend ignoring collision events where characters collided with themselves. Instead, you could use a non-character entity that is a child of your other characters: this entity should have no physics body but does need a collision component with mode set to .trigger.

Note that you will need to subscribe to CollisionEvents.Began to receive callbacks for collisions between characters and non-character entities. Here's an example where I replace your stationary sphere with a sphere that has a trigger (so the character passes through), but still receives a callback when subscribing to the collision event:

_ = content.subscribe(to: CollisionEvents.Began.self) {
    event in
    print("collision event began: \(event.entityA.name) collided with \(event.entityB.name)")
}

Then you can create your stationary sphere like this:

@State var stationary: Entity = createSphereTrigger(named: "stationary", radius: 0.05, color: .blue)

// ...

@MainActor
func createSphereTrigger(named name: String, radius: Float, color: UIColor) -> Entity {
    let sphere = ModelEntity(mesh: .generateSphere(radius: radius), materials: [SimpleMaterial(color: color, isMetallic: false)])
    sphere.name = name
    sphere.generateCollisionShapes(recursive: true)
    sphere.components[CollisionComponent.self]?.mode = .trigger
    return sphere
}

Let me know if you have any more questions!

moveCharacter reports collision with itself
 
 
Q