Swift - 跑酷游戏开发(SpriteKit游戏开发)
作者:hangge | 2015-05-18 15:57
一,下面演示了如何开发一个跑酷游戏,实现的功能如下:
四,源码下载
1,平台工厂会不断地生成平台,并且向左移动。当平台移出游戏场景时就可将其移除。
2,生成的平台宽度随机,高度随机。同时短平台踩踏的时候会下落。
3,奔跑小人设置了三种状态:奔跑,跳跃,打滚。
4,跳跃时可以再进行二段跳。
5,如果在一定高度落下,会先打滚再变成奔跑状态。同时平台会有震动效果。
6,起跳时会有特效(身后播放尘土飞扬特效)
7,跳跃,碰撞等都使用了苹果的物理引擎
二,效果图如下:

三,实现代码
1,奔跑小人类 - Runner.swift
注意:由于跳和打滚的动作不像跑的动作需要循环播放,所以就不需要用repeatActionForever。打滚动作结束后就执行跑步操作。而跳跃动作由于不知道什么时候落地,所以会由外部控制它的动作转变。
import SpriteKit
enum Status:Int{
case run=1,jump,jump2,roll;
}
class Runner : SKSpriteNode {
//跑的纹理集
let runAtlas = SKTextureAtlas(named: "run.atlas")
//跑的纹理数组
var runFrames = [SKTexture]()
//跳的纹理集
let jumpAtlas = SKTextureAtlas(named: "jump.atlas")
//存储跳的文理的数组
var jumpFrames = [SKTexture]();
//打滚的文理集合
let rollAtlas = SKTextureAtlas(named: "roll.atlas")
//存储打滚文理的数组
var rollFrames = [SKTexture]();
var status = Status.run
//起跳 y坐标
var jumpStart:CGFloat = 0.0
//落地 y坐标
var jumpEnd :CGFloat = 0.0
//起跳特效纹理集
let jumpEffectAtlas = SKTextureAtlas(named: "jump_effect.atlas")
//存储起跳特效纹理的数组
var jumpEffectFrames = [SKTexture]()
//起跳特效
var jumpEffect = SKSpriteNode()
//构造器
override init() {
let texture = runAtlas.textureNamed("panda_run_01")
let size = texture.size()
super.init(texture:texture,color:SKColor.whiteColor(),size:size)
var i:Int
//填充跑的纹理数组
for i=1 ; i<=runAtlas.textureNames.count ; i++ {
let tempName = String(format: "panda_run_%.2d", i)
let runTexture = runAtlas.textureNamed(tempName)
if runTexture != nil {
runFrames.append(runTexture)
}
}
//填充跳的纹理数组
for i=1 ; i<=jumpAtlas.textureNames.count ; i++ {
let tempName = String(format: "panda_jump_%.2d", i)
let jumpTexture = jumpAtlas.textureNamed(tempName)
if jumpTexture != nil {
jumpFrames.append(jumpTexture)
}
}
//填充打滚的纹理数组
for i=1 ; i<=rollAtlas.textureNames.count ; i++ {
let tempName = String(format: "panda_roll_%.2d", i)
let rollTexture = rollAtlas.textureNamed(tempName)
if rollTexture != nil{
rollFrames.append(rollTexture)
}
}
//起跳特效
for i=1 ; i <= jumpEffectAtlas.textureNames.count ; i++ {
let tempName = String(format: "jump_effect_%.2d", i)
let effectexture = jumpEffectAtlas.textureNamed(tempName)
if effectexture != nil {
jumpEffectFrames.append(effectexture)
}
}
jumpEffect = SKSpriteNode(texture: jumpEffectFrames[0])
jumpEffect.position = CGPointMake(-80, -30)
jumpEffect.hidden = true
self.addChild(jumpEffect)
run()
self.zPosition = 30
self.physicsBody = SKPhysicsBody(rectangleOfSize:texture.size())
self.physicsBody?.dynamic = true
self.physicsBody?.allowsRotation = false
//弹性
self.physicsBody?.restitution = 0
self.physicsBody?.categoryBitMask = BitMaskType.runner
self.physicsBody?.contactTestBitMask = BitMaskType.platform | BitMaskType.scene
self.physicsBody?.collisionBitMask = BitMaskType.platform
}
func run(){
//移除所有的动作
self.removeAllActions()
//将当前动作状态设为跑
self.status = .run
//通过SKAction.animateWithTextures将跑的文理数组设置为0.05秒切换一次的动画
// SKAction.repeatActionForever将让动画永远执行
// self.runAction执行动作形成动画
self.runAction(SKAction.repeatActionForever(
SKAction.animateWithTextures(runFrames, timePerFrame: 0.05)))
}
//跳
func jump (){
self.removeAllActions()
if status != Status.jump2 {
self.runAction(SKAction.animateWithTextures(jumpFrames, timePerFrame: 0.05))
//施加一个向上的力,让小人跳起来
self.physicsBody?.velocity = CGVectorMake(0, 450)
if status == Status.jump {
status = Status.jump2
self.jumpStart = self.position.y
}else{
showJumpEffect()
status = Status.jump
}
}
}
//打滚
func roll(){
self.removeAllActions()
status = .roll
self.runAction(SKAction.animateWithTextures(rollFrames, timePerFrame: 0.05),
completion:{() in self.run()})
}
//起跳特效
func showJumpEffect(){
//先将特效取消隐藏
jumpEffect.hidden = false
//利用action播放特效
var ectAct = SKAction.animateWithTextures( jumpEffectFrames, timePerFrame: 0.05)
//执行闭包,再次隐藏特效
var removeAct = SKAction.runBlock({() in
self.jumpEffect.hidden = true
})
//组成序列Action进行执行
jumpEffect.runAction(SKAction.sequence([ectAct,removeAct]))
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
2,平台类 - Platform.swift
//平台类
import SpriteKit
class Platform:SKNode{
//宽
var width :CGFloat = 0.0
//高
var height :CGFloat = 10.0
//是否下沉
var isDown = false
func onCreate(arrSprite:[SKSpriteNode]){
//通过接受SKSpriteNode数组来创建平台
for platform in arrSprite {
//以当前宽度为平台零件的x坐标
platform.position.x = self.width
//加载
self.addChild(platform)
//更新宽度
self.width += platform.size.width
}
//当平台的零件只有三样,左中右时,设为会下落的平台
if arrSprite.count<=3 {
isDown = true
}
self.zPosition = 20
//设置物理体为当前高宽组成的矩形
self.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(self.width, self.height),
center: CGPointMake(self.width/2, 0))
//设置物理标识
self.physicsBody?.categoryBitMask = BitMaskType.platform
//不响应响应物理效果
self.physicsBody?.dynamic = false
//不旋转
self.physicsBody?.allowsRotation = false
//弹性0
self.physicsBody?.restitution = 0
}
}
3,平台工厂类 - PlatformFactory.swift
它负责生产平台零件然后传给平台类进行组装。同时负责不断地移动平台,以及移除场景之外的平台。
import SpriteKit
class PlatformFactory: SKNode {
//定义平台左边纹理
let textureLeft = SKTexture(imageNamed: "platform_l")
//定义平台中间纹理
let textureMid = SKTexture(imageNamed: "platform_m")
//定义平台右边纹理
let textureRight = SKTexture(imageNamed: "platform_r")
//定义一个数组来储存组装后的平台
var platforms = [Platform]()
//游戏场景的宽度
var sceneWidth:CGFloat = 0
//ProtocolMainScene代理
var delegate:ProtocolMainScene?
//生成自定义位置的平台
func createPlatform(midNum:UInt32,x:CGFloat,y:CGFloat){
let platform = self.createPlatform(false, midNum: midNum, x: x, y: y)
delegate?.onGetData(platform.width - sceneWidth)
}
//生成随机位置的平台的方法
func createPlatformRandom(){
//随机平台的长度
let midNum:UInt32 = arc4random()%4 + 1
//随机间隔
let gap:CGFloat = CGFloat(arc4random()%8 + 1)
//随机x坐标
let x:CGFloat = self.sceneWidth + CGFloat( midNum*50 ) + gap + 100
//随机y坐标
let y:CGFloat = CGFloat(arc4random()%200 + 200)
let platform = self.createPlatform(true, midNum: midNum, x: x, y: y)
//回传距离用于判断什么时候生成新的平台
delegate?.onGetData(platform.width + x - sceneWidth)
}
func createPlatform(isRandom:Bool,midNum:UInt32,x:CGFloat,y:CGFloat)->Platform{
//声明一个平台类,用来组装平台。
var platform = Platform()
//生成平台的左边零件
let platform_left = SKSpriteNode(texture: textureLeft)
//设置中心点
platform_left.anchorPoint = CGPoint(x: 0, y: 0.9)
//生成平台的右边零件
let platform_right = SKSpriteNode(texture: textureRight)
//设置中心点
platform_right.anchorPoint = CGPoint(x: 0, y: 0.9)
//声明一个数组来存放平台的零件
var arrPlatform = [SKSpriteNode]()
//将左边零件加入零件数组
arrPlatform.append(platform_left)
//根据传入的参数来决定要组装几个平台的中间零件
//然后将中间的零件加入零件数组
for i in 1...midNum {
let platform_mid = SKSpriteNode(texture: textureMid)
platform_mid.anchorPoint = CGPoint(x: 0, y: 0.9)
arrPlatform.append(platform_mid)
}
//将右边零件加入零件数组
arrPlatform.append(platform_right)
//将零件数组传入
platform.onCreate(arrPlatform)
platform.name="platform"
//设置平台的位置
platform.position = CGPoint(x: x, y: y)
//放到当前实例中
self.addChild(platform)
//将平台加入平台数组
platforms.append(platform)
return platform
}
func move(speed:CGFloat){
//遍历所有
for p in platforms{
//x坐标的变化长生水平移动的动画
p.position.x -= speed
}
//移除平台
if platforms[0].position.x < -platforms[0].width {
platforms[0].removeFromParent()
platforms.removeAtIndex(0)
}
}
//重置方法
func reSet(){
//清除所有子对象
self.removeAllChildren()
//清空平台数组
platforms.removeAll(keepCapacity: false)
}
}
//定义一个协议,用来接收数据
protocol ProtocolMainScene {
func onGetData(dist:CGFloat)
}
4,碰撞标识类 - BitMaskType.swift
记录着物理世界里的种类标示符。有小人,平台,场景边缘三种标识。
class BitMaskType {
class var runner:UInt32{
return 1<<0
}
class var platform:UInt32{
return 1<<1
}
class var scene:UInt32{
return 1<<2
}
}
5,主场景类 - GameScene.swift
要将主场景CameScene设为物理世界,首先要让GameScene遵循SKPhysicsContactDelegatex协议。
这里面设置了重力,并进行了小人与场景边缘的碰撞检测,用于判断游戏是否结束。
同时在update()方法中,进行平台移动,新平台的创建,随游戏进行不断加速以及小人位置修正等功能。
import SpriteKit
class GameScene: SKScene,SKPhysicsContactDelegate,ProtocolMainScene {
lazy var runner = Runner()
lazy var platformFactory = PlatformFactory()
//跑了多远变量
var distance :CGFloat = 0.0
//移动速度
var moveSpeed:CGFloat = 15
//最大速度
var maxSpeed :CGFloat = 50.0
//判断最后一个平台还有多远完全进入游戏场景
var lastDis:CGFloat = 0.0
//是否game over
var isLose = false
override func didMoveToView(view: SKView) {
//物理世界代理
self.physicsWorld.contactDelegate = self
//重力设置
self.physicsWorld.gravity = CGVectorMake(0, -5)
//设置物理体
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
//设置种类标示
self.physicsBody?.categoryBitMask = BitMaskType.scene
//是否响应物理效果
self.physicsBody?.dynamic = false
//场景的背景颜色
let skyColor = SKColor(red:113/255,green:197/255,blue:207/255,alpha:1)
self.backgroundColor = skyColor
//给跑酷小人定一个初始位置
runner.position = CGPointMake(200, 400)
//将跑酷小人显示在场景中
self.addChild(runner)
//将平台工厂加入视图
self.addChild(platformFactory)
//将屏幕的宽度传到平台工厂类中
platformFactory.sceneWidth = self.frame.width
//设置代理
platformFactory.delegate = self
//初始平台让小人有立足之地
platformFactory.createPlatform(3, x: 0, y: 200)
}
//触碰屏幕响应的方法
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
if isLose {
reSet()
}else{
runner.jump()
}
}
//离开平台时记录起跳点
func didEndContact(contact: SKPhysicsContact!){
runner.jumpStart = runner.position.y
}
//碰撞检测方法
func didBeginContact(contact: SKPhysicsContact!) {
//小人和台子碰撞
if (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask)
== (BitMaskType.platform | BitMaskType.runner){
//假设平台不会下沉,用于给后面判断平台是否会被熊猫震的颤抖
var isDown = false
//用于判断接触平台后能否转变为跑的状态,默认值为false不能转换
var canRun = false
//如果碰撞体A是平台
if contact.bodyA.categoryBitMask == BitMaskType.platform {
//如果是会下沉的平台
if (contact.bodyA.node as Platform).isDown {
isDown = true
//让平台接收重力影响
contact.bodyA.node?.physicsBody?.dynamic = true
//不将碰撞效果取消,平台下沉的时候会跟着熊猫跑这不是我们希望看到的,
//大家可以将这行注释掉看看效果
contact.bodyA.node?.physicsBody?.collisionBitMask = 0
//如果是会升降的平台
}
if contact.bodyB.node?.position.y > contact.bodyA.node!.position.y {
canRun=true
}
//如果碰撞体B是平台
}else if contact.bodyB.categoryBitMask == BitMaskType.platform {
if (contact.bodyB.node as Platform).isDown {
contact.bodyB.node?.physicsBody?.dynamic = true
contact.bodyB.node?.physicsBody?.collisionBitMask = 0
isDown = true
}
if contact.bodyA.node?.position.y > contact.bodyB.node?.position.y {
canRun=true
}
}
//判断是否打滚
runner.jumpEnd = runner.position.y
if runner.jumpEnd-runner.jumpStart <= -70 {
runner.roll()
//如果平台下沉就不让它被震得颤抖一下
if !isDown {
downAndUp(contact.bodyA.node!)
downAndUp(contact.bodyB.node!)
}
}else{
if canRun {
runner.run()
}
}
}
//如果熊猫和场景边缘碰撞
if (contact.bodyA.categoryBitMask|contact.bodyB.categoryBitMask)
== (BitMaskType.scene | BitMaskType.runner) {
println("游戏结束")
isLose = true
}
}
override func update(currentTime: CFTimeInterval) {
//如果小人出现了位置偏差,就逐渐恢复
if runner.position.x < 200 {
var x = runner.position.x + 1
runner.position = CGPointMake(x, runner.position.y)
}
if !isLose {
lastDis -= moveSpeed
//速度以5为基础,以跑的距离除以2000为增量
var tempSpeed = CGFloat(5 + Int(distance/2000))
//将速度控制在maxSpeed
if tempSpeed > maxSpeed {
tempSpeed = maxSpeed
}
//如果移动速度小于新的速度就改变
if moveSpeed < tempSpeed {
moveSpeed = tempSpeed
}
if lastDis <= 0 {
platformFactory.createPlatformRandom()
}
platformFactory.move(self.moveSpeed)
distance += moveSpeed
}
}
func onGetData(dist:CGFloat){
self.lastDis = dist
}
//up and down 方法(平台震动一下)
func downAndUp(node :SKNode,down:CGFloat = -50,downTime:Double=0.05,
up:CGFloat=50,upTime:Double=0.1){
//下沉动作
let downAct = SKAction.moveByX(0, y: down, duration: downTime)
//上升动过
let upAct = SKAction.moveByX(0, y: up, duration: upTime)
//下沉上升动作序列
let downUpAct = SKAction.sequence([downAct,upAct])
node.runAction(downUpAct)
}
//重新开始游戏
func reSet(){
//重置isLose变量
isLose = false
//重置小人位置
runner.position = CGPointMake(200, 400)
//重置移动速度
moveSpeed = 15.0
//重置跑的距离
distance = 0.0
//重置首个平台完全进入游戏场景的距离
lastDis = 0.0
//平台工厂的重置方法
platformFactory.reSet()
//创建一个初始的平台给熊猫一个立足之地
platformFactory.createPlatform(3, x: 0, y: 200)
}
}
四,源码下载
全部评论(2)
请问假如在场景内同时显示1000个SKSpriteNode,怎么才能让fps不下降那么厉害,SKSpriteNode同一个纹理。
站长回复:SKSpriteNode数量太多造成性能下降,我目前也不知道有什么好办法解决。
请问计算两点之间角度和距离的公式是什么啊
站长回复:可以看我这篇文章 http://www.hangge.com/blog/cache/detail_674.html