[Swift]SpriteKit实现类似像素鸟的小游戏 - Crashy Plane

栏目: Swift · 发布时间: 6年前

内容简介:像素鸟曾经非常火爆,游戏简单,很有趣味性,仿写一个叫 crashy plane 的游戏,它的原理跟像素鸟是一样的,接下来用 SpriteKit 来实现它同时推荐一个不错的学习 Swift 的网站,这个 Crashy Plane 就是从那里偷来的

像素鸟曾经非常火爆,游戏简单,很有趣味性,仿写一个叫 crashy plane 的游戏,它的原理跟像素鸟是一样的,接下来用 SpriteKit 来实现它

同时推荐一个不错的学习 Swift 的网站,这个 Crashy Plane 就是从那里偷来的

hackingwithswift

demo 地址

目录:

开始:

0.创建项目

  • a.创建项目 选择 Game

    [Swift]SpriteKit实现类似像素鸟的小游戏 - Crashy Plane
  • b.找到 GameScene.sks,将 helloWorld 的 label删除掉,然后将宽高调整为 W:375 H:667, 锚点设置为 X:0 Y:0,重力设置为X:0 Y:-5

    [Swift]SpriteKit实现类似像素鸟的小游戏 - Crashy Plane
  • c.打开GameScene.swift 删除多余的代码,只留下 override func didMove(to view: SKView) , override func update(_ currentTime: TimeInterval)override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)

1.生成 ui

  • 飞机
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)
        }
    }
复制代码
  • 上下的两个石头(类似像素鸟的两个管道)
    • 上下两个石头是一样的 texture,只是上面的石头是旋转后再镜面翻转
    • 两个石头起始位置在屏幕右侧之外
    • 两个石头的距离固定,位置随机
    • 两个石头后面添加一个得分检测节点(SKNode),用于判断飞机飞过石头得分的
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)
    }
复制代码
  • 得分 Lable
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)
    }
复制代码
  • 效果图:
[Swift]SpriteKit实现类似像素鸟的小游戏 - Crashy Plane

2.生成动态效果

飞机只是一张图片,后面的山也没用动起来,别着急,接下来让所有的节点都动起来

  • 飞机
    • 素材库一共三张飞机的图片,每0.1秒改变一张图片,然后一直循环

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)
复制代码
  • 背景
    • 每个 background 在20秒内移动一个 texture 宽度的距离
    • 在0秒内把每个 background 复位
    • 上面两个动作重复 createBackground方法每个background 添加 如下action
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)
复制代码
  • 石头
    • 6.2秒内从屏幕右侧移动到屏幕左侧
    • 移除
    • 添加一个新方法循环添加新的石头
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)
}
复制代码

效果:

[Swift]SpriteKit实现类似像素鸟的小游戏 - Crashy Plane

3.添加 physicsBody

画面已经动起来了,接下来我希望我的飞机可以自由落体,然后可以和石头地面发生碰撞

///给飞机添加 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
复制代码

4.添加交互

飞机已经可以自由落体了,接下来实现点击屏幕时,飞机向上飞起,当碰撞到红色的区域时,获得得分.

在 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)
}

复制代码

5.碰撞判断

飞机的交互已经完成,接下来实现各种物体的碰撞判断(得分判断在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
复制代码

效果:

[Swift]SpriteKit实现类似像素鸟的小游戏 - Crashy Plane

6.添加音效

得分时爆炸时有音效,同时游戏还要有背景音.

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)

复制代码

7.完善游戏周期

现在游戏已经可以玩了,但是死亡后,没有办法重新开始,接下来,我们为游戏添加 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 了


以上所述就是小编给大家介绍的《[Swift]SpriteKit实现类似像素鸟的小游戏 - Crashy Plane》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Sprint

Sprint

Jake Knapp、John Zeratsky、Braden Kowitz / Simon & Schuster / 2016-3-8 / GBP 14.60

媒体推荐 “Every business leader I know worries about the same thing: Are we moving fast enough? The genius of Jake Knapp’s Sprint is its step-by-step breakdown of what it takes to solve big problems an......一起来看看 《Sprint》 这本书的介绍吧!

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具