xcode吧 关注:8,401贴子:34,056

Xcode+Swift开发2D游戏[教程]

只看楼主收藏回复

PS-1:我不常使用贴吧,使用不当之处请多多包容。
PS-2:如果想学明白,建议“只看楼主”。
PS-3:本人是学生,发帖速度=蜗牛速度,请谅解。
PS-4:小要求:转载请注明作者…
作者:Kelin.Sasha
(我很脆弱,请尊重我)
第一节:
需要人类一个,Mac一台,安装Xcode(我的是6.3.2,不建议太低)。不具备上述要求的请自觉离开。
第一次先不要有太宏伟的计划,先测试一下…
启动Xcode,选择File->New->Project新建工程(方法不唯一)。在弹出来的板子上选择IOS下的Application,然后找到Game(别告诉我看不见…)。点击Next。然后填上工程名称,组织。对了,后面一定要注意:
把Language确定为Swift。
把GameTechnology确定为SpriteKit。
把Devices确定为Universal。
然后Next。
选择保存目录就不说了…
点击Create。
至此你就创建了一个程序了…
稍后继续…


IP属地:美国来自iPhone客户端1楼2015-07-23 20:33回复
    你会进入如图的一个地方…左边一栏是文件们,中间的栏需要我们改动一下:
    找到中间的DeviceOrientatioa,这是确定设备方向的。把它改成你希望的设备方向。建议取消Portrait。我们暂时的测试程序要求不是那么多…注意在写程序的时候,手边有个字典非常有用,把不懂得查明白吧…
    如果你现在点击左上角的播放箭头,会在模拟器启动你的程序。等模拟器出来之后,你会看到一个HelloWorld。点一下试试,会出现一个旋转的飞机。好吧,把它退出来。
    别告诉我你的模拟器太大了,挡住了整个屏幕。记得在模拟器找到Window->Scale->50%足够了。好吧,稍后继续。


    IP属地:美国来自iPhone客户端2楼2015-07-23 20:42
    收起回复
      现在,我们来深入剖析这个程序。请盯着左边的文件栏。如果你把它调没了就花点时间把它调出来。
      首先,我讨厌复杂的东西。所以那些不需要知道的东西我没有多搭理它们。所以,我们忽略掉:
      AppDelegate文件
      GameScene.sks文件
      Main.Storyboard文件
      LaunchScreen文件
      其它文件只要做好,足以写好游戏。
      注意,主要工作集中在GameScene.swift上。所以我们单击它。


      IP属地:美国来自iPhone客户端3楼2015-07-23 21:15
      回复
        发现主要是一个GameScene类,里面有3个函数。
        didMoveToView函数你可以当成初始化函数理解。当游戏加载的时候启用该函数。
        touchesBegan当用户单击屏幕时被调用。
        update函数是每帧刷新时调用的,暂时没有用,可以删了,但是以后用处很多。
        didMoveToView内部有如下代码,我逐句解释:
        首先,容易发现那个HelloWorld标签就是在这里诞生的。
        let myLabel = SKLabelNode(fontNamed:"Chalkduster")
        定义一个myLabel,属于SKLabelNode,是一个标签,用fontNamed设置字体,字体为Chalkduster。
        你可以把括号里面的内容杀掉,然后运行试试。也不错,不是吗。
        myLabel.text = "Hello, World!"
        定义myLabel的text属性,其实就是文本内容,是"Hello, World!"
        myLabel.fontSize = 65
        设置字的大小,不多说,自己改改数值试试。
        myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
        定义位置。用一个CGPoint,即CG点,来表示位置。可以看出X位置是CGRectGetMidX(self.frame),也就是窗口的中心,Y位置也是在中心。
        特别注意,一般这些部件的坐标都是CGFloat类型的,不是int之类的。
        之前没学过编程?好吧。。。
        self.addChild(myLabel)
        这句话必不可少,用于把myLabel粘到屏幕上。
        作业:小试牛刀:在didMoveToView函数中,再写一组代码。构造一个新的标签,写上你的名字,调整合适大小,至于位置,给你提示:给你一个点:
        CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame)-CGFloat(100))。
        一组可能的答案:
        let k = SKLabelNode()
        k.text = "Bye";
        k.fontSize = 65;
        k.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame)-CGFloat(100));
        self.addChild(k)
        好吧,一会儿继续。


        IP属地:美国4楼2015-07-23 21:35
        收起回复
          开始剖析touchesBegan。
          先看看这些,是不是很眼熟?和上面提到的标签用法几乎一样吧,稍有改动而已。
          逐句解析:
          let sprite = SKSpriteNode(imageNamed:"Spaceship")
          定义一个东西,叫做sprite,精灵。注意,后面的括号不再表示什么字体了,而是图片文件。对了,想知道那个飞船图片从哪里来的?看看左边栏目中的Images文件夹,你会找到她的。以后你如果需要图片文件,往那个文件夹里Import即可。
          sprite.xScale = 0.5
          sprite.yScale = 0.5
          设置她的长和宽。注意,不是具体长度,是相对于原图片文件的缩放比例,如果没错的话。自己改改看看。
          sprite.position = location
          设置位置。这时你会发现location其实是一个CGPoint,location是从
          let location = touch.locationInNode(self)
          里面出来的。你能理解,但我们暂时不过多解释,因为还有很多工作要做。
          let action = SKAction.rotateByAngle(CGFloat(M_PI), duration:1)
          定义一个新动作,知道她为什么在转了吧。在SKAction下面还有许多动作,以后会用到的。
          sprite.runAction(SKAction.repeatActionForever(action))
          让她进行永远的动作。很好理解。需要把定义的动作填进括号里。
          以上两句可以合并:
          sprite.runAction(SKAction.repeatActionForever(SKAction.rotateByAngle(CGFloat(M_PI), duration:1)))
          不再多说了
          self.addChild(sprite)
          把她交给屏幕。。。
          好吧,现在开始正式进入第一个游戏的开发。尽管估计没几个人听明白了。不过,我已经把需要用到的讲完了。其实构成游戏的部分就是这些。只要用好,就没问题。


          IP属地:美国5楼2015-07-23 21:48
          收起回复
            介绍一下我们的测试项目:扔飞镖打移动的方块。改造得好一点可以弄成4399的海上保卫战游戏,不知道那个还在不在呢。
            图片我没有仔细做,需要一张飞镖,一张基地,一张敌人,还有,图标是重点,我以后会仔细说的,有很多人栽了。
            我有点偷懒,把基地和敌人都弄成了一张图片。你们可以做一个好一点的,但不要太好,这只是一个测试。
            当然,如果测试做完了,你自己非常喜欢,我可以教你建造菜单。
            好吧,等明天。


            IP属地:美国来自iPhone客户端6楼2015-07-23 21:57
            收起回复
              说一下那个坑爹的图标的问题。在左边的文件中找到蓝色的Images文件夹,请删掉AppIcon文件,然后右键NewAppIcon新建一个图标文件。
              你会看见许多卡槽。是的,这么多。如图,是我做的一款游戏的图标们。她们大小不一,所以非常让我头痛。
              我们需要把前两排填满。注意每几个卡槽下面都有什么29pt,40pt之类的,上面还有什么1x,2x之类的。注意,比如是40pt的,还是2x,你就需要准备80像素乘以80像素的图标文件。把她们相乘,明白吗…


              IP属地:美国来自iPhone客户端7楼2015-07-24 09:12
              收起回复
                除了图标,有人更关心的是当游戏运行时第一载入的图片,比如开发者,或者开发组织。
                这个叫launch screen,这个文件你可以找到,叫做LaunchScreen.xib。
                但是,在这里放置你们公司的图标 并不是很好。调整大小和位置都很复杂。不过如果你真的要试试我也不会拦着你,但是我建议你把整个LaunchScreen.xib文件的背景调整成黑色的,这样就不用管她了。
                至于那些公司图标,开发者名称等写在哪里,以后会说的。


                IP属地:美国8楼2015-07-24 09:17
                回复
                  第一步,参照HelloWorld的方法,把基地添加到屏幕中央。
                  以下代码写在didMoveToView函数中,就是那个我们认为是初始化的函数。
                  let b = SKSpriteNode(imageNamed: "base")
                  定义b为基地,导入base图片。
                  为什么我要实用b作为基地呢,因为b是base的首字母。这样比较方便。
                  b.xScale = 2
                  b.yScale = 2
                  设置大小。因图片而异,自己调整调整,不要吝啬于运行半成品。
                  b.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
                  弄到屏幕中心来。这句应该不用再说了吧,直接从Hello World粘贴过来。。。
                  self.addChild(b)
                  别忘了这句。
                  现在,基地已经进来了。


                  IP属地:美国10楼2015-07-24 09:30
                  收起回复
                    当然,这一点是不够的。我们还需要初始化飞镖,以及敌人,还有计分器等等。这些东西写在初始化函数里是不可以的,因为我们的touchesBegan以及update函数都需要用到这些元件。
                    忘了说了,所有代码主要写在GameScene.swift里面,不要告诉我你迷路了。
                    我们当然还需要表示生命值之类的变量。但是,在这个游戏中,基地是无敌的,基地的主人购买了无限飞镖以及无限生命。。。
                    代码写在这个位置:仔细观察,寻找这个位置:
                    class GameScene: SKScene {
                    //从这里开始
                    let e = SKSpriteNode(imageNamed: "enemy") //这句不用再说了
                    let k = SKSpriteNode(imageNamed:"knife") //这个也是...
                    var hp:Int = 100 //敌人的生命值
                    var t:Int = 10 //敌人的停顿时间,后面解释
                    var kl:Int=0 //敌人死了多少次了,Kill的缩写。。。
                    let myLabel = SKLabelNode()
                    //这里结束
                    override func didMoveToView(view: SKView) {


                    IP属地:美国11楼2015-07-24 09:43
                    收起回复
                      说一下上文的t是做什么的。敌人会随机运动,速度也是不确定的。如果有一局随机得特别快,中间一点也不停,那样鬼才打的到。所以,我们让她每次运动完了休息10帧,约等于0.4秒差不多。
                      对了,上文的myLabel是计分标签,忘了解释了。
                      继续:
                      现在写一个初始化敌人的函数:因为叫做SetUpE,只要调用,敌人的生命值回满,敌人归位,并停止之前的一切活动。
                      写在didMoveToView函数前面:
                      func setupe() { //定义函数名称
                      hp=100 //生命值回满
                      e.removeAllActions() //停止之前的一切活动
                      e.position = CGPoint(x: 0, y:0) //敌人归位
                      }
                      就这么简单,不是吗。
                      然后继续编辑didMoveToView函数的内部:
                      继续写在里面:
                      现在应该初始化一下飞镖和敌人的大小了:
                      e.xScale = 1
                      e.yScale = 1
                      设置敌人大小。具体因为我们图片不同,大小自己决定。
                      k.xScale=0.5
                      k.yScale=0.5
                      设置飞镖大小。具体因为我们图片不同,大小自己决定。
                      然后设置计分器:
                      myLabel.fontSize = 20;
                      字体大小
                      myLabel.position = CGPoint(x:Int(CGRectGetMidX(self.frame)), y:Int(CGRectGetMidY(self.frame))-70);
                      位置,在基地下面
                      myLabel.fontColor = SKColor.redColor()
                      字体颜色
                      注意,上述代码需要根据图片自己调整。如果你发现基地或飞镖充斥了整个屏幕,不要着急,不要伤心,生活没有欺骗你。自己改改吧。
                      继续
                      setupe()
                      初始化敌人
                      self.addChild(e)
                      self.addChild(k)
                      self.addChild(myLabel)
                      把敌人,标签,刀子加进去。


                      IP属地:美国12楼2015-07-24 09:59
                      收起回复
                        现在,进入点击扔飞镖阶段。
                        进入touchesBegan函数:
                        override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
                        for touch in (touches as! Set<UITouch>) {
                        }
                        }
                        把中间的全都删掉,只留下如上的一堆。
                        这里会用到一点向量缩放的知识。
                        当我们单击屏幕之后,我们会得到一个坐标,我们希望飞镖从中心开始,朝着那里飞。我们更希望她可以飞的再远一些,最后飞出屏幕。


                        IP属地:美国13楼2015-07-24 10:03
                        收起回复
                          如图可以解释清楚:


                          IP属地:美国来自iPhone客户端14楼2015-07-24 10:07
                          收起回复
                            以下代码写在touchesBegins函数中间,for语句内部:
                            我突然发现CGPoint其实可以把整数当作x,y坐标。所以我全都转成整数了:
                            let kx = (Int(touch.locationInNode(self).x)-Int(CGRectGetMidX(self.frame)))*100
                            let ky = (Int(touch.locationInNode(self).y)-Int(CGRectGetMidY(self.frame)))*100
                            解释清楚第一句,第二句也就明白了。
                            定义一个kx,是飞镖终点的X坐标,后面的ky也就是纵坐标了。
                            kx等于后面一大堆。后面一大堆是什么呢?
                            首先Int( touch.locationInNode(self).x )是点击的位置的X坐标,被转成了整数,让她减去屏幕中心点坐标,得到的就是点击位置到中心位置的坐标差。如果你点在基地右边,她就是正数,如果在左边,她就是负数。
                            也就是说,我们以屏幕中心建立了笛卡尔的平面直角坐标系,如果忽略上述代码最后的 乘以一百,那么kx和ky正好就是该坐标系中的点击的位置。
                            忘了说了,默认坐标系处于左下角。
                            如果把kx和ky同时乘上100,就相当于增加了长度,但是方向不变。具体我也不好解释了。多读几遍吧。
                            继续写:
                            let loc=CGPoint(x: kx, y: ky)
                            把kx,ky封印在一个叫做 loc的地方,就是终点坐标。loc就是location,位置。
                            现在有了终点,还需要起点:
                            k.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
                            不解释了,写了很多遍了。
                            然后先让她飞过去:
                            定义动作:
                            let action2 = SKAction.moveTo(loc, duration: 10)
                            当然,飞过去还不够,需要让她转起来:
                            let action1 = SKAction.rotateByAngle(CGFloat(M_PI), duration:10)
                            好了,现在让飞镖执行动作:
                            k.runAction(SKAction.repeatActionForever(action1))
                            k.runAction(SKAction.repeatActionForever(action2))
                            以上就是for语句内部的代码。
                            运行一下,成功吗?哈哈。


                            IP属地:美国15楼2015-07-24 10:22
                            收起回复
                              继续写碰撞函数:
                              我们先确定一个方块,就是以飞镖为中心的一个200*200的方块。
                              注意大小不合适的自己调整吧。
                              var xx:Int = Int(k.position.x)+100 //获取横坐标,加上100
                              var xn:Int = Int(k.position.x)-100
                              var yx:Int = Int(k.position.y)+100
                              var yn:Int = Int(k.position.y)-100
                              不过多解释,现在如果以飞镖为中心建立坐标系,直线x=xx,x=xn,y=yx,y=yn正好可以围成一个边长为200的正方形,不管你信不信。初中数学。
                              然后,如果敌人恰好在那个方块内部,她就会减血,突然变大一下。
                              if (Int(e.position.x) < xx) && (Int(e.position.x) > xn) && (Int(e.position.y) > yn) && (Int(e.position.y) < yx)
                              {//上面这句就是判断敌人的坐标是不是在里面的,需要同时判断4个条件,自己分析吧,不是难题。
                              hp=hp-5 //敌人减少生命值
                              k.removeAllActions() //这时呢,飞镖可以不用在动了
                              k.position = CGPoint(x:100000, y:100000) //不光是不用动了,她还需要躲得远远的
                              //让观众以为飞镖已经被敌人吸收了
                              e.xScale=1.5
                              e.yScale=1.5
                              //这时,敌人会变大
                              //她还会变回去的,看看update函数前面,我们再次定义了敌人的常规大小
                              //那句我还写错了。。。
                              if(hp<1){ //当然,如果敌人生命值已经小于1了,她就可以进棺材了
                              kl=kl+1 //证明成功杀死一个敌人,刷新计分器
                              setupe() //召唤一个新的敌人
                              }
                              }
                              当然,从头到尾我们一直只有一个敌人,只是我们等她死了之后,又把她拎上来整理了一下而已。
                              现在你测试的时候,敌人还看不见。但是如果你向左下角狂扫飞镖,你会发现标签有变化。这说明敌人存在,只是位置不是人类能看见的罢了。


                              IP属地:美国17楼2015-07-24 10:51
                              回复