I have a scene setup that uses places images on planes to mimic an RPG-style character interaction. There's a large scene background image and a smaller character image in the foreground. Both are added as content to a RealityView. There's one attachment that is a dialogue window for interaction with the character, and it is attached to the character image. When the scene changes, I need the images and the dialogue window to refresh. My current approach has been to remove everything from the scene and add the new content in the update closure.
@EnvironmentObject var narrativeModel: NarrativeModel
@EnvironmentObject var dialogueModel: DialogueViewModel
@State private var sceneChange = false
private let dialogueViewID = "dialogue"
var body: some View {
RealityView { content, attachments in
//at start, generate background image only and no characters
if narrativeModel.currentSceneIndex == -1 {
content.add(generateBackground(image: narrativeModel.backgroundImage!))
}
} update : { content, attachments in
print("update called")
if narrativeModel.currentSceneIndex != -1 {
print("sceneChange: \(sceneChange)")
if sceneChange {
//remove old entitites
if narrativeModel.currentSceneIndex != 0 {
content.remove(attachments.entity(for: dialogueViewID)!)
}
content.entities.removeAll()
//generate the background image for the scene
content.add(generateBackground(image: narrativeModel.scenes[narrativeModel.currentSceneIndex].backgroundImage))
//generate the characters for the scene
let character = generateCharacter(image: narrativeModel.scenes[narrativeModel.currentSceneIndex].characterImage)
content.add(character)
print(content)
if let character_attachment = attachments.entity(for: "dialogue"){
print("attachment clause executes")
character_attachment.position = [0.45, 0, 0]
character.addChild(character_attachment)
}
}
}
} attachments: {
Attachment(id: dialogueViewID){
DialogueView()
.environmentObject(dialogueModel)
.frame(width: 400, height: 600)
.glassBackgroundEffect()
}
}
//load scene images
.onChange(of:narrativeModel.currentSceneIndex){
print("SceneView onChange called")
DispatchQueue.main.async{
self.sceneChange = true
}
print("SceneView onChange toggle - sceneChange = \(sceneChange)")
}
}
If I don't use the dialogue window, this all works just fine. If I do, when I click the next button (in another view), which increments the current scene index, I enter some kind of loop where the sceneChange value gets toggled to true but never gets toggled back to false (even though it's changed in the update closure). The reason I have the sceneChange value is because I need to update the content and attachments whenever the scene index changes, and I need a state variable to trigger the update function to do this. My questions are:
- Why might I be entering this loop? Why would it only happen if I send a message in the dialogue view attachment, which is a whole separate view?
- Is there a better way to be doing this?
Apologies for the latency.
I have, but then the problem is that I can't get access to content outside of the make or update closure, to my knowledge.
You can create a root entity as a state variable, add that to content, then add all children to that root entity. Since the root entity is a state variable it will be available on update.
why would update keep firing based on an interaction with an attachment? I though it only happens when state changes?
As you mentioned render is called anytime state changes. The attachment has a binding to $dialogueModel.currentInput
so changing it changes the state and causes a rerender.