像素鸟曾经很是火爆,游戏简单,颇有趣味性,仿写一个叫 crashy plane 的游戏,它的原理跟像素鸟是同样的,接下来用 SpriteKit 来实现它node
同时推荐一个不错的学习 Swift 的网站,这个 Crashy Plane 就是从那里偷来的git
hackingwithswiftgithub
demo 地址swift
a.建立项目 选择 Game bash
b.找到 GameScene.sks,将 helloWorld 的 label删除掉,而后将宽高调整为 W:375 H:667, 锚点设置为 X:0 Y:0,重力设置为X:0 Y:-5 app
c.打开GameScene.swift 删除多余的代码,只留下 override func didMove(to view: SKView)
,override func update(_ currentTime: TimeInterval)
和override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
dom
func createPlayer() {
let playerTexture = SKTexture(imageNamed: R.image.player1.name)
player = SKSpriteNode(texture: playerTexture)
player.position = CGPoint(x: frame.width / 6, y: frame.height * 0.75)
player.zPosition = 10
addChild(player)
}
复制代码
func createSky() {
let topSky = SKSpriteNode(color: UIColor(hue: 0.55, saturation: 0.14, brightness: 0.97, alpha: 1), size: CGSize(width: frame.width , height: 0.67 * frame.height))
topSky.anchorPoint = CGPoint(x: 0.5, y: 1)
topSky.zPosition = -40
topSky.position = CGPoint(x: frame.midX, y: frame.maxY)
let bottomSky = SKSpriteNode(color: UIColor(hue: 0.55, saturation: 0.16, brightness: 0.96, alpha: 1), size: CGSize(width: frame.width , height: 0.33 * frame.height))
bottomSky.anchorPoint = CGPoint(x: 0.5, y: 1)
bottomSky.zPosition = -40
bottomSky.position = CGPoint(x: frame.midX, y: 0.33 * frame.height)
addChild(topSky)
addChild(bottomSky)
}
复制代码
func createBackground() {
let backgroundTexture = SKTexture(imageNamed: R.image.background.name)
for i in 0 ... 1 {
let background = SKSpriteNode(texture: backgroundTexture)
background.zPosition = -30
background.anchorPoint = .zero
background.position = CGPoint(x: CGFloat(i) * backgroundTexture.size().width, y: 100)
addChild(background)
}
}
复制代码
func createGround() {
let groundTexture = SKTexture(imageNamed: R.image.ground.name)
for i in 0...1{
let ground = SKSpriteNode(texture: groundTexture)
ground.zPosition = -10
ground.position = CGPoint(x: (CGFloat(i) + 0.5) * groundTexture.size().width, y: groundTexture.size().height/2)
addChild(ground)
}
}
复制代码
func createRocks() {
let rockTexture = SKTexture(imageNamed: R.image.rock.name)
let topRock = SKSpriteNode(texture: rockTexture)
topRock.zRotation = .pi
topRock.xScale = -1
topRock.zPosition = -20
let bottomRock = SKSpriteNode(texture: rockTexture)
bottomRock.zPosition = -20
let rockCollision = SKSpriteNode(color: .red, size: CGSize(width: 32, height: frame.height))
rockCollision.name = scoreDetect
addChild(topRock)
addChild(bottomRock)
addChild(rockCollision)
let xPosition = frame.width + topRock.frame.width
let max = CGFloat(frame.width/3)
let yPosition = CGFloat.random(in: -50...max)
let rockDistance: CGFloat = 70
topRock.position = CGPoint(x: xPosition, y: yPosition + topRock.size.height + rockDistance)
bottomRock.position = CGPoint(x: xPosition, y: yPosition - rockDistance)
rockCollision.position = CGPoint(x: xPosition + rockCollision.size.width * 2, y: frame.midY)
}
复制代码
func createScore() {
scoreLabel = SKLabelNode(fontNamed: "Optima-ExtraBlack")
scoreLabel.fontSize = 24
scoreLabel.position = CGPoint(x: frame.midX, y: frame.maxY - 60)
scoreLabel.text = "SCORE: 0"
scoreLabel.fontColor = UIColor.black
addChild(scoreLabel)
}
复制代码
飞机只是一张图片,后面的山也没用动起来,别着急,接下来让全部的节点都动起来ide
createPlayer方法添加代码学习
let frame2 = SKTexture(imageNamed: R.image.player2.name)
let frame3 = SKTexture(imageNamed: R.image.player3.name)
let animation = SKAction.animate(with: [playerTexture,frame2,frame3,frame2], timePerFrame: 0.1)
let forever = SKAction.repeatForever(animation)
player.run(forever)
复制代码
let move = SKAction.moveBy(x: -backgroundTexture.size().width, y: 0, duration: 20)
let reset = SKAction.moveBy(x: backgroundTexture.size().width, y: 0, duration: 0)
let sequence = SKAction.sequence([move,reset])
let forever = SKAction.repeatForever(sequence)
background.run(forever)
复制代码
let move = SKAction.moveBy(x: -groundTexture.size().width, y: 0, duration: 5)
let reset = SKAction.moveBy(x: groundTexture.size().width, y: 0, duration: 0)
let sequence = SKAction.sequence([move,reset])
let forever = SKAction.repeatForever(sequence)
ground.run(forever)
复制代码
let endPosition = frame.width + topRock.size.width * 2
let moveAction = SKAction.moveBy(x: -endPosition, y: 0, duration: 6.2)
let sequence = SKAction.sequence([moveAction,SKAction.removeFromParent()])
topRock.run(sequence)
bottomRock.run(sequence)
rockCollision.run(sequence)
复制代码
func startRocks() {
let createRocksAction = SKAction.run { [unowned self] in
self.createRocks()
}
let sequence = SKAction.sequence([createRocksAction,SKAction.wait(forDuration: 3)])
let repeatAction = SKAction.repeatForever(sequence)
run(repeatAction)
}
复制代码
效果:网站
画面已经动起来了,接下来我但愿个人飞机能够自由落体,而后能够和石头地面发生碰撞
///给飞机添加 physicsBody
player.physicsBody = SKPhysicsBody(texture: playerTexture, size: playerTexture.size())
player.physicsBody?.contactTestBitMask = player.physicsBody!.collisionBitMask
player.physicsBody?.isDynamic = true
///给地面添加 physicsBody
ground.physicsBody = SKPhysicsBody(texture: groundTexture, size: groundTexture.size())
ground.physicsBody?.isDynamic = false
///给石头添加 physicsBody
///上面的石头要在旋转以前添加 physicsBody 要让 physicsBody 跟着图形一块儿翻转过去
topRock.physicsBody = SKPhysicsBody(texture: rockTexture, size: rockTexture.size())
topRock.physicsBody?.isDynamic = false
bottomRock.physicsBody = SKPhysicsBody(texture: rockTexture, size: rockTexture.size())
bottomRock.physicsBody?.isDynamic = false
rockCollision.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 32, height: frame.height))
rockCollision.physicsBody?.isDynamic = false
复制代码
飞机已经能够自由落体了,接下来实现点击屏幕时,飞机向上飞起,当碰撞到红色的区域时,得到得分.
在 didMove(to view: SKView) 方法中添加以下代码
physicsWorld.contactDelegate = self
复制代码
GameScene 添加 score 属性
var score = 0 {
didSet {
scoreLabel.text = "SCORE: \(score)"
}
}
复制代码
实现SKPhysicsContactDelegate协议的didBegin(_ contact: SKPhysicsContact) 方法,判断飞机碰撞到得分断定区来加分,实际上这段应该放到碰撞里面讲,放到这里是为了更好的看飞机交互的效果
extension GameScene: SKPhysicsContactDelegate {
func didBegin(_ contact: SKPhysicsContact) {
guard let nodeA = contact.bodyA.node,let nodeB = contact.bodyB.node else { return }
if nodeA.name == scoreDetect || nodeB.name == scoreDetect {
if nodeA == player {
nodeB.removeFromParent()
}else if nodeB == player {
nodeA.removeFromParent()
}
score += 1
return
}
}
}
复制代码
每次点击屏幕时 飞机施加向上冲力
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
player.physicsBody?.velocity = CGVector.zero
player.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 20))
}
复制代码
为了飞机上升和降低的过程更真实,咱们根据飞机 Y 方向的速度来调整飞机头的朝向
override func update(_ currentTime: TimeInterval) {
guard player != nil else { return }
let rotate = SKAction.rotate(toAngle: player.physicsBody!.velocity.dy * 0.001, duration: 1)
player.run(rotate)
}
复制代码
飞机的交互已经完成,接下来实现各类物体的碰撞判断(得分判断在4.添加交互已经实现)
在didBegin(_ contact: SKPhysicsContact)方法中 已经判断的碰撞到得分点的状况,那么其余的状况就是碰到了地面或者上下的两个石头,添加以下代码,当碰到地面或者石头时,销毁飞机,并添加飞机位置添加爆炸特效,同时将 scene 的 speed 设置为0,画面就会停下了
guard let explosion = SKEmitterNode(fileNamed: R.file.playerExplosionSks.name) else {return}
explosion.position = player.position
addChild(explosion)
player.removeFromParent()
speed = 0
复制代码
效果:
得分时爆炸时有音效,同时游戏还要有背景音.
GameScene 添加一个 audio 节点 var backgroundMusic: SKAudioNode!
在 didMove 方法中 添加以下代码
if let url = R.file.musicM4a() {
backgroundMusic = SKAudioNode(url: url)
addChild(backgroundMusic)
}
复制代码
爆炸和得分的音效代码加到相应的位置
///得分
let sound = SKAction.playSoundFileNamed(R.file.coinWav.name, waitForCompletion: false)
run(sound)
///爆炸
let sound = SKAction.playSoundFileNamed(R.file.explosionWav.name, waitForCompletion: false)
run(sound)
复制代码
如今游戏已经能够玩了,可是死亡后,没有办法从新开始,接下来,咱们为游戏添加 logo 和 game over 和从新开始游戏的操做
声明一个 GameState 的枚举
enum GameState {
case showingLogo
case playing
case dead
}
复制代码
GameScene 添加一个 gameState 的属性,默认值为showingLogo
var gameState = GameState.showingLogo
添加 logo 和 gameOver 的节点属性
var logo: SKSpriteNode!
var gameOver: SKSpriteNode!
复制代码
生成 logo和 gameOver
func createLogo() {
logo = SKSpriteNode(imageNamed: R.image.logo.name)
logo.position = CGPoint(x: frame.midX, y: frame.midY)
addChild(logo)
gameOver = SKSpriteNode(imageNamed: R.image.gameover.name)
gameOver.position = CGPoint(x: frame.midX, y: frame.midY)
gameOver.alpha = 0
addChild(gameOver)
}
复制代码
修改touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
的逻辑
当游戏处于 showingLogo 状态点击屏幕执行隐藏 logo,恢复飞机的isDynamic属性,开始生成石头,将 gameState 改成 playing
处于playing状态时给飞机增长向上冲力
处于死亡状态时 从新生成 GameScene ,这样比把全部的节点恢复到初始状态要简单的多,从新生成 Scene
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
switch gameState {
case .showingLogo:
gameState = .playing
let fadeOut = SKAction.fadeOut(withDuration: 0.5)
let wait = SKAction.wait(forDuration: 0.5)
let activePlayer = SKAction.run { [weak self] in
self?.player.physicsBody?.isDynamic = true
self?.startRocks()
}
let sequence = SKAction.sequence([fadeOut,wait,activePlayer,SKAction.removeFromParent()])
logo.run(sequence)
case .playing:
player.physicsBody?.velocity = CGVector.zero
player.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 20))
case .dead:
let scene = GameScene(fileNamed: R.file.gameSceneSks.name)!
let transition = SKTransition.moveIn(with: .left, duration: 1)
self.view?.presentScene(scene, transition: transition)
}
}
复制代码
碰撞到地面和石头时 显示 gameOver,同时gameState改成 dead
gameOver.alpha = 1
gameState = .dead
复制代码
最后,别忘更改了一些细节
createPlayer方法中player.physicsBody.isDynamic要改成 false player.physicsBody?.isDynamic = false
didMove 方法中移出 startRocks()
的调用,由于生成石头是在游戏开始后
createRock
方法中,得分判断区的颜色要改成透明的
let rockCollision = SKSpriteNode(color: .clear, size: CGSize(width: 32, height: frame.height))
复制代码
回到GameViewController
中
把这3项设为 false
view.showsFPS = false //是否显示 FPS
view.showsNodeCount = false//是否显示节点数量
view.showsPhysics = false /// 是否显示物理区域()
复制代码
这样 一个简单的游戏就完成了,接下来就能够 enjoy your game 了