En introduksjon til SceneKit Brukerinteraksjon, Animasjoner og Fysikk

Hva du skal skape

Dette er den andre delen av vår introduksjonsserie på SceneKit. I denne opplæringen antar jeg at du er kjent med konseptene som er forklart i første del, inkludert å sette opp en scene med lys, skygger, kameraer, noder og materialer.

I denne opplæringen skal jeg lære deg noen av de mer kompliserte, men også mer nyttige funksjonene i SceneKit, som animasjon, brukerinteraksjon, partikkelsystemer og fysikk. Ved å implementere disse funksjonene kan du opprette interaktivt og dynamisk 3D-innhold i stedet for statiske objekter som du gjorde i den forrige opplæringen.

1. Sette opp scenen

Opprett et nytt Xcode-prosjekt basert på iOS> Program> Enkeltvisningsprogram mal.

Navngi prosjektet, sett Språk til Fort, og enheter til Universell.

Åpen ViewController.swift og importere SceneKit-rammeverket.

importere UIKit import SceneKit

Deretter erklæres følgende egenskaper i ViewController klasse.

var sceneView: SCNView! var kamera: SCNNode! var bakken: SCNNode! var lys: SCNNode! Var-knappen: SCNNode! var sphere1: SCNNode! var sphere2: SCNNode!

Vi satte opp scenen i viewDidLoad metode som vist nedenfor.

overstyr func viewDidLoad () super.viewDidLoad () sceneView = SCNView (ramme: self.view.frame) sceneView.scene = SCNScene () self.view.addSubview (sceneView) la groundGeometry = SCNFloor () groundGeometry.reflectivity = 0 la groundMaterial = SCNMaterial () groundMaterial.diffuse.contents = UIColor.blueColor () groundGeometry.materials = [groundMaterial] bakken = SCNNode (geometri: groundGeometry) la kamera = SCNCamera () camera.zFar = 10000 self.camera = SCNNode () selv .camera.camera = kamera self.camera.position = SCNVector3 (x: -20, y: 15, z: 20) la begrensning = SCNLookAtConstraint (mål: bakken) constraint.gimbalLockEnabled = true self.camera.constraints = [begrensning] la ambientLight = SCNLight () ambientLight.color = UIColor.darkGrayColor () ambientLight.type = SCNLightTypeAmbient self.camera.light = ambientLight la spotLight = SCNLight () spotLight.type = SCNLightTypeSpot spotLight.castsShadow = sant 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 = [begrensning] la sphereGeometry = SCNSphere (radius: 1,5) la sphereMaterial = SCNMaterial sphereMaterial.diffuse.contents = UIColor.greenColor () sphereGeometry.materials = [sphereMaterial] sphere1 = SCNNode (geometri: sphereGeometry) sphere1.position = SCNVector3 (x: -15, y: 1,5, z: 0) sphere2 = SCNNode : sphereGeometry) sphere2.position = SCNVector3 (x: 15, y: 1,5, z: 0) la buttonGeometry = SCNBox (bredde: 4, høyde: 1, lengde: 4, chamferRadius: 0) la buttonMaterial = SCNMaterial () buttonMaterial. diffuse.contents = UIColor.redColor () buttonGeometry.materials = [buttonMaterial] knappen = SCNNode (geometri: 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)

Gjennomføringen av viewDidLoad bør se kjent hvis du har lest den første delen av denne serien. Alt vi gjør er å sette opp scenen som vi skal bruke i denne opplæringen. De eneste nye tingene inkluderer SCNFloor klasse og zFar eiendom.

Som navnet tilsier, er det SCNFloor Klassen brukes til å lage et gulv eller et grunnlag for scenen. Dette er mye lettere sammenlignet med å skape og rotere en SCNPlane som vi gjorde i den forrige opplæringen.

De zFar Egenskapen bestemmer hvor langt inn i avstanden et kamera kan se eller hvor langt lys fra en bestemt kilde kan nå. Bygg og kjør appen din. Din scene skal se slik ut:

2. Brukerinteraksjon

Brukerinteraksjon håndteres i SceneKit av en kombinasjon av UIGestureRecognizer klasse og trefftester. For å oppdage et trykk, for eksempel, legger du først til en UITapGestureRecognizer til en SCNView, bestemme tappens posisjon i visningen, og se om den er i kontakt med eller treffer noen av noderne.

