Questa è la seconda parte della nostra serie introduttiva su SceneKit. In questo tutorial, presumo che tu abbia familiarità con i concetti spiegati nella prima parte, inclusa l'impostazione di una scena con luci, ombre, fotocamere, nodi e materiali.
In questo tutorial, ti spiegherò alcune delle più complicate, ma anche più utili, caratteristiche di SceneKit, come l'animazione, l'interazione dell'utente, i sistemi di particelle e la fisica. Implementando queste funzionalità, puoi creare contenuti 3D interattivi e dinamici piuttosto che oggetti statici come hai fatto nel tutorial precedente.
Creare un nuovo progetto Xcode basato sul iOS> Applicazione> Applicazione vista singola modello.
Assegna un nome al progetto, imposta linguaggio a veloce, e dispositivi a universale.
Aperto ViewController.swift e importa il framework di SceneKit.
importare UIKit import SceneKit
Quindi, dichiarare le seguenti proprietà in ViewController
classe.
var sceneView: SCNView! var camera: SCNNode! var ground: SCNNode! var light: SCNNode! pulsante var: SCNNode! var sphere1: SCNNode! var sphere2: SCNNode!
Abbiamo creato la scena nel viewDidLoad
metodo come mostrato di seguito.
override func viewDidLoad () super.viewDidLoad () sceneView = SCNView (frame: self.view.frame) sceneView.scene = SCNScene () self.view.addSubview (sceneView) let groundGeometry = SCNFloor () groundGeometry.reflectivity = 0 let groundMaterial = SCNMaterial () groundMaterial.diffuse.contents = UIColor.blueColor () groundGeometry.materials = [groundMaterial] ground = SCNNode (geometry: groundGeometry) lascia camera = SCNCamera () camera.zFar = 10000 self.camera = SCNNode () self .camera.camera = camera self.camera.position = SCNVector3 (x: -20, y: 15, z: 20) let constraint = SCNLookAtConstraint (target: ground) constraint.gimbalLockEnabled = true self.camera.constraints = [vincolo] let ambientLight = SCNLight () ambientLight.color = UIColor.darkGrayColor () ambientLight.type = SCNLightTypeAmbient self.camera.light = ambientLight lascia spotLight = SCNLight () spotLight.type = SCNLightTypeSpot spotLight.castsShadow = true spotLight.spotInnerAngle = 70.0 spotLight. spotOuterAngle = 90.0 spotLight.zFar = 500 l ight = SCNNode () light.light = spotLight light.position = SCNVector3 (x: 0, y: 25, z: 25) light.constraints = [vincolo] lascia sphereGeometry = SCNSphere (raggio: 1.5) lascia sphereMaterial = SCNMaterial () sphereMaterial.diffuse.contents = UIColor.greenColor () sphereGeometry.materials = [sphereMaterial] sphere1 = SCNNode (geometry: sphereGeometry) sphere1.position = SCNVector3 (x: -15, y: 1.5, z: 0) sphere2 = SCNNode (geometry : sphereGeometry) sphere2.position = SCNVector3 (x: 15, y: 1.5, z: 0) let buttonGeometry = SCNBox (larghezza: 4, altezza: 1, lunghezza: 4, chamferRadius: 0) let buttonMaterial = SCNMaterial () buttonMaterial. diffuse.contents = UIColor.redColor () buttonGeometry.materials = [buttonMaterial] button = SCNNode (geometry: buttonGeometry) button.position = SCNVector3 (x: 0, y: 0.5, z: 15) sceneView.scene? .rootNode.addChildNode (self.camera) sceneView.scene? .rootNode.addChildNode (ground) sceneView.scene? .rootNode.addChildNode (light) sceneView.scene? .rootNode.addChildNode (button) sceneView.scen e? .rootNode.addChildNode (sphere1) sceneView.scene? .rootNode.addChildNode (sphere2)
L'implementazione di viewDidLoad
dovrebbe sembrare familiare se hai letto la prima parte di questa serie. Tutto ciò che facciamo è impostare la scena che useremo in questo tutorial. Le uniche cose nuove includono il SCNFloor
classe e il zFar
proprietà.
Come suggerisce il nome, il SCNFloor
la classe è usata per creare un pavimento o un terreno per la scena. Questo è molto più facile rispetto alla creazione e alla rotazione di un SCNPlane
come abbiamo fatto nel precedente tutorial.
Il zFar
proprietà determina quanto lontano può vedere una telecamera o quanto può raggiungere la luce proveniente da una particolare fonte. Crea ed esegui la tua app. La tua scena dovrebbe assomigliare a questa:
L'interazione dell'utente viene gestita in SceneKit da una combinazione di UIGestureRecognizer
test di classe e hit. Ad esempio, per rilevare un tocco, aggiungi prima un UITapGestureRecognizer
a a SCNView
, determinare la posizione del rubinetto nella vista e vedere se è in contatto con o colpisce uno dei nodi.
Per capire meglio come funziona, useremo un esempio. Ogni volta che viene toccato un nodo, lo rimuoviamo dalla scena. Aggiungi il seguente snippet di codice al file viewDidLoad
metodo del ViewController
classe:
override func viewDidLoad () super.viewDidLoad () sceneView = SCNView (frame: self.view.frame) sceneView.scene = SCNScene () self.view.addSubview (sceneView) lascia tapRecognizer = UITapGestureRecognizer () tapRecognizer.numberOfTapsRequired = 1 tapRecognizer .numberOfTouchesRequired = 1 tapRecognizer.addTarget (self, action: "sceneTapped:") sceneView.gestureRecognizers = [tapRecognizer] ...
Quindi, aggiungere il seguente metodo al ViewController
classe:
func sceneTapped (recognizer: UITapGestureRecognizer) let location = recognizer.locationInView (sceneView) lascia hitResults = sceneView.hitTest (posizione, opzioni: nil) se hitResults? .count> 0 let result = hitResults! [0] as! SCNHitTestResult let node = result.node node.removeFromParentNode ()
In questo metodo, per prima cosa ottieni la posizione del rubinetto come a CGPoint
. Successivamente, si utilizza questo punto per eseguire un hit test su sceneView
oggetto e memorizzare il SCNHitTestResult
oggetti in un array chiamato hitResults
. Il opzioni
il parametro di questo metodo può contenere un dizionario di chiavi e valori, che puoi leggere nella documentazione di Apple. Verifichiamo quindi se il hit test ha restituito almeno un risultato e, in tal caso, rimuoviamo il primo elemento nell'array dal suo nodo genitore.
Se l'hit test ha restituito più risultati, gli oggetti vengono ordinati in base alla loro posizione z, ovvero l'ordine in cui vengono visualizzati dal punto di vista della videocamera corrente. Ad esempio, nella scena corrente, se tocchi una delle due sfere o il pulsante, il nodo che hai toccato formerà il primo elemento dell'array restituito. Poiché il terreno appare direttamente dietro questi oggetti dal punto di vista della telecamera, tuttavia, il nodo di terra sarà un altro elemento nella serie di risultati, il secondo in questo caso. Questo accade perché un colpetto nella stessa posizione colpirebbe il nodo di terra se le sfere e il pulsante non erano lì.
Crea ed esegui la tua app e tocca gli oggetti nella scena. Dovrebbero scomparire man mano che li colpisci.
Ora che possiamo determinare quando viene toccato un nodo, possiamo iniziare ad aggiungere animazioni al mix.
Esistono due classi che possono essere utilizzate per eseguire animazioni in SceneKit:
SCNAction
SCNTransaction
SCNAction
gli oggetti sono molto utili per animazioni semplici e riusabili, come movimento, rotazione e scala. È possibile combinare qualsiasi numero di azioni insieme in un oggetto azione personalizzato.
Il SCNTransaction
la classe può eseguire le stesse animazioni, ma è più versatile in alcuni modi, come ad esempio l'animazione dei materiali. Questa maggiore versatilità, tuttavia, ha un costo di SCNTransaction
le animazioni hanno solo la stessa riusabilità di una funzione e l'impostazione viene eseguita tramite i metodi di classe.
Per la tua prima animazione, ti mostrerò il codice usando entrambi SCNAction
e SCNTransaction
classi. L'esempio sposta il pulsante verso il basso e lo rende bianco quando viene toccato. Aggiorna l'implementazione di sceneTapped (_ :)
metodo come mostrato di seguito.
func sceneTapped (recognizer: UITapGestureRecognizer) let location = recognizer.locationInView (sceneView) lascia hitResults = sceneView.hitTest (posizione, opzioni: nil) se hitResults? .count> 0 let result = hitResults! [0] as! SCNHitTestResult let node = result.node if node == button SCNTransaction.begin () SCNTransaction.setAnimationDuration (0.5) lascia materiali = node.geometry? .Materials as! [SCNMaterial] let material = materials [0] material.diffuse.contents = UIColor.whiteColor () SCNTransaction.commit () lascia azione = SCNAction.moveByX (0, y: -0.8, z: 0, durata: 0.5) nodo. runAction (azione)
Nel sceneTapped (_ :)
metodo, otteniamo un riferimento al nodo che l'utente ha toccato e controlla se questo è il pulsante nella scena. Se lo è, animiamo il suo materiale da rosso a bianco, usando il SCNTransaction
classe, e spostarlo lungo l'asse y in direzione negativa usando un SCNAction
esempio. La durata dell'animazione è impostata su 0,5 secondi.
Crea ed esegui nuovamente la tua app e tocca il pulsante. Dovrebbe spostarsi verso il basso e cambiare il suo colore in bianco, come mostrato nello screenshot qui sotto.
L'impostazione di simulazioni fisiche realistiche è facile con il framework SceneKit. La funzionalità offerta dalle simulazioni fisiche di SceneKit è ampia, che va dalle velocità di base, alle accelerazioni e alle forze, ai campi gravitazionali e elettrici e persino al rilevamento delle collisioni.
Quello che stai per fare nella scena corrente è applicare un campo gravitazionale a una delle sfere in modo che la seconda sfera venga tirata verso la prima sfera come risultato della gravità. Questa forza di gravità diventerà attiva quando si preme il pulsante.
L'installazione per questa simulazione è molto semplice. Usa un SCNPhysicsBody
oggetto per ogni nodo a cui si desidera essere interessati dalla simulazione fisica e a SCNPhysicsField
oggetto per ogni nodo che vuoi essere la fonte di un campo. Aggiorna il viewDidLoad
metodo come mostrato di seguito.
override func viewDidLoad () ... buttonGeometry.materials = [buttonMaterial] button = SCNNode (geometry: buttonGeometry) button.position = SCNVector3 (x: 0, y: 0.5, z: 15) // Physics let groundShape = SCNPhysicsShape (geometry: groundGeometry, options: nil) let groundBody = SCNPhysicsBody (tipo: .Kinematic, shape: groundShape) ground.physicsBody = groundBody let gravityField = SCNPhysicsField.radialGravityField () gravityField.strength = 0 sphere1.physicsField = gravityField lascia shape = SCNPhysicsShape (geometry: sphereGeometry, options: nil) let sphere1Body = SCNPhysicsBody (tipo: .Kinematic, shape: shape) sphere1.physicsBody = sphere1Body let sphere2Body = SCNPhysicsBody (tipo: .Dynamic, shape: shape) sphere2.physicsBody = sphere2Body sceneView.scene? .rootNode .addChildNode (self.camera) sceneView.scene? .rootNode.addChildNode (ground) sceneView.scene? .rootNode.addChildNode (light) ...
Iniziamo creando un SCNPhysicsShape
istanza che specifica la forma effettiva dell'oggetto che prende parte alla simulazione fisica. Per le forme di base che stai utilizzando in questa scena, gli oggetti geometrici sono perfettamente adatti all'uso. Per i modelli 3D complicati, tuttavia, è meglio combinare più forme primitive per creare una forma approssimativa dell'oggetto per la simulazione fisica.
Da questa forma, quindi crei un SCNPhysicsBody
istanza e aggiungerla al terreno della scena. Questo è necessario, perché ogni scena di SceneKit ha per impostazione predefinita un campo di gravità esistente che trascina verso il basso ogni oggetto. Il cinematica
tipo che dai a questo SCNPhysicsBody
significa che l'oggetto prenderà parte alle collisioni, ma non è influenzato dalle forze (e non cadrà a causa della gravità).
Successivamente, si crea il campo gravitazionale e lo si assegna al primo nodo della sfera. Seguendo lo stesso processo del terreno, crei un corpo fisico per ciascuna delle due sfere. Si specifica la seconda sfera come a Dinamico
corpo fisico, però, perché vuoi che sia influenzato e mosso dal campo gravitazionale che hai creato.
Infine, è necessario impostare la forza di questo campo per attivarlo quando si tocca il pulsante. Aggiungi la seguente riga al sceneTapped (_ :)
metodo:
func sceneTapped (recognizer: UITapGestureRecognizer) ... if node == button ... sphere1.physicsField? .strength = 750
Crea ed esegui la tua app, tocca il pulsante e osserva come la seconda sfera accelera lentamente verso la prima. Nota che potrebbero passare alcuni secondi prima che la seconda sfera inizi a muoversi.
C'è solo una cosa da fare, tuttavia, far esplodere le sfere quando si scontrano.
Per creare l'effetto di un'esplosione, faremo leva sul SCNParticleSystem
classe. Un sistema di particelle può essere creato da un programma 3D esterno, da un codice sorgente o, come sto per mostrarvi, dall'editor di sistema di particelle di Xcode. Crea un nuovo file premendo Comando + N e scegliere SceneKit Particle System dal iOS> Risorsa sezione.
Impostare il modello del sistema di particelle su Reattore.Clic Il prossimo, nominare il file Esplosione, e salvarlo nella cartella del progetto.
Nel Project Navigator, ora vedrai due nuovi file, Explosion.scnp e spark.png. Il spark.png l'immagine è una risorsa utilizzata dal sistema di particelle, automaticamente aggiunta al tuo progetto. Se apri Explosion.scnp, lo vedrai animato e reso in tempo reale su Xcode. L'editor di sistema di particelle è uno strumento molto potente in Xcode e consente di personalizzare un sistema di particelle senza doverlo fare a livello di programmazione.
Con il sistema di particelle aperto, andare al Ispettore degli attributi a destra e cambia i seguenti attributi in Emettitore sezione:
Cambia i seguenti attributi in Simulazione sezione:
E infine, cambia i seguenti attributi in Ciclo vitale sezione:
Il tuo sistema di particelle dovrebbe ora sparare in tutte le direzioni e apparire simile allo screenshot seguente:
Aperto ViewController.swift e fai il tuo ViewController
classe conforme al SCNPhysicsContactDelegate
protocollo. L'adozione di questo protocollo è necessaria per rilevare una collisione tra due nodi.
class ViewController: UIViewController, SCNPhysicsContactDelegate
Quindi, assegna la corrente ViewController
istanza come il contactDelegate
del tuo physicsWorld
oggetto nel viewDidLoad
metodo.
override func viewDidLoad () super.viewDidLoad () sceneView = SCNView (frame: self.view.frame) sceneView.scene = SCNScene () sceneView.scene? .physicsWorld.contactDelegate = self self.view.addSubview (sceneView) ...
Infine, implementa il physicsWorld (_: didUpdateContact :)
metodo nel ViewController
classe:
func physicsWorld (world: SCNPhysicsWorld, didUpdateContact contact: SCNPhysicsContact) if (contact.nodeA == sphere1 || contact.nodeA == sphere2) && (contact.nodeB == sphere1 || contact.nodeB == sphere2) let particleSystem = SCNParticleSystem (denominato: "Explosion", inDirectory: nil) let systemNode = SCNNode () systemNode.addParticleSystem (particleSystem) systemNode.position = contact.nodeA.position sceneView.scene? .RootNode.addChildNode (systemNode) contact.nodeA.removeFromParentNode () contact.nodeB.removeFromParentNode ()
Per prima cosa controlliamo se i due nodi coinvolti nella collisione sono le due sfere. In tal caso, carichiamo il sistema di particelle dal file che abbiamo creato un momento fa e lo aggiungiamo a un nuovo nodo. Alla fine, rimuoviamo entrambe le sfere coinvolte nella collisione dalla scena.
Crea ed esegui nuovamente la tua app e tocca il pulsante. Quando le sfere entrano in contatto, dovrebbero sparire e il sistema di particelle dovrebbe apparire e animarsi.
In questo tutorial, ti ho mostrato come implementare l'interazione dell'utente, l'animazione, la simulazione fisica e i sistemi di particelle utilizzando il framework SceneKit. Le tecniche che hai imparato in questa serie possono essere applicate a qualsiasi progetto con un numero qualsiasi di animazioni, simulazioni fisiche, ecc.
Ora dovresti essere a tuo agio nel creare una scena semplice e aggiungere elementi dinamici ad essa, come i sistemi di animazione e di particelle. I concetti che hai appreso in questa serie sono applicabili alla scena più piccola con un singolo oggetto fino ad un gioco su larga scala.