美文网首页Swift & Obj-C
SceneKit框架详细解析(九) —— SceneKit 3D

SceneKit框架详细解析(九) —— SceneKit 3D

作者: 刀客传奇 | 来源:发表于2021-07-21 10:17 被阅读0次

    版本记录

    版本号 时间
    V1.0 2021.07.21 星期三

    前言

    SceneKit使用高级场景描述创建3D游戏并将3D内容添加到应用程序。 轻松添加动画,物理模拟,粒子效果和逼真的基于物理性的渲染。接下来这几篇我们就详细的解析一下这个框架。感兴趣的看下面几篇文章。
    1. SceneKit框架详细解析(一) —— 基本概览(一)
    2. SceneKit框架详细解析(二) —— 基于SceneKit的简单游戏示例的实现(一)
    3. SceneKit框架详细解析(三) —— 基于SceneKit的简单游戏示例的实现(二)
    4. SceneKit框架详细解析(四) —— 基于SceneKit的简单游戏示例的实现(三)
    5. SceneKit框架详细解析(五) —— 基于SceneKit的简单游戏示例的实现(四)
    6. SceneKit框架详细解析(六) —— 基于SceneKit的简单游戏示例的实现(五)
    7. SceneKit框架详细解析(七) —— 基于SceneKit的简单游戏示例的实现(六)
    8. SceneKit框架详细解析(八) —— SceneKit 3D编程入门(一)

    源码

    1. Swift

    首先看下工程组织结构

    下面就是源码了。

    1. Planet.swift
    
    import Foundation
    
    enum Planet: String, CaseIterable {
      case mercury
      case venus
      case earth
      case mars
      case saturn
    
      var name: String {
        rawValue.prefix(1).capitalized + rawValue.dropFirst()
      }
    }
    
    2. Planet+Info.swift
    
    import Foundation
    
    extension Planet {
      var moonCount: Int {
        switch self {
        case .mercury:
          return 0
        case .venus:
          return 0
        case .earth:
          return 1
        case .mars:
          return 2
        case .saturn:
          return 62
        }
      }
    
      var yearLength: String {
        switch self {
        case .mercury:
          return "88 Earth days"
        case .venus:
          return "225 Earth days"
        case .earth:
          return "365.25 days"
        case .mars:
          return "1.88 Earth years"
        case .saturn:
          return "29.45 earth years"
        }
      }
    
      var namesake: String {
        switch self {
        case .mercury:
          return "Roman god of speed"
        case .venus:
          return "Roman goddess of love"
        case .earth:
          return "The ground"
        case .mars:
          return "Roman god of war"
        case .saturn:
          return "Roman god of agriculture"
        }
      }
    }
    
    3. PlanetInfoRow.swift
    
    import SwiftUI
    
    struct PlanetInfoRow: View {
      let title: String
      let value: String
    
      var body: some View {
        HStack {
          VStack(alignment: .leading) {
            Text("\(title):").fontWeight(.thin).font(.title2)
            Text(value).fontWeight(.semibold)
          }
          .foregroundColor(.white)
          Spacer()
        }
      }
    }
    
    struct PlanetInfoRow_Previews: PreviewProvider {
      static var previews: some View {
        PlanetInfoRow(title: "Number of moons", value: "3")
      }
    }
    
    4. ColorPalette.swift
    
    import SwiftUI
    
    enum ColorPalette {
      static let primary = Color(
        red: 29 / 255,
        green: 17 / 255,
        blue: 53 / 255,
        opacity: 1)
    
      static let secondary = Color(
        red: 186 / 255,
        green: 30 / 255,
        blue: 104 / 255,
        opacity: 1)
    
      static let accent = Color(
        red: 252 / 255,
        green: 251 / 255,
        blue: 254 / 255,
        opacity: 1)
    }
    
    5. SolarButtonStyle.swift
    
    import SwiftUI
    
    struct SolarButtonStyle: ButtonStyle {
      func makeBody(configuration: Configuration) -> some View {
        configuration
          .label
          .foregroundColor(configuration.isPressed ? .gray : .white)
          .padding(8)
          .background(ColorPalette.secondary)
          .cornerRadius(8)
      }
    }
    
    6. ProgressViewWithBackgroundStyle.swift
    
    import SwiftUI
    
    struct ProgressViewWithBackgroundStyle: ProgressViewStyle {
      func makeBody(configuration: Configuration) -> some View {
        ColorPalette.accent
          .opacity(0.8)
          .overlay(ProgressView(configuration))
          .cornerRadius(5)
      }
    }
    
    7. AppMain.swift
    
    import SwiftUI
    
    @main
    struct AppMain: App {
      var body: some Scene {
        WindowGroup {
          ContentView()
            .buttonStyle(SolarButtonStyle())
        }
      }
    }
    
    8. ContentView.swift
    
    import SwiftUI
    import SceneKit
    
    struct ContentView: View {
      // 1
      static func makeScene() -> SCNScene? {
        let scene = SCNScene(named: "Solar Scene.scn")
        applyTextures(to: scene)
        return scene
      }
    
      static func applyTextures(to scene: SCNScene?) {
        // 1
        for planet in Planet.allCases {
          // 2
          let identifier = planet.rawValue
          // 3
          let node = scene?.rootNode.childNode(withName: identifier, recursively: false)
    
          // Images courtesy of Solar System Scope https://bit.ly/3fAWUzi
          // 4
          let texture = UIImage(named: identifier)
    
          // 5
          node?.geometry?.firstMaterial?.diffuse.contents = texture
        }
    
        // 1
        let skyboxImages = (1...6).map { UIImage(named: "skybox\($0)") }
        // 2
        scene?.background.contents = skyboxImages
      }
    
      // 2
      var scene = makeScene()
    
      @ObservedObject var viewModel = ViewModel()
    
      var body: some View {
        ZStack {
          SceneView(
            // 1
            scene: scene,
            // 2
            pointOfView: setUpCamera(planet: viewModel.selectedPlanet),
            // 3
            options: viewModel.selectedPlanet == nil ? [.allowsCameraControl] : [])
            // 4
            .background(ColorPalette.secondary)
            .edgesIgnoringSafeArea(.all)
    
          VStack {
            if let planet = viewModel.selectedPlanet {
              VStack {
                PlanetInfoRow(title: "Length of year", value: planet.yearLength)
                PlanetInfoRow(title: "Number of moons", value: "\(planet.moonCount)")
                PlanetInfoRow(title: "Namesake", value: planet.namesake)
              }
              .padding(8)
              .background(ColorPalette.primary)
              .cornerRadius(14)
              .padding(12)
            }
    
            Spacer()
    
            HStack {
              HStack {
                Button(action: viewModel.selectPreviousPlanet) {
                  Image(systemName: "arrow.backward.circle.fill")
                }
                Button(action: viewModel.selectNextPlanet) {
                  Image(systemName: "arrow.forward.circle.fill")
                }
              }
    
              Spacer()
              Text(viewModel.title).foregroundColor(.white)
              Spacer()
    
              if viewModel.selectedPlanet != nil {
                Button(action: viewModel.clearSelection) {
                  Image(systemName: "xmark.circle.fill")
                }
              }
            }
            .padding(8)
            .background(ColorPalette.primary)
            .cornerRadius(14)
            .padding(12)
          }
        }
      }
    
      func setUpCamera(planet: Planet?) -> SCNNode? {
        let cameraNode = scene?.rootNode.childNode(withName: "camera", recursively: false)
    
        // 1
        if let planetNode = planet.flatMap(planetNode(planet:)) {
          // 2
          let constraint = SCNLookAtConstraint(target: planetNode)
          cameraNode?.constraints = [constraint]
          // 3
          let globalPosition = planetNode.convertPosition(SCNVector3(x: 50, y: 10, z: 0), to: nil)
          // 4
          let move = SCNAction.move(to: globalPosition, duration: 1.0)
          cameraNode?.runAction(move)
        }
    
        return cameraNode
      }
    
      func planetNode(planet: Planet) -> SCNNode? {
        scene?.rootNode.childNode(withName: planet.rawValue, recursively: false)
      }
    }
    
    struct ContentView_Previews: PreviewProvider {
      static var previews: some View {
        ContentView()
      }
    }
    
    9. ViewModel.swift
    
    import Foundation
    
    class ViewModel: NSObject, ObservableObject {
      @Published var selectedPlanet: Planet?
    
      var title: String {
        selectedPlanet?.name ?? ""
      }
    
      func selectNextPlanet() {
        changeSelection(offset: 1)
      }
    
      func selectPreviousPlanet() {
        changeSelection(offset: -1)
      }
    
      func clearSelection() {
        selectedPlanet = nil
      }
    
      private func changeSelection(offset: Int) {
        guard
          let selectedPlanet = selectedPlanet,
          let index = Planet.allCases.firstIndex(of: selectedPlanet)
        else {
          selectedPlanet = Planet.allCases.first
          return
        }
    
        let newIndex = index + offset
    
        if newIndex < 0 {
          self.selectedPlanet = Planet.allCases.last
        } else if newIndex < Planet.allCases.count {
          self.selectedPlanet = Planet.allCases[newIndex]
        } else {
          self.selectedPlanet = Planet.allCases.first
        }
      }
    }
    

    后记

    本篇主要讲述了SceneKit 3D编程入门,感兴趣的给个赞或者关注~~~

    相关文章

      网友评论

        本文标题:SceneKit框架详细解析(九) —— SceneKit 3D

        本文链接:https://www.haomeiwen.com/subject/cbxdmltx.html