版本记录
版本号 | 时间 |
---|---|
V1.0 | 2017.08.12 |
前言
SpriteKit框架使用优化的动画系统,物理模拟和事件处理支持创建基于2D精灵的游戏。接下来这几篇我们就详细的解析一下这个框架。相关代码已经传至GitHub - 刀客传奇,感兴趣的可以阅读另外几篇文章。
1. SpriteKit框架详细解析(一) —— 基本概览(一)
2. SpriteKit框架详细解析(二) —— 一个简单的动画实例(一)
SpriteKit vs. Unity
首先需要说明:
说明:本文是在Swift 4, iOS 11, Xcode 9环境和条件下开发的。
SpriteKit 和 Swift 结合在一起还是很有意思的。
- SpriteKit:SpriteKit是在iOS上制作游戏的最佳方式之一。 它易于学习,功能强大,并得到Apple的全力支持。
- Swift:Swift是一种易于使用的语言,特别是如果您是iOS平台的初学者。
在本文中,您将学习如何使用Apple的2D游戏框架SpriteKit
创建一个简单的2D游戏 - 使用Swift!
目前SpriteKit最受欢迎的替代方案是一个名为Unity
的游戏框架。 Unity最初是作为3D引擎开发的,但它也具有完全内置的2D支持。
在开始之前,先考虑一下SpriteKit或Unity是否是游戏的最佳选择。
1. SpriteKit的优点
It’s built right into iOS - 它内置于iOS中。无需下载额外的库或具有外部依赖性。您还可以无缝地使用其他iOS API,如iAd,应用程序内购买等,而无需依赖额外的插件。
It leverages your existing skills - 它利用您现有的技能。如果你已经了解Swift和iOS开发,你可以非常快速地获取SpriteKit。
It’s written by Apple - 它是由Apple编写的。这让您有信心在Apple的所有新产品上都能得到很好的支持。例如,您可以使用相同的SpriteKit代码使您的游戏在iOS,macOS和tvOS上顺利运行。
It’s free - 免费。也许是最佳理由之一!您可以免费获得SpriteKit的所有功能。 Unity确实有免费版本,但它没有Pro版本的所有功能。例如,如果要避免使用Unity启动画面,则需要升级。
2. Unity的优势
Cross-platform - 跨平台。 这是最重要的一个。 如果您使用SpriteKit,您将被锁定在Apple生态系统中。 使用Unity,您可以轻松地将游戏移植到Android,Windows等。
Visual scene designer - 视觉场景设计师。 通过单击按钮,Unity可以非常轻松地布置您的关卡并实时测试您的游戏。 SpriteKit确实有一个场景编辑器,但与Unity提供的相比,它是非常基础的。
Asset store -资源商店。 Unity附带内置资源商店,您可以在其中为游戏购买各种组件。 其中一些组件可以为您节省大量的开发时间!
More powerful - 更加强大。 通常,Unity具有比
SpriteKit / Scene Kit
组合更多的特性和功能。
Which Should I Choose? - 应该如何选择
现在你可能会想,“好吧,我应该选择哪个2D框架?”
答案取决于您的目标:
- 如果您是一个完全的初学者,或者只关注Apple生态系统:使用SpriteKit - 它内置,易于学习,并且可以完成工作。
- 如果你想要跨平台,或者有一个更复杂的游戏:使用Unity - 它更强大,更灵活。
这里我们选择SpriteKit,所以请继续阅读以开始使用此SpriteKit教程!
Getting Started - 正式进入正题
我们新建立一个类名称为GameViewController.swift
,然后在viewDidLoad()
最后加入下面代码:
let scene = GameScene(size: view.bounds.size)
let skView = view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
scene.scaleMode = .resizeFill
skView.presentScene(scene)
GameViewController
是一个普通的UIViewController
,它的根视图是一个SKView
,它是一个包含SpriteKit
场景的视图。
在这里,您已经指示viewDidLoad()
在启动时创建GameScene
的新实例,其大小与视图本身相同。 将skView.showsFPS
设置为true表示将显示帧速率指示符。
这是初始设置,现在是时候在屏幕上获取一些东西了!
Adding a Sprite - 添加一个Spirit
打开GameScene.swift
,并在GameScene
中添加如下代码:
// 1
let player = SKSpriteNode(imageNamed: "player")
override func didMove(to view: SKView) {
// 2
backgroundColor = SKColor.white
// 3
player.position = CGPoint(x: size.width * 0.1, y: size.height * 0.5)
// 4
addChild(player)
}
这就是它的作用,一步一步:
- 1)在这里你为玩家声明一个私有常量(即忍者),这是精灵的一个例子。 如您所见,创建精灵很简单 - 只需传入要使用的图像名称即可。
- 2)在SpriteKit中设置场景的背景颜色就像设置
backgroundColor
属性一样简单。 在这里你把它设置为白色。 - 3)将精灵定位在水平方向上10%,并垂直方向居中。
- 4)要使精灵出现在场景中,必须将其添加为场景的子画面。
Build并运行,效果如下所示:
Moving Monsters - 移动怪物
接下来,你想在场景中添加一些怪物,让你的忍者进行战斗。 为了让事情变得更有趣,你希望怪物能够移动 - 否则就不会有太大的挑战! 您将在屏幕右侧略微创建怪物并设置一个动作,告诉他们向左移动。
将以下方法添加到类中的GameScene.swift
,在结束大括号之前:
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
func addMonster() {
// Create sprite
let monster = SKSpriteNode(imageNamed: "monster")
// Determine where to spawn the monster along the Y axis
let actualY = random(min: monster.size.height/2, max: size.height - monster.size.height/2)
// Position the monster slightly off-screen along the right edge,
// and along a random position along the Y axis as calculated above
monster.position = CGPoint(x: size.width + monster.size.width/2, y: actualY)
// Add the monster to the scene
addChild(monster)
// Determine speed of the monster
let actualDuration = random(min: CGFloat(2.0), max: CGFloat(4.0))
// Create the actions
let actionMove = SKAction.move(to: CGPoint(x: -monster.size.width/2, y: actualY),
duration: TimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
monster.run(SKAction.sequence([actionMove, actionMoveDone]))
}
addMonster()
的第一部分应该基于你到目前为止学到的东西是有意义的:你做一些简单的计算来确定你想要创建对象的位置,设置对象的位置,并将它添加到场景中。
这里的新元素是添加动作。 SpriteKit
提供了许多非常有用的内置操作,可帮助您轻松地随时间更改精灵的状态,例如移动操作,旋转操作,淡入淡出操作,动画操作等。在这里你对怪物使用三个动作:
- SKAction.move(to:duration :):您可以使用此操作使对象在屏幕外移动到左侧。您可以指定运动需要多长时间,此处您可以在2-4秒内随机改变持续时间。
- SKAction.removeFromParent():SpriteKit附带了一个有用的操作,可以从其父节点中删除节点,从而有效地将其从场景中删除。在这里,您可以使用此操作从不再可见的场景中移除怪物。这很重要,否则你将拥有无穷无尽的怪物供应,并最终消耗所有设备资源。
- SKAction.sequence(_ :):序列操作允许您将按顺序执行的一系列操作链接在一起,一次一个。这样,您可以先执行
move to
操作,一旦完成,您将执行remove from parent
操作。
注意:此代码块包含一些辅助方法,可使用
arc4random()
在一个范围内生成随机数。 这足以满足此游戏中简单的随机数生成需求,但如果您需要更高级的功能,请查看GameplayKit中
的随机数API。
继续前行之前的最后一件事。 你需要实际调用方法来创建怪物! 为了让事情变得有趣,让我们随着时间的推移不断产生怪物。
只需将以下代码添加到didMove(to :)
的末尾:
run(SKAction.repeatForever(
SKAction.sequence([
SKAction.run(addMonster),
SKAction.wait(forDuration: 1.0)
])
))
在这里,您运行一系列操作来调用代码块,然后等待1秒钟。 你无休止地重复这一系列的动作。
Build并运行项目; 现在你应该看到怪物愉快地在屏幕上移动:
Shooting Projectiles - 射击弹丸
在这一点上,忍者只是有一些行动 - 所以是时候添加射击了! 有许多方法可以实现拍摄,但是对于这个游戏,你要做到这一点,当用户点击屏幕时,一个射弹从玩家向点击的方向射击。
你需要使用move to
动作来实现这一点,但为了使用它你必须做一些数学运算。move to
动作需要您为射弹提供目的地,但您不能仅使用触摸点,因为触摸点代表相对于玩家射击的方向。 你实际上想要保持射弹穿过触摸点直到它离开屏幕。
这是一张说明此事的图片:
如您所见,您有一个由原点到触点的x和y偏移创建的小三角形。 你只需要制作一个具有相同比例的大三角形 - 而且你知道你希望其中一个端点离开屏幕。
要运行这些计算,如果您有一些基本矢量数学例程(比如添加和减去矢量的方法),这确实很有帮助。 但是,SpriteKit默认没有,所以你必须自己编写。
幸运的是,由于Swift运算符重载的强大功能,它们非常容易编写。 在GameScene
类之前,将这些函数添加到文件的顶部:
func +(left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x + right.x, y: left.y + right.y)
}
func -(left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x - right.x, y: left.y - right.y)
}
func *(point: CGPoint, scalar: CGFloat) -> CGPoint {
return CGPoint(x: point.x * scalar, y: point.y * scalar)
}
func /(point: CGPoint, scalar: CGFloat) -> CGPoint {
return CGPoint(x: point.x / scalar, y: point.y / scalar)
}
#if !(arch(x86_64) || arch(arm64))
func sqrt(a: CGFloat) -> CGFloat {
return CGFloat(sqrtf(Float(a)))
}
#endif
extension CGPoint {
func length() -> CGFloat {
return sqrt(x*x + y*y)
}
func normalized() -> CGPoint {
return self / length()
}
}
这些是一些矢量数学函数的标准实现。 如果您对此处发生的事情感到困惑或对矢量数学不熟悉,请查看此vector math explanation。
接下来,在关闭大括号之前,再向GameScene
类添加一个新方法:
override func touchesEnded(_ touches: Set, with event: UIEvent?) {
// 1 - Choose one of the touches to work with
guard let touch = touches.first else {
return
}
let touchLocation = touch.location(in: self)
// 2 - Set up initial location of projectile
let projectile = SKSpriteNode(imageNamed: "projectile")
projectile.position = player.position
// 3 - Determine offset of location to projectile
let offset = touchLocation - projectile.position
// 4 - Bail out if you are shooting down or backwards
if offset.x < 0 { return }
// 5 - OK to add now - you've double checked position
addChild(projectile)
// 6 - Get the direction of where to shoot
let direction = offset.normalized()
// 7 - Make it shoot far enough to be guaranteed off screen
let shootAmount = direction * 1000
// 8 - Add the shoot amount to the current position
let realDest = shootAmount + projectile.position
// 9 - Create the actions
let actionMove = SKAction.move(to: realDest, duration: 2.0)
let actionMoveDone = SKAction.removeFromParent()
projectile.run(SKAction.sequence([actionMove, actionMoveDone]))
}
这里做了很多事情,一步一步看一下。
- 1)关于SpriteKit的一个很酷的事情是它在
UITouch
上包含一个带有location(in :)
和previousLocation(in :)
方法的类别。这些可以让您在SKNode
的坐标系中找到触摸的坐标。在这种情况下,您可以使用它来找出触摸在场景坐标系中的位置。 - 2)然后你创建一个射弹并将其放置在玩家开始的位置。请注意,你还没有把它添加到场景中,因为你必须先做一些检查 - 这个游戏不允许忍者向后射击。
- 3)然后从触摸位置减去射弹的当前位置,以获得从当前位置到触摸位置的矢量。
- 4)如果X值小于0,则表示玩家正在尝试向后射击。这个游戏是不允许的(真正的忍者不回头!),所以return。
- 5)否则,可以将射弹添加到场景中。
- 6)通过调用
normalized()
将偏移量转换为单位向量(长度为1)。这将使得在同一方向上制作具有固定长度的矢量变得容易,因为1 * length = length
。 - 7)将单位矢量乘以您要设计的方向1000,为什么是1000?它肯定足够长,可以超越屏幕边缘。 :]
- 8)将射击量添加到当前位置,以获得最终应该在屏幕上显示的位置。
- 9)最后,创建
move(to:,duration :)
和removeFromParent()
动作,就像你之前为怪物做的那样。
Build并运行。现在,你的忍者应该能够对即将到来的怪物开火!
后记
本篇主要讲述了创建一个简单的2D游戏,感兴趣的给个赞或者关注~~~