开始游戏教程前,首先介绍一下SpriteKit是什么?
SpriteKit提供了一个图形渲染和动画的基础结构,你可以使用它让任意类型的纹理图片或者精灵动起来。SpriteKit使用渲染循环,利用图形硬件渲染动画的每一帧。
在iOS传统的view的系统中,view的内容被渲染一次后就将一直等待,直到需要渲染的内容发生改变(比如用户发生交互,view的迁移等)的时候,才进行下一次渲染。这主要是因为传统的view大多工作在静态环境下,并没有需要频繁改变的需求。而对于SpriteKit来说,其本身就是用来制作大多数时候是动态的游戏的,为了保证动画的流畅和场景的持续更新,在SpriteKit中view将会循环不断地重绘。
动画和渲染的进程是和SKScene对象绑定的,只有当场景被呈现时,这些渲染以及其中的action才会被执行。SKScene实例中,一个循环按执行顺序包括:
每一帧开始时,SKScene的-update:方法将被调用,参数是从开始时到调用时所经过的时间。在该方法中,我们应该实现一些游戏逻辑,包括AI,精灵行为等等,另外也可以在该方法中更新node的属性或者让node执行action
在update执行完毕后,SKScene将会开始执行所有的action。因为action是可以由开发者设定的(还记得runBlock:么),因此在这一个阶段我们也是可以执行自己的代码的。
在当前帧的action结束之后,SKScene的-didEvaluateActions将被调用,我们可以在这个方法里对结点做最后的调整或者限制,之后将进入物理引擎的计算阶段。
然后SKScene将会开始物理计算,如果在结点上添加了SKPhysicsBody的话,那么这个结点将会具有物理特性,并参与到这个阶段的计算。根据物理计算的结果,SpriteKit将会决定结点新的状态。
然后-didSimulatePhysics会被调用,这类似之前的-didEvaluateActions。这里是我们开发者能参与的最后的地方,是我们改变结点的最后机会。
一帧的最后是渲染流程,根据之前设定和计算的结果对整个呈现的场景进行绘制。完成之后,SpriteKit将开始新的一帧。
在了解了一些SpriteKit的基础概念后,就跟着iFIERO来创建一个简单的游戏作为开启游戏入门之旅吧。
此《宇宙大战 Space Battle》教程共分为三个章节系列,
(一)宇宙大战 Space Battle — 新建场景Scene、导入各个SpriteNode精灵、Particle粒子节点及建立背景音乐(你正在此处进行学习)
(二)宇宙大战 Space Battle — 创建无限循环的背景Endless、监测精灵之间的物体碰撞及物理引擎Accleroation
(三)宇宙大战 Space Battle — 各个场景SCENE之间的切换、利用UserDefaults统计分数
你将在此教程中的三个系列当中学到如下的技能:
SpaceBattle 宇宙大战 在此游戏中您将获得如下技能:
1、LaunchScreen 学习如何设置游戏启动画面;
2、Scenes 学习如何切换不同的场景 主菜单+游戏场景+游戏结束场景;
3、Accleroation 利用重力加速度 让飞船左右移动;
4、Endless Background 无限循环背景;
5、Scene Edit 直接使用可见即所得操作;
6、UserDefaults 保存游戏分数、最高分;
7、Random 利用可复用的随机函数生成Enemy;
8、Background Music 如何添加背景音乐;
9、Particle 粒子爆炸特效;
应用以上各项SpriteKit与Swift技能,你开发出来的最终手机游戏的效果为如下所示:
一、教程开始 Getting Started
游戏开始前,请下载本教程的初始项目(http://www.ifiero.com/uploads/SpaceBattle-01Starter.zip)。本游戏是由SpriteKit框架、Swift语言,XCODE开发工具进行开发的。
1、打开XCODE(请用正式版,非Beta版),选择Create a new Xcode project,选择iOS->Game,输入Product Name(这里命名为SpaceBattle),开发语言Language选择Swfit,点击Next,工程即新建完毕
2、选择Genrnal面板,因为此Space Battle游戏为竖屏游戏,所以去除勾选Deployment Info -> Device Orientation中的Landscape Left 与Landscape Right,我们不需要横屏效果
3、删除XCode左侧目录中的 Action.sks(暂时没有用到),修改GameScene.swift及GameViewController.swift的相关代码
二、可视化编辑场景 Introducing the Sprite Kit Visual Editor
1.首先需要编辑场景.sks文件,打开GameScene.sks文件,设置场景的尺寸为iPAD4:3的比例(W:1536,H:2048),并删除场景中的文字。
2.拖动音乐文件到导航栏navigator->SpaceBattle文件夹,勾选Copy items if needed,Added folders选择Create groups,Add to targets勾选SpaceBattle
3.拖动游戏工程所需要的图片到Assets.xcassets文件夹
4.资源导入后,左侧的导航栏navigator如下图所示
5.Assets图库中的图片尺寸分为1x,2x,3x,你只需设置1x的图片尺寸大小即可,SpriteKit会自动根据你运行的device设备尺寸(iPhoneX,iPhone,iPhone Plus,iPad)进行相应比例的调整。
非常的棒,你已经学会如何导入Mac电脑中的资源文件(图片、音乐、粒子)到SpaceBattle游戏工程内中了。
那么,现在我们就来学习如何新建SpriteKit精灵节点吧!
6.选择左侧导航栏Navigator的GameScene.sks,直接拖动一个Color Sprite到场景中,选中精灵,设置Position(0,0),修改texture为BG_SpaceBattle_planet(AssetsAssets.xcassets文件夹的名称),并命名精灵节点的名称 Name为bg。
7.现在你可以运行模拟器(XCode -> Product -> Run),看看你的游戏是否正确显示你刚刚建立的精灵节点。
棒棒哒! 你已学会了如何在场景中建立精灵节点及如何运行模拟器进行调试!
三、SpriteKit Physics 物理引擎
1.Spritekit提供了一个默认的物理模拟系统,用来模拟真实物理世界,可以使得编程者将注意力从力学碰撞和重力模拟的计算中解放出来,通过简单地代码来实现物理碰撞的模拟,而将注意力集中在更需要花费精力的地方。现在,让我们来学习这个系统的使用吧。
首先需要认识两个类,一个是场景scene的属性类SKPhysicsWorld,这个类基于场景,只能被修改但是不能被创建,这个类负责提供重力和检查碰撞(碰撞需要实现SKPhysicsContactDelegate代理协议),另一个就是SKPhysicsBody类,你可以对你的SKNode节点添加物理体属性,来让他们可以参与物理模拟的相关计算。
SpriteKit SKPhysicsBody类物理体的属性图表
属 性 | 功 能 | 图示 |
---|---|---|
mass | 它决定力是如何影响主体,以及当主体参与碰撞时它有多大的动量,以千克为单位 | |
friction | 它决定了物体的光滑程度.取值范围为从0.0(表面光滑,物体滑动很顺畅,就像小冰块似的)到1.0(在表面滑动是,物体会很快停止) | -- |
linearDamping | 物体的线性阻尼.取值范围为0.0(速度从不衰减)到1.0(速度立即衰减).默认值为0.1该属性被用于模拟水流或者空气的阻力. | |
angularDamping | 物体的角速度阻尼.取值范围为0.0(速度从不衰减)到1.0(速度立即衰减).默认值为0.1该属性被用于模拟水流或者空气的阻力. | |
restitution | 描述了当物理实体从另外一个物体上弹出时,还拥有多少能量.基本上我们称之为"反弹力".它的取值介于0.0(完全不反弹)到1.0(和物体碰撞反弹是所受的力与刚开始碰撞时的力的大小相同)之间.默认值为0.2 | |
density | 物体的密度,以千克每立方米为单位.密度是根据单位体积的质量来定义的.密度越高,体积越大,物体也就会越重.密度的默认值为1.0 | -- |
affectedByGravity | 设置物体是否受重力的影响.所有的物体默认的情况都是受重力影响,但是开发者可以简单的吧这个标记设置为NO,使其不受重力影响. | |
allowsRotation | 设置物体是否受到一个旋转力的影响,默认为YES,如果该值设置为NO,物理体将忽略施加在它身上所有的力 | -- |
resting | 设置物理体是否在休息.物理引擎对于一段时间内没有移动过的物体做了一个优化,把他们标记为"正在休息(resting)",这样,物理引擎就不需要对它们进行计算了.如果你想要手动的唤醒一个正在休息的物体,简单的把resting设置为NO即可 | -- |
categoryBitMask | 一个16进制数,定义了物体的类别.场景中每一个物理体都可以分配到超过32个不同的种类里面,每个对应位中的值 | |
collisionBitMask | 一个16进制数,定义哪种类别的物理体可以与之发生碰撞.当两个物体相关联的时候,就可能发生一个碰撞.这个物体的位相对于其他物体的类别做一个逻辑上的加法操作.如果结果是一个非零的值,则该物体收到碰撞的影响 | -- |
contactTestBitMask | 一个16进制数,两个物体碰撞后发出通知 didBegin可接收到通知 | -- |
usesPreciseCollisionDetection | 设置物体是否使用更精准的碰撞算法.默认情况下,除非确实有必要,Sprite Kit并不会启动精确的冲突检测,因为这样运行效率更高.但是不启动精确的冲突检测会有一个副作用,如果一个物体移动的非常快(比如一个子弹),它可能会直接穿过其他物体.如果这种情况确实发生了,你就应该尝试启动更精准的冲突检测了 | -- |
velocity | 物理体的速度矢量 | |
angularVelocity | 物理体的角速度.角速度是一个围绕着一个轴矢量(0.0,0.0,1.0)的速度,单位是弧度每秒 |
以上图表感谢简书作者的收集整理://www.greatytc.com/p/4046bab3a63d
2.对SpriteKit PhysicsBody类的基础的概念了解后,我们现在就来新建player玩家飞船节点playerNode还有alien外星飞船精灵节点,并设置他们的物理属性。
class GameScene: SKScene,SKPhysicsContactDelegate {
private var playerNode:SKSpriteNode! /// 玩家 宇宙飞船
override func didMove(to view: SKView) {
physicsWorld.gravity = CGVector(dx: 0, dy: -9.8) /// 建立物理世界 重力向下
physicsWorld.contactDelegate = self /// 碰撞接触代理为当前scene (GameScene)
setupPlayer()
}
//MARK: - 玩家 宇宙飞船
func setupPlayer(){
playerNode = childNode(withName: "playerNode") as! SKSpriteNode
playerNode.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "Player"), size: SKTexture(imageNamed: "Player").size())
playerNode.physicsBody?.affectedByGravity = false // 不受物理世界的重力影响
playerNode.physicsBody?.isDynamic = true
playerNode.physicsBody?.categoryBitMask = PhysicsCategory.SpaceShip /// 唯一标识
playerNode.physicsBody?.collisionBitMask = PhysicsCategory.None /// 碰撞后要弹开吗
playerNode.physicsBody?.contactTestBitMask = PhysicsCategory.Alien /// 碰撞后发出通知
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
随机生成alien精灵节点
//MARK: - 生成随机Alien
@objc func spawnAlien() {
// 1 or 2
let i = Int(CGFloat(arc4random()).truncatingRemainder(dividingBy: 2) + 1)
let imageName = "Enemy0\(i)"
let alien = SKSpriteNode(imageNamed: imageName)
alien.anchorPoint = CGPoint(x: 0.5, y: 0.5)
alien.zPosition = 1
alien.name = "Alien"
var xPosition:CGFloat = 0.0
// 生成随机的x-Axis轴的位置
xPosition = CGFloat.random(min: -self.frame.size.width+alien.size.width, max: self.frame.size.width - alien.size.width)
alien.position = CGPoint(x: xPosition, y: self.frame.size.height + alien.size.height * 2)
self.addChild(alien)
// 物理体 PhysicsBody
alien.physicsBody = SKPhysicsBody(circleOfRadius: alien.size.width / 2) /// 设置物理身体
alien.physicsBody?.affectedByGravity = false /// 不受重力影响,自定义飞船移动速度;
alien.physicsBody?.categoryBitMask = PhysicsCategory.Alien /// 1.设置唯一属性
alien.physicsBody?.contactTestBitMask = PhysicsCategory.BulletBlue | PhysicsCategory.SpaceShip /// 2.和哪些节点Node发生碰撞后发出通知
alien.physicsBody?.collisionBitMask = PhysicsCategory.None /// 3.碰撞后是否弹开
let duration = CGFloat.random(min: CGFloat(1.0), max: CGFloat(3.8)) ///随机函数 返回二个数之间的随机数
let actionDown = SKAction.move(to: CGPoint(x: xPosition, y: -self.frame.size.height), duration: TimeInterval(duration))
alien.run(SKAction.sequence([actionDown,
SKAction.run({
alien.removeFromParent() // 移除节点;
})]))
}
CGFloat.random 拓展函数 返回二个数之间的随机数
import CoreGraphics
import SpriteKit
public extension CGFloat {
#if !(arch(x86_64) || arch(arm64))
func sqrt(a: CGFloat) -> CGFloat {
return CGFloat(sqrtf(Float(a)))
}
#endif
public static func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
public static func random(min: CGFloat, max: CGFloat) -> CGFloat {
assert(min < max)
return CGFloat.random() * (max - min) + min
}
}
在override func didMove(to view: SKView) {}内应用Timer.scheduledTimer间隔0.5秒生成Alien
// spawnAlien()
Timer.scheduledTimer(timeInterval: TimeInterval(0.5), target: self, selector: #selector(GameScene.spawnAlien), userInfo: nil, repeats: true)
现在Command+R 运行工程下(或选择XCODE -> Product-> Run),你在模拟器中应可以看到源源不断的alien外星飞船正向下俯冲
3.生成子弹及粒子效果
// MARK: - 生成子弹; 点击屏幕后才发射
func spawnBulletAndFire(){
/// 子弹
let bulletNode = SKSpriteNode(imageNamed: "BulletBlue")
bulletNode.position.x = playerNode.position.x
// 子弹的Y轴位置 因为playNode的AnchorPoit位于飞船中心 所以子弹发射时的瞬间位置位于飞船正中心,要加上飞船的半径,位于枪口;
bulletNode.position.y = playerNode.position.y + playerNode.size.height / 2
bulletNode.zPosition = 1
self.addChild(bulletNode)
bulletNode.physicsBody = SKPhysicsBody(circleOfRadius: bulletNode.size.width / 2)
bulletNode.physicsBody?.affectedByGravity = false // 子弹不受重力影响;
bulletNode.physicsBody?.categoryBitMask = PhysicsCategory.BulletBlue
bulletNode.physicsBody?.contactTestBitMask = PhysicsCategory.Alien
bulletNode.physicsBody?.collisionBitMask = PhysicsCategory.None
bulletNode.physicsBody?.usesPreciseCollisionDetection = true ///子弹飞速运动,设置探测精细碰撞
/// 把子弹往上移出屏幕
let moveTo = CGPoint(x: playerNode.position.x, y: playerNode.position.y + self.frame.size.height)
/*
* 粒子效果
* 1.新建一个SKNODE => trailNode
* 2.新建粒子效果SKEmitterNode,设置tragetNode = trailNode
* 3.子弹加上emitterNode
*/
let trailNode = SKNode()
trailNode.zPosition = 1
trailNode.name = "trail"
addChild(trailNode)
let emitterNode = SKEmitterNode(fileNamed: "ShootTrailBlue")! // particles文件夹存放粒子效果
emitterNode.targetNode = trailNode /// 设置粒子效果的目标为trailNode => 跟随新建的trailNode
bulletNode.addChild(emitterNode) /// 在子弹节点Node加上粒子效果;
bulletNode.run(SKAction.sequence([
SKAction.move(to: moveTo, duration: TimeInterval(0.5)),
SKAction.run({
bulletNode.removeFromParent() /// 移除 子弹bulltedNode
trailNode.removeFromParent() /// 移除 trailNode
})]))
}
我们将在第二章节学习飞船子弹的发射以及粒子效果的知识
四、到此,此章节就接近尾声了
我们已经学会了很多技能,包括如何新建工程,如何建立Sprite精灵节点,还有如何应用SpriteKit Physics物理引擎。你可以在此下载此章节的工程完整代码。(http://www.iFIERO.com/uploads/SpaceBattle-01final.zip)
五、更多内容
在下一章节当中,(二)宇宙大战 Space Battle — 创建无限循环的背景Endless、监测精灵之间的物体碰撞及物理引擎Accleroation,我们将学习如何监测SpriteKit Physics物理之间碰撞,如何销毁对象,如何监测屏幕Scene的点击事件以及物理引擎Accleroation的相关知识。
请注意,此《宇宙大战 Space Battle》教程共分为三个章节系列:
(一)宇宙大战 Space Battle — 新建场景Scene、导入各个SpriteNode精灵、Particle粒子节点及建立背景音乐(你正在此处进行学习)
(二)宇宙大战 Space Battle — 创建无限循环的背景Endless、监测精灵之间的物体碰撞及物理引擎Accleroation
(三)宇宙大战 Space Battle — 各个场景SCENE之间的切换、利用UserDefaults统计分数
更多游戏教学:iFIERO.COM -- 开源手机游戏教程网,让手机游戏开发变得简单!