﻿1
00:00:04,480 --> 00:00:05,260
‫你好, 欢迎｡

2
00:00:05,260 --> 00:00:09,820
‫在这节课中, 我们将介绍行为树中的一个新事物, 即服务｡

3
00:00:09,820 --> 00:00:16,390
‫我们将了解什么是服务, 它们如何帮助我们实现持久的运行行为, 以及我们如何用C++编写自己的服务｡

4
00:00:16,390 --> 00:00:17,380
‫让我们开始吧｡

5
00:00:18,060 --> 00:00:23,940
‫现在, 我们的AI控制器中只剩下几个不在行为树中的逻辑｡

6
00:00:24,000 --> 00:00:27,000
‫其中之一是设置起始位置｡

7
00:00:27,000 --> 00:00:30,120
‫我仍然想在这里从I控制器设置｡

8
00:00:30,510 --> 00:00:36,210
‫但现在我想从我们的行为树中做像勾选这样的事情｡

9
00:00:36,390 --> 00:00:38,100
‫我想在这里做什么？

10
00:00:38,190 --> 00:00:40,020
‫我该怎么做？

11
00:00:40,410 --> 00:00:45,750
‫设置我们的玩家位置以更新最后一个已知玩家位置｡

12
00:00:46,200 --> 00:00:51,540
‫这两种功能都可以通过蓝图服务来实现｡

13
00:00:51,540 --> 00:00:56,030
‫我们来看看蓝图, 看看到底发生了什么｡

14
00:00:56,040 --> 00:01:03,870
‫所以如果我进入我的人工智能, 打开行为树, 将其停靠到主窗口, 然后我需要向您展示什么是服务｡

15
00:01:03,870 --> 00:01:10,770
‫我们以前从未使用过, 它本质上是一种每当特定路径处于活动状态时,

16
00:01:10,770 --> 00:01:14,790
‫每隔一个滴答声或每隔一段时间运行某个东西的方式｡

17
00:01:14,790 --> 00:01:22,380
‫所以如果我在这里有一个关于Chase行为的服务, 那么一些代码将能够在Chase行为激活的所有时间运行, 无论我们移动, 射击,

18
00:01:22,650 --> 00:01:28,800
‫加权这些东西, 它都将一直运行｡

19
00:01:28,800 --> 00:01:31,200
‫但如果我们在调查的话就不会了｡

20
00:01:31,410 --> 00:01:35,740
‫因此, 这正是我们希望使用服务的情况｡

21
00:01:35,760 --> 00:01:38,070
‫现在, 让我们来看一个示例服务｡

22
00:01:38,070 --> 00:01:44,670
‫我们在玩游戏时注意到的一件事是, 一旦人工智能进入我们的范围,

23
00:01:44,670 --> 00:01:51,480
‫我们四处移动, 他就不会旋转看我们, 我们可以通过这里的内置服务来解决这个问题｡

24
00:01:51,480 --> 00:02:00,210
‫因此, 如果我们右键单击此节点并转到“添加服务”, 然后查找默认的焦点服务, 您可以看到它的颜色不同, 绿色｡

25
00:02:00,390 --> 00:02:06,870
‫如果我们选择默认服务, 它也会在这些序列节点的下面, 而不是在它们上面, 这是装饰器所做的｡

26
00:02:07,350 --> 00:02:11,940
‫因此, 在选择了默认焦点的情况下, 您可以选择要聚焦的内容｡

27
00:02:11,940 --> 00:02:17,460
‫在这种情况下, 我们希望重点关注玩家位置, 继续单击保存, 然后单击播放｡

28
00:02:17,460 --> 00:02:23,370
‫现在如果我们去看看, 四处移动, 你可以看到他旋转着跟着我们｡

29
00:02:23,790 --> 00:02:24,840
‫很酷的东西｡

30
00:02:24,930 --> 00:02:32,160
‫现在, 我们希望能够在C++中实现一个类似的服务, 该服务将始终更新我们的玩家位置,

