SceneKit SCNSceneRendererDelegate - функция рендерера не вызывается

Я недавно задал вопрос, на который был довольно очевидный ответ. Я все еще работаю над тем же проектом и сталкиваюсь с другой проблемой. Мне нужно реализовать для каждого кадра логику и SCNSceneRendererDelegate Протокол прекрасно работал на iOS, но на OSX renderer функция не стрельба. Я создал небольшой пример проекта, чтобы проиллюстрировать мою проблему. Он состоит из Scene Kit View в раскадровке и следующего кода в ViewController учебный класс:

import Cocoa
import SceneKit

class ViewController: NSViewController, SCNSceneRendererDelegate {

    @IBOutlet weak var sceneView: SCNView!

    let cubeNode = SCNNode()

    override func viewDidLoad() {

        super.viewDidLoad()

        let scene = SCNScene()

        let sphere = SCNSphere(radius: 0.1)
        sphere.firstMaterial!.diffuse.contents = NSColor.yellowColor()
        let sphereNode = SCNNode(geometry: sphere)
        scene.rootNode.addChildNode(sphereNode)

        let cube = SCNBox(width: 0.2, height: 0.2, length: 0.2, chamferRadius: 0)
        cube.firstMaterial!.diffuse.contents = NSColor.greenColor()
        cubeNode.geometry = cube
        cubeNode.position = SCNVector3(1,0,0)
        scene.rootNode.addChildNode(cubeNode)

        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        cameraNode.position = SCNVector3(2, 1, 2)
        let constraint = SCNLookAtConstraint(target: cubeNode)
        cameraNode.constraints = [constraint]
        scene.rootNode.addChildNode(cameraNode)

        sceneView.scene = scene
        sceneView.backgroundColor = NSColor(red: 0.5, green: 0, blue: 0.3, alpha: 1)
        sceneView.allowsCameraControl = true

        sceneView.delegate = self
        sceneView.playing = true

    }

    func renderer(renderer: SCNSceneRenderer, updateAtTime time: NSTimeInterval) {
        cubeNode.position.x += 0.1
    }
}

Все, что я хочу, это в основном перемещать куб с каждым кадром. Но ничего не происходит. Что странно, что когда я устанавливаю sceneView.allowsCameraControl в true, renderer Функция вызывается всякий раз, когда я щелкаю или перетаскиваю экран (это имеет смысл, поскольку необходимо обновить вид в зависимости от ракурсов камеры). Но я бы хотел, чтобы это называлось каждый кадр.

Есть ли ошибка, которую я не вижу, или это ошибка в моем XCode?

Изменить: я попытался следовать инструкциям в ответе ниже, и теперь у меня есть следующий код для ViewController:

import Cocoa
import SceneKit

class ViewController: NSViewController {

    @IBOutlet weak var sceneView: SCNView!
    let scene = MyScene(create: true)

    override func viewDidLoad() {

        super.viewDidLoad()

        sceneView.scene = scene
        sceneView.backgroundColor = NSColor(red: 0.5, green: 0, blue: 0.3, alpha: 1)
        sceneView.allowsCameraControl = true

        sceneView.delegate = scene
        sceneView.playing = true

    }

}

И класс MyScene:

import Foundation
import SceneKit

final class MyScene: SCNScene, SCNSceneRendererDelegate {

    let cubeNode = SCNNode()

    convenience init(create: Bool) {
        self.init()

        let sphere = SCNSphere(radius: 0.1)
        sphere.firstMaterial!.diffuse.contents = NSColor.yellowColor()
        let sphereNode = SCNNode(geometry: sphere)
        rootNode.addChildNode(sphereNode)

        let cube = SCNBox(width: 0.2, height: 0.2, length: 0.2, chamferRadius: 0)
        cube.firstMaterial!.diffuse.contents = NSColor.greenColor()
        cubeNode.geometry = cube
        cubeNode.position = SCNVector3(1,0,0)
        rootNode.addChildNode(cubeNode)

        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        cameraNode.position = SCNVector3(2, 1, 2)
        let constraint = SCNLookAtConstraint(target: cubeNode)
        cameraNode.constraints = [constraint]

        rootNode.addChildNode(cameraNode)
    }