For å bedre forstå hvordan dette virker, bruker vi et eksempel. Når en knute blir tappet, fjerner vi den fra scenen. Legg til følgende kodestykke til viewDidLoad metode av ViewController klasse:

overstyr func viewDidLoad () super.viewDidLoad () sceneView = SCNView (frame: self.view.frame) sceneView.scene = SCNScene () self.view.addSubview (sceneView) la tapRecognizer = UITapGestureRecognizer () tapRecognizer.numberOfTapsRequired = 1 tapRecognizer .numberOfTouchesRequired = 1 tapRecognizer.addTarget (selv, handling: "sceneTapped:") sceneView.gestureRecognizers = [tapRecognizer] ...

Deretter legger du til følgende metode for ViewController klasse:

func sceneTapped (recognizer: UITapGestureRecognizer) la location = recognizer.locationInView (sceneView) la hitResults = sceneView.hitTest (plassering, alternativer: null) hvis hitResults? .count> 0 la resultat = hitResults! [0] as! SCNHitTestResult la node = result.node node.removeFromParentNode ()

Ved denne metoden får du først plassering av trykk som en CGPoint. Deretter bruker du dette poenget til å utføre en testtest på sceneView objekt og lagre SCNHitTestResult objekter i en oppringt gruppe hitResults. De opsjoner Parameteren til denne metoden kan inneholde en ordbok med nøkler og verdier, som du kan lese om i Apples dokumentasjon. Vi sjekker deretter for å se om hittesten returnerte minst ett resultat, og hvis det gjorde det, fjerner vi det første elementet i matrisen fra sin forelderknutepunkt.

Hvis hit-testen returnerte flere resultater, blir objektene sortert etter sin z-posisjon, det vil si den rekkefølgen de ser ut fra det nåværende kameraets synspunkt. For eksempel, i den aktuelle scenen, hvis du klikker på en av de to sfærene eller knappen, vil noden du tappet, danne det første elementet i den returnerte gruppen. Fordi bakken vises direkte bak disse objektene fra kameraets synspunkt, vil grunnkoden imidlertid være et annet element i rekke resultater, det andre i dette tilfellet. Dette skjer fordi et trykk på samme sted ville slå bakkenoden hvis sfærene og knappen ikke var der.

Bygg og kjør appen din, og trykk på objektene i scenen. De bør forsvinne når du trykker på hver enkelt.

Nå som vi kan bestemme når en knute blir tappet, kan vi begynne å legge til animasjoner i blandingen.

3. Animasjon

Det er to klasser som kan brukes til å utføre animasjoner i SceneKit:

  • SCNAction
  • SCNTransaction

SCNAction Objekter er svært nyttige for enkle og gjenbrukbare animasjoner, for eksempel bevegelse, rotasjon og skala. Du kan kombinere et antall handlinger sammen i et tilpasset handlingsobjekt.

De SCNTransaction klassen kan utføre de samme animasjonene, men det er mer allsidig på noen måter, for eksempel animerende materialer. Denne ekstra allsidigheten kommer imidlertid til bekostning av SCNTransaction animasjoner har bare samme gjenbruk som en funksjon og oppsettet gjøres via klassemetoder.

For din første animasjon, kommer jeg til å vise deg kode ved hjelp av begge SCNAction og SCNTransaction klasser. Eksemplet vil flytte knappen ned og slå den hvit når den er tappet. Oppdater implementeringen av sceneTapped (_ :) metode som vist nedenfor.

func sceneTapped (recognizer: UITapGestureRecognizer) la location = recognizer.locationInView (sceneView) la hitResults = sceneView.hitTest (plassering, alternativer: null) hvis hitResults? .count> 0 la resultat = hitResults! [0] as! SCNHitTestResult la node = result.node hvis node == button SCNTransaction.begin () SCNTransaction.setAnimationDuration (0.5) la materials = node.geometry? .Materialer som! [SCNMaterial] la material = materialer [0] material.diffuse.contents = UIColor.whiteColor () SCNTransaction.commit () la action = SCNAction.moveByX (0, y: -0,8, z: 0, varighet: 0,5) node. runAction (handling)

