项目地址:https://github.com/jakciehoo/KongfuPanda
欢迎加QQ群:260558552。大家一起交流iOS开发,我们可以一起学习,我很想集结一些志同道合的朋友,一起把iOS开发学好,学精,相互学习相互鼓励。
1.首先创建一个游戏项目:
2.将图片资源导入
将我项目里的
atlas整个目录
sound组里的音乐(background.mp3,fly.mp3,hit_platform.mp3,apple.mp3,hit.mp3,jump_from_platform.mp3,lose.mp3)
background组(background_f0.png,background_f1.png)
Images.xcassets的图片一个一个拷贝到相应的Images.xcassets.
3.再添加以下几个swift类
熊猫类
熊猫是我们游戏的主角,我给它添加了4个动作,跑,跳,翻滚,二次条,为了增加起跳时逼真性还添加了跑动增效动作。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// // Panda.swift // KongfuPanda // // Created by HooJackie on 15/7/1. // Copyright (c) 2015年 jackie. All rights reserved. // import SpriteKit enum Status :Int{ case run = 1, jump, jump2,roll } class Panda: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]() //增加跳起的逼真效果动画 let jumpEffectAtlas = SKTextureAtlas(named: "jump_effect.atlas") var jumpEffectFrames = [SKTexture]() var jumpEffect = SKSpriteNode() var status = Status.run var jumpStart:CGFloat = 0.0 var jumpEnd:CGFloat = 0.0 init(){ let texture = runAtlas.textureNamed("panda_run_01") let size = texture.size() super.init(texture: texture, color: SKColor.whiteColor(), size: size) //跑 for var 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 var 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 var 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 var 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) self.physicsBody = SKPhysicsBody(rectangleOfSize: size) self.physicsBody?.dynamic = true self.physicsBody?.allowsRotation = false self.physicsBody?.restitution = 0.1 //反弹力 self.physicsBody?.categoryBitMask = BitMaskType.panda self.physicsBody?.contactTestBitMask = BitMaskType.scene | BitMaskType.platform | BitMaskType.apple self.physicsBody?.collisionBitMask = BitMaskType.platform self.zPosition = 20 run() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func run(){ //清楚所有动作 self.removeAllActions() self.status = .run //重复跑动动作 self.runAction(SKAction.repeatActionForever(SKAction.animateWithTextures(runFrames, timePerFrame: 0.05))) } func jump(){ self.removeAllActions() if status != .jump2 { //Adds an action to the list of actions executed by the node. //Creates an action that animates changes to a sprite’s texture. self.runAction(SKAction.animateWithTextures(jumpFrames, timePerFrame: 0.05),withKey:"jump") //The physics body’s velocity vector, measured in meters per second. self.physicsBody?.velocity = CGVectorMake(0, 450) if status == Status.jump { self.runAction(SKAction.animateWithTextures(rollFrames, timePerFrame: 0.05)) status = Status.jump2 self.jumpStart = self.position.y }else { showJumpEffect() status = .jump } } } func roll(){ self.removeAllActions() self.status = .roll self.runAction(SKAction.animateWithTextures(rollFrames, timePerFrame: 0.05),completion:{ self.run() }) } func showJumpEffect(){ jumpEffect.hidden = false var ectAct = SKAction.animateWithTextures( jumpEffectFrames, timePerFrame: 0.05) var removeAct = SKAction.runBlock({() in self.jumpEffect.hidden = true }) // 执行两个动作,先显示,后隐藏 jumpEffect.runAction(SKAction.sequence([ectAct,removeAct])) } }
平台类
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// // Platform.swift // KongfuPanda // // Created by HooJackie on 15/7/1. // Copyright (c) 2015年 jackie. All rights reserved. // import SpriteKit class Platform:SKNode { var CGFloat = 0.0 var height:CGFloat = 10.0 var isDown = false var isShock = false //创建平台 func onCreate(arrSprite:[SKSpriteNode]){ for platform in arrSprite { platform.position.x = self.width self.addChild(platform) self.width += platform.size.width } //短到只有三小块的平台会下落 if arrSprite.count <= 3 { isDown = true }else { //随机振动 let random = arc4random() % 10 if random > 6 { isShock = true } } self.height = 10.0 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 self.physicsBody?.restitution = 0 self.zPosition = 20 } }
平台生成类
难点1:在理解平台如何生成。可以查看资源图platform_l ,platform_m,platform_r.将他们拼接到一起,根据Platfom_m的数量不同,产生不同的长度的平台。
难点2:游戏中的熊猫移动我们眼睛看以为是熊猫在跑,其实移动的是平台,通过平台向左移动,给我们的错觉是熊猫在向右跑。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// // PlatformFactory.swift // KongfuPanda // // Created by HooJackie on 15/7/1. // Copyright (c) 2015年 jackie. All rights reserved. // 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 screenWdith:CGFloat = 0.0 var delegate:ProtocolMainscreen? // 随机生成一定长度的平台 func createPlatformRandom(){ let midNum = arc4random()%4 + 1 let gap:CGFloat = CGFloat(arc4random()%8 + 1) let x = self.screenWdith + CGFloat(midNum*50) + gap + 100 let y = CGFloat(arc4random()%200 + 200) createPlatform(midNum, x: x, y: y) } //生成平台方法 func createPlatform(midNum:UInt32,x:CGFloat,y:CGFloat){ let platform = Platform() let platform_left = SKSpriteNode(texture: textureLeft) platform_left.anchorPoint = CGPointMake(0, 0.9) let platform_right = SKSpriteNode(texture: textureRight) platform_right.anchorPoint = CGPointMake(0, 0.9) var arrPlatform = [SKSpriteNode]() arrPlatform.append(platform_left) platform.position = CGPointMake(x, y) for i in 1...midNum { let platform_mid = SKSpriteNode(texture: textureMid) platform_mid.anchorPoint = CGPointMake(0, 0.9) arrPlatform.append(platform_mid) } arrPlatform.append(platform_right) platform.onCreate(arrPlatform) platform.name = "platform" self.addChild(platform) platforms.append(platform) self.delegate?.onGetData(platform.width + x - screenWdith,theY:y) } //平台向左移动的方法 func move(speed:CGFloat){ for p in platforms { let position = p.position p.position = CGPointMake(position.x - speed, position.y) } if platforms[0].position.x < -platforms[0].width{ platforms[0].removeFromParent() platforms.removeAtIndex(0) } } //清楚所有的Node func reset(){ self.removeAllChildren() platforms.removeAll(keepCapacity: false) } }
背景音乐类
生成各种音效。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// // BackGround.swift //场景的动态背景图类 // KongfuPanda // // Created by HooJackie on 15/7/1. // Copyright (c) 2015年 jackie. All rights reserved. // import SpriteKit class Background:SKNode { //近处的背景 var arrBG = [SKSpriteNode]() //远处的背景 var arrFar = [SKSpriteNode]() override init() { super.init() var farTexture = SKTexture(imageNamed: "background_f1") var farBg0 = SKSpriteNode(texture: farTexture) farBg0.position.y = 150 farBg0.zPosition = 9 farBg0.anchorPoint = CGPointMake(0, 0) var farBg1 = SKSpriteNode(texture: farTexture) farBg1.position.y = 150 farBg1.zPosition = 9 farBg1.anchorPoint = CGPointMake(0, 0) farBg1.position.x = farBg1.frame.width var farBg2 = SKSpriteNode(texture: farTexture) farBg2.position.y = 150 farBg2.zPosition = 9 farBg2.anchorPoint = CGPointMake(0, 0) farBg2.position.x = farBg2.frame.width*2 self.addChild(farBg0) self.addChild(farBg1) self.addChild(farBg2) arrFar.append(farBg0) arrFar.append(farBg1) arrFar.append(farBg2) var texture = SKTexture(imageNamed: "background_f0") var bg0 = SKSpriteNode(texture: texture) bg0.anchorPoint = CGPointMake(0, 0) bg0.position.y = 70 bg0.zPosition = 10 var bg1 = SKSpriteNode(texture: texture) bg1.anchorPoint = CGPointMake(0, 0) bg1.position.y = 70 bg1.zPosition = 10 bg1.position.x = bg0.frame.size.width self.addChild(bg0) self.addChild(bg1) arrBG.append(bg0) arrBG.append(bg1) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func move(speed:CGFloat){ //近景 for bg in arrBG { bg.position.x -= speed } if arrBG[0].position.x + arrBG[0].frame.size.width < speed { arrBG[0].position.x = 0 arrBG[1].position.x = arrBG[0].frame.size.width } //远景 for far in arrFar { far.position.x -= speed/4 } if arrFar[0].position.x + arrFar[0].frame.size.width < speed/4 { arrFar[0].position.x = 0 arrFar[1].position.x = arrFar[0].frame.size.width arrFar[2].position.x = arrFar[0].frame.size.width * 2 } } }
位运算标识类
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// // BitMaskType.swift // KongfuPanda // // Created by HooJackie on 15/7/1. // Copyright (c) 2015年 jackie. All rights reserved. // class BitMaskType { class var panda:UInt32 { return 1<<0 } class var platform:UInt32 { return 1<<1 } class var apple:UInt32 { return 1<<2 } class var scene:UInt32{ return 1<<3 } }
苹果生成类
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import SpriteKit class AppleFactory:SKNode{ let appleTexture = SKTexture(imageNamed: "apple") var sceneWidth:CGFloat = 0.0 var arrApple = [SKSpriteNode]() var timer = NSTimer() var theY:CGFloat = 0.0 override init() { super.init() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func onInit(CGFloat, y:CGFloat) { self.sceneWidth = width self.theY = y timer = NSTimer.scheduledTimerWithTimeInterval( 0.2, target: self, selector: "createApple", userInfo: nil, repeats: true) } func createApple(){ var random = arc4random() % 10 if random > 8 { var apple = SKSpriteNode(texture: appleTexture) apple.physicsBody = SKPhysicsBody(rectangleOfSize: apple.size) apple.physicsBody!.restitution = 0 apple.physicsBody!.categoryBitMask = BitMaskType.apple apple.physicsBody!.dynamic = false apple.anchorPoint = CGPointMake(0, 0) apple.zPosition = 40 apple.position = CGPointMake(sceneWidth+apple.frame.width , theY + 150) arrApple.append(apple) self.addChild(apple) } } func move(speed:CGFloat){ for apple in arrApple { apple.position.x -= speed } if arrApple.count > 0 && arrApple[0].position.x < -20{ arrApple[0].removeFromParent() arrApple.removeAtIndex(0) } } func reSet(){ self.removeAllChildren() arrApple.removeAll(keepCapacity: false) } }
游戏主界面类
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import SpriteKit class GameScene: SKScene,SKPhysicsContactDelegate , ProtocolMainscreen{ lazy var panda = Panda() lazy var platformFactory = PlatformFactory() lazy var sound = SoundManager() lazy var bg = Background() lazy var appleFactory = AppleFactory() let scoreLab = SKLabelNode(fontNamed:"Chalkduster") let appLab = SKLabelNode(fontNamed:"Chalkduster") let myLabel = SKLabelNode(fontNamed:"Chalkduster") var appleNum = 0 var moveSpeed :CGFloat = 15.0 var maxSpeed :CGFloat = 50.0 var distance:CGFloat = 0.0 var lastDis:CGFloat = 0.0 var theY:CGFloat = 0.0 var isLose = false override func didMoveToView(view: SKView) { let skyColor = SKColor(red:113.0/255.0, green:197.0/255.0, blue:207.0/255.0, alpha:1.0) self.backgroundColor = skyColor scoreLab.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.Left scoreLab.position = CGPointMake(20, self.frame.size.height-150) scoreLab.text = "run: 0 km" self.addChild(scoreLab) appLab.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.Left appLab.position = CGPointMake(400, self.frame.size.height-150) appLab.text = "eat: (appleNum) apple" self.addChild(appLab) myLabel.text = ""; myLabel.fontSize = 65; myLabel.zPosition = 100 myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame)); self.addChild(myLabel) 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 panda.position = CGPointMake(200, 400) self.addChild(panda) self.addChild(platformFactory) platformFactory.screenWdith = self.frame.width platformFactory.delegate = self platformFactory.createPlatform(3, x: 0, y: 200) self.addChild(bg) self.addChild(sound) sound.playBackgroundMusic() appleFactory.onInit(self.frame.width, y: theY) self.addChild( appleFactory ) } func didBeginContact(contact: SKPhysicsContact){ //熊猫和苹果碰撞 if (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask) == (BitMaskType.apple | BitMaskType.panda){ sound.playEat() self.appleNum++ if contact.bodyA.categoryBitMask == BitMaskType.apple { contact.bodyA.node!.hidden = true }else{ contact.bodyB.node!.hidden = true } } //熊猫和台子碰撞 if (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask) == (BitMaskType.platform | BitMaskType.panda){ var isDown = false var canRun = false 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 }else if (contact.bodyA.node as! Platform).isShock { (contact.bodyA.node as! Platform).isShock = false downAndUp(contact.bodyA.node!, down: -50, downTime: 0.2, up: 100, upTime: 1, isRepeat: true) } if contact.bodyB.node!.position.y > contact.bodyA.node!.position.y { canRun=true } }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 }else if (contact.bodyB.node as! Platform).isShock { (contact.bodyB.node as! Platform).isShock = false downAndUp(contact.bodyB.node!, down: -50, downTime: 0.2, up: 100, upTime: 1, isRepeat: true) } if contact.bodyA.node!.position.y > contact.bodyB.node!.position.y { canRun=true } } panda.jumpEnd = panda.position.y if panda.jumpEnd-panda.jumpStart <= -70 { panda.roll() sound.playRoll() if !isDown { downAndUp(contact.bodyA.node!) downAndUp(contact.bodyB.node!) } }else{ if canRun { panda.run() } } } if (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask) == (BitMaskType.scene | BitMaskType.panda) { println("game over") myLabel.text = "game over"; sound.playDead() isLose = true sound.stopBackgroundMusic() } //落地后jumpstart数据要设为当前位置,防止自由落地计算出错 panda.jumpStart = panda.position.y } func didEndContact(contact: SKPhysicsContact){ panda.jumpStart = panda.position.y } func downAndUp(node :SKNode,down:CGFloat = -50,downTime:CGFloat=0.05,up:CGFloat=50,upTime:CGFloat=0.1,isRepeat:Bool=false){ let downAct = SKAction.moveByX(0, y: down, duration: Double(downTime)) //moveByX(CGFloat(0), y: down, duration: downTime) let upAct = SKAction.moveByX(0, y: up, duration: Double(upTime)) let downUpAct = SKAction.sequence([downAct,upAct]) if isRepeat { node.runAction(SKAction.repeatActionForever(downUpAct)) }else { node.runAction(downUpAct) } } override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) { if isLose { reSet() }else{ if panda.status != Status.jump2 { sound.playJump() } panda.jump() } } //重新开始游戏 func reSet(){ isLose = false panda.position = CGPointMake(200, 400) myLabel.text = "" moveSpeed = 15.0 distance = 0.0 lastDis = 0.0 self.appleNum = 0 platformFactory.reset() appleFactory.reSet() platformFactory.createPlatform(3, x: 0, y: 200) sound.playBackgroundMusic() } override func update(currentTime: CFTimeInterval) { if isLose { }else{ if panda.position.x < 200 { var x = panda.position.x + 1 panda.position = CGPointMake(x, panda.position.y) } distance += moveSpeed lastDis -= moveSpeed var tempSpeed = CGFloat(5 + Int(distance/2000)) if tempSpeed > maxSpeed { tempSpeed = maxSpeed } if moveSpeed < tempSpeed { moveSpeed = tempSpeed } if lastDis < 0 { platformFactory.createPlatformRandom() } distance += moveSpeed scoreLab.text = "run: (Int(distance/1000*10)/10) km" appLab.text = "eat: (appleNum) apple" platformFactory.move(moveSpeed) bg.move(moveSpeed/5) appleFactory.move(moveSpeed) } } func onGetData(dist:CGFloat,theY:CGFloat){ self.lastDis = dist self.theY = theY appleFactory.theY = theY } } protocol ProtocolMainscreen { func onGetData(dist:CGFloat,theY:CGFloat) }
4.最后的游戏的效果图: