German (Deutsch) translation by Katharina Grigorovich-Nevolina (you can also view the original English article)
Das ist der zweite Teil unserer Einführungsserie auf SceneKit. In diesem Tutorial gehe ich davon aus, dass Sie mit den im ersten Teil erläuterten Konzepten vertraut sind, einschließlich der Einrichtung einer Szene mit Lichtern, Schatten, Kameras, Knoten und Materialien.
In diesem Tutorial werde ich Ihnen einige der komplizierteren, aber auch nützlicheren Funktionen von SceneKit vorstellen, wie z. B. Animation, Benutzerinteraktion, Partikelsysteme und Physik. Durch das Implementieren dieser Features können Sie interaktive und dynamische 3D-Inhalte anstelle von statischen Objekten erstellen, wie im vorherigen Lernprogramm.
1. Einrichten der Szene
Erstellen Sie ein neues Xcode-Projekt basierend auf der Vorlage iOS > Application > Einzelansicht Anwendungsvorlage (iOS > Application > Single View Application template).



Benennen Sie das Projekt, legen Sie Language to Swift und Devices to Universal fest.



Öffnen Sie ViewController.swift und importieren Sie das SceneKit-Framework.
1 |
import UIKit |
2 |
import SceneKit |
Deklarieren Sie als Nächstes die folgenden Eigenschaften in der ViewController
-Klasse.
1 |
var sceneView: SCNView! |
2 |
var camera: SCNNode! |
3 |
var ground: SCNNode! |
4 |
var light: SCNNode! |
5 |
var button: SCNNode! |
6 |
var sphere1: SCNNode! |
7 |
var sphere2: SCNNode! |
Wir richten die Szene in der viewDidLoad
-Methode wie unten gezeigt ein.
1 |
override func viewDidLoad() { |
2 |
super.viewDidLoad() |
3 |
|
4 |
sceneView = SCNView(frame: self.view.frame) |
5 |
sceneView.scene = SCNScene() |
6 |
self.view.addSubview(sceneView) |
7 |
|
8 |
let groundGeometry = SCNFloor() |
9 |
groundGeometry.reflectivity = 0 |
10 |
let groundMaterial = SCNMaterial() |
11 |
groundMaterial.diffuse.contents = UIColor.blueColor() |
12 |
groundGeometry.materials = [groundMaterial] |
13 |
ground = SCNNode(geometry: groundGeometry) |
14 |
|
15 |
let camera = SCNCamera() |
16 |
camera.zFar = 10000 |
17 |
self.camera = SCNNode() |
18 |
self.camera.camera = camera |
19 |
self.camera.position = SCNVector3(x: -20, y: 15, z: 20) |
20 |
let constraint = SCNLookAtConstraint(target: ground) |
21 |
constraint.gimbalLockEnabled = true |
22 |
self.camera.constraints = [constraint] |
23 |
|
24 |
let ambientLight = SCNLight() |
25 |
ambientLight.color = UIColor.darkGrayColor() |
26 |
ambientLight.type = SCNLightTypeAmbient |
27 |
self.camera.light = ambientLight |
28 |
|
29 |
let spotLight = SCNLight() |
30 |
spotLight.type = SCNLightTypeSpot |
31 |
spotLight.castsShadow = true |
32 |
spotLight.spotInnerAngle = 70.0 |
33 |
spotLight.spotOuterAngle = 90.0 |
34 |
spotLight.zFar = 500 |
35 |
light = SCNNode() |
36 |
light.light = spotLight |
37 |
light.position = SCNVector3(x: 0, y: 25, z: 25) |
38 |
light.constraints = [constraint] |
39 |
|
40 |
let sphereGeometry = SCNSphere(radius: 1.5) |
41 |
let sphereMaterial = SCNMaterial() |
42 |
sphereMaterial.diffuse.contents = UIColor.greenColor() |
43 |
sphereGeometry.materials = [sphereMaterial] |
44 |
sphere1 = SCNNode(geometry: sphereGeometry) |
45 |
sphere1.position = SCNVector3(x: -15, y: 1.5, z: 0) |
46 |
sphere2 = SCNNode(geometry: sphereGeometry) |
47 |
sphere2.position = SCNVector3(x: 15, y: 1.5, z: 0) |
48 |
|
49 |
let buttonGeometry = SCNBox(width: 4, height: 1, length: 4, chamferRadius: 0) |
50 |
let buttonMaterial = SCNMaterial() |
51 |
buttonMaterial.diffuse.contents = UIColor.redColor() |
52 |
buttonGeometry.materials = [buttonMaterial] |
53 |
button = SCNNode(geometry: buttonGeometry) |
54 |
button.position = SCNVector3(x: 0, y: 0.5, z: 15) |
55 |
|
56 |
sceneView.scene?.rootNode.addChildNode(self.camera) |
57 |
sceneView.scene?.rootNode.addChildNode(ground) |
58 |
sceneView.scene?.rootNode.addChildNode(light) |
59 |
sceneView.scene?.rootNode.addChildNode(button) |
60 |
sceneView.scene?.rootNode.addChildNode(sphere1) |
61 |
sceneView.scene?.rootNode.addChildNode(sphere2) |
62 |
}
|
Die Implementierung von viewDidLoad
sollte Ihnen bekannt vorkommen, wenn Sie den ersten Teil dieser Serie gelesen haben. Alles, was wir tun, ist die Szene, die wir in diesem Tutorial verwenden werden. Die einzigen neuen Dinge sind die SCNFloor
-Klasse und die zFar
-Eigenschaft.
Wie der Name schon sagt, wird die SCNFloor
-Klasse verwendet, um einen Boden oder Boden für die Szene zu erstellen. Dies ist viel einfacher im Vergleich zum Erstellen und Drehen eines SCNPlane
wie im vorherigen Tutorial.
Die zFar
-Eigenschaft bestimmt, wie weit die Kamera in der Entfernung sehen kann oder wie weit das Licht von einer bestimmten Quelle entfernt sein kann. Erstellen und führen Sie Ihre App aus. Ihre Szene sollte ungefähr so aussehen:
2. Benutzerinteraktion
Benutzerinteraktion wird in SceneKit durch eine Kombination der UIGestureRecognizer
-Klassen- und Treffertests behandelt. Um beispielsweise ein Antippen zu erkennen, fügen Sie zuerst einem SCNView
einen UITapGestureRecognizer
hinzu, ermitteln die Position des Tap in der Ansicht und sehen, ob er mit einem der Knoten in Kontakt steht oder auf einen dieser Knoten trifft.
Um besser zu verstehen, wie dies funktioniert, verwenden wir ein Beispiel. Wenn ein Knoten angetippt wird, entfernen wir ihn aus der Szene. Fügen Sie der viewDidLoad
-Methode der ViewController
-Klasse das folgende Code-Snippet hinzu:
1 |
override func viewDidLoad() { |
2 |
super.viewDidLoad() |
3 |
|
4 |
sceneView = SCNView(frame: self.view.frame) |
5 |
sceneView.scene = SCNScene() |
6 |
self.view.addSubview(sceneView) |
7 |
|
8 |
let tapRecognizer = UITapGestureRecognizer() |
9 |
tapRecognizer.numberOfTapsRequired = 1 |
10 |
tapRecognizer.numberOfTouchesRequired = 1 |
11 |
tapRecognizer.addTarget(self, action: "sceneTapped:") |
12 |
sceneView.gestureRecognizers = [tapRecognizer] |
13 |
|
14 |
...
|
15 |
}
|
Fügen Sie als Nächstes der ViewController
-Klasse die folgende Methode hinzu:
1 |
func sceneTapped(recognizer: UITapGestureRecognizer) { |
2 |
let location = recognizer.locationInView(sceneView) |
3 |
|
4 |
let hitResults = sceneView.hitTest(location, options: nil) |
5 |
if hitResults?.count > 0 { |
6 |
let result = hitResults![0] as! SCNHitTestResult |
7 |
let node = result.node |
8 |
node.removeFromParentNode() |
9 |
}
|
10 |
}
|
Bei dieser Methode erhalten Sie zuerst die Position des Tap als CGPoint
. Als Nächstes verwenden Sie diesen Punkt, um einen Treffer-Test für das sceneView
-Objekt durchzuführen und die SCNHitTestResult
-Objekte in einem Array namens hitResults
zu speichern. Der options
-Parameter dieser Methode kann ein Wörterbuch mit Schlüsseln und Werten enthalten, über das Sie in der Dokumentation von Apple nachlesen können. Wir überprüfen dann, ob der Treffertest mindestens ein Ergebnis zurückgegeben hat, und wenn dies der Fall ist, entfernen wir das erste Element im Array von seinem übergeordneten Knoten.
Wenn der Treffertest mehrere Ergebnisse zurückgibt, werden die Objekte nach ihrer z-Position sortiert, dh nach der Reihenfolge, in der sie aus der Sicht der aktuellen Kamera angezeigt werden. Wenn Sie beispielsweise in der aktuellen Szene auf eine der beiden Kugeln oder auf die Schaltfläche tippen, wird der angetippte Knoten das erste Element im zurückgegebenen Array bilden. Da der Boden jedoch aus Sicht der Kamera direkt hinter diesen Objekten erscheint, ist der Bodenknoten ein weiterer Punkt in der Ergebnismatrix, der zweite in diesem Fall. Das geschieht, weil ein Abgriff an derselben Stelle den Bodenknoten treffen würde, wenn die Kugeln und der Knopf nicht vorhanden wären.
Erstellen und starten Sie Ihre App und tippen Sie auf die Objekte in der Szene. Sie sollten verschwinden, wenn Sie auf jeden tippen.
Jetzt, da wir feststellen können, wann ein Knoten angetippt wurde, können wir mit dem Hinzufügen von Animationen beginnen.
3. Animation
Es gibt zwei Klassen, die zum Ausführen von Animationen in SceneKit verwendet werden können:
SCNAction
SCNTransaktion
SCNAction
-Objekte sind sehr nützlich für einfache und wiederverwendbare Animationen wie Bewegung, Rotation und Skalierung. Sie können beliebig viele Aktionen zu einem benutzerdefinierten Aktionsobjekt kombinieren.
Die SCNTransaction
-Klasse kann dieselben Animationen ausführen, ist jedoch in mancher Hinsicht vielseitiger, z. B. beim Animieren von Materialien. Diese zusätzliche Vielseitigkeit geht jedoch auf Kosten von SCNTransaction
-Animationen, die nur die gleiche Wiederverwendbarkeit wie eine Funktion haben und der Aufbau über Klassenmethoden erfolgt.
Für Ihre erste Animation zeige ich Ihnen Code mit den Klassen SCNAction
und SCNTransaction
. In diesem Beispiel wird der Button nach unten bewegt und weiß, wenn er angetippt wird. Aktualisieren Sie die Implementierung der Methode sceneTapped(_:)
wie unten gezeigt.
1 |
func sceneTapped(recognizer: UITapGestureRecognizer) { |
2 |
let location = recognizer.locationInView(sceneView) |
3 |
|
4 |
let hitResults = sceneView.hitTest(location, options: nil) |
5 |
if hitResults?.count > 0 { |
6 |
let result = hitResults![0] as! SCNHitTestResult |
7 |
let node = result.node |
8 |
|
9 |
if node == button { |
10 |
SCNTransaction.begin() |
11 |
SCNTransaction.setAnimationDuration(0.5) |
12 |
let materials = node.geometry?.materials as! [SCNMaterial] |
13 |
let material = materials[0] |
14 |
material.diffuse.contents = UIColor.whiteColor() |
15 |
SCNTransaction.commit() |
16 |
|
17 |
let action = SCNAction.moveByX(0, y: -0.8, z: 0, duration: 0.5) |
18 |
node.runAction(action) |
19 |
}
|
20 |
}
|
21 |
}
|
In der Methode sceneTapped(_:)
erhalten wir einen Verweis auf den Knoten, den der Benutzer angetippt hat, und prüfen, ob das die Taste in der Szene ist. Wenn das der Fall ist, animieren wir sein Material von rot nach weiß mit der SCNTransaction
-Klasse und verschieben es entlang der y-Achse in einer negativen Richtung mithilfe einer SCNAction
-Instanz. Die Dauer der Animation ist auf 0,5 Sekunden eingestellt.
Erstellen und starten Sie Ihre App erneut und tippen Sie auf die Schaltfläche. Es sollte sich nach unten bewegen und seine Farbe in Weiß ändern, wie im folgenden Screenshot gezeigt.
4. Physik
Mit dem SceneKit-Framework können Sie realistische physikalische Simulationen erstellen. Die Funktionalität, die die Physiksimulationen von SceneKit bieten, ist umfangreich und reicht von einfachen Geschwindigkeiten, Beschleunigungen und Kräften über Gravitations- und elektrische Felder bis hin zur Kollisionserkennung.
Was Sie in der aktuellen Szene tun werden, ist, ein Gravitationsfeld auf eine der Kugeln aufzubringen, so dass die zweite Kugel aufgrund der Schwerkraft in Richtung der ersten Kugel gezogen wird. Diese Schwerkraft wird aktiv, wenn die Taste gedrückt wird.
Das Setup für diese Simulation ist sehr einfach. Verwenden Sie ein SCNPhysicsBody
-Objekt für jeden Knoten, für den Sie von der Physiksimulation betroffen sein sollen, und ein SCNPhysicsField
-Objekt für jeden Knoten, der die Quelle eines Felds sein soll. Aktualisieren Sie die viewDidLoad
-Methode wie unten gezeigt.
1 |
override func viewDidLoad() { |
2 |
...
|
3 |
|
4 |
buttonGeometry.materials = [buttonMaterial] |
5 |
button = SCNNode(geometry: buttonGeometry) |
6 |
button.position = SCNVector3(x: 0, y: 0.5, z: 15) |
7 |
|
8 |
// Physics
|
9 |
let groundShape = SCNPhysicsShape(geometry: groundGeometry, options: nil) |
10 |
let groundBody = SCNPhysicsBody(type: .Kinematic, shape: groundShape) |
11 |
ground.physicsBody = groundBody |
12 |
|
13 |
let gravityField = SCNPhysicsField.radialGravityField() |
14 |
gravityField.strength = 0 |
15 |
sphere1.physicsField = gravityField |
16 |
|
17 |
let shape = SCNPhysicsShape(geometry: sphereGeometry, options: nil) |
18 |
let sphere1Body = SCNPhysicsBody(type: .Kinematic, shape: shape) |
19 |
sphere1.physicsBody = sphere1Body |
20 |
let sphere2Body = SCNPhysicsBody(type: .Dynamic, shape: shape) |
21 |
sphere2.physicsBody = sphere2Body |
22 |
|
23 |
sceneView.scene?.rootNode.addChildNode(self.camera) |
24 |
sceneView.scene?.rootNode.addChildNode(ground) |
25 |
sceneView.scene?.rootNode.addChildNode(light) |
26 |
|
27 |
...
|
28 |
}
|
Wir beginnen mit der Erstellung einer SCNPhysicsShape
-Instanz, die die tatsächliche Form des Objekts angibt, das an der Physiksimulation beteiligt ist. Für die grundlegenden Formen, die Sie in dieser Szene verwenden, sind die Geometrieobjekte vollkommen in Ordnung. Bei komplizierten 3D-Modellen ist es jedoch besser, mehrere primitive Formen zu kombinieren, um eine ungefähre Form Ihres Objekts für die Physiksimulation zu erstellen.
Aus dieser Form erstellen Sie dann eine SCNPhysicsBody
-Instanz und fügen sie dem Boden der Szene hinzu. Das ist notwendig, da jede SceneKit-Szene standardmäßig ein vorhandenes Gravitationsfeld besitzt, das jedes Objekt nach unten zieht. Der Kinematic
-Typ, den Sie diesem SCNPhysicsBody
geben, bedeutet, dass das Objekt an Kollisionen beteiligt ist, aber nicht von Kräften beeinflusst wird (und nicht aufgrund der Schwerkraft fallen wird).
Als Nächstes erstellen Sie das Gravitationsfeld und weisen es dem ersten Sphärenknoten zu. Nach dem gleichen Prozess wie für den Boden erstellen Sie dann einen Physikkörper für jede der beiden Sphären. Sie geben die zweite Sphäre jedoch als Dynamic
-Physikkörper an, weil Sie möchten, dass sie von dem von Ihnen erzeugten Gravitationsfeld beeinflusst und bewegt wird.
Zuletzt müssen Sie die Stärke dieses Feldes einstellen, um es zu aktivieren, wenn die Schaltfläche angetippt wird. Fügen Sie der Methode sceneTapped(_:)
die folgende Zeile hinzu:
1 |
func sceneTapped(recognizer: UITapGestureRecognizer) { |
2 |
...
|
3 |
if node == button { |
4 |
...
|
5 |
sphere1.physicsField?.strength = 750 |
6 |
}
|
7 |
}
|
8 |
}
|
Erstellen und starten Sie Ihre App, tippen Sie auf die Schaltfläche und beobachten Sie, wie sich die zweite Kugel langsam auf die erste beschleunigt. Beachten Sie, dass es einige Sekunden dauern kann, bis sich die zweite Kugel bewegt.
Es gibt nur noch eine Sache, die Kugeln explodieren zu lassen, wenn sie kollidieren.
5. Kollisionserkennung und Partikelsysteme
Um den Effekt einer Explosion zu erzeugen, nutzen wir die SCNParticleSystem
-Klasse. Ein Partikelsystem kann von einem externen 3D-Programm, Quellcode oder, wie ich Ihnen gleich zeigen werde, Xcode's Partikelsystemeditor erstellt werden. Erstellen Sie eine neue Datei, indem Sie Command+N drücken, und wählen Sie Scenekit Particle System im Abschnitt iOS > Ressource (iOS > Resource section).