I sceneTapped (_ :) Metode, vi får en referanse til noden brukeren har tappet og kontroller om dette er knappen i scenen. Hvis det er, animerer vi materialet fra rød til hvit, ved hjelp av SCNTransaction klassen, og flytt den langs y-aksen i en negativ retning ved å bruke en SCNAction forekomst. Varigheten av animasjonen er satt til 0,5 sekunder.

Bygg og kjør appen din igjen, og trykk på knappen. Den skal bevege seg ned og endre fargen til hvit som vist på skjermbildet nedenfor.

4. Fysikk

Oppsett av realistiske fysikk simuleringer er enkelt med SceneKit rammen. Funksjonen som SceneKits fysikk simuleringer tilbyr, er omfattende, alt fra grunnleggende hastigheter, akselerasjoner og krefter, til tyngdekraft og elektriske felt, og til og med kollisjon gjenkjenning.

Det du skal gjøre i den nåværende scenen er, bruk et tyngdefelt til en av kulene slik at den andre sfæren trekkes mot den første sfæren som et resultat av tyngdekraften. Denne tyngdekraften blir aktiv når du trykker på knappen.

Oppsettet for denne simuleringen er veldig enkelt. Bruk en SCNPhysicsBody objekt for hver knute som du vil bli påvirket av fysikk simuleringen og en SCNPhysicsField objekt for hver node du vil være kilden til et felt. Oppdater viewDidLoad metode som vist nedenfor.

overstyre func viewDidLoad () ... buttonGeometry.materials = [buttonMaterial] -knappen = SCNNode (geometri: buttonGeometry) button.position = SCNVector3 (x: 0, y: 0,5, z: 15) // Fysikk la groundShape = SCNPhysicsShape (geometri: groundGeometry, alternativer: null) la groundBody = SCNPhysicsBody (type: .Kinematisk, form: groundShape) ground.physicsBody = groundBody lar gravityField = SCNPhysicsField.radialGravityField () gravityField.strength = 0 sphere1.physicsField = gravityField let shape = SCNPhysicsShape (geometri: sphereGeometry, alternativer: null) la sphere1Body = SCNPhysicsBody (type: .Kinematisk, form: form) sphere1.physicsBody = sphere1Body la sphere2Body = SCNPhysicsBody (type: .Dynamisk, form: form) sphere2.physicsBody = sphere2Body sceneView.scene? .rootNode .addChildNode (self.camera) sceneView.scene? .rootNode.addChildNode (ground) sceneView.scene? .rootNode.addChildNode (light) ...

Vi starter med å skape en SCNPhysicsShape eksempel som spesifiserer den faktiske formen på objektet som deltar i fysikk simuleringen. For de grunnleggende figurene du bruker i denne scenen, er geometriobjektene helt fine å bruke. For kompliserte 3D-modeller er det imidlertid bedre å kombinere flere primitive former sammen for å skape en omtrentlig form for objektet ditt for fysikk simulering.

Fra denne formen lager du en SCNPhysicsBody forekomst og legg det til grunnen av scenen. Dette er nødvendig, fordi hver SceneKit-scene har som standard et eksisterende tyngdekraftsfelt som trekker hvert objekt nedover. De kinematisk skriv det du gir til dette SCNPhysicsBody betyr at objektet vil delta i kollisjoner, men er upåvirket av krefter (og vil ikke falle på grunn av tyngdekraften).

Deretter lager du gravitasjonsfeltet og tilordner dette til den første kuleknuten. Etter samme prosess som for bakken lager du en fysikk kropp for hver av de to kulene. Du angir den andre sfæren som en dynamisk fysikk kroppen skjønt, fordi du vil at den skal bli berørt og flyttet av gravitasjonsfeltet du opprettet.

Til slutt må du sette styrken på dette feltet for å aktivere det når knappen er tappet. Legg til følgende linje i sceneTapped (_ :) metode:

func sceneTapped (recognizer: UITapGestureRecognizer) ... hvis node == button ... sphere1.physicsField? .strength = 750

Bygg og kjør appen din, trykk på knappen, og se ettersom den andre sfæren sakte akselererer mot den første. Merk at det kan ta noen sekunder før den andre sfæren begynner å bevege seg.

