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