Black screen on ARView - swift

I am trying to programmatically instantiate an ARView. This is my code for the View Controller
import Foundation
import UIKit
import RealityKit
class ARViewController: UIViewController {
var arView: ARView = {
let arview = ARView()
arview.cameraMode = .ar
arview.automaticallyConfigureSession = true
arview.translatesAutoresizingMaskIntoConstraints = false
return arview
}()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
setupUIConstraints()
}
func setupUI() {
view.addSubview(arView)
// Load the "Cupcake" scene from the "Experience" Reality File
let boxAnchor = try! Experience.loadBox()
// Add the box anchor to the scene
arView.scene.anchors.append(boxAnchor)
}
func setupUIConstraints() {
view.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
view.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
view.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
view.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
}
}
The app will appropriately ask me for Camera permissions, but even after accepting, the 'view' is just a black screen. I am on an iPhone XS, and have imported arkit into my info.plist. Any help would be much appreciated.

In setupUIConstraints you're setting the constraints for the ARViewController.view to its own constraints, rather than the ARView.
Change to the ARView and you should be good!

Related

How to add a new scene to an existing anchor and remove the previous scene?

I have an rcproject file with about 12 scenes (500mb or so). In order to lessen the load on iOS devices I tried breaking it apart into separate rcproject files and change the scene using notification triggers. However when doing this and adding the new scene as a child to the main anchor, the new scene renders in a new spot, breaking the AR experience. There must be a way to add the new scenes to the exact same anchor/position. Alternatively, is there a better way than seperating the rcproject to lessen load on ram etc?
Here is my ARView
struct ARViewContainer: UIViewRepresentable {
func makeUIView(context: Context) -> ARView {
let arView = ARView(frame: .zero)
// The experience consists of a "Base" (it acts as a permanent platform for all the scenes to be rendered on)
let baseAnchor = try! Base.loadIntro()
let introAnchor = try! IntroSceneOM.loadIntro()
introAnchor.actions.changeStoriesWithTrigger.onAction = loadStories
arView.scene.anchors.append(baseAnchor)
arView.scene.anchors.append(introAnchor)
func loadStories(_ entity: Entity?) -> Void {
arView.scene.anchors.remove(introAnchor)
let storiesAnchor = try! StoriesSceneOM.loadStoriesScene()
baseAnchor.addChild(storiesAnchor)
}
return arView
}
func updateUIView(_ uiView: ARView, context: Context) {} }
EDIT:
Recreated the project using an implementation of Andy Jazz's code.
func makeUIView(context: Context) -> ARView {
let arView = ARView(frame: .zero)
var anchor = AnchorEntity()
let scene01 = try! Experience.loadBoxScene()
let scene02 = try! Experience.loadBallScene()
// Base Scene
let scene03 = try! Experience.loadFloppyScene()
anchor = AnchorEntity(.plane(.horizontal, classification: .any,
minimumBounds: [0.1, 0.1]))
scene01.actions.boxTapped.onAction = loadScene02
scene02.actions.ballTapped.onAction = loadScene01
anchor.addChild(scene01)
anchor.addChild(scene03)
arView.scene.anchors.append(anchor)
func loadScene02(_ entity: Entity?) -> Void {
scene01.removeFromParent()
anchor.addChild(scene02)
}
func loadScene01(_ entity: Entity?) -> Void {
scene02.removeFromParent()
anchor.addChild(scene01)
}
return arView
}
However I still get the same issue where the anchor moves each time a new scene is added.
The code is quite simple, but regarding the issue of loading scenes with a large number of polygons, remains unresolved. At the maximum, the current scene should contain no more than 100K polygons, but ideally they should be within 50...70K. Texture resolution should not exceed 2K.
import RealityKit
class ViewController: UIViewController {
#IBOutlet var arView: ARView!
#IBOutlet var label: UILabel!
var anchor = AnchorEntity()
let scene01 = try! Experience.loadBox()
let scene02 = try! Experience.loadBall()
var cube = ModelEntity()
var sphere = ModelEntity()
override func viewDidLoad() {
super.viewDidLoad()
self.cube = scene01.steelBox?.children[0] as! ModelEntity
self.sphere = scene02.ball?.children[0] as! ModelEntity
self.anchor = AnchorEntity(.plane(.horizontal, classification: .any,
minimumBounds: [0.1, 0.1]))
self.anchor.addChild(cube)
arView.scene.anchors.append(anchor)
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
self.label.text = String(describing: self.anchor.id)
}
}
#IBAction func pressed(_ sender: UIButton) {
self.cube.removeFromParent()
self.anchor.addChild(sphere)
self.label.text = String(describing: self.anchor.id)
}
}