31
00:02:32,160 --> 00:02:38,940
‫并在我们追逐时更新我们最后一次已知的玩家位置｡

32
00:02:38,940 --> 00:02:45,000
‫因此, 我们将作为两个单独的服务来执行此操作,

33
00:02:45,000 --> 00:02:51,090
‫您可以看到, 自动服务如何使操作比在tick中执行操作更高效, 因为只有在行为树的正确部分中,

34
00:02:51,090 --> 00:02:54,870
‫您才能运行最后一个已知的播放器位置｡

35
00:02:54,870 --> 00:03:00,840
‫现在, 我们转到沙箱, 转到内容浏览器, 添加新的C++类, 显示所有类,

36
00:03:00,840 --> 00:03:02,880
‫并查看我们的选项｡

37
00:03:03,000 --> 00:03:11,370
‫我们正在寻找BT服务, 然后有几个选项, 就像我们的BT任务一样, 我们有黑板基地,

38
00:03:11,490 --> 00:03:18,180
‫我们实际上想要黑板基地, 因为我们想知道我们应该更新哪个键｡

39
00:03:18,180 --> 00:03:20,640
‫比如, 是不是应该是玩家所在地？

40
00:03:20,640 --> 00:03:23,190
‫是否应该是最后一个已知玩家位置？

41
00:03:23,310 --> 00:03:28,830
‫接下来我们将选择Blackboard Base,

42
00:03:28,830 --> 00:03:36,540
‫并将其命名为BT服务, 在播放器位置下划线, 然后创建该类.

43
00:03:36,660 --> 00:03:42,870
‫完成编译后, 我们将进入Visual Studio代码, 您可以看到我们已经获得了此BT服务播放器位置, 它继承自Service

44
00:03:42,870 --> 00:03:48,390
‫Blackboard Base Call｡

45
00:03:48,390 --> 00:03:49,110
‫这就是我们想要的｡

46
00:03:49,110 --> 00:03:51,870
‫现在, 我们要将哪些功能移到其中？

47
00:03:52,050 --> 00:04:01,650
‫我将尝试移动最简单的功能, 就是设置最后一个已知玩家位置的值.

48
00:04:01,650 --> 00:04:08,340
‫虽然这个键是在行为树中配置的, 而不是在纯文本中, 但它会得到玩家的位置,

49
00:04:08,340 --> 00:04:12,390
‫并将其设置为黑板键.

50
00:04:12,390 --> 00:04:17,640
‫它不会执行任何视线检查, 原因是如果我们只在追逐时在行为树中运行它,

51
00:04:17,640 --> 00:04:21,930
‫那么我们就不需要视线检查｡

52
00:04:21,930 --> 00:04:25,470
‫我们可以看到玩家自动地我们知道是这样的.

53
00:04:25,470 --> 00:04:29,010
‫这又是一个简化的服务｡

54
00:04:29,520 --> 00:04:36,840
‫如果我们知道如何在BT服务中调用一个方法, 这应该是非常简单的｡

55
00:04:37,230 --> 00:04:41,940
‫让我们来看看我们的BT服务, 看看那里有什么可用的｡

56
00:04:41,940 --> 00:04:44,100
‫文件是什么, 我们能做什么？

57
00:04:44,130 --> 00:04:51,120
‫这里你可以看到的主要方法是on become relevant on C relevant tick node和on search start｡

58
00:04:51,150 --> 00:04:52,020
‫搜索开始时｡

59
00:04:52,020 --> 00:04:54,240
‫我们对tick节点不是特别感兴趣｡

60
00:04:54,240 --> 00:05:00,870
‫是的, 这就是我们将在此服务处于活动路径上的所有时间获得更新的位置｡

61
00:05:01,110 --> 00:05:02,970
‫这就是我们想要实现的时候｡

62
00:05:02,980 --> 00:05:06,180
‫我们在这个文件中搜索, 找到完整的签名｡

63
00:05:06,390 --> 00:05:08,010
‫我们可以看到它就在这里｡

