我正在参与社区游戏创意投稿大赛个人赛,详情请看:游戏创意投稿大赛
前语
记住很多年以前玩过一款很好玩的手游叫雷霆战机,那时分玩的很疯狂。那么今天咱们运用SpriteKit
来完成一个简易版的雷霆战机
装置
1、iOS
端装置有点难过,不像安卓直接给一个apk
就好了,我这边的话上了一个TestFlight
,能够下载一个TestFlight
APP,运用TestFlight
装置
运用微信翻开这个地址按提示装置就能够了:
testflight.apple.com/join/YCDnN4…
翻开后大概是这样的:
2、能够运转代码装置,demo地址在这
玩法
1、该游戏为闯关游戏,玩家拖动飞机消除敌人来获取分数
2、初始的时分玩家有3条生命,每一关会随机掉落一个血包和增加飞机的攻击力。敌人的生命值也是随着闯关级数逐级增加的,每一关都会有一个boss,打败boss会取得大量分数
下面咱们就开始一步一步的完成吧。
完成
1、新建项目,在项目里创立一个PlanScene
场景
1.1、在didMove(to view: SKView)
办法里设置重力加速度为0,0
override func didMove(to view: SKView) {
super.didMove(to: view)
//设置重力加速度
physicsWorld.gravity = .zero
}
2、背景循环翻滚
2.1、要让背景循环翻滚的话,咱们需要创立两个一样的背景精灵,bgNode1
的position
设置成CGPoint(x: 0, y: 0)
,bgNode2
的position
设置成CGPoint(x: 0, y: size.height)
private lazy var bgNode1: SKSpriteNode = {
let view = SKSpriteNode(imageNamed: "plan_bg")
view.position = CGPoint(x: 0, y: 0)
view.size = size
view.anchorPoint = CGPoint(x: 0, y: 0)
view.zPosition = 0
view.name = "bgNode"
return view
}()
private lazy var bgNode2: SKSpriteNode = {
let view = SKSpriteNode(imageNamed: "plan_bg")
view.position = CGPoint(x: 0, y: size.height)
view.size = size
view.anchorPoint = CGPoint(x: 0, y: 0)
view.zPosition = 0
view.name = "bgNode"
return view
}()
2.2、在override func update(_ currentTime: TimeInterval)
办法里从头设置bgNode1
和bgNode2
的position
override func update(_ currentTime: TimeInterval) {
super.update(currentTime)
backgroudScrollUpdate()
}
private func backgroudScrollUpdate() {
bgNode1.position = CGPoint(x: bgNode1.position.x, y: bgNode1.position.y - 4)
bgNode2.position = CGPoint(x: bgNode2.position.x, y: bgNode2.position.y - 4)
if bgNode1.position.y <= -size.height {
bgNode1.position = CGPoint(x: 0, y: 0)
bgNode2.position = CGPoint(x: 0, y: size.height)
}
}
这样的话背景就能够无限翻滚起来了
3、增加飞机并拖动飞机移动
3.1、假如想要拖动飞机,要给当时场景的view
增加pan
手势,因为SKSpriteNode
是不能增加手势的,所以,咱们要把手势增加到当时场景的view
上,然后在touchesBegan
办法里判别当时接触的是不是飞机,所以咱们先界说两个变量
// 接触的是否是飞机
private var isTouchPlan: Bool = false
// 当时飞机的point
private var planPoint: CGPoint = .zero
3.2、创立飞机精灵
// 飞机
private lazy var planNode: SKSpriteNode = {
let view = SKSpriteNode(imageNamed: "plan02")
view.position = CGPoint(x: size.width / 2, y: size.height / 2)
view.anchorPoint = CGPoint(x: 0.5, y: 0.5)
view.size = CGSize(width: 70, height: 54)
view.zPosition = 2
view.name = "plan"
view.physicsBody = SKPhysicsBody(rectangleOf: view.size)
//物理体是否受力
view.physicsBody?.isDynamic = false
//设置物理体的标识符
view.physicsBody?.categoryBitMask = 1
//设置可与哪一类的物理体发生磕碰
view.physicsBody?.contactTestBitMask = 2
return view
}()
3.3、在touchesBegan
办法里边判别接触的是不是飞机
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
isTouchPlan = false
guard let touch = (touches as NSSet).anyObject() as? UITouch else { return }
let point = touch.location(in: self)
let node = atPoint(point)
switch node.name {
case "plan":
// 假如点击的是飞机,则将isTouchPlan置为true
isTouchPlan = true
default:
break
}
}
3.4、给当时场景增加一个pan
手势来完成飞机拖动
private func addPanGestureRecognizer(_ view: SKView) {
let pan = UIPanGestureRecognizer(target: self, action: #selector(panAction(_:)))
view.addGestureRecognizer(pan)
}
@objc private func panAction(_ sender: UIPanGestureRecognizer) {
if isTouchPlan {
var position = sender.location(in: sender.view)
// 因为SpriteKit的坐标原点在左下角,所以需要转换一下
position = CGPoint(x: position.x, y: size.height - position.y)
planNode.position = position
planPoint = position
}
}
4、飞机发射子弹
4.1、发射子弹的话咱们创立一个定时器,在定时器办法里边生成子弹,因为咱们是关卡制,所以间隔时刻依据关卡来定,关卡越高时刻越短
private func startBulletTimer() {
var ti = 0.2 - TimeInterval(leve) * 0.02
if ti <= 0.05 {
ti = 0.05
}
bulletTimer = Timer.scheduledTimer(timeInterval: ti, target: self, selector: #selector(createBullet), userInfo: nil, repeats: true)
}
// 创立子弹
@objc private func createBullet() {
let bulletNode = SKSpriteNode(imageNamed: "plan_bullet")
bulletNode.position = planPoint
bulletNode.anchorPoint = CGPoint(x: 0.5, y: 1)
bulletNode.size = CGSize(width: 10, height: 10)
bulletNode.zPosition = 1
bulletNode.name = "bullet"
addChild(bulletNode)
var ti = 3 - TimeInterval(leve) * 0.5
if ti <= 0.5 {
ti = 0.5
}
// 让子弹向上移动
bulletNode.run(SKAction.moveTo(y: size.height, duration: ti)) {
bulletNode.removeAllActions()
bulletNode.removeFromParent()
}
bulletNode.physicsBody = SKPhysicsBody(rectangleOf: bulletNode.size)
//物理体是否受力
bulletNode.physicsBody?.isDynamic = true
bulletNode.physicsBody?.allowsRotation = false
bulletNode.physicsBody?.collisionBitMask = 0
//设置物理体的标识符
bulletNode.physicsBody?.categoryBitMask = 1
//设置可与哪一类的物理体发生磕碰
bulletNode.physicsBody?.contactTestBitMask = 2
}
5、别离增加返回按钮以及显示关卡、得分、生命的精灵,代码比较简单,就不贴出来了
6、创立敌机、血包和增加攻击力包
6.1、创立敌机咱们仍是运用定时器
// 敌机定时器
private func startEnemyTimer() {
var ti = 0.5 - TimeInterval(leve) * 0.05
if ti <= 0.1 {
ti = 0.1
}
enemyTimer = Timer.scheduledTimer(timeInterval: ti, target: self, selector: #selector(createEnemy), userInfo: nil, repeats: true)
}
// 创立敌机
@objc private func createEnemy() {
let enemyNode = SKSpriteNode(imageNamed: "plan01")
// 随机敌机呈现的位置
let pointX = CGFloat(arc4random_uniform(UInt32(size.width - 40)))
enemyNode.position = CGPoint(x: pointX + 20, y: size.height)
enemyNode.anchorPoint = CGPoint(x: 0.5, y: 0.5)
enemyNode.size = CGSize(width: 40, height: 40)
enemyNode.zPosition = 1
enemyNode.name = "enemy"
addChild(enemyNode)
var ti = 6 - TimeInterval(leve) * 0.3
if ti <= 1 {
ti = 1
}
// 敌机向下移动,移动速度依据关卡来
enemyNode.run(SKAction.moveTo(y: 0, duration: ti)) {
enemyNode.removeAllActions()
enemyNode.removeFromParent()
}
enemyNode.physicsBody = SKPhysicsBody(rectangleOf: enemyNode.size)
//物理体是否受力
enemyNode.physicsBody?.isDynamic = true
enemyNode.physicsBody?.allowsRotation = false
//设置物理体的标识符
enemyNode.physicsBody?.categoryBitMask = 2
//设置可与哪一类的物理体发生磕碰
enemyNode.physicsBody?.contactTestBitMask = 1
enemyNode.physicsBody?.collisionBitMask = 0
enemyNode.physicsBody?.mass = CGFloat(enemyLife)
}
6.2、创立血包和增加攻击力包,这两个每个关卡别离只会呈现一次,每个关卡时长里随机呈现
private func createPotionOrAttack() {
// 创立药水
DispatchQueue.main.asyncAfter(deadline: .now() + TimeInterval(arc4random_uniform(5))) {
self.createNode("plan_potion")
}
// 增加攻击力
DispatchQueue.main.asyncAfter(deadline: .now() + TimeInterval(arc4random_uniform(5))) {
self.createNode("plan_attack")
}
}
private func createNode(_ name: String) {
let node = SKSpriteNode(imageNamed: name)
let pointX = CGFloat(arc4random_uniform(UInt32(self.size.width - 40)))
node.position = CGPoint(x: pointX + 20, y: self.size.height)
node.anchorPoint = CGPoint(x: 0.5, y: 0.5)
node.size = CGSize(width: 40, height: 40)
node.zPosition = 1
node.name = name
self.addChild(node)
var ti = 6 - TimeInterval(leve) * 0.3
if ti <= 1 {
ti = 1
}
node.run(SKAction.moveTo(y: 0, duration: ti)) {
node.removeAllActions()
node.removeFromParent()
}
node.physicsBody = SKPhysicsBody(rectangleOf: node.size)
//物理体是否受力
node.physicsBody?.isDynamic = true
node.physicsBody?.allowsRotation = false
//设置物理体的标识符
node.physicsBody?.categoryBitMask = 2
//设置可与哪一类的物理体发生磕碰
node.physicsBody?.contactTestBitMask = 1
node.physicsBody?.collisionBitMask = 0
}
7、创立Boss与Boss发射子弹
7.1、创立boss咱们仍是运用定时器,没错仍是定时器。每个关卡多长时刻,这个boss的定时器就设置多长时刻,为了方便测试,暂时设定5s
// boss呈现定时器
private func startBossTimer() {
bossTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(createBoss), userInfo: nil, repeats: true)
}
// 创立boss
@objc private func createBoss() {
bossNode = SKSpriteNode(imageNamed: "boss01")
bossNode?.position = CGPoint(x: size.width / 2, y: size.height - 50)
bossNode?.anchorPoint = CGPoint(x: 0.5, y: 1)
bossNode?.size = CGSize(width: 98, height: 127)
bossNode?.zPosition = 1
bossNode?.name = "boss"
addChild(bossNode!)
// 让boss左右移动
let wait = SKAction.wait(forDuration: 2)
let action1 = SKAction.moveTo(x: 64, duration: 1.5)
let action2 = SKAction.moveTo(x: size.width - 64, duration: 3)
bossNode?.run(wait) {
self.bossNode?.run(SKAction.repeatForever(SKAction.sequence([action1, action2])))
}
bossNode?.physicsBody = SKPhysicsBody(rectangleOf: bossNode?.size ?? .zero)
//物理体是否受力
bossNode?.physicsBody?.isDynamic = true
bossNode?.physicsBody?.allowsRotation = false
//设置物理体的标识符
bossNode?.physicsBody?.categoryBitMask = 2
//设置可与哪一类的物理体发生磕碰
bossNode?.physicsBody?.contactTestBitMask = 1
bossNode?.physicsBody?.collisionBitMask = 0
bossNode?.physicsBody?.mass = CGFloat(leve) * 1000
// 生命值
bossLife = leve * 1000
bossLifeNode = SKLabelNode(text: "\(bossLife)/\(bossLife)")
bossLifeNode?.fontColor = .green
bossLifeNode?.fontSize = 20
bossLifeNode?.position = bossNode?.position ?? .zero
bossLifeNode?.horizontalAlignmentMode = .center
bossLifeNode?.zPosition = 1
addChild(bossLifeNode!)
bossLifeNode?.run(SKAction.wait(forDuration: 2)) {
self.bossLifeNode?.run(SKAction.repeatForever(SKAction.sequence([
SKAction.moveTo(x: 64, duration: 1.5),
SKAction.moveTo(x: self.size.width - 64, duration: 3)
])))
}
// 中止敌机创立定时器
stopEnemyTimer()
// 中止Boss定时器
stopBossTimer()
// boss发射子弹
startBossBulletTimer()
}
7.2、Boss发射子弹,这儿咱们开启一个boss发射子弹的定时器, 并在createBoss()
办法里调用
// boss发射子弹定时器
private func startBossBulletTimer() {
var ti = 1 - TimeInterval(leve) * 0.05
if ti <= 0.1 {
ti = 0.1
}
bossBulletTimer = Timer.scheduledTimer(timeInterval: ti, target: self, selector: #selector(createBossBullet), userInfo: nil, repeats: true)
}
// 创立boss Bullet
@objc private func createBossBullet() {
let bossBulletNode = SKSpriteNode(imageNamed: "boss_bullet")
bossBulletNode.position = bossNode?.position ?? CGPoint(x: size.width / 2, y: size.height)
bossBulletNode.anchorPoint = CGPoint(x: 0.5, y: 1)
bossBulletNode.size = CGSize(width: 12, height: 25)
bossBulletNode.zPosition = 1
bossBulletNode.name = "bossBullet"
addChild(bossBulletNode)
var ti = 6 - TimeInterval(leve) * 0.3
if ti <= 1 {
ti = 1
}
bossBulletNode.run(SKAction.moveTo(y: 0, duration: ti)) {
bossBulletNode.removeAllActions()
bossBulletNode.removeFromParent()
}
bossBulletNode.physicsBody = SKPhysicsBody(rectangleOf: bossBulletNode.size)
//物理体是否受力
bossBulletNode.physicsBody?.isDynamic = true
bossBulletNode.physicsBody?.allowsRotation = false
//设置物理体的标识符
bossBulletNode.physicsBody?.categoryBitMask = 2
//设置可与哪一类的物理体发生磕碰
bossBulletNode.physicsBody?.contactTestBitMask = 1
bossBulletNode.physicsBody?.collisionBitMask = 0
}
该创立的创立好了,该动的动起来了,下面咱们来完成各个节点的磕碰,来消除敌机与boss
8、消除敌机与boss
8.1、消除敌机与boss咱们就运用物理引擎的磕碰来完成,首要是运用SKPhysicsContactDelegate
中的didBegin(_ contact: SKPhysicsContact)
这个署理办法完成的。
8.2、首先,咱们要对场景的physicsWorld
特点设置署理目标为场景自身
physicsWorld.contactDelegate = self
8.3、遵守SKPhysicsContactDelegate
并完成didBegin(_ contact: SKPhysicsContact)
办法
extension PlanScene: SKPhysicsContactDelegate {
func didBegin(_ contact: SKPhysicsContact) {
}
}
在didBegin(_ contact: SKPhysicsContact)
办法中返回了一个SKPhysicsContact
目标
open class SKPhysicsContact : NSObject {
// 磕碰体A
open var bodyA: SKPhysicsBody { get }
// 磕碰体B
open var bodyB: SKPhysicsBody { get }
// 在场景坐标中,两个物理体之间的接触点。
open var contactPoint: CGPoint { get }
// 指定磕碰方向的法向量。
open var contactNormal: CGVector { get }
// 这两个物体在牛顿秒内彼此撞击的强度。
open var collisionImpulse: CGFloat { get }
}
咱们首要运用SKPhysicsContact
中的bodyA
和bodyB
来获取两个磕碰的节点。那么咱们怎么知道bodyA
和bodyB
是敌机仍是咱们飞机呢?检查文档咱们会发现有这么一段描述,咱们能够通过给场景中的每个物理体,设置categoryBitMask(物理体的标识符)
和contactTestBitMask(标记可与哪一类的物理体发生磕碰)
特点
8.4、在整个游戏中,咱们创立了有飞机
、机发射的子弹
、敌机
、boss
、boss发射的子弹
、攻击力包
和血包
这几个物理体
其间
飞机
、飞机发射的子弹
咱们设置categoryBitMask
为1
,contactTestBitMask
为2
。
那么对应的敌机
、boss
、boss发射的子弹
、攻击力包
和血包
咱们设置categoryBitMask
为2
,contactTestBitMask
为1
。
8.5、判别出bodyA
和bodyB
别离代表什么
extension PlanScene: SKPhysicsContactDelegate {
func didBegin(_ contact: SKPhysicsContact) {
// 飞机、机发射的子弹
var planeNode: SKSpriteNode?
// 敌机、boss、boss发射的子弹、攻击力包和血包
var enemyNode: SKSpriteNode?
if contact.bodyA.categoryBitMask == 1 && contact.bodyB.categoryBitMask == 2 {
planeNode = contact.bodyA.node as? SKSpriteNode
enemyNode = contact.bodyB.node as? SKSpriteNode
} else {
planeNode = contact.bodyB.node as? SKSpriteNode
enemyNode = contact.bodyA.node as? SKSpriteNode
}
}
}
区分好bodyA
和bodyB
别离代表什么之后,咱们就开始做相应处理
8.5、处理节点磕碰
extension PlanScene: SKPhysicsContactDelegate {
func didBegin(_ contact: SKPhysicsContact) {
var planeNode: SKSpriteNode?
var enemyNode: SKSpriteNode?
if contact.bodyA.categoryBitMask == 1 && contact.bodyB.categoryBitMask == 2 {
planeNode = contact.bodyA.node as? SKSpriteNode
enemyNode = contact.bodyB.node as? SKSpriteNode
} else {
planeNode = contact.bodyB.node as? SKSpriteNode
enemyNode = contact.bodyA.node as? SKSpriteNode
}
guard let planeNode = planeNode, let enemyNode = enemyNode else { return }
// 增加生命
if enemyNode.name == "plan_potion", planeNode.name == "plan" {
ownLife += 1
enemyNode.removeAllActions()
enemyNode.removeFromParent()
return
}
// 增加攻击力
if enemyNode.name == "plan_attack", planeNode.name == "plan" {
aggressivity += 10
enemyNode.removeAllActions()
enemyNode.removeFromParent()
return
}
// 处理飞机
switch planeNode.name {
case "bullet" where (enemyNode.name != "bossBullet" &&
enemyNode.name != "plan_potion" &&
enemyNode.name != "plan_attack"):
planeNode.removeAllActions()
planeNode.removeFromParent()
if enemyNode.name == "boss" {
bossLife -= aggressivity
bossLifeNode?.text = "\(bossLife)/\(leve*1000)"
}
case "plan":
ownLife -= 1
if ownLife <= 0 {
gameOver()
planeNode.removeAllActions()
planeNode.removeFromParent()
}
default:
break
}
// 假如是boss发射的子弹,则不做处理
if enemyNode.name == "bossBullet" {
return
}
if enemyNode.name == "plan_potion" {
return
}
if enemyNode.name == "plan_attack" {
return
}
enemyNode.physicsBody?.mass -= CGFloat(aggressivity)
if enemyNode.physicsBody?.mass ?? 0 <= CGFloat(aggressivity) {
enemyNode.removeAllActions()
enemyNode.removeFromParent()
switch enemyNode.name {
case "enemy":
score += enemyLife
case "boss":
score += leve * 1000
leve += 1
enemyLife += leve * 10
stopBossBulletTimer()
bossNode?.removeAllActions()
bossNode?.removeFromParent()
bossLifeNode?.removeAllActions()
bossLifeNode?.removeFromParent()
startBulletTimer()
startEnemyTimer()
startBossTimer()
default:
break
}
}
}
}
基本功能已经完成了,下面咱们就来增加磕碰作用
8.6、增加磕碰作用
磕碰作用咱们运用SKEmitterNode
来做,然后在磕碰的节点处显示出来。咱们挑选SpriteKit Particle File
来新建一个目标销毁时的作用Blast.sks
和击打boss发生的作用Strike.sks
文件
然后别离选中两个文件,设置相应参数,我这边是随意设置的就不多说了。
依据这两个文件名称来初始化,并在相应的位置加载
private func blast(_ position: CGPoint, fileName: String) {
if let blast = SKEmitterNode(fileNamed: fileName) {
blast.zPosition = 2
blast.position = position
addChild(blast)
// 0.3秒后消失
blast.run(SKAction.sequence([
SKAction.wait(forDuration: 0.3),
SKAction.run {
blast.removeAllActions()
blast.removeFromParent()
}
]))
}
}
最后作用就入顶部展现的那样
最后
整个游戏到此就结束了,快来秀出你的战绩吧