RealityKit – updateHandler of trackedRaycast not called

I call trackedRaycast() from startTracing() which is called from viewDidLoad(). My goal is to have the AR content be placed wherever the raycast from the camera hits a horizontal surface and that it updates when the raycast intersects a different location. When the user taps the screen the updates stop.
The updateHandler closure of the function trackedRaycast is never executed.
import RealityKit
import ARKit
class ViewController: UIViewController {
#IBOutlet var arView: ARView!
var gameAnchor: Experience.PreviewBoard!
var raycast: ARTrackedRaycast?
var gameIsPlaced = false
override func viewDidLoad() {
super.viewDidLoad()
setupARConfiguration()
loadGameBoard()
startTracing()
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap(recognizer:)))
arView.addGestureRecognizer(tapGestureRecognizer)
}
func setupARConfiguration() {
arView.automaticallyConfigureSession = false
let config = ARWorldTrackingConfiguration()
config.planeDetection = [.horizontal]
config.isCollaborationEnabled = true
config.environmentTexturing = .automatic
arView.session.run(config)
}
func loadGameBoard() {
gameAnchor = try! Experience.loadPreviewBoard()
arView.scene.addAnchor(gameAnchor)
}
func startTracing() {
raycast = arView.trackedRaycast(from: view.center, allowing: .estimatedPlane, alignment: .horizontal) { results in
print("This is never executed")
// Refine the game position with raycast update
if let result = results.first {
self.gameAnchor.setTransformMatrix(result.worldTransform, relativeTo: nil)
}
}
}
func stopTracing() {
print("raycast has stopped")
raycast?.stopTracking()
}
#objc func handleTap(recognizer: UITapGestureRecognizer) {
if !gameIsPlaced {
gameIsPlaced = true
stopTracing()
}
}
}
What could be the issue?
There appears to be some kind of lag when the view is initialized, leading to the tracked raycast not being initialized from the usual lifecycle methods. When I tried initializing it via a tap on the screen, it worked as intended. You can check this with the following code:
var r : ARTrackedRaycast? {
didSet {
print("Raycast initialized")
}
}
r = self.arView.trackedRaycast(from: self.view.center, allowing: .existingPlaneInfinite, alignment: .any) { result in
print("Callback received")
}
If you initialize your raycast from viewDidLoad() or viewDidAppear() even, the setter is never called.
The solution for me was to delay the raycast's initialization by a few seconds, like so:
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
r = self.arView.trackedRaycast(from: self.view.center, allowing: .existingPlaneInfinite, alignment: .any) { result in
print("Callback received")
}
This is, obviously, far from ideal, but right now I'm not sure how to go about solving that issue any differently.

How to position SKTilemap Scene in the middle of screen in Swift

I have created a TilemapScene (CKTilemapScene.sks) in Swift 5. This is basically to lay a tile map background of my game project. Refer to the second screenshot below.
Then, in my main swift code, I load all the background with the standard codes.
But, somehow the tile map is not centered. Refer to the first screenshot below . But, at the bottom left of the screen. I tried to play with anchor point. That doesn't help. Did I set anything wrong?
import UIKit
import SpriteKit
import GameplayKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
if let scene = SKScene(fileNamed: "CKTilemapScene") {
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
view.showsPhysics = true
}
}
override var shouldAutorotate: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .landscape
//.allButUpsideDown
} else {
return .all
}
}
Set the position of SKScene to the SKView's center.
override func viewDidLoad() {
super.viewDidLoad()
if let view = view as? SKView {
if let scene = SKScene(fileNamed: "CKTilemapScene") {
scene.position = view.center
view.presentScene(scene)
}
}
//...
}

How to solve that SKTransition effect doesn't work during transiting?

