版本记录
版本号 | 时间 |
---|---|
V1.0 | 2019.10.26 星期六 |
前言
SpriteKit框架使用优化的动画系统,物理模拟和事件处理支持创建基于2D精灵的游戏。接下来这几篇我们就详细的解析一下这个框架。相关代码已经传至GitHub - 刀客传奇,感兴趣的可以阅读另外几篇文章。
1. SpriteKit框架详细解析(一) —— 基本概览(一)
2. SpriteKit框架详细解析(二) —— 一个简单的动画实例(一)
3. SpriteKit框架详细解析(三) —— 创建一个简单的2D游戏(一)
4. SpriteKit框架详细解析(四) —— 创建一个简单的2D游戏(二)
5. SpriteKit框架详细解析(五) —— 基于SpriteKit的游戏编程的三角函数(一)
6. SpriteKit框架详细解析(六) —— 基于SpriteKit的游戏编程的三角函数(二)
7. SpriteKit框架详细解析(七) —— 基于SpriteKit的类Cut the Rope游戏简单示例(一)
开始
题外话:周一就去成都出差了,祝自己一路顺风吧~~
1. Swift
首先看下工程组织结构
接着就是sb中的内容了
接着就是源码了
1. Constants.swift
import CoreGraphics
enum ImageName {
static let background = "Background"
static let ground = "Ground"
static let water = "Water"
static let vineTexture = "VineTexture"
static let vineHolder = "VineHolder"
static let crocMouthClosed = "CrocMouthClosed"
static let crocMouthOpen = "CrocMouthOpen"
static let crocMask = "CrocMask"
static let prize = "Pineapple"
static let prizeMask = "PineappleMask"
}
enum SoundFile {
static let backgroundMusic = "CheeZeeJungle.caf"
static let slice = "Slice.caf"
static let splash = "Splash.caf"
static let nomNom = "NomNom.caf"
}
enum Layer {
static let background: CGFloat = 0
static let crocodile: CGFloat = 1
static let vine: CGFloat = 1
static let prize: CGFloat = 2
static let foreground: CGFloat = 3
}
enum PhysicsCategory {
static let crocodile: UInt32 = 1
static let vineHolder: UInt32 = 2
static let vine: UInt32 = 4
static let prize: UInt32 = 8
}
enum GameConfiguration {
static let vineDataFile = "VineData.plist"
static let canCutMultipleVinesAtOnce = false
}
enum Scene {
static let particles = "Particle.sks"
}
2. GameViewController.swift
import UIKit
import SpriteKit
import GameplayKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
// Create and configure the scene.
let scene = GameScene(size: CGSize(width: 375, height: 667))
scene.scaleMode = .aspectFill
// Present the scene.
skView.presentScene(scene)
}
}
3. GameScene.swift
import SpriteKit
import AVFoundation
class GameScene: SKScene {
private var particles: SKEmitterNode?
private var crocodile: SKSpriteNode!
private var prize: SKSpriteNode!
private static var backgroundMusicPlayer: AVAudioPlayer!
private var sliceSoundAction: SKAction!
private var splashSoundAction: SKAction!
private var nomNomSoundAction: SKAction!
private var isLevelOver = false
private var didCutVine = false
override func didMove(to view: SKView) {
setUpPhysics()
setUpScenery()
setUpPrize()
setUpVines()
setUpCrocodile()
setUpAudio()
}
//MARK: - Level setup
private func setUpPhysics() {
physicsWorld.contactDelegate = self
physicsWorld.gravity = CGVector(dx: 0.0, dy: -9.8)
physicsWorld.speed = 1.0
}
private func setUpScenery() {
let background = SKSpriteNode(imageNamed: ImageName.background)
background.anchorPoint = CGPoint(x: 0, y: 0)
background.position = CGPoint(x: 0, y: 0)
background.zPosition = Layer.background
background.size = CGSize(width: size.width, height: size.height)
addChild(background)
let water = SKSpriteNode(imageNamed: ImageName.water)
water.anchorPoint = CGPoint(x: 0, y: 0)
water.position = CGPoint(x: 0, y: 0)
water.zPosition = Layer.foreground
water.size = CGSize(width: size.width, height: size.height * 0.2139)
addChild(water)
}
private func setUpPrize() {
prize = SKSpriteNode(imageNamed: ImageName.prize)
prize.position = CGPoint(x: size.width * 0.5, y: size.height * 0.7)
prize.zPosition = Layer.prize
prize.physicsBody = SKPhysicsBody(circleOfRadius: prize.size.height / 2)
prize.physicsBody?.categoryBitMask = PhysicsCategory.prize
prize.physicsBody?.collisionBitMask = 0
prize.physicsBody?.density = 0.5
addChild(prize)
}
//MARK: - Vine methods
private func setUpVines() {
// load vine data
let decoder = PropertyListDecoder()
guard
let dataFile = Bundle.main.url(
forResource: GameConfiguration.vineDataFile,
withExtension: nil),
let data = try? Data(contentsOf: dataFile),
let vines = try? decoder.decode([VineData].self, from: data)
else {
return
}
for (i, vineData) in vines.enumerated() {
let anchorPoint = CGPoint(
x: vineData.relAnchorPoint.x * size.width,
y: vineData.relAnchorPoint.y * size.height)
let vine = VineNode(length: vineData.length, anchorPoint: anchorPoint, name: "\(i)")
vine.addToScene(self)
vine.attachToPrize(prize)
}
}
//MARK: - Croc methods
private func setUpCrocodile() {
crocodile = SKSpriteNode(imageNamed: ImageName.crocMouthClosed)
crocodile.position = CGPoint(x: size.width * 0.75, y: size.height * 0.312)
crocodile.zPosition = Layer.crocodile
crocodile.physicsBody = SKPhysicsBody(
texture: SKTexture(imageNamed: ImageName.crocMask),
size: crocodile.size)
crocodile.physicsBody?.categoryBitMask = PhysicsCategory.crocodile
crocodile.physicsBody?.collisionBitMask = 0
crocodile.physicsBody?.contactTestBitMask = PhysicsCategory.prize
crocodile.physicsBody?.isDynamic = false
addChild(crocodile)
animateCrocodile()
}
private func animateCrocodile() {
let duration = Double.random(in: 2...4)
let open = SKAction.setTexture(SKTexture(imageNamed: ImageName.crocMouthOpen))
let wait = SKAction.wait(forDuration: duration)
let close = SKAction.setTexture(SKTexture(imageNamed: ImageName.crocMouthClosed))
let sequence = SKAction.sequence([wait, open, wait, close])
crocodile.run(.repeatForever(sequence))
}
private func runNomNomAnimation(withDelay delay: TimeInterval) {
crocodile.removeAllActions()
let closeMouth = SKAction.setTexture(SKTexture(imageNamed: ImageName.crocMouthClosed))
let wait = SKAction.wait(forDuration: delay)
let openMouth = SKAction.setTexture(SKTexture(imageNamed: ImageName.crocMouthOpen))
let sequence = SKAction.sequence([closeMouth, wait, openMouth, wait, closeMouth])
crocodile.run(sequence)
}
//MARK: - Touch handling
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
didCutVine = false
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let startPoint = touch.location(in: self)
let endPoint = touch.previousLocation(in: self)
// check if vine cut
scene?.physicsWorld.enumerateBodies(
alongRayStart: startPoint,
end: endPoint,
using: { body, _, _, _ in
self.checkIfVineCut(withBody: body)
})
// produce some nice particles
showMoveParticles(touchPosition: startPoint)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
particles?.removeFromParent()
particles = nil
}
private func showMoveParticles(touchPosition: CGPoint) {
if particles == nil {
particles = SKEmitterNode(fileNamed: Scene.particles)
particles!.zPosition = 1
particles!.targetNode = self
addChild(particles!)
}
particles!.position = touchPosition
}
//MARK: - Game logic
private func checkIfVineCut(withBody body: SKPhysicsBody) {
if didCutVine && !GameConfiguration.canCutMultipleVinesAtOnce {
return
}
let node = body.node!
// if it has a name it must be a vine node
if let name = node.name {
// snip the vine
node.removeFromParent()
// fade out all nodes matching name
enumerateChildNodes(withName: name, using: { node, _ in
let fadeAway = SKAction.fadeOut(withDuration: 0.25)
let removeNode = SKAction.removeFromParent()
let sequence = SKAction.sequence([fadeAway, removeNode])
node.run(sequence)
})
crocodile.removeAllActions()
crocodile.texture = SKTexture(imageNamed: ImageName.crocMouthOpen)
animateCrocodile()
run(sliceSoundAction)
didCutVine = true
}
}
private func switchToNewGame(withTransition transition: SKTransition) {
let delay = SKAction.wait(forDuration: 1)
let sceneChange = SKAction.run {
let scene = GameScene(size: self.size)
self.view?.presentScene(scene, transition: transition)
}
run(.sequence([delay, sceneChange]))
}
//MARK: - Audio
private func setUpAudio() {
if GameScene.backgroundMusicPlayer == nil {
let backgroundMusicURL = Bundle.main.url(
forResource: SoundFile.backgroundMusic,
withExtension: nil)
do {
let theme = try AVAudioPlayer(contentsOf: backgroundMusicURL!)
GameScene.backgroundMusicPlayer = theme
} catch {
// couldn't load file :[
}
GameScene.backgroundMusicPlayer.numberOfLoops = -1
}
if !GameScene.backgroundMusicPlayer.isPlaying {
GameScene.backgroundMusicPlayer.play()
}
sliceSoundAction = .playSoundFileNamed(
SoundFile.slice,
waitForCompletion: false)
splashSoundAction = .playSoundFileNamed(
SoundFile.splash,
waitForCompletion: false)
nomNomSoundAction = .playSoundFileNamed(
SoundFile.nomNom,
waitForCompletion: false)
}
}
extension GameScene: SKPhysicsContactDelegate {
override func update(_ currentTime: TimeInterval) {
if isLevelOver {
return
}
if prize.position.y <= 0 {
isLevelOver = true
run(splashSoundAction)
switchToNewGame(withTransition: .fade(withDuration: 1.0))
}
}
func didBegin(_ contact: SKPhysicsContact) {
if isLevelOver {
return
}
if (contact.bodyA.node == crocodile && contact.bodyB.node == prize)
|| (contact.bodyA.node == prize && contact.bodyB.node == crocodile) {
isLevelOver = true
// shrink the pineapple away
let shrink = SKAction.scale(to: 0, duration: 0.08)
let removeNode = SKAction.removeFromParent()
let sequence = SKAction.sequence([shrink, removeNode])
prize.run(sequence)
run(nomNomSoundAction)
runNomNomAnimation(withDelay: 0.15)
// transition to next level
switchToNewGame(withTransition: .doorway(withDuration: 1.0))
}
}
}
4. VineNode.swift
import UIKit
import SpriteKit
class VineNode: SKNode {
private let length: Int
private let anchorPoint: CGPoint
private var vineSegments: [SKNode] = []
init(length: Int, anchorPoint: CGPoint, name: String) {
self.length = length
self.anchorPoint = anchorPoint
super.init()
self.name = name
}
required init?(coder aDecoder: NSCoder) {
length = aDecoder.decodeInteger(forKey: "length")
anchorPoint = aDecoder.decodeCGPoint(forKey: "anchorPoint")
super.init(coder: aDecoder)
}
func addToScene(_ scene: SKScene) {
// add vine to scene
zPosition = Layer.vine
scene.addChild(self)
// create vine holder
let vineHolder = SKSpriteNode(imageNamed: ImageName.vineHolder)
vineHolder.position = anchorPoint
vineHolder.zPosition = 1
addChild(vineHolder)
vineHolder.physicsBody = SKPhysicsBody(circleOfRadius: vineHolder.size.width / 2)
vineHolder.physicsBody?.isDynamic = false
vineHolder.physicsBody?.categoryBitMask = PhysicsCategory.vineHolder
vineHolder.physicsBody?.collisionBitMask = 0
// add each of the vine parts
for i in 0..<length {
let vineSegment = SKSpriteNode(imageNamed: ImageName.vineTexture)
let offset = vineSegment.size.height * CGFloat(i + 1)
vineSegment.position = CGPoint(x: anchorPoint.x, y: anchorPoint.y - offset)
vineSegment.name = name
vineSegments.append(vineSegment)
addChild(vineSegment)
vineSegment.physicsBody = SKPhysicsBody(rectangleOf: vineSegment.size)
vineSegment.physicsBody?.categoryBitMask = PhysicsCategory.vine
vineSegment.physicsBody?.collisionBitMask = PhysicsCategory.vineHolder
}
// set up joint for vine holder
let joint = SKPhysicsJointPin.joint(
withBodyA: vineHolder.physicsBody!,
bodyB: vineSegments[0].physicsBody!,
anchor: CGPoint(
x: vineHolder.frame.midX,
y: vineHolder.frame.midY))
scene.physicsWorld.add(joint)
// set up joints between vine parts
for i in 1..<length {
let nodeA = vineSegments[i - 1]
let nodeB = vineSegments[i]
let joint = SKPhysicsJointPin.joint(
withBodyA: nodeA.physicsBody!,
bodyB: nodeB.physicsBody!,
anchor: CGPoint(
x: nodeA.frame.midX,
y: nodeA.frame.minY))
scene.physicsWorld.add(joint)
}
}
func attachToPrize(_ prize: SKSpriteNode) {
// align last segment of vine with prize
let lastNode = vineSegments.last!
lastNode.position = CGPoint(x: prize.position.x,
y: prize.position.y + prize.size.height * 0.1)
// set up connecting joint
let joint = SKPhysicsJointPin.joint(withBodyA: lastNode.physicsBody!,
bodyB: prize.physicsBody!,
anchor: lastNode.position)
prize.scene?.physicsWorld.add(joint)
}
}
5. VineData.swift
import UIKit
struct VineData: Decodable {
let length: Int
let relAnchorPoint: CGPoint
}
后记
本篇主要讲述了基于SpriteKit的类Cut the Rope游戏简单示例,感兴趣的给个赞或者关注~~~