I have an AR game using ARKit with SceneKit that works just fine in iOS 17.
In the iOS 18 betas, the AR background image shows black instead of showing the real world. As a result there's no tracking and obviously the whole game is useless.
I narrowed down the issue to showing the Game Center Access Point.
My app has ViewController 1 (VC1) showing the main menu and that's where I want to show the GC Access Point. From there you open VC2 which shows a list of levels. Selecting any level will open VC3 which has the ARScene.
Following is the code I use to start Game Center in VC1:
GKLocalPlayer.local.authenticateHandler = { gcAuthVC, error in
let isGameCenterReady = (gcAuthVC == nil) && (error == nil)
if let viewController = gcAuthVC {
self.present (viewController, animated: true, completion: nil)
}
if error != nil {
print(error?.localizedDescription ?? "")
}
if isGameCenterReady {
GKAccessPoint.shared.location = .topLeading
GKAccessPoint.shared.showHighlights = true
GKAccessPoint.shared.isActive = true
}
}
When switching to VC2 I run GKAccessPoint.shared.isActive = false so that the Access Point will no longer show in any of the following VCs. I tried running it in VC1, VC2, and again in VC3 - it doesn't change anything. Once I reach VC3, the background is black.
If in VC1 I don't run GKAccessPoint.shared.isActive = true, so I don't activate the access point, the behavior is as follows:
If I wait until after the Game Center login animation completes and closes on its own and then I proceed to VC2 and VC3, the camera image will show correctly
If I quickly move to VC2 before the Game Center login animation has completed, so my code will close it by setting active = false, and then I continue to VC3, I will see the black background problem.
So it does look like activating the access point and then de-activating it causes the issue. BTW, if I activate the access point and leave it on in all VCs, the same black background issue persists.
Other than that, when I'm in VC3 with the black background and I switch to another app (so my game moves to the background), when it returns to the foreground, the camera suddenly shows the real world correctly!
I tried to manually reset the AR session by pausing and restarting it, but that didn't change anything. Also, when I check with the debugger, it looks like when the app comes back to the foreground it also doesn't run the session start code.
But something does seem to reset itself so I wonder what that is. Maybe I could trigger the same manually in my cdoe???
I repeat that everything works just fine in iOS 17 and below. This problem only started with the iOS 18 beta (currently on beta 5, but it started in some of the previous betas as well).
So could this be a bug in iOS 18?
As a workaround I could check the iOS version and if it's iOS18 not activate the access point, hoping that the user will not jump to VC2 too quickly, and show my own button which will open Game Center. But I'd rather give the users the full experience with their own avatar and the highlights showing up. Plus, certainly some users will move quickly to VC2 and that will be an awful experience.
Any help would be greatly appreciated. Thanks!
Post
Replies
Boosts
Views
Activity
In an ARKit game using SceneKit, I’m detecting collisions between the real user and walls that I build out of SCNBoxes. I have a cylinder that follows the device’s pointOfView to accomplish that.
The node of boxes is set as dynamic and the cylinder is set as kinematic, that way when the user moves and collides with the boxes, the boxes will move along.
All of the above works exactly right.
I use both physicsWorld didBegin and didEnd contact delegate methods because I need to know both when collisions start and when they end to disable and re-enable a certain button.
So I have standard code like the following.
In didBegin contact I hide the button as such:
func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
if contact.nodeA.physicsBody?.categoryBitMask == BodyType.wallsCategory.rawValue || contact.nodeB.physicsBody?.categoryBitMask == BodyType.wallsCategory.rawValue {
freezeButton.isHidden = true
}
}
Then in didEnd contact I re-enable the button like that:
func physicsWorld(_ world: SCNPhysicsWorld, didEnd contact: SCNPhysicsContact) {
freezeButton.isHidden = false
}
Most of the time this works fine, however, sometimes the didBegin method runs last, even though I clearly moved away and there are no more collisions so my app still thinks that we’re in a collision state and my button isn't restored.
I have SCNDebugOptions.showPhysicsShapes as debugOptions so I can clearly see that the cylinder is not touching the walls at all.
Yet, through debugging the code I can see that sometimes didBegin was run last.
I then need to cause a new collision then walk back to have the didEnd function be called and get my button back. But I cannot expect the user to perform such a workaround because my game is buggy.
I'm not using the didUpdate contact call as I don't need it.
How come didBegin is called last when clearly the collision has ended? What could cause this to happen like that?
And what could I do to fix this?
I am thinking of a workaround which would check if some time has passed since the last didBegin call that would indicate this is an old collision.
Or is there something else I could check if a collision is currently still in progress? AFAIK, SceneKit doesn't have a function similar to allContactedBodies as in SpriteKit
Is there something else I could use to check that 2 bodies are still in contact or not?
Thanks!
In my AR app, I rotate my node around some position in the real world.
In order to do that, I use the pivot or simdPivot attribute of the node to rotate around that position. Otherwise it would rotate around the node's center which is elsewhere and the whole node would move around instead of just rotating in place.
Up to here all is fine.
The problem is that in ARKit, using a pivot also moves the camera to the pivot's location.
I can compensate by changing the node's position in the opposite direction. But then, if I continue this way, the user will never be in the correct "real" position inside the node, but in some "other" location and only due to the pivot he will "see" the correct location.
This is a problem, because later on I will need to catch collisions between the user and elements in the scene. I could continue compensating for the pivot's location, but that's a real pain and not so elegant.
I thought that I could use the pivot, rotate, and then reset the pivot.
But it turns out that doing so will not keep the rotation that I set before. Rather, the rotation will behave as if the pivot was never created and will give me the exact behavior I didn't want to achieve.
So let's say I do this in my code:
let translation = simd_float4x4(SCNMatrix4MakeTranslation(0,0,-2))
node?.simdPivot = translation
let rotationMatrix = simd_float4x4(SCNMatrix4MakeRotation(.pi/4, 0, 1, 0))
node?.simdTransform = rotationMatrix
(I found that setting the simdRotation attribute didn't rotate the node so I set the simdTransform instead).
The above code will correctly rotate the node at position (0, 0, -2)
But then if I reset the pivot as such:
node?.simdPivot = matrix_identity_float4x4
Now the node will be rotated around (0, 0, 0).
Is there any other way to resolve this?
Can I move my node in any other way without having to always compensate for the pivot's location so that the user's location will always correspond to his real location inside the node?
Or is there another way to remove the pivot, but leave the rotation that I accomplished with the pivot and freeze the node in its new position/rotation?
Thanks!
I have an AR scene (built as a node with children nodes) that I want the user to walk inside it. Because it can be large, I added a pause button in the corner so that if the user hits an obstacle in the real world, he'll just need to press-and-hold that button to freeze the scene, move elsewhere in the real world, and when the button will be released the scene would have moved as-is to that new location, retaining the same relative position and rotation inside that scene.
What I did is very simple. When button is touched down I just did:
sceneView.session.pause()
And when the button is Touch Up Inside I ran:
sceneView.session.run(configuration)
This seemed to do the trick.
However, after I clicked and moved once or twice more and moved back to where I was before, my whole AR scene suddenly jumped back to its previous location in the real world.
So it did freeze and showed up in the new location for a second or two, but then it jumped back.
So I tried to resume the session with several of the available options.
If I ran:
sceneView.session.run(configuration, options: [.resetTracking])
the whole scene would move to the new location correctly as I wanted, but it would be set to its initial position in front of the camera (as opposed to being already "inside" the node) and rotation as if I was standing at (0,0,0) when I started the app, but now moved to the new location. In other words, it lost the current location and rotation that I was at when I pressed the pause button and just reset the scene.
All the other options that I tried had the same effect as if I didn't add any options.
I should add that I'm not using any anchors (so far) nor any ray casting. I didn't try different combinations of those options (except removeExistingAnchors and resetTracking together), but I doubt that that would help. Am I wrong?
What am I doing wrong? Anyone have a solution for my problem?
Or will I need to manually record the current position and rotation when pressing the pause button and then restore those with some position change functions of the node? I was hoping I could skip having to do it this way. And if I do need to do it manually, any tips on how to achieve this?
Thanks for your help!