SKTransition doesn't work during transition from a scene to MainScene. It just jumps to MainScene without transition effect. You can find the codes below.
import UIKit
import SpriteKit
import GameplayKit
import FBSDKLoginKit
import TwitterKit
import Firebase
import GoogleSignIn
import GoogleMobileAds
protocol SceneManagerDelegate {
func presentMainScene()
func presentGameScene()
func presentWelcomeScene()
func presentGameOverScene()
func presentSettingsScene()
}
class GameViewController: UIViewController, TransitionDelegate, GIDSignInDelegate, GIDSignInUIDelegate, GADRewardBasedVideoAdDelegate, UIAlertViewDelegate{
/.....
}
/....
extension GameViewController: SceneManagerDelegate {
func presentSettingsScene() {
let settingsScene = SettingsScene(fileNamed: "SettingsScene")
settingsScene?.sceneManagerDelegate = self
present(scene: settingsScene!)
}
func presentGameOverScene() {
let gameOverScene = GameOverScene(fileNamed: "GameOverScene")
gameOverScene?.sceneManagerDelegate = self
present(scene: gameOverScene!)
}
func presentWelcomeScene() {
let welcomeScene = WelcomeScene(fileNamed: "WelcomeScene")
welcomeScene?.sceneManagerDelegate = self
present(scene: welcomeScene!)
}
func presentMainScene() {
let mainScene = MainScene(fileNamed: "MainScene")
mainScene?.sceneManagerDelegate = self
present(scene: mainScene!)
}
func presentGameScene() {
let gameScene = GameScene(fileNamed: "GameScene")
gameScene?.sceneManagerDelegate = self
present(scene: gameScene!)
}
func presentMatchingScene() {
let matchingScene = MatchingScene(fileNamed: "MatchingScene")
matchingScene?.sceneManagerDelegate = self
present(scene: matchingScene!)
}
func present(scene: SKScene) {
let transition = SKTransition.reveal(with: .left, duration: 1)
if let view = self.view as! SKView? {
if let gestureRecognizers = view.gestureRecognizers {
for recognizer in gestureRecognizers {
view.removeGestureRecognizer(recognizer)
}
}
scene.scaleMode = .aspectFit
scene.delegate = self as TransitionDelegate
view.presentScene(scene, transition: transition)
view.ignoresSiblingOrder = true
view.showsNodeCount = true
view.showsFPS = true
}
}
}
To call the method from other scenes
var sceneManagerDelegate: SceneManagerDelegate? // sceneManagerDelegate.present....
Your transition function is OK but your delegate implementation is faulty. In your present(scene:) method you set scene.delegate = self as TransitionDelegate, it would not be there. I have implemented your transition Code here in gameOver() method in Swift 4, and it works great. You can see this tutorial for Delegate implementation for transition in SpriteKit. Also read this answer given by KnightOfDragon.

how to access entity from code - gameplay kit

I have a node I added in the scene. Also in the scene I give it a component to bounce. The component looks like this:
class BounceComponent: GKComponent, ComponentSetupProt {
var bounce: SKAction?
var node: SKSpriteNode?
#GKInspectable var diff: CGFloat = 0.2
#GKInspectable var startScale: CGFloat = 1
#GKInspectable var duration: TimeInterval = 0.5
override init() {
super.init()
comps.append(self)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setup() {
print("setup")
node = entity?.component(ofType: GKSKNodeComponent.self)!.node as? SKSpriteNode
setupBounce()
}
func setupBounce() {
...
}
func performBounce() {
print("performing")
node?.run(bounce!)
}
}//
In the didMove function on my scene file, it calls the components setup(). This works fine. I'm trying to call the function performBounce() when I click on the button...
if (play?.contains(pos))! {
print("test")
if let _ = play?.entity {
print("we have an entity")
}
play?.entity?.component(ofType: BounceComponent.self)?.performBounce()
}
When I click, the only thing it prints is "test" and the entity is nil. I was under the assumption that when you add a node to the editor, it also sets up the entity for that node, so I'm not sure why it is nil. Curious if anyone could shed some light on this?
Thanks for any help!
The entity on the SKSpriteNode is a weak reference, you need to make sure you retain your entities from your gameplaykit object
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Load 'GameScene.sks' as a GKScene. This provides gameplay related content
// including entities and graphs.
if let scene = GKScene(fileNamed: "Main") {
// Get the SKScene from the loaded GKScene
if let sceneNode = scene.rootNode as? Main {
sceneNode.entities = scene.entities // add this
// Set the scale mode to scale to fit the window
sceneNode.scaleMode = .aspectFill
// Present the scene
if let view = self.view as! SKView? {
view.presentScene(sceneNode)
view.showsDrawCount = true
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
}
}
class Main: Base {
var entities = [GKEntity]() // add this