RealityKit Basics: Using Convenience Entities

When should we use Convenience Entities vs. components.

Overview

RealityKit ships with a handful of Convenience Entities. These are wrappers for common functionality. The most common one is ModelEntity, which we’ve used in a number of examples.

When we use ModelEntity RealityKit creates something that is equivalent to an Entity with a Model Component.

// Left
let leftSphere = ModelEntity(mesh: .generateSphere(radius: 0.01), materials: [redMat])

// Right
let right = Entity()
let model = ModelComponent(mesh: .generateSphere(radius: 0.01), materials: [redMat])
right.components.set(model)

In both cases we create a small red sphere.

Let’s look at a more complex comparison. We’ll create an anchor attached to the index finger. We’ll visualize this with a sphere and place a small attachment with a label.

On the right hand we’ll create a single entity and add all three components.

let rightHand = Entity()
let model = ModelComponent(mesh: .generateSphere(radius: 0.01), materials: [redMat])
let anchor = AnchoringComponent(.hand(.right, location: .indexFingerTip))
let attachment = ViewAttachmentComponent(rootView: SimpleLabel(text: "Right"))
rightHand.components.set([anchor, model, attachment])
content.add(rightHand)

On the left hand we’ll use Convenience Entities. The AnchorEntity will be our root, with child entities for the ModelEntity and ViewAttachmentEntity.

// Left Hand: Use an Anchor Entity as the root, then attach children to it
let leftHand = AnchorEntity(.hand(.left, location: .indexFingerTip))
content.add(leftHand)

// Add a sphere to track the anchor
let leftSphere = ModelEntity(mesh: .generateSphere(radius: 0.01), materials: [redMat])
leftHand.addChild(leftSphere)

// Add an attachment just above the sphere
let leftAttachment = ViewAttachmentEntity()
leftAttachment.attachment = .init(rootView: SimpleLabel(text: "Left"))
leftAttachment.position.y = 0.05
leftHand.addChild(leftAttachment)

If you run this code you’ll notice that the attachment on the right hand overlaps the red sphere. The anchor, model, and attachment all share a single transform. The left hand example fixes this by offsetting the attachment slightly.

We could of course do the same thing with regular entities. Here is another version of the right hand that fixes the attachment issue.

// Build a root with the model and anchor
let rightHand = Entity()
let model = ModelComponent(mesh: .generateSphere(radius: 0.01), materials: [redMat])
let anchor = AnchoringComponent(.hand(.right, location: .indexFingerTip))
rightHand.components.set([anchor, model])

// Add a child eneity for the attachment
let attachment = ViewAttachmentComponent(rootView: SimpleLabel(text: "Right"))
let rightAttachment = Entity()
rightAttachment.components.set([attachment])
rightAttachment.position.y = 0.05
rightHand.addChild(rightAttachment)
content.add(rightHand)

There is something important to keep in mind when using these Convenience Entities. They don’t create entities that match the same structure as regular entities. If you’re app needs to crawl the entity hierarchy then you may not find what you expect. You can see the differences for yourself in the Xcode by using the button to Capture Entity Hierarchy.

Right demo with an entity using several components.
Right demo with an entity using several components.
Left demo: named Convenience Entities
Left demo: named Convenience Entities

When to use Convenience Entities really comes down to a matter of taste. They can save you a bit of time but may lead to an expected scene hierarchy. We’ve found it best to use Entity in most cases, attaching the components and creating child entities as needed.

Video Demo

Video demo showing the two sets of entities in action.

Example Code

struct Example139: View {
    var body: some View {
        RealityView { content in
            let redMat = SimpleMaterial(color: .stepRed, isMetallic: false)

            // Right hand: Build an entity and attach all components to it
            let rightHand = Entity()
            let model = ModelComponent(mesh: .generateSphere(radius: 0.01), materials: [redMat])
            let anchor = AnchoringComponent(.hand(.right, location: .indexFingerTip))
            let attachment = ViewAttachmentComponent(rootView: SimpleLabel(text: "Right"))
            rightHand.components.set([anchor, model, attachment])
            content.add(rightHand)

        

            // Right hand alternative: combine both options
//            let rightHand = Entity()
//            let model = ModelComponent(mesh: .generateSphere(radius: 0.01), materials: [redMat])
//            let anchor = AnchoringComponent(.hand(.right, location: .indexFingerTip))
//            rightHand.components.set([anchor, model])
//            let attachment = ViewAttachmentComponent(rootView: SimpleLabel(text: "Right"))
//            let rightAttachment = Entity()
//            rightAttachment.components.set([attachment])
//            rightAttachment.position.y = 0.05
//            rightHand.addChild(rightAttachment)
//            content.add(rightHand)


            // Left Hand: Use an Anchor Entity as the root, then attach children to it
            let leftHand = AnchorEntity(.hand(.left, location: .indexFingerTip))
            content.add(leftHand)

            // Add a sphere to track the anchor
            let leftSphere = ModelEntity(mesh: .generateSphere(radius: 0.01), materials: [redMat])
            leftHand.addChild(leftSphere)

            // Add an attachment just above the sphere
            let leftAttachment = ViewAttachmentEntity()
            leftAttachment.attachment = .init(rootView: SimpleLabel(text: "Left"))
            leftAttachment.position.y = 0.05
            leftHand.addChild(leftAttachment)


        }
    }
}

fileprivate struct SimpleLabel: View {
    var text: String
    var body: some View {
        Text(text)
            .font(.headline)
            .padding()
            .background(.black)
            .cornerRadius(10)
    }
}

Support our work so we can continue to bring you new examples and articles.

Download the Xcode project with this and many more examples from Step Into Vision.
Some examples are provided as standalone Xcode projects. You can find those here.

Questions or feedback?