版本记录
版本号 | 时间 |
---|---|
V1.0 | 2018.10.20 星期六 |
前言
SceneKit
使用高级场景描述创建3D游戏并将3D内容添加到应用程序。 轻松添加动画,物理模拟,粒子效果和逼真的基于物理性的渲染。接下来这几篇我们就详细的解析一下这个框架。感兴趣的看下面几篇文章。
1. SceneKit框架详细解析(一) —— 基本概览(一)
2. SceneKit框架详细解析(二) —— 基于SceneKit的简单游戏示例的实现(一)
3. SceneKit框架详细解析(三) —— 基于SceneKit的简单游戏示例的实现(二)
4. SceneKit框架详细解析(四) —— 基于SceneKit的简单游戏示例的实现(三)
5. SceneKit框架详细解析(五) —— 基于SceneKit的简单游戏示例的实现(四)
6. SceneKit框架详细解析(六) —— 基于SceneKit的简单游戏示例的实现(五)
源码
1. Swift
首先看一下工程文档结构
看一下sb中的内容
下面就是直接看代码了
1. GameViewController.swift
import UIKit
import SceneKit
class GameViewController: UIViewController {
var scnView: SCNView!
var scnScene: SCNScene!
var cameraNode: SCNNode!
var spawnTime: TimeInterval = 0
var game = GameHelper.sharedInstance
var splashNodes: [String: SCNNode] = [:]
override func viewDidLoad() {
super.viewDidLoad()
setupView()
setupScene()
setupCamera()
setupHUD()
setupSplash()
setupSounds()
}
override var shouldAutorotate: Bool {
return true
}
override var prefersStatusBarHidden: Bool {
return true
}
func setupView() {
scnView = self.view as! SCNView
//scnView.showsStatistics = true
//scnView.allowsCameraControl = false
scnView.autoenablesDefaultLighting = true
scnView.delegate = self
scnView.isPlaying = true
}
func setupScene() {
scnScene = SCNScene()
scnView.scene = scnScene
scnScene.background.contents =
"GeometryFighter.scnassets/Textures/Background_Diffuse.png"
}
func setupCamera() {
cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 5, z: 10)
scnScene.rootNode.addChildNode(cameraNode)
}
func spawnShape() {
var geometry: SCNGeometry
switch ShapeType.random() {
case .box:
geometry = SCNBox(width: 1.0, height: 1.0, length: 1.0, chamferRadius: 0.0)
case .sphere:
geometry = SCNSphere(radius: 0.5)
case .pyramid:
geometry = SCNPyramid(width: 1.0, height: 1.0, length: 1.0)
case .torus:
geometry = SCNTorus(ringRadius: 0.5, pipeRadius: 0.25)
case .capsule:
geometry = SCNCapsule(capRadius: 0.3, height: 2.5)
case .cylinder:
geometry = SCNCylinder(radius: 0.3, height: 2.5)
case .cone:
geometry = SCNCone(topRadius: 0.25, bottomRadius: 0.5, height: 1.0)
case .tube:
geometry = SCNTube(innerRadius: 0.25, outerRadius: 0.5, height: 1.0)
}
let color = UIColor.random()
geometry.materials.first?.diffuse.contents = color
let geometryNode = SCNNode(geometry: geometry)
geometryNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
let randomX = Float.random(min: -2, max: 2)
let randomY = Float.random(min: 10, max: 18)
let force = SCNVector3(x: randomX, y: randomY , z: 0)
let position = SCNVector3(x: 0.05, y: 0.05, z: 0.05)
geometryNode.physicsBody?.applyForce(force, at: position, asImpulse: true)
let trailEmitter = createTrail(color: color, geometry: geometry)
geometryNode.addParticleSystem(trailEmitter)
if color == UIColor.black {
geometryNode.name = "BAD"
game.playSound(scnScene.rootNode, name: "SpawnBad")
} else {
geometryNode.name = "GOOD"
game.playSound(scnScene.rootNode, name: "SpawnGood")
}
scnScene.rootNode.addChildNode(geometryNode)
}
func cleanScene() {
for node in scnScene.rootNode.childNodes {
if node.presentation.position.y < -2 {
node.removeFromParentNode()
}
}
}
func createTrail(color: UIColor, geometry: SCNGeometry) -> SCNParticleSystem {
let trail = SCNParticleSystem(named: "Trail.scnp", inDirectory: nil)!
trail.particleColor = color
trail.emitterShape = geometry
return trail
}
func setupHUD() {
game.hudNode.position = SCNVector3(x: 0.0, y: 10.0, z: 0.0)
scnScene.rootNode.addChildNode(game.hudNode)
}
func createSplash(name: String, imageFileName: String) -> SCNNode {
let plane = SCNPlane(width: 5, height: 5)
let splashNode = SCNNode(geometry: plane)
splashNode.position = SCNVector3(x: 0, y: 5, z: 0)
splashNode.name = name
splashNode.geometry?.materials.first?.diffuse.contents = imageFileName
scnScene.rootNode.addChildNode(splashNode)
return splashNode
}
func showSplash(splashName: String) {
for (name,node) in splashNodes {
if name == splashName {
node.isHidden = false
} else {
node.isHidden = true
}
}
}
func setupSplash() {
splashNodes["TapToPlay"] = createSplash(name: "TAPTOPLAY",
imageFileName: "GeometryFighter.scnassets/Textures/TapToPlay_Diffuse.png")
splashNodes["GameOver"] = createSplash(name: "GAMEOVER",
imageFileName: "GeometryFighter.scnassets/Textures/GameOver_Diffuse.png")
showSplash(splashName: "TapToPlay")
}
func setupSounds() {
game.loadSound("ExplodeGood",
fileNamed: "GeometryFighter.scnassets/Sounds/ExplodeGood.wav")
game.loadSound("SpawnGood",
fileNamed: "GeometryFighter.scnassets/Sounds/SpawnGood.wav")
game.loadSound("ExplodeBad",
fileNamed: "GeometryFighter.scnassets/Sounds/ExplodeBad.wav")
game.loadSound("SpawnBad",
fileNamed: "GeometryFighter.scnassets/Sounds/SpawnBad.wav")
game.loadSound("GameOver",
fileNamed: "GeometryFighter.scnassets/Sounds/GameOver.wav")
}
override func touchesBegan(_ touches: Set, with event: UIEvent?) {
if game.state == .GameOver {
return
}
if game.state == .TapToPlay {
game.reset()
game.state = .Playing
showSplash(splashName: "")
return
}
let touch = touches.first
let location = touch!.location(in: scnView)
let hitResults = scnView.hitTest(location, options: nil)
if let result = hitResults.first {
if result.node.name == "HUD" ||
result.node.name == "GAMEOVER" ||
result.node.name == "TAPTOPLAY" {
return
} else if result.node.name == "GOOD" {
handleGoodCollision()
} else if result.node.name == "BAD" {
handleBadCollision()
}
createExplosion(geometry: result.node.geometry!,
position: result.node.presentation.position,
rotation: result.node.presentation.rotation)
result.node.removeFromParentNode()
}
}
func handleGoodCollision() {
game.score += 1
game.playSound(scnScene.rootNode, name: "ExplodeGood")
}
func handleBadCollision() {
game.lives -= 1
game.playSound(scnScene.rootNode, name: "ExplodeBad")
game.shakeNode(cameraNode)
if game.lives <= 0 {
game.saveState()
showSplash(splashName: "GameOver")
game.playSound(scnScene.rootNode, name: "GameOver")
game.state = .GameOver
scnScene.rootNode.runAction(SCNAction.waitForDurationThenRunBlock(5) { (node:SCNNode!) -> Void in
self.showSplash(splashName: "TapToPlay")
self.game.state = .TapToPlay
})
}
}
func createExplosion(geometry: SCNGeometry, position: SCNVector3,
rotation: SCNVector4) {
let explosion =
SCNParticleSystem(named: "Explode.scnp", inDirectory:
nil)!
explosion.emitterShape = geometry
explosion.birthLocation = .surface
let rotationMatrix =
SCNMatrix4MakeRotation(rotation.w, rotation.x,
rotation.y, rotation.z)
let translationMatrix =
SCNMatrix4MakeTranslation(position.x, position.y, position.z)
let transformMatrix =
SCNMatrix4Mult(rotationMatrix, translationMatrix)
scnScene.addParticleSystem(explosion, transform: transformMatrix)
}
}
extension GameViewController: SCNSceneRendererDelegate {
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time:
TimeInterval) {
if game.state == .Playing {
if time > spawnTime {
spawnShape()
spawnTime = time + TimeInterval(Float.random(min: 0.2, max: 1.5))
}
cleanScene()
}
game.updateHUD()
}
}
2. ShapeType.swift
import Foundation
// 1
enum ShapeType:Int {
case box = 0
case sphere
case pyramid
case torus
case capsule
case cylinder
case cone
case tube
// 2
static func random() -> ShapeType {
let maxValue = tube.rawValue
let rand = arc4random_uniform(UInt32(maxValue+1))
return ShapeType(rawValue: Int(rand))!
}
}
3. Double+Extensions.swift
import Foundation
public extension Double {
public static func random(min: Double, max: Double) -> Double {
let r64 = Double(arc4random(UInt64.self)) / Double(UInt64.max)
return (r64 * (max - min)) + min
}
}
4. Float+Extensions.swift
import Foundation
public extension Float {
public static func random(min: Float, max: Float) -> Float {
let r32 = Float(arc4random(UInt32.self)) / Float(UInt32.max)
return (r32 * (max - min)) + min
}
}
5. GameHelper.swift
import Foundation
import SceneKit
import SpriteKit
public enum GameStateType {
case Playing
case TapToPlay
case GameOver
}
class GameHelper {
var score:Int
var highScore:Int
var lastScore:Int
var lives:Int
var state = GameStateType.TapToPlay
var hudNode:SCNNode!
var labelNode:SKLabelNode!
static let sharedInstance = GameHelper()
var sounds:[String:SCNAudioSource] = [:]
private init() {
score = 0
lastScore = 0
highScore = 0
lives = 3
let defaults = UserDefaults.standard
score = defaults.integer(forKey: "lastScore")
highScore = defaults.integer(forKey: "highScore")
initHUD()
}
func saveState() {
lastScore = score
highScore = max(score, highScore)
let defaults = UserDefaults.standard
defaults.set(lastScore, forKey: "lastScore")
defaults.set(highScore, forKey: "highScore")
}
func getScoreString(_ length:Int) -> String {
return String(format: "%0\(length)d", score)
}
func initHUD() {
let skScene = SKScene(size: CGSize(width: 500, height: 100))
skScene.backgroundColor = UIColor(white: 0.0, alpha: 0.0)
labelNode = SKLabelNode(fontNamed: "Menlo-Bold")
labelNode.fontSize = 48
labelNode.position.y = 50
labelNode.position.x = 250
skScene.addChild(labelNode)
let plane = SCNPlane(width: 5, height: 1)
let material = SCNMaterial()
material.lightingModel = SCNMaterial.LightingModel.constant
material.isDoubleSided = true
material.diffuse.contents = skScene
plane.materials = [material]
hudNode = SCNNode(geometry: plane)
hudNode.name = "HUD"
hudNode.rotation = SCNVector4(x: 1, y: 0, z: 0, w: 3.14159265)
}
func updateHUD() {
let scoreFormatted = String(format: "%0\(4)d", score)
let highScoreFormatted = String(format: "%0\(4)d", highScore)
labelNode.text = "❤️\(lives) \(highScoreFormatted) \(scoreFormatted)"
}
func loadSound(_ name:String, fileNamed:String) {
if let sound = SCNAudioSource(fileNamed: fileNamed) {
sound.load()
sounds[name] = sound
}
}
func playSound(_ node:SCNNode, name:String) {
let sound = sounds[name]
node.runAction(SCNAction.playAudio(sound!, waitForCompletion: false))
}
func reset() {
score = 0
lives = 3
}
func shakeNode(_ node:SCNNode) {
let left = SCNAction.move(by: SCNVector3(x: -0.2, y: 0.0, z: 0.0), duration: 0.05)
let right = SCNAction.move(by: SCNVector3(x: 0.2, y: 0.0, z: 0.0), duration: 0.05)
let up = SCNAction.move(by: SCNVector3(x: 0.0, y: 0.2, z: 0.0), duration: 0.05)
let down = SCNAction.move(by: SCNVector3(x: 0.0, y: -0.2, z: 0.0), duration: 0.05)
node.runAction(SCNAction.sequence([
left, up, down, right, left, right, down, up, right, down, left, up,
left, up, down, right, left, right, down, up, right, down, left, up]))
}
}
6. Generics.swift
import Foundation
public func arc4random (_ type: T.Type) -> T {
var r: T = 0
arc4random_buf(&r, Int(MemoryLayout.size))
return r
}
7. Int+Extensions.swift
import Foundation
public extension Int {
public static func random(min: Int , max: Int) -> Int {
return Int(arc4random_uniform(UInt32(max - min + 1))) + min
}
}
8. SCNAction+Extensions.swift
import SceneKit
import Foundation
extension SCNAction {
class func waitForDurationThenRemoveFromParent(_ duration:TimeInterval) -> SCNAction {
let wait = SCNAction.wait(duration: duration)
let remove = SCNAction.removeFromParentNode()
return SCNAction.sequence([wait,remove])
}
class func waitForDurationThenRunBlock(_ duration:TimeInterval, block: @escaping ((SCNNode!) -> Void) ) -> SCNAction {
let wait = SCNAction.wait(duration: duration)
let runBlock = SCNAction.run { (node) -> Void in
block(node)
}
return SCNAction.sequence([wait,runBlock])
}
class func rotateByXForever(_ x:CGFloat, y:CGFloat, z:CGFloat, duration:TimeInterval) -> SCNAction {
let rotate = SCNAction.rotateBy(x: x, y: y, z: z, duration: duration)
return SCNAction.repeatForever(rotate)
}
}
9. UIColor+Extensions.swift
import UIKit
import SceneKit
let UIColorList:[UIColor] = [
UIColor.black,
UIColor.white,
UIColor.red,
UIColor.lime,
UIColor.blue,
UIColor.yellow,
UIColor.cyan,
UIColor.silver,
UIColor.gray,
UIColor.maroon,
UIColor.olive,
UIColor.brown,
UIColor.green,
UIColor.lightGray,
UIColor.magenta,
UIColor.orange,
UIColor.purple,
UIColor.teal
]
extension UIColor {
public static func random() -> UIColor {
let maxValue = UIColorList.count
let rand = Int(arc4random_uniform(UInt32(maxValue)))
return UIColorList[rand]
}
public static var lime: UIColor {
return UIColor(red: 0.0, green: 1.0, blue: 0.0, alpha: 1.0)
}
public static var silver: UIColor {
return UIColor(red: 192/255, green: 192/255, blue: 192/255, alpha: 1.0)
}
public static var maroon: UIColor {
return UIColor(red: 0.5, green: 0.0, blue: 0.0, alpha: 1.0)
}
public static var olive: UIColor {
return UIColor(red: 0.5, green: 0.5, blue: 0.0, alpha: 1.0)
}
public static var teal: UIColor {
return UIColor(red: 0.0, green: 0.5, blue: 0.5, alpha: 1.0)
}
public static var navy: UIColor {
return UIColor(red: 0.0, green: 0.0, blue: 128, alpha: 1.0)
}
}
下面看一下实现效果
后记
本篇主要讲述了基于SceneKit的简单游戏示例的实现,感兴趣的给个赞或者关注~~~