Xcode+Swift开发2D游戏[教程]
xcode吧
全部回复
仅看楼主
level 6
Kelin🌌 楼主
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。
至此你就创建了一个程序了…
稍后继续…
2015年07月23日 12点07分 1
level 6
Kelin🌌 楼主
你会进入如图的一个地方…左边一栏是文件们,中间的栏需要我们改动一下:
找到中间的DeviceOrientatioa,这是确定设备方向的。把它改成你希望的设备方向。建议取消Portrait。我们暂时的测试程序要求不是那么多…注意在写程序的时候,手边有个字典非常有用,把不懂得查明白吧…
如果你现在点击左上角的播放箭头,会在模拟器启动你的程序。等模拟器出来之后,你会看到一个HelloWorld。点一下试试,会出现一个旋转的飞机。好吧,把它退出来。
别告诉我你的模拟器太大了,挡住了整个屏幕。记得在模拟器找到Window->Scale->50%足够了。好吧,稍后继续。
2015年07月23日 12点07分 2
点箭头,创建失败
2015年08月03日 13点08分
回复 a752075717 :…新建一个不做任何更改的程序,会出现同样问题吗。
2015年08月04日 05点08分
怎么找到window
2015年08月04日 06点08分
回复
��ǹ����14
:会
2015年08月04日 11点08分
level 6
Kelin🌌 楼主
现在,我们来深入剖析这个程序。请盯着左边的文件栏。如果你把它调没了就花点时间把它调出来。
首先,我讨厌复杂的东西。所以那些不需要知道的东西我没有多搭理它们。所以,我们忽略掉:
AppDelegate文件
GameScene.sks文件
Main.Storyboard文件
LaunchScreen文件
其它文件只要做好,足以写好游戏。
注意,主要工作集中在GameScene.swift上。所以我们单击它。
2015年07月23日 13点07分 3
level 6
Kelin🌌 楼主
发现主要是一个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)
好吧,一会儿继续。
2015年07月23日 13点07分 4
赞!还有可能答案
2015年07月30日 09点07分
我感觉我的一只脚已经踏进去了
2015年08月04日 06点08分
原来那个是x y点 中心点
2015年08月04日 07点08分
self.addChild(k)改不了啊
2015年08月04日 07点08分
level 6
Kelin🌌 楼主
开始剖析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)
把她交给屏幕。。。
好吧,现在开始正式进入第一个游戏的开发。尽管估计没几个人听明白了。不过,我已经把需要用到的讲完了。其实构成游戏的部分就是这些。只要用好,就没问题。
2015年07月23日 13点07分 5
如何恢复默认设置
2015年08月04日 07点08分
我作业不会做怎么版
2015年08月04日 07点08分
教程没有什么问题
2015年08月04日 07点08分
回复
������7788
:...非常抱歉,我...我帮不了您...
2015年08月04日 12点08分
level 6
Kelin🌌 楼主
介绍一下我们的测试项目:扔飞镖打移动的方块。改造得好一点可以弄成4399的海上保卫战游戏,不知道那个还在不在呢。
图片我没有仔细做,需要一张飞镖,一张基地,一张敌人,还有,图标是重点,我以后会仔细说的,有很多人栽了。
我有点偷懒,把基地和敌人都弄成了一张图片。你们可以做一个好一点的,但不要太好,这只是一个测试。
当然,如果测试做完了,你自己非常喜欢,我可以教你建造菜单。
好吧,等明天。
2015年07月23日 13点07分 6
老板,这是截图么!!
2015年07月30日 10点07分
回复 zz我的海角 :…拍摄的…模拟器…
2015年07月30日 15点07分
图片上哪里找
2015年08月04日 08点08分
我当时用的是系统自带的画图。。。当然PS更好。希望大家DIY。。。
2015年08月04日 12点08分
level 6
Kelin🌌 楼主
说一下那个坑爹的图标的问题。在左边的文件中找到蓝色的Images文件夹,请删掉AppIcon文件,然后右键NewAppIcon新建一个图标文件。
你会看见许多卡槽。是的,这么多。如图,是我做的一款游戏的图标们。她们大小不一,所以非常让我头痛。
我们需要把前两排填满。注意每几个卡槽下面都有什么29pt,40pt之类的,上面还有什么1x,2x之类的。注意,比如是40pt的,还是2x,你就需要准备80像素乘以80像素的图标文件。把她们相乘,明白吗…
2015年07月24日 01点07分 7
好坑我一定是xcode版本太高,找不到image文件夹
2015年07月30日 10点07分
。。mvc模式。。
2015年07月30日 10点07分
回复 zz我的海角 :找到了吗?一个可爱的蓝色文件夹…上面画着许多矩形…
2015年07月30日 15点07分
新版是在assets.xcassets 里
2015年07月31日 00点07分
level 6
Kelin🌌 楼主
除了图标,有人更关心的是当游戏运行时第一载入的图片,比如开发者,或者开发组织。
这个叫launch screen,这个文件你可以找到,叫做LaunchScreen.xib。
但是,在这里放置你们公司的图标 并不是很好。调整大小和位置都很复杂。不过如果你真的要试试我也不会拦着你,但是我建议你把整个LaunchScreen.xib文件的背景调整成黑色的,这样就不用管她了。
至于那些公司图标,开发者名称等写在哪里,以后会说的。
2015年07月24日 01点07分 8
level 6
Kelin🌌 楼主
第一步,参照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)
别忘了这句。
现在,基地已经进来了。
2015年07月24日 01点07分 10
这一步不太懂
2015年08月04日 12点08分
对比一下这个帖子,和4楼的帖子,原理一模一样,只不过更换了内容,一个是标签,一个是图片。。。
2015年08月04日 12点08分
回复
��ǹ����14
:图片拖进去之后变成标签了
2015年08月05日 03点08分
回复
��ǹ����14
:这个代码和什么替换 能发张图吗 邮箱10374031592qq.com
2015年08月05日 03点08分
level 6
Kelin🌌 楼主
当然,这一点是不够的。我们还需要初始化飞镖,以及敌人,还有计分器等等。这些东西写在初始化函数里是不可以的,因为我们的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) {
2015年07月24日 01点07分 11
出现错误了
2015年08月07日 03点08分
出现了consecutive declarations on a line must be separated by ;
2015年08月07日 04点08分
还有Expected a digit after integer literal prefix
2015年08月07日 04点08分
这两个错误
2015年08月07日 04点08分
level 6
Kelin🌌 楼主
说一下上文的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)
把敌人,标签,刀子加进去。
2015年07月24日 01点07分 12
楼主
2015年08月07日 03点08分
didMoveToView函数前面能说清楚一点吗[酷]
2015年08月09日 04点08分
楼主 我顺利进入这一步了
2015年08月10日 07点08分
xy有问题啊
2015年08月10日 07点08分
level 6
Kelin🌌 楼主
现在,进入点击扔飞镖阶段。
进入touchesBegan函数:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch in (touches as! Set<UITouch>) {
}
}
把中间的全都删掉,只留下如上的一堆。
这里会用到一点向量缩放的知识。
当我们单击屏幕之后,我们会得到一个坐标,我们希望飞镖从中心开始,朝着那里飞。我们更希望她可以飞的再远一些,最后飞出屏幕。
2015年07月24日 02点07分 13
没有找到这个函数
2015年08月09日 04点08分
是真的没有
2015年08月10日 12点08分
level 6
Kelin🌌 楼主
如图可以解释清楚:
2015年07月24日 02点07分 14
这里出错了 怎么发图片给你
2015年08月10日 12点08分
显示了头文件没有包含进来
2015年08月10日 12点08分
level 6
Kelin🌌 楼主
以下代码写在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语句内部的代码。
运行一下,成功吗?哈哈。
2015年07月24日 02点07分 15
这个函数没找到啊
2015年08月10日 07点08分
我发给你我的
2015年08月10日 07点08分
我用什么把我的发给你
2015年08月10日 07点08分
level 6
Kelin🌌 楼主
继续写碰撞函数:
我们先确定一个方块,就是以飞镖为中心的一个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() //召唤一个新的敌人
}
}
当然,从头到尾我们一直只有一个敌人,只是我们等她死了之后,又把她拎上来整理了一下而已。
现在你测试的时候,敌人还看不见。但是如果你向左下角狂扫飞镖,你会发现标签有变化。这说明敌人存在,只是位置不是人类能看见的罢了。
2015年07月24日 02点07分 17
level 6
Kelin🌌 楼主
好吧,让敌人动起来:
还记得那个t变量吗?让敌人停止10帧频的变量,现在该用上了。
写一个框架:
if t<0 {
//一会在这里写东西,记住了,这里是“主位置”
t=10
}else{
t=t-1
}
以上代码就可以让敌人休息10帧。
中间那个位置写移动代码。
首先,敌人向哪个方向移动是随机的。
所以,在”主位置“里写下如下代码:
//switch语句用于选择分支
//arc4random()用于生产一个庞大的随机数据
//arc4random()%4可以产生0到3的数据
//Int(arc4random()%4)得到随机的整数,从0到3,一共4个数,代表四个方向
switch Int(arc4random()%4) {
case 0: //12点钟方向
break
case 1: //3点钟方向
break
case 2: //6点钟方向
break
case 3: //9点钟方向
break
default: //这个东西不写会报错
break
}
然后分别填写代码:
2015年07月24日 03点07分 18
level 6
Kelin🌌 楼主
switch Int(arc4random()%4) {
case 0:
let elo=CGPoint(x: Int(en.position.x), y: Int(en.position.y)+100+Int(arc4random()%1000))
//向12点钟方向前进100到1100之间的随机距离,控制Y坐标
let ac = SKAction.moveTo(elo, duration: 2)
//定义动作
en.runAction(SKAction.repeatActionForever(ac))
//执行动作
break
case 1:
let elo=CGPoint(x: Int(en.position.x)+100+Int(arc4random()%1000), y: Int(en.position.y))
//向3点钟方向前进100到1100之间的随机距离,控制X坐标
let ac = SKAction.moveTo(elo, duration: 2)
en.runAction(SKAction.repeatActionForever(ac))
break
case 2:
let elo=CGPoint(x: Int(en.position.x), y: Int(en.position.y)-100-Int(arc4random()%1000))
let ac = SKAction.moveTo(elo, duration: 2)
en.runAction(SKAction.repeatActionForever(ac))
break
case 3:
let elo=CGPoint(x: Int(en.position.x)-100-Int(arc4random()%1000), y: Int(en.position.y))
let ac = SKAction.moveTo(elo, duration: 2)
en.runAction(SKAction.repeatActionForever(ac))
break
default:
break
}
现在测试一下,你或许会看见敌人。
但是她很可能朝着一个固定的方向运动很久,然后再也不会回来了。。。
2015年07月24日 03点07分 19
level 6
Kelin🌌 楼主
所以我们需要检测它有没有越界:
switch Int(arc4random()%4) {
case 0:
if Int(e.position.y) > Int(CGRectGetMaxY(self.frame)){ //如果越界
ch=2 //ch就等于2
}
let elo=CGPoint(x: Int(en.position.x), y: Int(en.position.y)+100+Int(arc4random()%1000))
let ac = SKAction.moveTo(elo, duration: 2)
e.runAction(SKAction.repeatActionForever(ac))
break
case 1:
if Int(e.position.x) > Int(CGRectGetMaxX(self.frame)){ //如果越界
ch=3 //ch就等于3
}
let elo=CGPoint(x: Int(en.position.x)+100+Int(arc4random()%1000), y: Int(en.position.y))
let ac = SKAction.moveTo(elo, duration: 2)
e.runAction(SKAction.repeatActionForever(ac))
break
case 2:
if Int(e.position.y) < 100 {
ch=0
}
let elo=CGPoint(x: Int(en.position.x), y: Int(en.position.y)-100-Int(arc4random()%1000))
let ac = SKAction.moveTo(elo, duration: 2)
e.runAction(SKAction.repeatActionForever(ac))
break
case 3:
if Int(e.position.x) < 100 {
ch=1
}
let elo=CGPoint(x: Int(en.position.x)-100-Int(arc4random()%1000), y: Int(en.position.y))
let ac = SKAction.moveTo(elo, duration: 2)
e.runAction(SKAction.repeatActionForever(ac))
break
default:
break
}
2015年07月24日 03点07分 20
level 6
Kelin🌌 楼主
那么这个ch到底是做什么的呢?
我规定了4个方向:
0=12点方向
1=3点方向
2=6点方向
3=9点方向
四个顺时针方向。
在上面的代码中,比如 case 0 里面,因为进入了 case 0 ,我们就希望敌人向着12点方向前进。但是如果敌人已经要出去了,我们自然希望她回来,所以我们希望她向着6点方向回来。因此ch就等于6点钟方向的数值,也就是2.
这就是为什么case 0里面如果越界,ch要等于2.
2015年07月24日 03点07分 21
level 6
Kelin🌌 楼主
现在,我们需要读取ch,让敌人回来。
接着写(和switch并列着,写一个新的switch):
switch ch {
case 0:
let elo=CGPoint(x: Int(en.position.x), y: Int(en.position.y)+100+Int(arc4random()%1000))
let ac = SKAction.moveTo(elo, duration: 1)
e.runAction(SKAction.repeatActionForever(ac))
break
case 1:
let elo=CGPoint(x: Int(en.position.x)+100+Int(arc4random()%1000), y: Int(en.position.y))
let ac = SKAction.moveTo(elo, duration: 1)
e.runAction(SKAction.repeatActionForever(ac))
break
case 2:
let elo=CGPoint(x: Int(en.position.x), y: Int(en.position.y)-100-Int(arc4random()%1000))
let ac = SKAction.moveTo(elo, duration: 1)
e.runAction(SKAction.repeatActionForever(ac))
break
case 3:
let elo=CGPoint(x: Int(en.position.x)-100-Int(arc4random()%1000), y: Int(en.position.y))
let ac = SKAction.moveTo(elo, duration: 1)
e.runAction(SKAction.repeatActionForever(ac))
break
default:
break
}
和刚才的代码几乎一样,你完全可以粘一些。现在就算是写完了。
2015年07月24日 03点07分 22
1 2 3 尾页