原文:Trigonometry for Games – Sprite Kit and Swift Tutorial: Part 1/2,译者:Dev端
添加血条
接第一部分,在本教程中,你会学到如何让炮台向飞船发射导弹,炮台会给飞船带来伤害。为了显示飞船的健康程度,我们给它添加一个血条的sprite。
在GameScene.swift的最上面申明下面几个新的常量:
let MaxHealth = 100 let HealthBarWidth: CGFloat = 40 let HealthBarHeight: CGFloat = 4
再给GameScene添加下面的属性
let playerHealthBar = SKSpriteNode() let cannonHealthBar = SKSpriteNode() var playerHP = MaxHealth var cannonHP = MaxHealth
现在,把下面的代码添加进didMoveToView(),位置是在startMonitoringAcceleration()之前:
addChild(playerHealthBar) addChild(cannonHealthBar) cannonHealthBar.position = CGPoint( x: cannonSprite.position.x, y: cannonSprite.position.y - cannonSprite.size.height/2 - 10 )
playerHealthBar和cannonHealthBar都是SKSpriteNode,但是你没有给他们任何的图片。你要做的是用CoreGraphics给它们画上血条。
注意,你在大炮的下面一点放置了血条,但是没有playerHealthBar任何位置信息更新。这是因为大炮并不移动,所以只要给它一次位置的定义,接着就可以忘掉它了。但是飞船不一样,飞船要一直移动,所以血条也会一直跟着移动。这要在updatePlayer()方法里实现:
playerHealthBar.position = CGPoint( x: playerSprite.position.x, y: playerSprite.position.y - playerSprite.size.height/2 - 15 )
现在,剩下的工作就只有绘制血条了,在你的类型里添加下面的方法:
func updateHealthBar(node: SKSpriteNode, withHealthPoints hp: Int) { let barSize = CGSize(width: HealthBarWidth, height: HealthBarHeight); let fillColor = UIColor(red: 113.0/255, green: 202.0/255, blue: 53.0/255, alpha:1) let borderColor = UIColor(red: 35.0/255, green: 28.0/255, blue: 40.0/255, alpha:1) // create drawing context UIGraphicsBeginImageContextWithOptions(barSize, false, 0) let context = UIGraphicsGetCurrentContext() // draw the outline for the health bar borderColor.setStroke() let borderRect = CGRect(origin: CGPointZero, size: barSize) CGContextStrokeRectWithWidth(context, borderRect, 1) // draw the health bar with a colored rectangle fillColor.setFill() let barWidth = (barSize.width - 1) * CGFloat(hp) / CGFloat(MaxHealth) let barRect = CGRect(x: 0.5, y: 0.5, width: barWidth, height: barSize.height - 1) CGContextFillRect(context, barRect) // extract image let spriteImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() // set sprite texture and size node.texture = SKTexture(image: spriteImage) node.size = barSize }
这些行代码画了一个单独的血条出来。首先设置它的填充颜色和边缘颜色,然后创建绘制环境,并且画两个矩形出来:其一是背景矩形,保持和整体一样的大小;其二是血条本身,会根据健康程度来改变。这个方法之后从绘制环境中提供出来一个UIImage,然后作为纹理赋值给sprite。
你需要引用这个方法两次,一次是给飞船,一次是给大炮。因为绘制血条会消耗很多系统资源,你不能每一次刷新都重新绘制。所以我们用的办法是,仅仅当血条的血量发生变化的时候才启用重新绘制的方法。暂时的,我们只需要调用它一次来设置初始的血条。添加下面的代码到didMoveToView()
startMonitoringAcceleration() 跑一跑程序,你会发现现在两者下面都有血条了。
目前为止,飞船可以飞过大炮,就好像完全没有任何障碍一样。最好我们可以在两者碰触的时候可以知道他们碰了,然后给予飞船一定量的伤害。
在这个时候,很多游戏开发者会想到“我需要一个物理引擎”,确实SpriteKit的物理引擎可以很轻松地帮你做这件事,但是,你自己做碰撞检测也是很容易的,尤其是当你的sprite是简单的圆的时候。
(检测两个圆的碰撞是小菜一碟的事:你要做的仅仅是计算两者中心之间的距离和两者半径之和的关系) 给你的Scene添加下面两个常量
let CannonCollisionRadius: CGFloat = 20 let PlayerCollisionRadius: CGFloat = 10
事实上,你的飞船形状并不是一个圆。但是一个圆通常可以模拟一个sprite的形状来得到它的碰撞体积。这样的话,可以更简便地计算碰撞。 添加下面的方法来做碰撞检测:
func checkShipCannonCollision() { let deltaX = playerSprite.position.x - turretSprite.position.x let deltaY = playerSprite.position.y - turretSprite.position.y let distance = sqrt(deltaX * deltaX + deltaY * deltaY) if distance runAction(collisionSound) } }
在update()方法的末尾加上下面的代码:
checkShipCannonCollision()
let collisionSound = SKAction.playSoundFileNamed("Collision.wav", waitForCompletion: false)
碰撞检测仅仅是这个问题的前半部分,后半部分是碰撞检测之后的反馈。你不仅想得到音效的反馈,你还想能够得到物理的反馈-飞船应该从大炮身边弹开。在GameScene.swift文件里添加上下面的常量。
let CollisionDamping: CGFloat = 0.8
playerAcceleration.dx = -playerAcceleration.dx * CollisionDamping playerAcceleration.dy = -playerAcceleration.dy * CollisionDamping playerVelocity.dx = -playerVelocity.dx * CollisionDamping playerVelocity.dy = -playerVelocity.dy * CollisionDamping
与其为飞船设置反弹,你需要给飞船一个物理力来让它远离大炮。
为了实现之,你需要计算大炮和飞船之间的矢量,之前你计算过两者之间的距离的时候就用到了deltaX和deltaY两个矢量。你要怎么通过这个合起来的矢量来给飞船力呢?那个由deltaX和deltaY组合起来的矢量已经指向了正确的防线,但是长度不对。你需要的长度是半径之和减去两者距离,这样飞船就不会和大炮重叠了。
(CannonCollisionRadius + PlayerCollisionRadius – distance)
let offsetDistance = CannonCollisionRadius + PlayerCollisionRadius - distance let offsetX = deltaX / distance * offsetDistance let offsetY = deltaY / distance * offsetDistance playerSprite.position = CGPoint( x: playerSprite.position.x + offsetX, y: playerSprite.position.y + offsetY )
这时,你需为飞船扣除一些血量:
playerHP = max(0, playerHP - 20) cannonHP = max(0, cannonHP - 5) updateHealthBar(playerHealthBar, withHealthPoints: playerHP) updateHealthBar(cannonHealthBar, withHealthPoints: cannonHP)
添加旋转
为了一个漂亮你效果,你需要在碰撞之后给飞船添加一些旋转。这里的旋转不会影响之前我们设置的飞船的旋转方向,添加下面的常量:
let PlayerCollisionSpin: CGFloat = 180
var playerSpin: CGFloat = 0
playerSpin = PlayerCollisionSpin
if playerSpin > 0 { playerAngle += playerSpin * DegreesToRadians previousAngle = playerAngle playerSpin -= PlayerCollisionSpin * CGFloat(dt) if playerSpin playerSpin = 0 } }
旋转特效重写了飞机方向的特效,并没有影响速度。这样的旋转在一秒之后就会消失。在旋转的时候,你把飞机当前的角度赋值为旋转特效的角度,以防止旋转结束的时候,飞机突然变化角度。
跑一下程序,你就能看到旋转的飞船了!