64
00:05:08,010 --> 00:05:09,270
‫让我们看看, 它在哪个部分？

65
00:05:09,270 --> 00:05:10,770
‫在受保护的区域｡

66
00:05:10,770 --> 00:05:16,890
‫我们将转到已创建的BT服务, 并创建一个受保护部分｡

67
00:05:17,530 --> 00:05:24,220
‫然后粘贴进去, 它在这里是自动覆盖的, 因为BT服务本身是从父类继承的,

68
00:05:24,220 --> 00:05:27,660
‫但是我们自己再次覆盖它没有什么坏处｡

69
00:05:27,670 --> 00:05:32,920
‫我们可以执行一个控制移位P来创建实现, 然后我们想调用super｡

70
00:05:32,920 --> 00:05:39,550
‫因此, 我们需要超级分笔成交点节点, 并希望传入我们自己的comp和节点内存｡

71
00:05:39,550 --> 00:05:41,500
‫让我看看, 这里还有其他的东西｡

72
00:05:41,500 --> 00:05:46,930
‫我们有一个Delta秒数, 以及我们需要传递的信息, 最后用分号结束,

73
00:05:46,990 --> 00:05:49,960
‫应该完成我们的tick节点实现｡

74
00:05:49,960 --> 00:05:56,320
‫在这里, 我们基本上要从I控制器CP执行该行为, 即获取黑板组件,

75
00:05:56,320 --> 00:05:59,590
‫然后将值设置为矢量｡

76
00:05:59,590 --> 00:06:04,120
‫好了, 我们知道如何从所有者组件引用中获取黑板组件｡

77
00:06:04,120 --> 00:06:05,410
‫这很简单｡

78
00:06:05,410 --> 00:06:11,590
‫我们只需要输入所有者comp点,

79
00:06:11,590 --> 00:06:17,980
‫然后输入get blackboard组件, 记住我们需要在这里为行为树正斜杠blackboard组件哈希include｡

80
00:06:17,980 --> 00:06:20,410
‫然后我们可以使用箭头运算符｡

81
00:06:20,500 --> 00:06:27,370
‫这里我们要做的就是把值设为向量, 然后我们需要一个键｡

82
00:06:27,370 --> 00:06:30,490
‫好吧, 我们知道该怎么处理, 但还是让我们回忆一下吧｡

83
00:06:30,490 --> 00:06:36,370
‫如果我们转到BT服务下划线Blackboard Base, 您可以看到与任务相同的内容,

84
00:06:36,370 --> 00:06:38,380
‫我们有获取选定黑板键｡

85
00:06:38,380 --> 00:06:39,310
‫这就是我们要用的｡

86
00:06:39,340 --> 00:06:45,940
‫获取选定的黑板键获取在行为树中选定的键, 然后我们需要向量值｡

87
00:06:45,940 --> 00:06:46,840
‫我们去哪弄那个？

88
00:06:46,840 --> 00:06:48,580
‫我们将完全一样的人工智能控制器｡

89
00:06:48,580 --> 00:06:52,960
‫我们要让球员——首先也是最重要的是在顶部｡

90
00:06:52,960 --> 00:06:57,880
‫然后我们需要检查它是否为空, 并且我们还需要包括我们的游戏静态｡

91
00:06:57,880 --> 00:07:06,130
‫因此, 让我们确保我们有游戏静态包括从控制器CP包括在我们的球员服务球员的位置｡

92
00:07:06,790 --> 00:07:20,080
‫如果参与者的兵等于空指针, 那么我们要做的就是再次提前返回这里它返回空所以我们可以快速的提前返回.

93
00:07:20,140 --> 00:07:30,070
‫然后我们可以在这里使用我们的玩家卒来简单地获得演员的位置, 我们可能需要在这里做一个包含｡

94
00:07:30,070 --> 00:07:30,970
‫我们看看怎么走｡

95
00:07:30,970 --> 00:07:34,420
‫好吧, 安全一点也没坏处, 对吧, 而且对这些事情直言不讳｡