    @objc func renderer(aRenderer: SCNSceneRenderer, updateAtTime time: NSTimeInterval) {
        cubeNode.position.x += 0.01
    }
}

Тем не менее, это все еще не работает. Что я делаю неправильно?

Редактировать: установка sceneView.loops = true устраняет описанную проблему

6 ответов

Решение

Я подозреваю, что ответ зависит от того, что Керент подразумевает под "каждым кадром". Керент, вероятно, должен уточнить это, но я все равно постараюсь ответить, потому что я такой.

Простейшая интерпретация - это, вероятно, "каждый кадр, который все равно будет визуализироваться", но вряд ли это то, что нужно, если только куб не предназначен для мониторинга активности рендерера, что тоже маловероятно; Есть гораздо лучшие подходы к этому.

Querent может потребоваться многократно выполнять рендеринг, пока игровое свойство представления равно YES. Если это так, то, возможно, ответ так же прост, как установка свойства Loop представления в YES. Это недавно решило для меня проблему, в которой я хотел, чтобы рендеринг происходил, пока нажата клавиша на клавиатуре. (Я заметил, что настройка воспроизведения на ДА вызовет один вызов для моего делегата.)

Я не понимаю, в чем причина проблемы, но я смог ее воспроизвести. Я получил его на работу, хотя, добавив бессмысленный SCNAction:

let dummyAction = SCNAction.scaleBy(1.0, duration: 1.0)
let repeatAction = SCNAction.repeatActionForever(dummyAction)
cubeNode.runAction(repeatAction)

Цикл рендеринга срабатывает только в том случае, если сцена "играет" (см. SKScene перестает отвечать во время простоя). Я ожидаю, что установка

    sceneView.isPlaying = true

(как вы уже делаете) будет достаточно для запуска обратных вызовов рендеринга.

Код, который я имею выше, не является решением. Это неприятный хак, чтобы обойти вашу проблему и позволить вам жить.

Для тех, у кого все еще есть проблемы, настройка делегата и воспроизведение переменных будет работать.

sceneView.delegate = self
sceneView.isPlaying = true

В дополнение к нескольким полезным подсказкам в этой цепочке, последним для меня, чтобы вызвать делегата, было следующее: Если вы используете методы pre Swift 4 для SCNSceneRendererDelegate класс, он прекрасно компилируется без ошибок и предупреждений, но делегат никогда не вызывается.

Таким образом, устаревшее определение до Swift 4:

func renderer(aRenderer:SCNSceneRenderer, updateAtTime time:TimeInterval) {...}

(который я получил из сниппета в сети) скомпилирован просто отлично и никогда не вызывался, в то время как правильное определение

func renderer(_ renderer:SCNSceneRenderer, updateAtTimet time:TimeInterval) {...}

компилируется и вызывается!

поскольку SCNSceneRendererDelegate является протоколом, обычная защита Swift, предоставляемая переопределением, не подходит. поскольку SCNSceneRendererDelegate определяет его методы как необязательные (которые мне нравятся), они также не отслеживаются.

Я боролся с некоторыми похожими ошибками, используя SCNRenderer скорее, чем SCNView, и это было первое попадание в Google, поэтому я просто хотел добавить примечание к своему решению на случай, если оно кому-то поможет.

Я использовал

      .render(withViewport:commandBuffer:passDescriptor:)

но это не вызывает метод рендеринга делегата. Вместо этого используйте

      .render(atTime:viewport:commandBuffer:passDescriptor:)

даже если вы не используете параметр временного интервала. Затем метод делегата renderer(_:updateAtTime:) будет называться правильно.

Попробуйте это... вместо этого поместите свой код в класс сцены - держите контроллер представления в чистоте.

final class MySCNScene:SCNScene, SCNSceneRendererDelegate
{
    @objc func renderer(aRenderer:SCNSceneRenderer, updateAtTime time:NSTimeInterval)
    {
    }
}

Также установите делегата представления на вашу сцену:

mySCNView!.delegate = mySCNScene

Другие вопросы по тегам