这是GameplayKit简介的第三部分。 如果您还没有完成第一部分和第二部分 ,那么我建议您先阅读那些教程,然后再继续学习。
介绍
在第三个也是最后一个教程中,我将向您介绍可以在自己的游戏中使用的另外两个功能:
- 随机值生成器
- 规则系统
在本教程中,我们将首先使用GameplayKit的随机值生成器之一来优化我们的初始敌人生成算法。 然后,我们将结合其他随机分布实施基本规则系统,以处理敌人的重生行为。
对于本教程,您可以使用第二个教程中已完成项目的副本,也可以从GitHub下载源代码的新副本。
1.随机值发生器
可以使用符合GKRandom
协议的任何类在GameplayKit中生成随机值。 GameplayKit提供了五个符合此协议的类。 这些类包含三个随机源和两个随机分布 。 随机源与随机分布之间的主要区别在于,分布使用随机源来生成特定范围内的值,并且可以通过各种其他方式来操纵随机值输出。
框架提供了上述类,因此您可以在游戏的性能和随机性之间找到适当的平衡。 一些随机值生成算法比其他算法更复杂,因此会影响性能。
例如,如果您需要每帧(每秒60次)生成一个随机数,那么最好使用一种更快的算法。 相反,如果只偶尔生成一个随机值,则可以使用更复杂的算法以产生更好的结果。
GameplayKit框架提供的三个随机源类是GKARC4RandomSource
, GKLinearCongruentialRandomSource
和GKMersenneTwisterRandomSource
。
GKARC4RandomSource
此类使用ARC4算法,适用于大多数目的。 该算法通过产生基于种子的一系列随机数来工作。 如果需要从游戏的其他部分复制随机行为,则可以使用特定的种子初始化GKARC4RandomSource
。 可以从其seed
只读属性中检索现有源的种子。
GKLinearCongruentialRandomSource
该随机源类使用基本的线性同余生成器算法。 该算法比ARC4算法更有效且性能更好,但是它还会生成随机性较低的值。 您可以获取GKLinearCongruentialRandomSource
对象的种子,并以与GKARC4RandomSource
对象相同的方式用它创建新的源。
GKMersenneTwisterRandomSource
此类使用Mersenne Twister算法并生成最多的随机结果,但效率最低。 就像其他两个随机源类一样,您可以检索GKMersenneTwisterRandomSource
对象的种子并使用它来创建新的源。
GameplayKit中的两个随机分布类是GKGaussianDistribution
和GKShuffledDistribution
。
GKGaussianDistribution
这种分布类型可确保生成的随机值遵循高斯分布(也称为正态分布)。 这意味着大多数生成的值将落在您指定范围的中间。
例如,如果设置了一个GKGaussianDistribution
对象具有1的最小值,为10的最大值,并且为1的标准偏差,大约结果的69%将是或者4,5,或6。 在本教程后面的部分中,当我们向游戏添加一个分布时,我将更详细地解释这种分布。
GKShuffledDistribution
此类可用于确保随机值在指定范围内均匀分布。 例如,如果生成1和10,以及4之间的值,则说明另一个4将不会生成,直到所有的1和10之间的其他数字也已经产生。
现在是时候将所有这些付诸实践了。 我们将在我们的游戏中添加两个随机分布。 在Xcode中打开您的项目,然后转到GameScene.swift 。 我们将添加的第一个随机分布是GKGaussianDistribution
。 稍后,我们还将添加GKShuffledDistribution
。 将以下两个属性添加到GameScene
类。
var initialSpawnDistribution = GKGaussianDistribution(randomSource: GKARC4RandomSource(), lowestValue: 0, highestValue: 2)
var respawnDistribution = GKShuffledDistribution(randomSource: GKARC4RandomSource(), lowestValue: 0, highestValue: 2)
在此代码段中,我们创建两个分布,最小值为0 ,最大值为2 。 对于GKGaussianDistribution
,将根据以下方程式自动计算平均值和偏差:
-
mean = (maximum - minimum) / 2
-
deviation = (maximum - minimum) / 6
高斯分布的平均值是其中点,偏差用于计算从平均值开始一定范围内的值百分比。 一定范围内的值的百分比为:
- 68.27%与平均值相差1个以内
- 95%偏离平均值2
- 与平均值相差3%的100%
这意味着大约69%的生成值应等于1。这将导致与绿色和黄色点成比例的更多红色点。 为此,我们需要更新initialSpawn
方法。
在for
循环中,替换以下行:
let respawnFactor = arc4random() % 3 // Will produce a value between 0 and 2 (inclusive)
具有以下内容:
let respawnFactor = self.initialSpawnDistribution.nextInt()
可以在符合GKRandom
协议的任何对象上调用nextInt
方法,该方法将基于源和您使用的分布(如果适用)返回一个随机值。
生成并运行您的应用,然后在地图上移动。 与绿色和黄色点相比,您应该看到更多的红色点。
当处理基于规则系统的重生行为时,我们将在游戏中使用第二个随机分布。
2.规则系统
GameplayKit规则系统用于更好地组织游戏中的条件逻辑,并引入模糊逻辑。 通过引入模糊逻辑,您可以使游戏中的实体根据一系列不同的规则和变量(例如玩家健康状况,当前敌人人数和与敌人的距离)做出决策。 与简单的if
和switch
语句相比,这可能是非常有利的。
由GKRuleSystem
类表示的规则系统具有三个关键部分:
- 议程 这是已添加到规则系统的规则集。 默认情况下,将按照将这些规则添加到规则系统的顺序对其进行评估。 您可以更改任何规则的
salience
属性,以指定何时对其进行评估。 - 状态信息 。
GKRuleSystem
对象的state
属性是一个字典,您可以向其中添加任何数据,包括自定义对象类型。 然后,返回结果时,规则系统的规则可以使用此数据。
- 事实 规则系统中的事实代表从规则评估中得出的结论。 事实也可以用游戏中的任何对象类型表示。 每个事实还具有相应的成员资格等级 ,该等级的值介于0.0到1.0之间。 该成员等级表示规则系统中事实的包含或存在。
由GKRule
类表示的规则本身具有两个主要组成部分:
- 谓词 。 规则的此部分返回一个布尔值,指示是否已满足规则的要求。 可以使用
NSPredicate
对象或代码段来创建规则的谓词。 - 行动 。 当规则的谓词返回
true
,将执行其动作。 此操作是一个代码块,您可以在其中满足规则要求的情况下执行任何逻辑。 在这里,您通常可以在父规则系统内声明(添加)或撤回(删除)事实。
让我们看看所有这些在实践中如何工作。 对于我们的规则系统,我们将创建以下三个规则:
- 从生成点到播放器的距离。 如果此值相对较小,我们将使游戏更有可能生成红色敌人。
- 场景的当前节点数。 如果这个值太高,我们不希望在场景中添加更多的点。
- 生成点处是否已经存在一个点。 如果没有,那么我们要在此处生成一个点。
首先,将以下属性添加到GameScene
类中:
var ruleSystem = GKRuleSystem()
接下来,将以下代码段添加到didMoveToView(_:)
方法中:
let playerDistanceRule = GKRule(blockPredicate: { (system: GKRuleSystem) -> Bool in
if let value = system.state["spawnPoint"] as? NSValue {
let point = value.CGPointValue()
let xDistance = abs(point.x - self.playerNode.position.x)
let yDistance = abs(point.y - self.playerNode.position.y)
let totalDistance = sqrt((xDistance*xDistance) + (yDistance*yDistance))
if totalDistance <= 200 {
return true
} else {
return false
}
} else {
return false
}
}) { (system: GKRuleSystem) -> Void in
system.assertFact("spawnEnemy")
}
let nodeCountRule = GKRule(blockPredicate: { (system: GKRuleSystem) -> Bool in
if self.children.count <= 50 {
return true
} else {
return false
}
}) { (system: GKRuleSystem) -> Void in
system.assertFact("shouldSpawn", grade: 0.5)
}
let nodePresentRule = GKRule(blockPredicate: { (system: GKRuleSystem) -> Bool in
if let value = system.state["spawnPoint"] as? NSValue where self.nodesAtPoint(value.CGPointValue()).count == 0 {
return true
} else {
return false
}
}) { (system: GKRuleSystem) -> Void in
let grade = system.gradeForFact("shouldSpawn")
system.assertFact("shouldSpawn", grade: (grade + 0.5))
}
self.ruleSystem.addRulesFromArray([playerDistanceRule, nodeCountRule, nodePresentRule])
使用此代码,我们创建了三个GKRule
对象,并将它们添加到规则系统中。 规则在其动作块内主张特定事实。 如果不提供等级值,而只是调用assertFact(_:)
方法(就像对playerDistanceRule
,则事实的默认等级为1.0 。
您会注意到,对于nodeCountRule
我们仅断言等级为0.5的"shouldSpawn"
事实。 然后, nodePresentRule
声明相同的事实,并添加0.5的等级值。 这样做是为了在以后检查事实时,等级值为1.0表示两个规则都已满足。
您还将看到playerDistanceRule
和nodePresentRule
访问规则系统state
字典的"spawnPoint"
值。 我们将在评估规则系统之前分配此值。
最后,在GameScene
类中找到并用以下实现替换respawn
方法:
func respawn() {
let endNode = GKGraphNode2D(point: float2(x: 2048.0, y: 2048.0))
self.graph.connectNodeUsingObstacles(endNode)
for point in self.spawnPoints {
self.ruleSystem.reset()
self.ruleSystem.state["spawnPoint"] = NSValue(CGPoint: point)
self.ruleSystem.evaluate()
if self.ruleSystem.gradeForFact("shouldSpawn") == 1.0 {
var respawnFactor = self.respawnDistribution.nextInt()
if self.ruleSystem.gradeForFact("spawnEnemy") == 1.0 {
respawnFactor = self.initialSpawnDistribution.nextInt()
}
var node: SKShapeNode? = nil
switch respawnFactor {
case 0:
node = PointsNode(circleOfRadius: 25)
node!.physicsBody = SKPhysicsBody(circleOfRadius: 25)
node!.fillColor = UIColor.greenColor()
case 1:
node = RedEnemyNode(circleOfRadius: 75)
node!.physicsBody = SKPhysicsBody(circleOfRadius: 75)
node!.fillColor = UIColor.redColor()
case 2:
node = YellowEnemyNode(circleOfRadius: 50)
node!.physicsBody = SKPhysicsBody(circleOfRadius: 50)
node!.fillColor = UIColor.yellowColor()
default:
break
}
if let entity = node?.valueForKey("entity") as? GKEntity,
let agent = node?.valueForKey("agent") as? GKAgent2D where respawnFactor != 0 {
entity.addComponent(agent)
agent.delegate = node as? ContactNode
agent.position = float2(x: Float(point.x), y: Float(point.y))
agents.append(agent)
let startNode = GKGraphNode2D(point: agent.position)
self.graph.connectNodeUsingObstacles(startNode)
let pathNodes = self.graph.findPathFromNode(startNode, toNode: endNode) as! [GKGraphNode2D]
if !pathNodes.isEmpty {
let path = GKPath(graphNodes: pathNodes, radius: 1.0)
let followPath = GKGoal(toFollowPath: path, maxPredictionTime: 1.0, forward: true)
let stayOnPath = GKGoal(toStayOnPath: path, maxPredictionTime: 1.0)
let behavior = GKBehavior(goals: [followPath, stayOnPath])
agent.behavior = behavior
}
self.graph.removeNodes([startNode])
agent.mass = 0.01
agent.maxSpeed = 50
agent.maxAcceleration = 1000
}
node!.position = point
node!.strokeColor = UIColor.clearColor()
node!.physicsBody!.contactTestBitMask = 1
self.addChild(node!)
}
}
self.graph.removeNodes([endNode])
}
该方法将每秒调用一次,与initialSpawn
方法非常相似。 尽管在for
循环中有许多重要的区别。
- 我们首先通过调用其
reset
方法来重置规则系统。 当顺序评估规则系统时,需要执行此操作。 这将删除所有断言的事实和相关数据,以确保不会从上一个评估中遗留任何可能干扰下一个评估的信息。 - 然后,我们将派生点分配给规则系统的
state
字典。 我们使用NSValue
对象,因为CGPoint
数据类型不符合Swift的AnyObject
协议,并且无法分配给此NSMutableDictionary
属性。 - 我们通过调用
evaluate
系统来评估规则系统。 - 然后,我们为
"shouldSpawn"
事实检索规则系统的成员资格等级。 如果等于1 ,我们继续重新生成点。 - 最后,我们检查规则系统对
"spawnEnemy"
事实的等级,如果等于1 ,则使用正态分布的随机生成器创建我们的spawnFactor
。
respawn
方法的其余部分与initialSpawn
方法相同。 最后一次构建并运行您的游戏。 即使不四处走动,当满足必要条件时,您也会看到新的点生成。
结论
在GameplayKit的这个系列中,您学到了很多东西。 让我们简要总结一下我们所涵盖的内容。
GameplayKit是iOS 9和OS X El Capitan的重要补充。 它消除了许多游戏开发的复杂性。 我希望本系列能够激发您对框架进行更多的试验,并发现其功能。
与往常一样,请确保在下面留下您的评论和反馈。
翻译自: https://code.tutsplus.com/tutorials/an-introduction-to-gameplaykit-part-3--cms-24611