96
00:07:34,420 --> 00:07:38,890
‫所以我们要为游戏框架做一个包含, 然后是正斜杠｡

97
00:07:38,890 --> 00:07:46,090
‫它将被典当在H上这样我们就可以确保这里的一切都是有序的然后我们就把这个值设为向量.

98
00:07:46,090 --> 00:07:47,350
‫这应该是我们所需要的｡

99
00:07:47,350 --> 00:07:49,210
‫让我们编译一下, 看看它是否有效｡

100
00:07:49,210 --> 00:07:50,350
‫事实上, 还有一件事｡

101
00:07:50,350 --> 00:07:54,700
‫我们可能需要一个漂亮的名字, 所以这里应该有一个构造函数｡

102
00:07:54,700 --> 00:08:00,550
‫所以我将创建一个带有构造函数的公共部分, 它将是类的名称,

103
00:08:00,550 --> 00:08:03,760
‫然后我们继续创建该实现｡

104
00:08:03,760 --> 00:08:08,590
‫我将把我的实现放在tick节点的实现之上, 然后我们将把节点名设置为,

105
00:08:08,590 --> 00:08:17,680
‫它应该是, 更新播放器位置, 让我们来构建, 看看它是否有效.

106
00:08:17,710 --> 00:08:19,300
‫好像我少了一个分号｡

107
00:08:19,300 --> 00:08:20,170
‫我们再来一次｡

108
00:08:20,170 --> 00:08:23,350
‫一旦编译成功, 我们就可以进入我们的敌人｡

109
00:08:23,350 --> 00:08:29,350
‫除了设置焦点, 我还想添加一个新的服务, 我们在这里显示为播放器位置,

110
00:08:29,350 --> 00:08:34,600
‫其中漂亮的名称基本上是更新播放器位置｡

111
00:08:34,600 --> 00:08:40,720
‫你可以看到它想它告诉你滴答的频率每0｡  4比0｡  六秒｡

112
00:08:40,720 --> 00:08:43,990
‫这比使用分笔成交点要好, 因为我们知道分笔成交点在这里运行每一帧｡

113
00:08:43,990 --> 00:08:46,660
‫这是运行频率较低, 这是一件好事｡

114
00:08:46,660 --> 00:08:52,450
‫我们可以进入细节, 创建一个黑板键, 下拉并选择我们想要更新的不仅是球员位置,

115
00:08:52,450 --> 00:08:58,720
‫而且是最后一个已知球员位置, 因为只要我们在追逐, 每一帧都会发生,

116
00:08:58,720 --> 00:09:02,800
‫这是我们想要的行为｡

117
00:09:02,800 --> 00:09:11,230
‫因此, 我们要继续创建的另一个服务节点将执行AI控制器的行为, 以便向下查看剩下的内容｡

118
00:09:11,230 --> 00:09:17,530
‫我们在这里要做的是根据我们是否有视线来更新玩家位置｡

119
00:09:17,530 --> 00:09:19,390
‫所以我们想让它更新玩家的位置｡

120
00:09:19,390 --> 00:09:22,450
‫如果我们有视线, 如果我们没有, 清除位置｡

121
00:09:22,450 --> 00:09:25,750
‫因此, 这将是一个新的服务节点, 因为它的行为略有不同｡

122
00:09:25,780 --> 00:09:28,270
‫我们将添加新的C++类｡

123
00:09:28,270 --> 00:09:33,220
‫我们将展示所有的类, 我们将使用BT服务,

124
00:09:33,220 --> 00:09:39,910
‫在黑板基底上画下划线, 我们的基类, 我们将称之为Beat Service Underscore.

125
00:09:40,590 --> 00:09:44,010
‫玩家位置（如果看到）｡

126
00:09:44,130 --> 00:09:46,980
‫然后继续创建该类｡

127
00:09:46,980 --> 00:09:51,930
‫因为这些都是您以前见过得内容, 所以实施此服务将是您面临得挑战.

