博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Swift - 跑酷游戏开发(SpriteKit游戏开发)
阅读量:4600 次
发布时间:2019-06-09

本文共 13356 字,大约阅读时间需要 44 分钟。

一,下面演示了如何开发一个跑酷游戏,实现的功能如下:
1,平台工厂会不断地生成平台,并且向左移动。当平台移出游戏场景时就可将其移除。
2,生成的平台宽度随机,高度随机。同时短平台踩踏的时候会下落。
3,奔跑小人设置了三种状态:奔跑,跳跃,打滚。
4,跳跃时可以再进行二段跳。
5,如果在一定高度落下,会先打滚再变成奔跑状态。同时平台会有震动效果。
6,起跳时会有特效(身后播放尘土飞扬特效)
7,跳跃,碰撞等都使用了苹果的物理引擎
二,效果图如下:
 
  
  
三,实现代码
1,奔跑小人类 - Runner.swift
注意:由于跳和打滚的动作不像跑的动作需要循环播放,所以就不需要用repeatActionForever。打滚动作结束后就执行跑步操作。而跳跃动作由于不知道什么时候落地,所以会由外部控制它的动作转变。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//平台类
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
它负责生产平台零件然后传给平台类进行组装。同时负责不断地移动平台,以及移除场景之外的平台。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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
记录着物理世界里的种类标示符。有小人,平台,场景边缘三种标识。
1
2
3
4
5
6
7
8
9
10
11
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()方法中,进行平台移动,新平台的创建,随游戏进行不断加速以及小人位置修正等功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
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)
    
}
}
四,源码下载
   

转载于:https://www.cnblogs.com/Free-Thinker/p/4841103.html

你可能感兴趣的文章
GSM模块_STM32实现GPRS与服务器数据传输经验总结
查看>>
5.Python进阶_循环设计
查看>>
Android采访开发——2.通用Android基础笔试题
查看>>
UVa 442 Matrix Chain Multiplication(矩阵链,模拟栈)
查看>>
多种方法求解八数码问题
查看>>
spring mvc ModelAndView向前台传值
查看>>
(黑客游戏)HackTheGame1.21 过关攻略
查看>>
Transparency Tutorial with C# - Part 2
查看>>
android 文件上传
查看>>
ASCII 码表对照
查看>>
javascript的DOM操作获取元素
查看>>
Shuffle'm Up(串)
查看>>
20145219 《Java程序设计》第06周学习总结
查看>>
C# 执行bat文件并取得回显
查看>>
基于YOLO的Autonomous driving application__by 何子辰
查看>>
javascript中的继承
查看>>
iOS-如何写好一个UITableView
查看>>
如何在Objective-C中实现链式语法
查看>>
select2 下拉搜索控件
查看>>
WebAPI常见的鉴权方法,及其适用范围
查看>>