Partikelsystem VorlageStellen Sie die Partikelsystemvorlage auf Reactor ein. Klicken Sie auf Weiter, benennen Sie die Datei Explosion und speichern Sie sie in Ihrem Projektordner.



Im Projektnavigator sehen Sie nun zwei neue Dateien, Explosion.scnp und spark.png. Das Bild spark.png ist eine vom Partikelsystem verwendete Ressource, die Ihrem Projekt automatisch hinzugefügt wird. Wenn Sie Explosion.scnp öffnen, werden Sie in Xcode in Echtzeit animiert und gerendert angezeigt. Der Partikelsystemeditor ist ein sehr leistungsfähiges Werkzeug in Xcode und ermöglicht es Ihnen, ein Partikelsystem anzupassen, ohne es programmatisch zu machen.



Wenn das Partikelsystem geöffnet ist, gehen Sie rechts zum Attribute-Inspektor und ändern Sie die folgenden Attribute im Emitter-Bereich:
- Geburtenrate bis 300
- Richtungsmodus auf Zufällig
Ändern Sie die folgenden Attribute im Simulation-Abschnitt :
- Lebensdauer bis 3
- Geschwindigkeitsfaktor auf 2
Und schließlich, ändern Sie die folgenden Attribute im Abschnitt Lebenszyklus:
- Emission dur. bis 1
- Looping to Plays einmal






