利用Sprite Kit开发游戏入门指南
在游戏开发中,Sprite Kit 是一个强大的工具,它可以帮助开发者轻松创建出具有丰富交互性和动画效果的游戏。下面将详细介绍如何使用 Sprite Kit 来开发一款简单的游戏,包括玩家移动、敌人创建、射击功能等。
1. 调试信息设置
在开发过程中,场景右下角的节点计数和帧率信息对于调试非常有用,但在游戏发布时,你可能不希望它们显示出来。可以在
GameViewController
的
viewDidLoad
方法中,将
SKView
的
showsFPS
和
showsNodeCount
属性设置为
false
来关闭这些信息。
// 在 GameViewController 的 viewDidLoad 方法中添加以下代码
skView.showsFPS = false
skView.showsNodeCount = false
2. 玩家移动功能实现
为了实现玩家移动功能,需要创建一个代表玩家的类,并将其实例添加到场景中。
2.1 创建 PlayerNode 类
使用 Xcode 的文件菜单创建一个名为
PlayerNode
的新 Cocoa Touch 类,它是
SKNode
的子类。在
PlayerNode.swift
文件中,导入
SpriteKit
和
Foundation
框架,并添加以下代码:
import UIKit
import SpriteKit
import Foundation
class PlayerNode: SKNode {
override init() {
super.init()
name = "Player \(self)"
initNodeGraph()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
private func initNodeGraph() {
let label = SKLabelNode(fontNamed: "Courier")
label.fontColor = SKColor.darkGrayColor()
label.fontSize = 40
label.text = "v"
label.zRotation = CGFloat(M_PI)
label.name = "label"
self.addChild(label)
}
}
PlayerNode
本身不会显示任何内容,因为普通的
SKNode
没有绘制功能。
init()
方法会设置一个子节点
SKLabelNode
来进行实际的绘制。同时,为标签设置了旋转值,使其中的小写字母 “v” 倒置显示。
2.2 将玩家添加到场景中
在
GameScene.swift
文件中,添加一个属性来表示玩家节点,并在
init(size:, levelNumber:)
方法的末尾添加代码将玩家节点添加到场景中。
class GameScene: SKScene {
private var levelNumber: UInt
private var playerLives: Int
private var finished = false
private let playerNode: PlayerNode = PlayerNode()
init(size: CGSize, levelNumber: UInt) {
// 其他代码...
addChild(level)
playerNode.position = CGPointMake(CGRectGetMidX(frame), CGRectGetHeight(frame) * 0.1)
addChild(playerNode)
}
}
现在构建并运行应用程序,你会看到玩家出现在屏幕底部中间附近。
2.3 处理触摸事件实现玩家移动
在
GameScene.swift
文件的
touchesBegan(_, withEvent:)
方法中添加逻辑,让玩家可以通过触摸屏幕来移动。
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if location.y < CGRectGetHeight(frame) * 0.2 {
let target = CGPointMake(location.x, playerNode.position.y)
playerNode.moveToward(target)
}
}
}
由于
moveToward
方法尚未定义,需要在
PlayerNode.swift
文件中添加该方法的实现。
func moveToward(location: CGPoint) {
removeActionForKey("movement")
let distance = pointDistance(position, location)
let screenWidth = UIScreen.mainScreen().bounds.size.width
let duration = NSTimeInterval(2 * distance/screenWidth)
runAction(SKAction.moveTo(location, duration: duration), withKey:"movement")
}
该方法会计算移动的距离和时间,并创建一个
SKAction
来实现移动动画。同时,为了避免用户快速点击产生多个冲突的动作,会先移除之前的移动动作。
2.4 几何计算函数
在上述代码中使用了
pointDistance
函数,但 Xcode 无法找到该函数。需要创建一个新的 Swift 文件
Geometry.swift
,并添加以下几何计算函数:
import Foundation
import UIKit
func vectorMultiply(v: CGVector, m: CGFloat) -> CGVector {
return CGVectorMake(v.dx * m, v.dy * m)
}
func vectorBetweenPoints(p1: CGPoint, p2: CGPoint) -> CGVector {
return CGVectorMake(p2.x - p1.x, p2.y - p1.y)
}
func vectorLength(v: CGVector) -> CGFloat {
return CGFloat(sqrtf(powf(Float(v.dx), 2) + powf(Float(v.dy), 2)))
}
func pointDistance(p1: CGPoint, p2: CGPoint) -> CGFloat {
return CGFloat(sqrtf(powf(Float(p2.x - p1.x), 2) + powf(Float(p2.y - p1.y), 2)))
}
这些函数用于执行点、向量和浮点数的计算,如向量乘法、计算两点之间的向量和距离等。
2.5 添加摇摆动画
为了让玩家的移动更加生动,可以添加摇摆动画。在
PlayerNode
的
moveToward
方法中添加以下代码:
func moveToward(location: CGPoint) {
removeActionForKey("movement")
removeActionForKey("wobbling")
let distance = pointDistance(position, location)
let screenWidth = UIScreen.mainScreen().bounds.size.width
let duration = NSTimeInterval(2 * distance/screenWidth)
runAction(SKAction.moveTo(location, duration: duration), withKey:"movement")
let wobbleTime = 0.3
let halfWobbleTime = wobbleTime/2
let wobbling = SKAction.sequence([
SKAction.scaleXTo(0.2, duration: halfWobbleTime),
SKAction.scaleXTo(1.0, duration: halfWobbleTime)
])
let wobbleCount = Int(duration/wobbleTime)
runAction(SKAction.repeatAction(wobbling, count: wobbleCount), withKey:"wobbling")
}
摇摆动画通过先将玩家节点在 x 轴上缩放至原来的 2/10,再缩放回原始大小,形成一个摇摆效果。并根据移动时间计算摇摆的次数。
3. 敌人创建与添加
为了让游戏更具挑战性,需要创建敌人并将它们添加到场景中。
3.1 创建 EnemyNode 类
使用 Xcode 创建一个名为
EnemyNode
的新 Cocoa Touch 类,它是
SKNode
的子类。在
EnemyNode.swift
文件中添加以下代码:
import UIKit
import SpriteKit
class EnemyNode: SKNode {
override init() {
super.init()
name = "Enemy \(self)"
initNodeGraph()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
private func initNodeGraph() {
let topRow = SKLabelNode(fontNamed: "Courier-Bold")
topRow.fontColor = SKColor.brownColor()
topRow.fontSize = 20
topRow.text = "x x"
topRow.position = CGPointMake(0, 15)
addChild(topRow)
let middleRow = SKLabelNode(fontNamed: "Courier-Bold")
middleRow.fontColor = SKColor.brownColor()
middleRow.fontSize = 20
middleRow.text = "x"
addChild(middleRow)
let bottomRow = SKLabelNode(fontNamed: "Courier-Bold")
bottomRow.fontColor = SKColor.brownColor()
bottomRow.fontSize = 20
bottomRow.text = "x x"
bottomRow.position = CGPointMake(0, -15)
addChild(bottomRow)
}
}
EnemyNode
通过添加多行文本节点来构建敌人的外观。
3.2 将敌人添加到场景中
在
GameScene.swift
文件中,添加一个属性来保存敌人节点,并创建
spawnEnemies
方法来生成敌人。
class GameScene: SKScene {
private var levelNumber: UInt
private var playerLives: Int
private var finished = false
private let playerNode: PlayerNode = PlayerNode()
private let enemies = SKNode()
private func spawnEnemies() {
let count = UInt(log(Float(levelNumber))) + levelNumber
for var i: UInt = 0; i < count; i++ {
let enemy = EnemyNode()
let size = frame.size;
let x = arc4random_uniform(UInt32(size.width * 0.8)) + UInt32(size.width * 0.1)
let y = arc4random_uniform(UInt32(size.height * 0.5)) + UInt32(size.height * 0.5)
enemy.position = CGPointMake(CGFloat(x), CGFloat(y))
enemies.addChild(enemy)
}
}
init(size: CGSize, levelNumber: UInt) {
// 其他代码...
addChild(playerNode)
addChild(enemies)
spawnEnemies()
}
}
通过
spawnEnemies
方法,根据当前关卡等级生成一定数量的敌人,并随机设置它们的位置。将敌人节点添加到场景中后,所有子敌人节点也会显示在场景中。
4. 射击功能实现
接下来实现射击功能,让玩家可以攻击敌人。这需要使用 Sprite Kit 内置的物理引擎来处理子弹的移动和碰撞检测。
4.1 定义物理类别
物理类别用于区分不同类型的对象,以便物理引擎可以根据不同的类别处理碰撞。创建一个新的 Swift 文件
PhysicsCategories.swift
,并添加以下代码:
import Foundation
let PlayerCategory: UInt32 = 1 << 1
let EnemyCategory: UInt32 = 1 << 2
let PlayerMissileCategory: UInt32 = 1 << 3
这些类别使用位掩码表示,每个类别必须是 2 的幂次方,方便在物理引擎的 API 中进行逻辑运算。
4.2 创建 BulletNode 类
创建一个名为
BulletNode
的新 Cocoa Touch 类,它是
SKNode
的子类。在
BulletNode.swift
文件中,导入
SpriteKit
框架,并添加以下代码:
import UIKit
import SpriteKit
class BulletNode: SKNode {
var thrust: CGVector = CGVectorMake(0, 0)
override init() {
super.init()
let dot = SKLabelNode(fontNamed: "Courier")
dot.fontColor = SKColor.blackColor()
dot.fontSize = 40
dot.text = "."
addChild(dot)
let body = SKPhysicsBody(circleOfRadius: 1)
body.dynamic = true
body.categoryBitMask = PlayerMissileCategory
body.contactTestBitMask = EnemyCategory
body.collisionBitMask = EnemyCategory
body.mass = 0.01
physicsBody = body
name = "Bullet \(self)"
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let dx = aDecoder.decodeFloatForKey("thrustX")
let dy = aDecoder.decodeFloatForKey("thrustY")
thrust = CGVectorMake(CGFloat(dx), CGFloat(dy))
}
override func encodeWithCoder(aCoder: NSCoder) {
super.encodeWithCoder(aCoder)
aCoder.encodeFloat(Float(thrust.dx), forKey: "thrustX")
aCoder.encodeFloat(Float(thrust.dy), forKey: "thrustY")
}
class func bullet(from start: CGPoint, toward destination: CGPoint) -> BulletNode {
let bullet = BulletNode()
bullet.position = start
let movement = vectorBetweenPoints(start, destination)
let magnitude = vectorLength(movement)
let scaledMovement = vectorMultiply(movement, 1/magnitude)
let thrustMagnitude = CGFloat(100.0)
bullet.thrust = vectorMultiply(scaledMovement, thrustMagnitude)
return bullet
}
}
BulletNode
类表示子弹,包含一个
thrust
属性来表示推力向量。在
init()
方法中,创建一个代表子弹的点,并配置物理体,设置其类别和碰撞检测信息。
bullet(from:toward:)
方法是一个工厂方法,用于创建一个新的子弹,并根据起始点和目标点计算推力向量。
通过以上步骤,你可以使用 Sprite Kit 开发出一款具有基本功能的游戏,包括玩家移动、敌人创建和射击功能。后续还可以根据需求进一步扩展和优化游戏,如添加音效、更多关卡等。
利用Sprite Kit开发游戏入门指南(续)
5. 物理引擎的工作原理与应用
在实现射击功能时,我们使用了 Sprite Kit 的物理引擎。物理引擎是一个软件组件,它负责跟踪虚拟世界中多个物理对象(通常称为物体)以及作用在它们上的力,确保物体以逼真的方式移动。它可以考虑重力的影响,处理物体之间的碰撞(避免物体同时占据同一空间),甚至模拟摩擦和弹性等物理特性。
需要注意的是,物理引擎通常与图形引擎是分开的。虽然苹果提供了方便的 API 让我们同时使用这两者,但它们本质上是独立的。在游戏中,像显示当前关卡编号和剩余生命数的标签等显示对象,可能与物理引擎完全无关;也可以创建具有物理体但实际上不显示任何内容的对象。
6. 射击功能的进一步完善
6.1 处理触摸事件发射子弹
在
GameScene.swift
文件中,修改
touchesBegan
方法,让玩家在屏幕上半部分 80% 的区域触摸时发射子弹。
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if location.y > CGRectGetHeight(frame) * 0.2 {
let bullet = BulletNode.bullet(from: playerNode.position, toward: location)
addChild(bullet)
let thrust = bullet.thrust
bullet.physicsBody?.applyImpulse(thrust)
} else if location.y < CGRectGetHeight(frame) * 0.2 {
let target = CGPointMake(location.x, playerNode.position.y)
playerNode.moveToward(target)
}
}
}
在上述代码中,当触摸位置在屏幕上半部分 80% 的区域时,会创建一个新的子弹,并将其添加到场景中。然后,通过
applyImpulse
方法给子弹的物理体施加一个冲量,使其根据推力向量移动。
6.2 碰撞检测与处理
为了检测子弹与敌人的碰撞,需要让
GameScene
遵循
SKPhysicsContactDelegate
协议,并在
init
方法中设置物理世界的代理。
class GameScene: SKScene, SKPhysicsContactDelegate {
// 其他属性...
init(size: CGSize, levelNumber: UInt) {
// 其他代码...
physicsWorld.contactDelegate = self
}
func didBeginContact(contact: SKPhysicsContact) {
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if ((firstBody.categoryBitMask & PlayerMissileCategory) != 0) &&
((secondBody.categoryBitMask & EnemyCategory) != 0) {
if let bullet = firstBody.node as? BulletNode, enemy = secondBody.node as? EnemyNode {
bullet.removeFromParent()
enemy.removeFromParent()
}
}
}
}
在
didBeginContact
方法中,首先确定两个碰撞物体的顺序,然后检查是否是玩家子弹与敌人发生碰撞。如果是,则将子弹和敌人从场景中移除。
7. 游戏优化与扩展
7.1 性能优化
在游戏开发中,性能优化是非常重要的。可以通过以下方法进行优化:
-
减少节点数量
:避免在场景中创建过多不必要的节点,及时移除不再使用的节点。
-
优化物理模拟
:合理设置物理体的属性,避免过于复杂的物理模拟。
-
使用纹理图集
:将多个小纹理合并成一个大纹理,减少纹理切换的开销。
7.2 功能扩展
可以根据需求对游戏进行扩展,例如:
-
添加音效
:在玩家移动、射击、碰撞等事件发生时添加音效,增强游戏的沉浸感。可以使用
AVFoundation
框架来实现音效播放。
-
增加关卡
:设计更多的关卡,每个关卡有不同的敌人数量和布局,提高游戏的挑战性。可以通过修改
spawnEnemies
方法来实现不同关卡的敌人生成逻辑。
-
添加道具
:在游戏中添加道具,如增加子弹威力、增加生命等,丰富游戏玩法。
8. 总结
通过以上步骤,我们使用 Sprite Kit 开发了一款具有基本功能的游戏,包括玩家移动、敌人创建、射击功能以及碰撞检测等。在开发过程中,我们学习了如何创建自定义节点类、使用物理引擎、处理触摸事件和实现动画效果等。
以下是整个开发过程的关键步骤总结:
1.
调试信息设置
:在
GameViewController
中关闭调试信息显示。
2.
玩家移动功能实现
:创建
PlayerNode
类,将玩家添加到场景中,处理触摸事件实现移动,并添加摇摆动画。
3.
敌人创建与添加
:创建
EnemyNode
类,生成敌人并随机放置在场景中。
4.
射击功能实现
:定义物理类别,创建
BulletNode
类,处理触摸事件发射子弹,并实现碰撞检测。
5.
优化与扩展
:对游戏进行性能优化,并根据需求扩展功能。
通过不断学习和实践,可以进一步完善和扩展这款游戏,开发出更加精彩的游戏作品。
开发流程流程图
graph TD;
A[开始] --> B[调试信息设置];
B --> C[玩家移动功能实现];
C --> D[敌人创建与添加];
D --> E[射击功能实现];
E --> F[优化与扩展];
F --> G[结束];
关键类和方法总结表格
| 类名 | 主要功能 | 关键方法 |
|---|---|---|
PlayerNode
| 表示玩家节点,实现玩家的显示和移动 |
init()
、
moveToward()
|
EnemyNode
| 表示敌人节点,实现敌人的显示 |
init()
、
initNodeGraph()
|
BulletNode
| 表示子弹节点,实现子弹的创建和移动 |
init()
、
bullet(from:toward:)
|
GameScene
| 游戏场景,处理游戏逻辑和触摸事件 |
init()
、
touchesBegan()
、
didBeginContact()
|
超级会员免费看
114

被折叠的 条评论
为什么被折叠?



