GameplayKit简介:第3部分

这是GameplayKit简介的第三部分。 如果您还没有完成第一部分第二部分 ,那么我建议您先阅读那些教程,然后再继续学习。

介绍

在第三个也是最后一个教程中,我将向您介绍可以在自己的游戏中使用的另外两个功能:

  • 随机值生成器
  • 规则系统

在本教程中,我们将首先使用GameplayKit的随机值生成器之一来优化我们的初始敌人生成算法。 然后,我们将结合其他随机分布实施基本规则系统,以处理敌人的重生行为。

对于本教程,您可以使用第二个教程中已完成项目的副本,也可以从GitHub下载源代码的新副本。

1.随机值发生器

可以使用符合GKRandom协议的任何类在GameplayKit中生成随机值。 GameplayKit提供了五个符合此协议的类。 这些类包含三个随机和两个随机分布 。 随机源与随机分布之间的主要区别在于,分布使用随机源来生成特定范围内的值,并且可以通过各种其他方式来操纵随机值输出。

框架提供了上述类,因此您可以在游戏的性能和随机性之间找到适当的平衡。 一些随机值生成算法比其他算法更复杂,因此会影响性能。

例如,如果您需要每帧(每秒60次)生成一个随机数,那么最好使用一种更快的算法。 相反,如果只偶尔生成一个随机值,则可以使用更复杂的算法以产生更好的结果。

GameplayKit框架提供的三个随机源类是GKARC4RandomSourceGKLinearCongruentialRandomSourceGKMersenneTwisterRandomSource

GKARC4RandomSource

此类使用ARC4算法,适用于大多数目的。 该算法通过产生基于种子的一系列随机数来工作。 如果需要从游戏的其他部分复制随机行为,则可以使用特定的种子初始化GKARC4RandomSource 。 可以从其seed只读属性中检索现有源的种子。

GKLinearCongruentialRandomSource

该随机源类使用基本的线性同余生成器算法。 该算法比ARC4算法更有效且性能更好,但是它还会生成随机性较低的值。 您可以获取GKLinearCongruentialRandomSource对象的种子,并以与GKARC4RandomSource对象相同的方式用它创建新的源。

GKMersenneTwisterRandomSource

此类使用Mersenne Twister算法并生成最多的随机结果,但效率最低。 就像其他两个随机源类一样,您可以检索GKMersenneTwisterRandomSource对象的种子并使用它来创建新的源。

GameplayKit中的两个随机分布类是GKGaussianDistributionGKShuffledDistribution

GKGaussianDistribution

这种分布类型可确保生成的随机值遵循高斯分布(也称为正态分布)。 这意味着大多数生成的值将落在您指定范围的中间。

例如,如果设置了一个GKGaussianDistribution对象具有1的最小值,为10的最大值,并且为1的标准偏差,大约结果的69%将是或者4,5,6。 在本教程后面的部分中,当我们向游戏添加一个分布时,我将更详细地解释这种分布。

GKShuffledDistribution

此类可用于确保随机值在指定范围内均匀分布。 例如,如果生成110,以及4之间的值,则说明另一个4将不会生成,直到所有的110之间的其他数字也已经产生。

现在是时候将所有这些付诸实践了。 我们将在我们的游戏中添加两个随机分布。 在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规则系统用于更好地组织游戏中的条件逻辑,并引入模糊逻辑。 通过引入模糊逻辑,您可以使游戏中的实体根据一系列不同的规则和变量(例如玩家健康状况,当前敌人人数和与敌人的距离)做出决策。 与简单的ifswitch语句相比,这可能是非常有利的。

GKRuleSystem类表示的规则系统具有三个关键部分:

  • 议程 这是已添加到规则系统的规则集。 默认情况下,将按照将这些规则添加到规则系统的顺序对其进行评估。 您可以更改任何规则的salience属性,以指定何时对其进行评估。
  • 状态信息GKRuleSystem对象的state属性是一个字典,您可以向其中添加任何数据,包括自定义对象类型。 然后,返回结果时,规则系统的规则可以使用此数据。
  • 事实 规则系统中的事实代表从规则评估中得出的结论。 事实也可以用游戏中的任何对象类型表示。 每个事实还具有相应的成员资格等级 ,该等级的值介于0.01.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表示两个规则都已满足。

您还将看到playerDistanceRulenodePresentRule访问规则系统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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值