Ihr Partikelsystem sollte nun in alle Richtungen schießen und ähnlich dem folgenden Screenshot aussehen:



Öffnen Sie ViewController.swift und sorgen Sie dafür, dass Ihre ViewController
-Klasse dem SCNPhysicsContactDelegate
-Protokoll entspricht. Dieses Protokoll ist notwendig, um eine Kollision zwischen zwei Knoten zu erkennen.
1 |
class ViewController: UIViewController, SCNPhysicsContactDelegate |
Ordnen Sie als Nächstes die aktuelle ViewController
-Instanz als contactDelegate
Ihres physicsWorld
-Objekts in der viewDidLoad
-Methode zu
1 |
override func viewDidLoad() { |
2 |
super.viewDidLoad() |
3 |
|
4 |
sceneView = SCNView(frame: self.view.frame) |
5 |
sceneView.scene = SCNScene() |
6 |
sceneView.scene?.physicsWorld.contactDelegate = self |
7 |
self.view.addSubview(sceneView) |
8 |
|
9 |
...
|
10 |
}
|
.Implementieren Sie schließlich die physicsWorld (_:didUpdateContact:)
-Methode in der ViewController
-Klasse:
1 |
func physicsWorld(world: SCNPhysicsWorld, didUpdateContact contact: SCNPhysicsContact) { |
2 |
if (contact.nodeA == sphere1 || contact.nodeA == sphere2) && (contact.nodeB == sphere1 || contact.nodeB == sphere2) { |
3 |
let particleSystem = SCNParticleSystem(named: "Explosion", inDirectory: nil) |
4 |
let systemNode = SCNNode() |
5 |
systemNode.addParticleSystem(particleSystem) |
6 |
systemNode.position = contact.nodeA.position |
7 |
sceneView.scene?.rootNode.addChildNode(systemNode) |
8 |
|
9 |
contact.nodeA.removeFromParentNode() |
10 |
contact.nodeB.removeFromParentNode() |
11 |
}
|
12 |
}
|
Wir prüfen zunächst, ob die beiden an der Kollision beteiligten Knoten die beiden Kugeln sind. Wenn das der Fall ist, laden wir das Partikelsystem aus der Datei, die wir gerade erstellt haben, und fügen es einem neuen Knoten hinzu. Schließlich entfernen wir beide Sphären aus der Szene.
Erstellen und starten Sie Ihre App erneut und tippen Sie auf die Schaltfläche. Wenn sich die Kugeln berühren, sollten sie beide verschwinden und Ihr Partikelsystem sollte erscheinen und beleben.
Schlussfolgerung
In diesem Tutorial habe ich Ihnen gezeigt, wie Sie Benutzerinteraktion, Animation, Physiksimulation und Partikelsysteme mithilfe des SceneKit-Frameworks implementieren. Die Techniken, die Sie in dieser Serie gelernt haben, können auf jedes Projekt mit beliebig vielen Animationen, Physiksimulationen usw. angewendet werden.
Sie sollten nun komfortabel eine einfache Szene erstellen und dynamische Elemente wie Animation und Partikel-Systeme hinzufügen. Die Konzepte, die Sie in dieser Serie gelernt haben, sind auf die kleinste Szene mit einem einzelnen Objekt bis hin zu einem großen Spiel anwendbar.