Это третья часть « Введение в GameplayKit» . Если вы еще не прошли первую часть и вторую часть , я рекомендую сначала прочитать эти учебные пособия, прежде чем продолжить с этим.
Вступление
В этом третьем и последнем уроке я расскажу вам еще о двух функциях, которые вы можете использовать в своих собственных играх:
- генераторы случайных значений
- системы правил
В этом уроке мы сначала будем использовать один из генераторов случайных значений GameplayKit для оптимизации нашего начального алгоритма появления врага. Затем мы реализуем базовую систему правил в сочетании с другим случайным распределением для обработки поведения врагов.
Для этого урока вы можете использовать свою копию завершенного проекта из второго урока или загрузить свежую копию исходного кода с GitHub .
1. Генераторы случайных значений
Случайные значения могут быть сгенерированы в GameplayKit с использованием любого класса, соответствующего протоколу GKRandom
. GameplayKit предоставляет пять классов, соответствующих этому протоколу. Эти классы содержат три случайных источника и два случайных распределения . Основное различие между случайными источниками и случайными распределениями заключается в том, что в распределениях используется случайный источник для получения значений в определенном диапазоне, и они могут манипулировать выводом случайного значения различными другими способами.
Вышеупомянутые классы предоставляются платформой, чтобы вы могли найти правильный баланс между производительностью и случайностью для вашей игры. Некоторые алгоритмы генерации случайных значений являются более сложными, чем другие, и, следовательно, влияют на производительность.
Например, если вам нужно случайное число, генерируемое в каждом кадре (шестьдесят раз в секунду), то лучше всего использовать один из более быстрых алгоритмов. Напротив, если вы генерируете случайное значение нечасто, вы можете использовать более сложный алгоритм для получения лучших результатов.
Три класса случайных источников, предоставляемых платформой GameplayKit, — это GKARC4RandomSource
, GKLinearCongruentialRandomSource
и GKMersenneTwisterRandomSource
.
GKARC4RandomSource
Этот класс использует алгоритм ARC4 и подходит для большинства целей. Этот алгоритм работает путем создания серии случайных чисел на основе начального числа. Вы можете инициализировать GKARC4RandomSource
с определенным начальным GKARC4RandomSource
если вам нужно воспроизвести случайное поведение из другой части вашей игры. Семя существующего источника может быть получено из его свойства только для чтения.
GKLinearCongruentialRandomSource
Этот случайный источник использует базовый алгоритм линейного конгруэнтного генератора. Этот алгоритм более эффективен и работает лучше, чем алгоритм ARC4, но он также генерирует значения, которые являются менее случайными. Вы можете получить GKLinearCongruentialRandomSource
объекта GKLinearCongruentialRandomSource
и создать новый источник с ним таким же образом, как объект GKARC4RandomSource
.
GKMersenneTwisterRandomSource
Этот класс использует алгоритм Мерсенна Твистера и генерирует наиболее случайные результаты, но он также наименее эффективен. Как и два других случайных исходных класса, вы можете получить GKMersenneTwisterRandomSource
объекта 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
.
1
2
|
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 отклонений от среднего
- 100% в пределах 3 отклонений от среднего
Это означает, что примерно 69% сгенерированных значений должны быть равны 1. Это приведет к увеличению количества красных точек пропорционально зеленым и желтым точкам. Чтобы это работало, нам нужно обновить метод initialSpawn
.
В цикле for
замените следующую строку:
1
|
let respawnFactor = arc4random() % 3 // Will produce a value between 0 and 2 (inclusive)
|
со следующим:
1
|
let respawnFactor = self.initialSpawnDistribution.nextInt()
|
Метод nextInt
может быть вызван для любого объекта, который соответствует протоколу GKRandom
, и возвращает случайное значение в зависимости от источника и, если применимо, используемого вами распределения.
Создайте и запустите ваше приложение, и перемещайтесь по карте. Вы должны увидеть намного больше красных точек по сравнению с зелеными и желтыми точками.
Второе случайное распределение, которое мы будем использовать в игре, вступит в игру при обработке поведения респауна на основе системы правил.
2. Системы правил
Системы правил GameplayKit используются для лучшей организации условной логики в вашей игре, а также для введения нечеткой логики. Вводя нечеткую логику, вы можете заставить сущности в вашей игре принимать решения на основе ряда различных правил и переменных, таких как здоровье игрока, текущее количество врагов и расстояние до врага. Это может быть очень выгодно по сравнению с простыми операторами if
и switch
.
Системы правил, представленные классом GKRuleSystem
, имеют три ключевые части:
- Повестка дня . Это набор правил, которые были добавлены в систему правил. По умолчанию эти правила оцениваются в порядке их добавления в систему правил. Вы можете изменить свойство
salience
любого правила, чтобы указать, когда вы хотите, чтобы оно оценивалось. - Государственная информация . Свойство
state
объектаGKRuleSystem
представляет собой словарь, в который можно добавлять любые данные, включая пользовательские типы объектов. Эти данные могут затем использоваться правилами системы правил при возврате результата. - Факты Факты в системе правил представляют собой выводы, сделанные из оценки правил. Факт также может быть представлен любым типом объекта в вашей игре. Каждый факт также имеет соответствующую оценку членства , которая составляет от 0,0 до 1,0 . Этот членский балл представляет собой включение или наличие факта в системе правил.
Сами правила, представленные классом GKRule
, имеют два основных компонента:
- Предикат Эта часть правила возвращает логическое значение, указывающее, были ли выполнены требования правила. Предикат правила может быть создан с использованием объекта
NSPredicate
или, как мы будем делать в этом руководстве, блока кода. - Действие Когда предикат правила возвращает
true
, его действие выполняется. Это действие представляет собой блок кода, в котором вы можете выполнить любую логику, если соблюдены требования правила. Здесь вы обычно утверждаете (добавляете) или убираете (удаляете) факты в родительской системе правил.
Посмотрим, как все это работает на практике. Для нашей системы правил мы собираемся создать три правила, которые смотрят на:
- расстояние от точки появления до игрока. Если это значение будет относительно небольшим, мы повысим вероятность появления красных врагов в игре.
- текущий счетчик узлов сцены. Если это слишком высоко, мы не хотим, чтобы больше точек было добавлено к сцене.
- присутствует ли точка в точке появления. Если нет, то мы хотим перейти к появлению точки здесь.
Сначала добавьте следующее свойство в класс GameScene
:
1
|
var ruleSystem = GKRuleSystem()
|
Затем добавьте следующий фрагмент кода в метод didMoveToView(_:)
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
let playerDistanceRule = GKRule(blockPredicate: { (system: GKRuleSystem) -> Bool in
if let value = system.state[«spawnPoint»] as?
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?
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
мы только утверждаем факт "shouldSpawn"
с оценкой 0,5 . Затем nodePresentRule
подтверждает тот же факт и добавляет значение оценки 0,5 . Это сделано для того, чтобы, когда мы проверим этот факт позже, значение оценки 1,0 означает, что оба правила были выполнены.
Вы также увидите, что playerDistanceRule
и nodePresentRule
доступ к "spawnPoint"
в словаре state
системы правил. Мы присвоим это значение до оценки системы правил.
Наконец, найдите и замените метод respawn
в классе GameScene
на следующую реализацию:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
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?
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?
let agent = node?.valueForKey(«agent») as?
entity.addComponent(agent)
agent.delegate = node as?
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!
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
данныхCGPoint
не соответствует протоколу SwiftAnyObject
и не может быть назначен этому свойствуNSMutableDictionary
. - Мы оцениваем систему правил, вызывая ее метод
evaluate
. - Затем мы получаем оценку членства в системе
"shouldSpawn"
факта"shouldSpawn"
. Если это равно 1 , мы продолжим с появлением точки. - Наконец, мы проверяем уровень системы правил для
"spawnEnemy"
и, если он равен 1 , используем нормально распределенный генератор случайных чисел для создания нашегоspawnFactor
.
Остальная часть метода respawn
такая же, как метод initialSpawn
. Создайте и запустите свою игру в последний раз. Даже не двигаясь, вы увидите, как появляются новые точки, когда выполняются необходимые условия.
Вывод
В этой серии о GameplayKit вы узнали много нового. Давайте вкратце подведем итоги.
- Сущности и компоненты
- Государственные машины
- Агенты, цели и поведение
- Найти путь
- Генераторы случайных значений
- Системы Правил
GameplayKit является важным дополнением к iOS 9 и OS X El Capitan. Это устраняет множество сложностей разработки игр. Я надеюсь, что эта серия побудила вас больше экспериментировать с фреймворком и узнать, на что он способен.
Как всегда, пожалуйста, оставляйте свои комментарии и отзывы ниже.