内容简介:像素鸟曾经非常火爆,游戏简单,很有趣味性,仿写一个叫 crashy plane 的游戏,它的原理跟像素鸟是一样的,接下来用 SpriteKit 来实现它同时推荐一个不错的学习 Swift 的网站,这个 Crashy Plane 就是从那里偷来的
像素鸟曾经非常火爆,游戏简单,很有趣味性,仿写一个叫 crashy plane 的游戏,它的原理跟像素鸟是一样的,接下来用 SpriteKit 来实现它
同时推荐一个不错的学习 Swift 的网站,这个 Crashy Plane 就是从那里偷来的
目录:
开始:
0.创建项目
-
a.创建项目 选择 Game
-
b.找到 GameScene.sks,将 helloWorld 的 label删除掉,然后将宽高调整为 W:375 H:667, 锚点设置为 X:0 Y:0,重力设置为X:0 Y:-5
-
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) } 复制代码
- 效果图:
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) } 复制代码
效果:
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 复制代码
效果:
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》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 掌握web开发基础系列--物理像素、逻辑像素、css像素
- 【译】只用 CSS 就能做到的像素画/像素动画
- 透明度叠加算法:如何计算半透明像素叠加到另一个像素上的实际可见像素值(附 WPF 和 HLSL 的实现)
- 微信小游戏 - 初体验
- 2018年小游戏开发总结
- 用jQuery手写一个小游戏
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。