SpriteKit Game Tutorial by iFIERO.com
动画演示 (https://upload-images.jianshu.io/upload_images/3896436-7bc99f99cf931ac8.gif)
- *** 游戏元素使用条款及注意事项 ***
- 游戏中的所有元素全部由iFIERO所原创(除注明引用之外),包括人物、音乐、场景等,
- 创作的初衷就是让更多的游戏爱好者可以在开发游戏中获得自豪感 -- 让手机游戏开发变得简单。
- 秉着开源分享的原则,iFIERO发布的游戏都尽可能的易懂实用,并开放所有源码,
- 任何使用者都可以使用游戏中的代码块,也可以进行拷贝、修改、更新、升级,无须再经过iFIERO的同意。
- 但这并不表示可以任意复制、拆分其中的游戏元素:
- 用于[商业目的]而不注明出处,
- 用于[任何教学]而不注明出处,
- 用于[游戏上架]而不注明出处;
- 另外,iFIERO有商用授权游戏元素,获得iFIERO官方授权后,即无任何限制!
- 请尊重帮助过你的iFIERO的知识产权,非常感谢!
- Created by VANGO杨 && ANDREW陈
- Copyright © 2018 iFiero. All rights reserved.
- www.iFIERO.com
- iFIERO -- 为游戏开发深感自豪
- FlyingPenguin 飞吧企鹅 在此游戏中您将获得如下技能:
- 1、LaunchScreen 学习如何设置游戏启动画面;
- 2、Endless Background 无限循环背景;
- 3、Scene Edit 直接使用可见即所得操作,注意scebe场景的中心点是anchor0.5*0.5;
- 4、UserDefaults 保存游戏分数、最高分;
- 5、Random+moveBy 利用可复用的随机函数生成Obstacle障碍物;
- 6、Juice:Particle 粒子特效;
- 7、ScreenShot+Share 截屏+分享链接GameScene传值给ViewController;
- 8、Protocol代理 代理传值保存图片时须设置程序读取手机图片的权限info.plist中的Privicy;
- 9、StateMachine GameplayKit 运用之场景; (**** 中级技能)
- 10、Entity+Component Entity对象+Component组件的运用; (**** 中级技能)
- 11、Velocity+Rotate Velocity向量(速度+方向)及角度计算;
- 12、Wobbling 利用moveBy+reverse制作出企鹅上下舞动的效果;
- 12B、Juice:ScreenShake Juice特效:学习企鹅撞到障碍物后,整个屏幕发生抖动;(**** 高级技能)
*/
import SpriteKit import GameplayKit
// 为何设置代理:ViewController须弹出分享View protocol GameSceneDelegate:class { func screenShot() -> UIImage // 截屏代理 func shareUrl(_ textString:String,url:URL,image:UIImage) // 分享链接代理、传递图片、说明文字 }
class GameScene: SKScene,SKPhysicsContactDelegate {
weak var gameSceneDelegate:GameSceneDelegate?
var moveAllowed = false // 场景是否可以移动
//MARK: - StateMachine 场景中各个舞台State
lazy var stateMachine:GKStateMachine = GKStateMachine(states: [
WaitingForTapState(scene: self),
PlayingState(scene: self),
FallingState(scene:self),
GameOverState(scene: self)])
let appStoreLink = "http://www.iFiero.com" //游戏上线后换成app store上的游戏下载地址;
var score = 0 // 游戏分数
var dt:TimeInterval = 0 // 每一frame的时间差
var lastUpdateTimeInterval:TimeInterval = 0 // 最后更新的时间
/* UI元素中的场景移动速度 注意:用真机运行FPS 60s,模拟器simular的FPS 10s */
let cloudDistance:CGFloat = 5 // 白云
let treeDistance:CGFloat = 8 // 树
let mounationDistance:CGFloat = 2 // 山
let groundSpeed: CGFloat = 9 // 地板的移动速度;
let obstacleSpeed:CGFloat = 450 // 值越大,移动速度越快
let obstacleDelayTime:CGFloat = 2.2 // 每次生成障碍物的时间间隔 2.0s
let firstSpawnDelay: TimeInterval = 0.8 // 首次生成 Obstacle
var everySpawnDelay: TimeInterval = 4.0 // 每个Obstacle生成的时间间隔 值越大 游戏难度越小;
let numberOfScenes:CGFloat = 2 // 有二个场景
/*物理重力*/
let impluse:CGFloat = 600 // 每次拍打的向上动力
let gravity:CGFloat = -1800 // 向下的重力
var initVelocity = CGPoint.zero // 速度+方向
var velocityModifier:CGFloat = 1000.0 //数值转化为弧度;
var angularVelocity:CGFloat = 0.0 // 角度;
var lastTouchY:CGFloat = 0.0 // 最新点击的Y轴位置;
let minDegree:CGFloat = -25 // 水平线 :向下的角度(左到右)
let maxDegree:CGFloat = 25 // 向上的角度
/*场景中的所有SpriteNode*/
private var worldNode:SKSpriteNode!
private var groundNode:SKSpriteNode!
private var playerNode:SKSpriteNode!
private var crownNode:SKSpriteNode!
// let obstacle = ObstacleEntity(imageName: "obstacle") // 障碍物;
/*
* 若有取得场景中的白云节点,需命名场景中的每一个节点的名称 Attritubes inspector面板命名;
*/
private var cloud1_1:SKSpriteNode! // 白云1
private var cloud1_2:SKSpriteNode! //
private var cloud1_3:SKSpriteNode! //
private var cloud1_4:SKSpriteNode! //
private var cloud2_1:SKSpriteNode! // 白云2
private var cloud2_2:SKSpriteNode! //
private var cloud2_3:SKSpriteNode! //
private var cloud2_4:SKSpriteNode! //
var playableHeight:CGFloat = 0 // 企鹅的可飞行区域
var playableStart:CGFloat = 0 // 地板的位置
var playerTextureAtlas = SKTextureAtlas()
var playerTextures = [SKTexture]()
override func didMove(to view: SKView) {
physicsWorld.gravity = CGVector.zero // 物理世界的重力
physicsWorld.contactDelegate = self // 碰撞代理;
setupBgSound() // ** 加入背景音乐
worldNode = childNode(withName: "worldNode") as! SKSpriteNode
//MARK:- 分享按钮 注意观察GameScene.sks的层级 shareNode是属于worldNode的下一级
setupBackground() /// 场景
setupSceneUI() /// 取得可视化Scene编辑下的UI元素
setupPlayer() /// 加入玩家企鹅
startWobble() /// 上下浮动 + 拍打翅膀
stateMachine.enter(WaitingForTapState.self) /// 进入场景后 直接进入WaitingForTap State
}
//MARK:- 场景总节点
func setupBackground(){
// 注意:用可视化拖拉sprite到scene时,有二个节点,需要用enumerateChildNodes找到所有的ground;
groundNode = worldNode.childNode(withName: "ground") as! SKSpriteNode
worldNode.enumerateChildNodes(withName: "ground") { (node, error) in
let groundNode = node as! SKSpriteNode
let topLeft = CGPoint(x: 0, y: groundNode.size.height)
let topRight = CGPoint(x: self.size.width, y: groundNode.size.height)
groundNode.physicsBody = SKPhysicsBody(edgeFrom: topLeft, to: topRight)
groundNode.physicsBody?.affectedByGravity = true /// 不受重力影响
groundNode.physicsBody?.isDynamic = false
groundNode.physicsBody?.categoryBitMask = PhysicsCategory.Ground
groundNode.physicsBody?.contactTestBitMask = PhysicsCategory.Player | PhysicsCategory.Crown
groundNode.physicsBody?.collisionBitMask = PhysicsCategory.Crown
}
// playableStart = groundNode.size.height // 从地板高度height的开始点;
// playableHeight = size.height - playableStart // 企鹅的可飞行区域;
}
//MARK: - 取得可视化Scene编辑下的UI元素
func setupSceneUI(){
// 白云
// 场景1的白云 cloud1_1.position.x + size.width = 场景2的白云位置 cloud2_1.position.x (Y轴不变)
cloud1_1 = worldNode.childNode(withName: "cloud1_1") as! SKSpriteNode
cloud1_2 = worldNode.childNode(withName: "cloud1_2") as! SKSpriteNode
cloud1_3 = worldNode.childNode(withName: "cloud1_3") as! SKSpriteNode
cloud1_4 = worldNode.childNode(withName: "cloud1_4") as! SKSpriteNode
cloud2_1 = worldNode.childNode(withName: "cloud2_1") as! SKSpriteNode
cloud2_2 = worldNode.childNode(withName: "cloud2_2") as! SKSpriteNode
cloud2_3 = worldNode.childNode(withName: "cloud2_3") as! SKSpriteNode
cloud2_4 = worldNode.childNode(withName: "cloud2_4") as! SKSpriteNode
}
//MARK: - 移动地板(注:另一方法为移动Camera)
func moveEndlessGround(dt:TimeInterval){
// 检测场景中名称为 tree的所有节点;
worldNode.enumerateChildNodes(withName: "ground") { (node, error) in
let groundNode = node as! SKSpriteNode
let moveAmount = CGPoint(x: -self.groundSpeed,y: 0)
groundNode.position.x += moveAmount.x
if groundNode.position.x < -self.size.width {
groundNode.position.x += SCENE_WIDTH * self.numberOfScenes
}
}
}
//MARK: - 移动白云(注意:此处白云没有一直生成并销毁)
func moveEndlessCloud(dt:TimeInterval){
//白云
cloud1_1.position.x -= cloudDistance
cloud1_2.position.x -= cloudDistance*1.2 //变化速度
cloud1_3.position.x -= cloudDistance
cloud1_4.position.x -= cloudDistance*0.7
cloud2_1.position.x -= cloudDistance
cloud2_2.position.x -= cloudDistance*1.2
cloud2_3.position.x -= cloudDistance
cloud2_4.position.x -= cloudDistance*0.7
// 第一朵
if cloud1_1.position.x < -SCENE_WIDTH {
cloud1_1.position.x += SCENE_WIDTH * numberOfScenes
}
if cloud2_1.position.x < -size.width {
cloud2_1.position.x += SCENE_WIDTH * numberOfScenes
}
// 第二朵
if cloud1_2.position.x < -size.width {
cloud1_2.position.x += SCENE_WIDTH * numberOfScenes
}
if cloud2_2.position.x < -size.width {
cloud2_2.position.x += SCENE_WIDTH * numberOfScenes
}
// 第三朵
if cloud1_3.position.x < -size.width {
cloud1_3.position.x += SCENE_WIDTH * numberOfScenes
}
if cloud2_3.position.x < -size.width {
cloud2_3.position.x += SCENE_WIDTH * numberOfScenes
}
// 第四朵
if cloud1_4.position.x < -size.width {
cloud1_4.position.x += SCENE_WIDTH * numberOfScenes
}
if cloud2_4.position.x < -size.width {
cloud2_4.position.x += SCENE_WIDTH * numberOfScenes
}
}
//MARK:- 移动TREE
func moveEndlessTree(dt:TimeInterval){
// 检测场景中名称为 tree的所有节点;
worldNode.enumerateChildNodes(withName: "tree") { (node, error) in
let treeNode = node as! SKSpriteNode
let moveAmount = CGPoint(x: -self.treeDistance,y: 0)
treeNode.position.x += moveAmount.x
if treeNode.position.x < -self.size.width {
treeNode.position.x += SCENE_WIDTH * self.numberOfScenes
}
}
}
//MARK:- 移动山
func moveEndlessMountain(dt:TimeInterval){
// 检测场景中名称为 mounation的所有节点;
worldNode.enumerateChildNodes(withName: "mountain") { (node, error) in
let mounNode = node as! SKSpriteNode
let moveAmount = CGPoint(x: -self.mounationDistance,y: 0)
mounNode.position.x += moveAmount.x
if mounNode.position.x < -self.size.width {
mounNode.position.x += SCENE_WIDTH * self.numberOfScenes
}
}
}
//MARK:- 加入玩家Penguin
func setupPlayer(){
playerNode = worldNode.childNode(withName: "player") as! SKSpriteNode // 企鹅属于worldNode的子层级;
playerTextureAtlas = SKTextureAtlas(named: "penguin")
for i in 1...playerTextureAtlas.textureNames.count {
let imageName = "penguin0\(i)"
playerTextures.append(SKTexture(imageNamed: imageName))
}
/* 建立物理体
* playerNode.zPosition = 6 直接在GameScene.sks设置 > 位于地板上层
* 核心知识:
* 1.原有sprite拖到scene后,只要大小有缩小(变化) scale=0.7,则texture也要相应缩小0.7;
* 2.sprite的anchorPoint有变化,须重新设置物理体的center,中心点位于物理体的正中心,即x=playerNode.size.width/2;
* 3.设置后物理体的碰撞就非常精确;
*/
let width = playerNode.size.width * 0.5 // 缩小物理体
let height = playerNode.size.height * 0.7
playerNode.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: width, height: height),center:CGPoint(x: playerNode.size.width/2/2, y:0)) // X轴:playerNode.size.width/2,Y轴:0
// 监测碰撞
// print("playerNode.size:\(playerNode.size),向右移动:\(playerNode.size.width/2/2),碰撞width:\(width)")
playerNode.physicsBody?.affectedByGravity = false
playerNode.physicsBody?.categoryBitMask = PhysicsCategory.Player // 1.标识
playerNode.physicsBody?.contactTestBitMask = PhysicsCategory.Ground | PhysicsCategory.Obstacle // 2.会和谁相撞发出通知
playerNode.physicsBody?.collisionBitMask = PhysicsCategory.None // 3.会相撞(相互作用)吗
playerNode.physicsBody?.usesPreciseCollisionDetection = true
}
//MARK:- 随机大量产生金币 Timer
func spawningCoins(){
Timer.scheduledTimer(timeInterval: TimeInterval(3.0), target: self, selector: #selector(spawnSingleCoin), userInfo: nil, repeats: true)
}
//MARK:- 加入金币 Timer 函数前加@objc
@objc func spawnSingleCoin(){
if moveAllowed {
// 1.生成随机位置的coin
let coinNode = CoinSprite.sharedInstance()
let minY = groundNode.size.height // 开始处
let maxY = size.height
let randomY = CGFloat.random(minY, max: maxY)
let minX = SCENE_WIDTH * 0.1
let maxX = SCENE_WIDTH * 1.2
let randomX = CGFloat.random(minX, max: maxX)
coinNode.position = CGPoint(x: size.width + randomX, y: randomY)
worldNode.addChild(coinNode)
// 2.移动coin 并销除;
// MARK: -2.移动障碍物;
let moveByX = SCENE_WIDTH * 2 + coinNode.size.width * 2
let moveDuration = moveByX / obstacleSpeed
let move = SKAction.moveBy(x: -moveByX, y: 0, duration: TimeInterval(moveDuration))
let sequence = SKAction.sequence([move,SKAction.removeFromParent()])
coinNode.run(sequence)
}
}
// MARK:- 生成单个Obstcale障碍物并移动
// Anchor(0.5,0)** Y轴位置如果不明白,可以拖一个ColorSprite到场景中,可以直观的进行Y轴的定位;
// 高度为 1536 - Player 200 / 2 为总体的尺寸
func spawnSingleObstacle(){
// MARK: -1.生成一个障碍物对象
//X轴的位置 位于屏幕的右侧+obstacle.width 产生后再移动 屏幕的左侧 并消除对象
let bottomObstacle = createObstacle()
let topObstacle = createObstacle()
let wallObstacle = SKNode()
let randomY = CGFloat.random(0, max: groundNode.size.height)
var startX = size.width
//worldNode -> wallObstacle ->
startX = startX + bottomObstacle.size.width // 位置位于屏幕的最右侧;
bottomObstacle.position = CGPoint(x: startX, y: 0)
wallObstacle.addChild(bottomObstacle)
topObstacle.zRotation = CGFloat(180).degreesToRadians() // 旋转180° CGFloat(Double.pi)
topObstacle.position.x = bottomObstacle.position.x
topObstacle.position.y = self.size.height
wallObstacle.addChild(topObstacle)
wallObstacle.position.y += randomY // 返回在Y轴随机位置
wallObstacle.name = "wallObstacle"
worldNode.addChild(wallObstacle)
// MARK: -2.移动障碍物;
let moveByX = size.width + bottomObstacle.size.width * 2
let moveDuration = moveByX / obstacleSpeed
let moveAction = SKAction.moveBy(x: -moveByX, y: 0, duration: TimeInterval(moveDuration))
let sequence = SKAction.sequence([moveAction,SKAction.removeFromParent()])
wallObstacle.run(sequence)
}
//MARK:-- 不断生成Obstcale 只执行一次didMove;(挑战,delay时间间隔不同 产生的obstacle的间距不同)
// 区别于生成coins的Timer方法
// PlayingState 调用
func spawningObstcale(_ dt:TimeInterval){
let spawn = SKAction.run(spawnSingleObstacle) // 生成一个障碍物
let delay = SKAction.wait(forDuration: TimeInterval(obstacleDelayTime)) // obstacleDelayTime间距
let spawnSequence = SKAction.sequence([spawn,delay])
let foreverSpawn = SKAction.repeatForever(spawnSequence)
let firstDelay = SKAction.wait(forDuration: firstSpawnDelay)
let overallSequence = SKAction.sequence([firstDelay, foreverSpawn])
run(overallSequence, withKey: "spawn")
}
//MARK:-分享链接
func shareScore(){
let urlString = appStoreLink
let url = URL(string: urlString)
let screenShot = gameSceneDelegate?.screenShot() // 取得截图
let textString = "嘿,我在Flying Peguin飞吧企鹅中取得了\(self.score)分数,你也快来挑战吧!"
// 调用代理,把shareUrl传统到ViewController;
gameSceneDelegate?.shareUrl(textString, url: url!, image: screenShot!)
}
//MARK:- option+command+<- 折叠
func setupBgSound(){
let bgSound = SKAudioNode(fileNamed: "jazzmusic.mp3")
bgSound.autoplayLooped = true
addChild(bgSound)
}
//MARK:- 开始拍打翅膀
func startAnimation(){
let playerAnimation = SKAction.animate(with: playerTextures, timePerFrame: 0.07)
let repeatAction = SKAction.repeatForever(playerAnimation)
playerNode.run(repeatAction, withKey: "Flap")
}
func stopAnimation(_ name:String){
playerNode.removeAction(forKey:name) // Player
}
// MARK: - 不再生成了;
func stopSpawning(){
playerNode.removeAction(forKey: "Flap")
playerNode.removeAction(forKey: "Wobble-Flap")
removeAction(forKey: "spawn")
//停止产生 obstacle let wallObstacle = SKNode()
worldNode.enumerateChildNodes(withName: "wallObstacle") { (node, error) in
node.removeAllActions()
node.enumerateChildNodes(withName: "Obstacle", using: { (node, error) in
node.removeAllActions()
})
}
worldNode.enumerateChildNodes(withName: "coin") { (node, error) in
print("coin")
node.removeAllActions()
}
}
//MARK:- 上下浮动
func startWobble(){
let moveUp = SKAction.moveBy(x: 0, y: 50, duration: 0.5)
moveUp.timingMode = .easeInEaseOut
let moveDown = moveUp.reversed()
let sequence = SKAction.sequence([moveUp,moveDown])
let repeatWobble = SKAction.repeatForever(sequence)
playerNode.run(repeatWobble, withKey: "Wobble")
//MARK:- Emitter juice 加入果酱
let trailNode = SKNode()
trailNode.zPosition = 5
worldNode.addChild(trailNode)
let emitter = SKEmitterNode(fileNamed: "Trail")!
emitter.targetNode = trailNode
playerNode.addChild(emitter)
let playerAnimation = SKAction.animate(with: playerTextures, timePerFrame: 0.07)
let repeatAction = SKAction.repeatForever(playerAnimation)
playerNode.run(repeatAction, withKey: "Wobble-Flap")
}
//MARK:- 移动皇冠 (相对于Player的位置)
func moveCrown(){
crownNode = playerNode.childNode(withName: "crown") as! SKSpriteNode
//皇冠上下跳动的时间 < Wobble 0.5s的时间
let moveUp = SKAction.moveBy(x: 0, y: 30, duration: TimeInterval(0.15))
moveUp.timingMode = .easeInEaseOut
let moveDown = moveUp.reversed()
let sequence = SKAction.sequence([moveUp,moveDown])
crownNode.run(sequence)
}
func stopWobble(){
stopAnimation("Wobble")
stopAnimation("Wobble-Flap")
}
//MARK:- 初始化向上的速度 initVelocity的
func applyInitialImpluse(){
initVelocity = CGPoint(x: 0, y: impluse * 1.7)
}
//MARK:-每次点击touchesBegin时执行此函数;
func applyImpluse(_ lastUpdateTime:TimeInterval){
moveCrown()
initVelocity = CGPoint(x:0,y:impluse)
angularVelocity = velocityModifier.degreesToRadians()
lastTouchY = playerNode.position.y
// 运行拍打的声音
let flapSoundAction = SKAction.playSoundFileNamed("flapping.wav", waitForCompletion: false)
playerNode.run(flapSoundAction)
}
//MARK:- *** 时时更新游戏 update 游戏中每帧要更新的代码放在此处 ***
func applyInstantlyMovement(_ seconds:TimeInterval){
// 执行Gravity 重力 * 调用Utility CGPoint+Extension
let gravityStep = CGPoint(x: 0, y: gravity) * CGFloat(seconds)
initVelocity += gravityStep
// 运行Velocity 方向+速度
let velocityStep = initVelocity * CGFloat(seconds)
playerNode.position += velocityStep
//MARK:- 更新企鹅的角度
//1.要转的角度
if playerNode.position.y < lastTouchY {
angularVelocity = -velocityModifier.degreesToRadians()
}
// 转化角度;
let angularStep = angularVelocity * CGFloat(seconds)
playerNode.zRotation += angularStep
// 限制角度
playerNode.zRotation = min(max(playerNode.zRotation, minDegree.degreesToRadians()), maxDegree.degreesToRadians())
// 撞到地面了; didBegin 进行物理碰撞检测;
// 物理体的Y值有变化,所以监测playerNode.position.y的高度是否 < (groundNode.size.height + playerNode.size.height / 2)
if playerNode.position.y < (groundNode.size.height + playerNode.size.height / 2) {
playerNode.position = CGPoint(x: playerNode.position.x, y: (groundNode.size.height + playerNode.size.height / 2))
// 进入游戏结束state
stateMachine.enter(GameOverState.self)
}
}
//MARK:- 收集金币 COINS
func collectionCoins(nodeA:SKSpriteNode,nodeB:SKSpriteNode){
// bodyB is Coin 查看Constant.swift的排序;
let coinAction = SKAction.playSoundFileNamed("coin.wav", waitForCompletion: false)
worldNode.run(coinAction)
// 加入果酱 Juice
/*
let emitter = SKEmitterNode(fileNamed: "Coin")!
emitter.position = nodeA.position
worldNode.addChild(emitter)
emitter.run(SKAction.sequence([
SKAction.wait(forDuration: 0.3),
SKAction.run {emitter.removeFromParent()}
])
)
*/
//MARK:-JUICE 建立一个路径,绕企鹅一圈
//移出B节点;
nodeB.removeFromParent()
}
//MARK: - 重新开始游戏;
func restartGame(){
let newScene = GameScene(fileNamed: "GameScene")!
newScene.size = CGSize(width: SCENE_WIDTH, height: SCENE_HEIGHT)
newScene.anchorPoint = CGPoint(x: 0, y: 0)
newScene.scaleMode = .aspectFill
let transition = SKTransition.flipHorizontal(withDuration: 0.5)
view?.presentScene(newScene, transition:transition)
}
//MARK:- 点击屏幕 stateMachines
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {
return
}
let touchLocation = touch.location(in: self) ///获得点击的位置
/// 判断目前的GameScene场景舞台是哪个state
switch stateMachine.currentState {
case is WaitingForTapState:
/// stateMachine获取点击位置=>State场景要通过physicsWorld.body进行获得点击点
guard let body = physicsWorld.body(at: touchLocation) else {
return
}
let playButton = body.node?.childNode(withName: "worldNode")?.childNode(withName: "playButton")
let startLogo = body.node?.childNode(withName: "worldNode")?.childNode(withName: "startLogo")
if (playButton?.contains(touchLocation))! {
// Hide logo + PlayButton
playButton?.isHidden = true
startLogo?.isHidden = true
stateMachine.enter(PlayingState.self) /// 进入开始游戏;
}
case is PlayingState:
applyImpluse(lastUpdateTimeInterval) /// 移动;
case is GameOverState:
/// 游戏结束的state
/// stateMachine获取点击位置
guard let body = physicsWorld.body(at: touchLocation) else {
return
}
// TapToPlay按钮;
if let tapToPlay = body.node?.childNode(withName: "worldNode")?.childNode(withName: "tapToPlay") {
if tapToPlay.contains(touchLocation){
restartGame()
}
}
default:
break
}
}
//MARK:- 物理碰撞 didBegin
func didBegin(_ contact: SKPhysicsContact) {
let bodyA:SKPhysicsBody
let bodyB:SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
bodyA = contact.bodyA
bodyB = contact.bodyB
}else{
bodyA = contact.bodyB
bodyB = contact.bodyA
}
// 收集硬币
if bodyA.categoryBitMask == PhysicsCategory.Player && bodyB.categoryBitMask == PhysicsCategory.Coin {
collectionCoins(nodeA: bodyA.node as! SKSpriteNode, nodeB: bodyB.node as! SKSpriteNode)
}
// 撞到obscatle
if bodyA.categoryBitMask == PhysicsCategory.Player && bodyB.categoryBitMask == PhysicsCategory.Obstacle {
print("scene:player hit the obstacles")
stateMachine.enter(FallingState.self)
}
// 撞到地面
if bodyA.categoryBitMask == PhysicsCategory.Player && bodyB.categoryBitMask == PhysicsCategory.Ground {
print("scene:player dropped down to the ground")
stateMachine.enter(GameOverState.self)
}
if bodyA.categoryBitMask == PhysicsCategory.Ground && bodyB.categoryBitMask == PhysicsCategory.Crown {
print("scene:crown dropped down to the ground")
}
}
override func update(_ currentTime: TimeInterval) {
// 获取时间差
if lastUpdateTimeInterval == 0 {
lastUpdateTimeInterval = currentTime
}
dt = currentTime - lastUpdateTimeInterval
lastUpdateTimeInterval = currentTime
if moveAllowed {
moveEndlessGround(dt: dt) // Endless 无限循环地板
moveEndlessTree(dt: dt) // 移动Tree
moveEndlessMountain(dt: dt) // 山
moveEndlessCloud(dt: dt) // Endless 云
/* 一、此处可以直接调用 GameScene的applyImpluse */
// applyInstantlyMovement(dt)
/*
* 二、下列为学习如何调用stateMachine方法
* (1)、stateMachine.update 时时更新
* (2)、进入PlayingState的update
* (3)、PlayingState.update调用 Scene的applyImpluse方法
*/
}
stateMachine.update(deltaTime: dt)
}
public func randomDelay() -> CGFloat {
let random = CGFloat.random(CGFloat(everySpawnDelay), max: 8.0)// max值载大 间距越大;
return random
}
}
更多游戏教程:http://www.iFIERO.com