感知与被感知流程
刺激源的配置与注册
- 配置结构
UPROPERTY(EditAnywhere, Category = "AI Perception", BlueprintReadOnly) TArray<TSubclassOf<UAISense> > RegisterAsSourceForSenses;
- 1
- 2
向刺激源组件
UAIPerceptionStimuliSourceComponent
中添加配置
- 注册刺激配置到感知系统
- 重写父类里的
OnRegister
函数,过滤掉RegisterAsSourceForSenses
里无效的配置,根据bAutoRegisterAsSource
的成员变量控制是否自注册到感知系统bAutoRegisterAsSource
如果为true则执行RegisterWithPerceptionSystem
函数,该函数首先拿到感知系统实例指针,然后迭代配置结构中的UAISense
调用感知系统中的RegisterSourceForSenseClass
来完成刺激源Sense的注册及刺激源组件持有者的注册感知组件的配置、注册、与信息更新
- 感知配置项
UPROPERTY(EditDefaultsOnly, Instanced, Category = "AI Perception") TArray<TObjectPtr<UAISenseConfig>> SensesConfig;
- 1
- 2
这是感知类型配置,与刺激源配置不同的是,感知配置对UAISense进行了封装,里面添加了感知间隔的有效时间(Age),调试颜色控制,开启允许与否的控制等
- 注册感知配置到感知系统
- 同样的配方,在重写的
OnRegister
函数里进行处理,首先在持有者AIController的OnEndPlay
委托上绑定OnOwnerEndPlay
函数,该函数用于在AIController玩法结束时取消所有的感知系统里的感知注册- 循环
SensesConfig
调用RegisterSenseConfig函数,该函数首先对SenseConfig进行脱壳,获取UAISense
,然后调用感知系统的RegisterSenseClass
函数注册UAISense配置,这个配置与刺激源注册配置是一致的- 添加感知组件的
PerceptionFilter
白名单- 设置刺激源留存的最大有效间隔时间(Age),这个留存时间保留在
TArray<float> MaxActiveAge
里,这个数组的索引与SenseID保持一致- 最后一步是调用感知系统的
UpdateListener
函数来完成在感知系统中的感知注册,该函数做了以下处理:
- 检查当前传入的Listener是否有效,如果有效,则说明传入的是已经注册过的Listener,那么调用
FPerceptionListener
的UpdateListenerProperties
函数来更新该Listener的teamid及感知白名单,再调用感知系统的OnListenerUpdate
函数,而该函数是迭代了已经注册的Senses配置实例,转调了Sense的OnListenerUpdate
,而内部封装的是委托执行(接后续)- 如果无效,则说明这是一个新的感知组件,则分配一个有效的感知ID,并向
ListenerContainer
添加一个新的感知实例成员,接着调用感知组件的OnNewListener
函数,该函数与OnListenerUpdate
函数类似,迭代注册的Senses
,调用Sense的OnNewListener
函数(接后续)感知系统中对刺激源的处理
- 对刺激源的注册请求处理
- 接收到
UAIPerceptionStimuliSourceComponent
刺激源组件的RegisterSourceForSenseClass
函数调用后,获取该刺激源的FAISenseID
并检查该ID是否被实例化过,没有被实例化则调用RegisterSenseClass
,接着调用RegisterSource
注册该刺激Sense的持有者RegisterSenseClass
函数首先做的事情就是通过CDO调用UpdateSenseID
更新FAISenseID
分配一个有效的ID给当前Sense配置,接下来关键的一步是生成一个UAISense实例添加到感知系统的Senses结构里。然后根据UAISense
里的bWantsNewPawnNotification
和bAutoRegisterAllPawnsAsSources
控制参数,来决定是否把世界场景下的所有Pawn都注册为该配置的刺激源Actor,如果是,则在World下一帧开启一个计时器执行刺激源Actor的添加,具体流程如下:
- 调用
RegisterAllPawnsAsSourcesForSense
函数,根据传入的SenseID迭代World下的APawn
,然后调用RegisterSource
函数RegisterSource
函数根据迭代把每一个APawn添加为SourcesToRegister
的数据成员
- 调用
RegisterSource
函数,把当前持有该UAISense
的作为刺激源添加到SourcesToRegister
里,其实这里可以用if..else..
来区分开的,不过问题不大,因为向SourcesToRegister
里添加数据的时候用的是AddUnqiue
函数
- 对感知组件的SenseConfig的处理
- 感知组件是直接调用了
RegisterSenseClass
函数,该函数上面已经说过,不再赘述- 也就是说与刺激源不同的是感知组件没有把组件的持有者注册给感知系统,因为是感知一方,除非感知配置开启了把所有Pawn注册为刺激源的选项
感知系统数据更新及与刺激源,感知组件三者间的数据交互
- 刺激源数据的更新(添加及删除刺激源数据)
- 刺激源数据的添加:
- 在感知组件的
Tick
函数里调用PerformSourceRegistation
函数,查找注册的Senses里与SourcesToRegister
数据成员里关联的Sense,然后把该SourceActor通过Sense的RegisterSource
函数添加到Sense感知配置实例里去,以便后续处理- 绑定
StimuliSourceEndPlayDelegate
委托用于在SourceActor结束玩法时取消该Actor注册的刺激信息(比如下线,死亡等)- 添加一个刺激源数据
FPerceptionStimuliSource
到RegisteredStimuliSources结构里,同时调用RelevantSenses
的AcceptChannel
函数来设置刺激源针对于该Sense的白名单(这个白名单的用途是说明该SourceActor对该类型的感知配置有效)- 重置SourcesToRegister
- 剔除刺激源Actor的操作
- 这些操作来自于三方面的调用:感知组件执行
Cleanup
操作;刺激源组件主动调用取消注册函数;感知系统委托回调OnPerceptionStimuliSourceEndPlay
- 这些操作都是调用了
UnregisterSource
函数,该函数操作如下:
- 查找
RegisteredStimuliSources
里的刺激源结构成员,然后根据传入Sense的有效性分为两种情况处理,首先是有效的情况下,说明只针对某一类型的Sense进行处理,则首先检查刺激源中的通道白名单,有效则说明该Sense里有该SourceActor,则调用Sense的UnregisterSource
函数来做删除处理, 接着调用刺激源数据里的RelevantSenses
的FilterOutChannel
函数,剔除掉SourceActor对该Sense配置的响应(拉入黑名单)- 如果无效则需要考虑迭代所有的Sense了,然后根据与指定Sense的剔除操作相同,首先检查刺激源白名单,然后剔除,最后调用刺激源
RelevantSenses
的Clear
函数清空对所有Sense的响应- 如果刺激源的
RelevanttSenses
是被清空了,则说明是该SourceActor已经对感知系统无效了,则从RegisteredStimuliSoruces
里移除,同时在SourceActor的OnEndPlay
里取消StimuliSourceEndPlayDelegate
的委托注册,因为SourceActor主动撤销了,而不需要等到OnEndPlay
- 从
SourceToRegister
待处理的刺激源数据里移除掉关于该SourceActor的注册数据
- 感知Listener的更新
- 之前在注册感知配置的时候是一种添加方式
- 还有一种方式是请求更新,主要是调用
RequestStimuliListenerUpdate
函数,而该函数主要调用了UpdateListener
函数- 感知系统中的
OnListenerConfigUpdated
函数用于处理感知组件中指定SenseConfig变更时的操作,主要调用已注册的Sense里的配置更新回调
- 感知的移除
- 感知的移除主要来自于两个方面,一个是感知组件执行清除操作时对感知系统
UnregisterSource
的调用;另一个是来自于感知系统Tick
函数里来自于Listener的有效性检查UnregisterSource
函数迭代了注册的Senses
,Listener中是否包含该Sense的判断来调用Sense里的OnListenerRemoved
函数
- 刺激源的到期处理
- 理解这个问题之前首先要搞清楚的几个感知系统里的成员变量:
PerceptionAgingRate
该属性决定了多久执行一次感知系统对刺激源信息的处理,默认为0.3f秒,可配置NextStimuliAgingTick
该属性标示了下一次要执行刺激源留存时间计算的时间,由当前系统时间加上PerceptionAgingRate
得出PerceptionAgingRate + (CurrentTime - NextStimuliAgingTick)
不难看出这个是用来计算出上一次执行到现在真正过去的时间,这个计算结果用于处理刺激源信息里Age计算
- 在感知系统的Tick函数里,通过
NextStimuliAgingTick
检查来决定是否对刺激源信息进行处理,在处理的时候调用感知系统里的AgeStimuli
,该函数主要是迭代注册的ListenerContainer
,而最终会调用感知组件里的AgeStimuli
这个函数用来检索感知组件上的感知数据PerceptualData
里的每一个感知数据,然后迭代次信息上一次获取的刺激源信息LastSensedStimuli
,计算Age加上当前距上一次计算过去的时间,看是否达到间隙留存时间上限,如果已达到留存时间则则给该刺激源信息打上过期标签,并调用RegisterStimulus
函数添加到StimulusToProcess
结构里,该结构在ProcessStimuli
函数处理数据时调用,最终需要说明的是当AgeStimuli
返回的结果为true时,会给FPerceptionListener
打上刺激源待处理标签,在处理刺激源消息时会检查该标签以此决定是否要处理刺激消息更新- 那么有哪些情况会更新处理刺激源信息哪?
- 在感知系统的Tick里处理的了三种情况:1. 刺激源信息里有数据达到留存时间过期标识;2. Senses里有Sense到期Tick;3. 有Listener处于过期状态需要处理
- 当有以上三种情况发生时,会调用感知组件的
ProcessStimuli
函数进行刺激更新数据处理