Det er bare en ting igjen å gjøre, men gjør at kulene eksploderer når de kolliderer.

5. Kollisjonsdeteksjon og partikkelsystemer

For å skape effekten av en eksplosjon skal vi utnytte SCNParticleSystem klasse. Et partikkelsystem kan opprettes av et eksternt 3D-program, kildekode eller, som jeg skal vise deg, Xcodes partikkelsystemredigerer. Opprett en ny fil ved å trykke Command + N og velg SceneKit Particle System fra iOS> Ressurs seksjon.

Sett partikkelsystemmalen til Reaktor.Klikk neste, navn filen Eksplosjon, og lagre det i prosjektmappen din.

I Prosjektnavigator, Du vil nå se to nye filer, Explosion.scnp og spark.png. De spark.png bildet er en ressurs som brukes av partikkelsystemet, automatisk lagt til prosjektet ditt. Hvis du åpner Explosion.scnp, Du vil se at den blir animert og gjengitt i sanntid i Xcode. Partikkelsystemredigereren er et veldig kraftig verktøy i Xcode og lar deg tilpasse et partikkelsystem uten å måtte gjøre det programmatisk. 

Når partikkelsystemet er åpent, gå til Attributtsinspektør til høyre og endre følgende attributter i emitter seksjon:

  • Fødselsrate til 300
  • Retningsmodus til Tilfeldig

Endre følgende attributter i simulering seksjon:

  • Levetid til 3
  • Hastighetsfaktor til 2

Og til slutt, endre følgende attributter i Livssyklus seksjon:

  • Utslipp dur. til 1
  • looping til Spiller en gang

Ditt partikkelsystem skal nå skyte ut i alle retninger og ligner på følgende skjermbilde:

Åpen ViewController.swift og gjør din ViewController klassen samsvarer med SCNPhysicsContactDelegate protokoll. Vedtaket av denne protokollen er nødvendig for å oppdage en kollisjon mellom to noder.

klasse ViewController: UIViewController, SCNPhysicsContactDelegate

Deretter tilordnes gjeldende ViewController eksempel som contactDelegate av din physicsWorld objekt i viewDidLoad metode.

overstyr func viewDidLoad () super.viewDidLoad () sceneView = SCNView (ramme: self.view.frame) sceneView.scene = SCNScene () sceneView.scene? .physicsWorld.contactDelegate = self self.view.addSubview (sceneView) ...

Endelig, implementer physicsWorld (_: didUpdateContact :) metode i ViewController klasse:

func physics World (verden: SCNPhysicsWorld, didUpdateKontakt kontakt: SCNPhysicsContact) if (contact.nodeA == sphere1 || contact.nodeA == sphere2) && (contact.nodeB == sphere1 || contact.nodeB == sphere2) la particleSystem = SCNParticleSystem (kalt "Explosion", inDirectory: null) la systemNode = SCNNode () systemNode.addParticleSystem (particleSystem) systemNode.position = contact.nodeA.position sceneView.scene? .RootNode.addChildNode (systemNode) contact.nodeA.removeFromParentNode () contact.nodeB.removeFromParentNode ()

Vi sjekker først for å se om de to noder som er involvert i kollisjonen, er de to sfærene. Hvis så er tilfelle, laster vi partikkelsystemet fra filen vi opprettet for øyeblikket, og legger den til en ny knutepunkt. Til slutt fjerner vi begge sfærene som er involvert i kollisjonen fra scenen.

Bygg og kjør appen din på nytt, og trykk på knappen. Når kulene gjør kontakt, bør de begge forsvinne og partikkelsystemet ditt skal vises og animere.

Konklusjon

I denne veiledningen viste jeg deg hvordan du implementerer brukerinteraksjon, animasjon, fysikk simulering og partikkel systemer ved hjelp av SceneKit rammen. Teknikkene du har lært i denne serien kan brukes til et hvilket som helst prosjekt med et hvilket som helst antall animasjoner, fysikk simuleringer osv.

Du bør nå være komfortabel å lage en enkel scene og legge til dynamiske elementer i den, for eksempel animasjon og partikelsystemer. Begrepene du har lært i denne serien, gjelder for den minste scenen med et enkelt objekt helt opp til et stort spill.