用Swift做个游戏Lecture02 —— Player的诞生

系列:用Swift作个游戏
作者:pmst(1345614869)
微博:PPPPPPMST

01.添加游戏音乐

音乐主要有:Player挥动翅膀上升的声音、撞击障碍物的声音、坠落至地面的声音、过关得分的声音等等。请打开项目看到Resource中的Sounds文件夹,包含了上述所有声音,格式为.wav

SpritKit提供playSoundFileNamed(soundFile: , waitForCompletion wait: )->SKAction方法用于实现音乐的播放,注意播放音乐也是一个Action动作。请定位到GameScene.swift文件,找到GameScene类中的var playableHeight:CGFloat = 0,在其下方添加如下代码:

// MARK: 音乐Action
let dingAction = SKAction.playSoundFileNamed("ding.wav", waitForCompletion: false)
let flapAction = SKAction.playSoundFileNamed("flapping.wav", waitForCompletion: false)
let whackAction = SKAction.playSoundFileNamed("whack.wav", waitForCompletion: false)
let fallingAction = SKAction.playSoundFileNamed("falling.wav", waitForCompletion: false)
let hitGroundAction = SKAction.playSoundFileNamed("hitGround.wav", waitForCompletion: false)
let popAction = SKAction.playSoundFileNamed("pop.wav", waitForCompletion: false)
let coinAction = SKAction.playSoundFileNamed("coin.wav", waitForCompletion: false)

之后在需要音乐播放的时候调用这些已经定义的动作即可。

02.添加Player

通过课程一的代码练习,添加一个Player只需实例化一个SKSpriteNode实例,纹理为Bird0这张照片。由于这个精灵之后将在各个函数调用,因此设定了全局变量,请在var playableHeight: CGFloat = 0下添加如下代码实例化一个名为"Player"的精灵。如下:

let player = SKSpriteNode(imageNamed: "Bird0")

注意到此时我们并未添加该精灵到场景中的worldNode节点中,因此我们需要实现一个名为setupPlayer()的方法,代码如下

func setupPlayer(){
    player.position = CGPointMake(size.width * 0.2, playableHeight * 0.4 + playableStart)
    player.zPosition = Layer.Player.rawValue
    worldNode.addChild(player)
}

函数中仅设置了position以及zPosition属性,而锚点anchorPoint并未设置,采用默认值(0.5,0.5)。找到didMoveToView(view:)中的setupForeground()这行代码,将上述方法添加至其下方。

点击运行程序,Player出现在场景之中。

03.update方法

不知道你有没有玩过翻书动画,先准备一个厚厚的小本子,然后在每一页上描画,最后通过快速翻阅组成最简短的动画。如下:

L02-Animation
L02-Animation

前文谈及右下角的30fps客官可曾记得?fpsFrame Per Second的缩写,即每秒的帧数,而一帧为一个画面。因此30fps意味着在一秒钟时间内,App要渲染30次左右,平均每隔0.033333秒就要重新绘制一次画面。而渲染(绘制)完毕立刻跳入update(currentTime:)方法中,大约间隔33.33毫秒左右,执行方法内的代码。不妨你在该函数中设个断点感受一下。

注意到左下角的帧数并不是始终保持在30fps,而是不断在上下浮动变化。相邻两帧画面之间的时间并不固定,可能是0.033秒,也可能是0.030秒。不妨测试打印下两帧之间的时间差值,请在player下添加两个全局变量:lastUpdateTime以及dt

var lastUpdateTime :NSTimeInterval = 0  //记录上次更新时间
var dt:NSTimeInterval = 0               //两次时间差值

接着在Update(currenTime:)方法中添加如下方法:

override func update(currentTime: CFTimeInterval) {
   if lastUpdateTime > 0{
       dt = currentTime - lastUpdateTime
   }else{
       dt = 0
   }
   lastUpdateTime = currentTime
   print("时间差值为:\(dt*1000) 毫秒")
}

可以看到打印结果(注意红色框框处):

当应用刚启动时,帧数并不稳定,导致时间间隔略大,不过之后基本稳定在33毫秒左右。

04.Player的下落公式

这里可能要涉及一些高中的物理知识。地球上的重力加速度为9.8g。物体在半空中静止到下落,每隔dt时间。

  • 速度V = V1 + a * dt,即当前速度=初速度 + 加速度 * 时间间隔
  • dt时间内,下落距离d =V * dt,这里采用平均速度 * 时间差得到下落距离。

游戏中设定且只有Y轴方向上的重力加速度kGravity = -1500,这个值是可调节的,我觉得恰到好处;此外每次玩家点击屏幕,对Player要有一个向上的拉力,不妨设为kImpulse = 400;最后声明一个变量playerVelocity追踪当前Player的速度。请添加上述三个全局变量的声明,现在GameScene类中的全局变量有以下这些:

// MARK: - 常量
let kGravity:CGFloat = -1500.0  //重力
let kImpulse:CGFloat = 400      //上升力

let worldNode = SKNode()
var playableStart:CGFloat = 0
var playableHeight:CGFloat = 0
let player = SKSpriteNode(imageNamed: "Bird0")
var lastUpdateTime :NSTimeInterval = 0
var dt:NSTimeInterval = 0
var playerVelocity = CGPoint.zero   //速度 注意变量类型为一个点
//...其他内容

请在GameScene类中添加一个方法,将先前公式用swift实现更新playerposition

func updatePlayer(){
    // 只有Y轴上的重力加速度为-1500
    let gravity = CGPoint(x: 0, y: kGravity)
    let gravityStep = gravity * CGFloat(dt) //计算dt时间下速度的增量
    playerVelocity += gravityStep           //计算当前速度
    
    // 位置计算
    let velocityStep = playerVelocity * CGFloat(dt) //计算dt时间中下落或上升距离
    player.position += velocityStep                 //计算player的位置
    
    // 倘若Player的Y坐标位置在地面上了就不能再下落了 直接设置其位置的y值为地面的表层坐标
    if player.position.y - player.size.height/2 < playableStart {
        player.position = CGPoint(x: player.position.x, y: playableStart + player.size.height/2)
    }
}

将该方法添加至update(currentTime)方法中的最下面。意味着每隔33.3毫秒左右就要更新一次Player的位置。

点击运行,Player自由落地至地面,不错吧!

05.让Player动起来

游戏中我们点击一次屏幕,Player会获得一个向上的牵引力,挥动翅膀向上飞一段距离,倘若之后没有持续的力,则开始自由落体。怎么实现呢?实现机制不难,只需每次玩家点击屏幕,使得Player获得向上的速度,具体为先前设定的400即可。

因此,添加一个方法到GameScene类中,用于每次用户点击屏幕时调用,作用是让Player获得向上的速度!

func flapPlayer(){
    // 发出一次煽动翅膀的声音
    runAction(flapAction)
    // 重新设定player的速度!!
    playerVelocity  = CGPointMake(0, kImpulse)
}

正如前面谈到的,方法中主要做两件事:1.发出一次挥动翅膀的声音。2.重新设定player的速度。

而用户每次点击都会调用touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)方法。不用我多说了吧,把flapPlayer()方法添加进去吧。

运行工程,player坠落,点击几下,哇靠,飞起来了!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容