128
00:09:51,930 --> 00:09:57,570
‫因此, 您将使用AI控制器中的代码来实现tic节点｡

129
00:09:57,570 --> 00:10:04,350
‫作为您的指导, 您需要从BT服务中获取AI控制器指针｡

130
00:10:04,350 --> 00:10:11,340
‫现在这和我们在BT任务中做的完全一样, 所以用它作为获得AI控制器指针的指导｡

131
00:10:11,520 --> 00:10:15,270
‫你需要AI控制器指针来检查视线｡

132
00:10:15,270 --> 00:10:21,630
‫请记住, 视线是AI控制器上的一种方法, 然后您需要根据视线检查更新或清除关键点,

133
00:10:21,660 --> 00:10:27,270
‫然后将此节点放置在行为树中｡

134
00:10:27,270 --> 00:10:32,130
‫您认为行为树的哪个级别适合运行此服务？

135
00:10:32,130 --> 00:10:33,660
‫会怎么想呢？

136
00:10:33,660 --> 00:10:37,110
‫下巴有点擦伤, 暂停一段视频, 试一下｡

137
00:10:38,070 --> 00:10:38,310
‫好吧, 我会的

138
00:10:38,310 --> 00:10:38,870
‫欢迎回来｡

139
00:10:38,880 --> 00:10:44,670
‫因此, 这在Visual Studio代码中打开了一个新的美容服务｡

140
00:10:44,670 --> 00:10:51,750
‫现在它在头中几乎是相同的,

141
00:10:51,750 --> 00:11:00,300
‫所以我将继续并将其复制到新生成的头中, 例外的是我们需要更改构造函数的名称以匹配｡

142
00:11:00,300 --> 00:11:07,650
‫现在我们要为构造函数创建一个实现, 并给予它一个节点名, 以便在看到播放器时更新播放器位置,

143
00:11:08,010 --> 00:11:12,690
‫并在此处的末尾添加一个分号｡

144
00:11:12,720 --> 00:11:14,400
‫这次别忘了｡

145
00:11:14,400 --> 00:11:21,660
‫然后回到C++, 实现tick节点, 如果我们能找到播放器的位置,

146
00:11:21,660 --> 00:11:25,080
‫我就复制过来.

147
00:11:25,080 --> 00:11:32,610
‫复制到super调用, 然后得到玩家兵, 然后检查null返回值.

148
00:11:32,610 --> 00:11:36,090
‫事实上, 我会复制整个身体, 然后我们会从那里修改它｡

149
00:11:36,600 --> 00:11:43,380
‫复制到黑板组件, 我们还要复制黑板组件的访问权限, 包括黑板组件,

150
00:11:43,740 --> 00:11:50,160
‫游戏静态和兵, 因为我们需要访问所有这些组件.

151
00:11:50,160 --> 00:11:53,130
‫事实上, 许多功能都在这里共享｡

152
00:11:53,130 --> 00:12:00,360
‫你可以考虑让他们以某种方式共享这些代码, 但我认为没有那么多的重复, 我们关心不够｡

153
00:12:00,840 --> 00:12:05,430
‫所以我们要做的是检查我们有没有眼控制器｡

154
00:12:05,430 --> 00:12:15,450
‫所以我会做一个, 如果所有者组件或者所有者组件, get, I, owner, 等于空指针,

155
00:12:15,720 --> 00:12:19,470
‫那么我们会在这个阶段继续返回.

156
00:12:19,950 --> 00:12:24,360
‫接下来我们要做的是检查我们的能见度｡

157
00:12:24,360 --> 00:12:32,070
‫我们要做一个if owner, comp, . get, I, owner,

158
00:12:32,070 --> 00:12:34,440
‫然后我们要做一个箭头, 我们需要在这里包括我们的控制器｡

159
00:12:34,440 --> 00:12:36,900
‫所以它是散列包含｡

160
00:12:38,120 --> 00:12:41,810
‫然后是I控制器H｡

161
00:12:42,380 --> 00:12:48,470
‫然后我们要画一条线看看这里的正确用法是什么｡

162
00:12:48,470 --> 00:12:54,650
‫如果我们转到I控制器CP, 它的视线2是我们想要的｡

163
00:12:54,650 --> 00:13:00,380
‫所以视线二我们需要对玩家兵做视线｡

164
00:13:00,380 --> 00:13:02,870
‫在这种情况下, 我们是否可以看到玩家的兵？

165
00:13:03,320 --> 00:13:04,820
‫这就是我们在指挥所做的吗

166
00:13:04,820 --> 00:13:05,810
‫我们再确认一下｡

167
00:13:05,810 --> 00:13:06,680
‫是的, 我们是｡

168
00:13:07,220 --> 00:13:11,450
‫然后, 我们将继续设置不同的值｡

169
00:13:11,510 --> 00:13:15,800
‫所以我们把向量值设为玩家卒得到演员的位置｡

170
00:13:15,800 --> 00:13:18,620
‫是的, 如果我们有其他的视线｡

171
00:13:18,620 --> 00:13:22,430
‫如果我们没有视线, 我们知道我们需要清除该值｡

172
00:13:22,430 --> 00:13:26,030
‫所以我们仍然会得到所有者组件, 黑板组件.

173
00:13:26,060 --> 00:13:31,640
‫然后是清零值,

174
00:13:31,640 --> 00:13:37,790
‫我们仍然会清零黑板键的值, 这应该足够我们实现玩家位置了.

175
00:13:37,790 --> 00:13:41,480
‫如果看到了, 让我们去编译, 看看它是否工作得很好｡

176
00:13:41,480 --> 00:13:42,710
‫编译成功｡

177
00:13:42,710 --> 00:13:44,570
‫让我们进入行为树｡

178
00:13:44,570 --> 00:13:50,420
‫现在我希望它运行的级别基本上是在初始选择器的顶层,

179
00:13:50,420 --> 00:13:59,600
‫因为我希望它基本上一直更新播放器的位置, 因为我们需要能够根据所设置的播放器位置, 中止任何其他行为｡

180
00:13:59,600 --> 00:14:02,810
‫因此我们需要能够随时设置和取消设置它｡

181
00:14:02,900 --> 00:14:08,870
‫我将右键单击顶级选择器, 添加服务, 如果看到播放位置, 则添加播放位置｡

182
00:14:09,200 --> 00:14:15,260
‫然后我们需要选择这个服务节点, 并选择它的黑板键, 这就是我们的参与者位置.

183
00:14:15,710 --> 00:14:20,270
‫因此, 在玩家位置中设置一个, 更新玩家位置,

184
00:14:20,270 --> 00:14:22,220
‫更愚蠢的一个是更新最后一个已知玩家位置｡

185
00:14:22,550 --> 00:14:29,270
‫我们现在应该能够进入我们的AI控制器, 基本上删除TTIC中的所有代码,

186
00:14:29,270 --> 00:14:33,740
‫因为它已经被我们的服务所取代｡

187
00:14:33,740 --> 00:14:36,350
‫我们可以继续进行编译, 看看是否可行｡

188
00:14:36,350 --> 00:14:41,060
‫让我们到编辑那里去玩, 看看我们的敌人是否还在向我们走来｡

189
00:14:41,060 --> 00:14:46,580
‫他朝我们跑来, 失去了视线, 然后又朝我们跑来, 开始射击｡

190
00:14:46,580 --> 00:14:46,850
‫是的｡

191
00:14:46,850 --> 00:14:55,730
‫基本上一切都和以前一样, 除了现在它完全通过我们用C++为自己编写的服务来完成｡

192
00:14:55,760 --> 00:15:01,100
‫下节课, 我们将修复那个烦人的bug, 就是人工智能不断地射击,

193
00:15:01,100 --> 00:15:03,290
‫最后设法把自己杀死｡

194
00:15:03,290 --> 00:15:04,160
‫我会看到你在那里｡

