首页 > 其他分享 >UE5: 探究Actor Tick的注册与执行

UE5: 探究Actor Tick的注册与执行

时间:2023-11-08 18:14:59浏览次数:31  
标签:调用 函数 BeginPlay Actor 注册 UE5 Tick

1. 前情提要

因工作需要,有在编辑器模式下执行Actor的Tick函数的需求。经过查阅资料,了解到重载Actor::ShouldTickIfViewportOnly函数可以实现在编辑器视口下也可以执行Tick函数。

已知Actor和ActorComponent都有自己的Tick函数,并且进入游戏并执行BeginPlay后才会开始Tick。

出于好奇心,产生了一系列的疑问:

  1. Actor和组件的Tick函数是由谁管理和统一调用的?
  2. 在执行BeginPlay后才会开始Tick,但是编辑器模式下默认并不会调用BeginPlay,那么为什么重载了ShouldTickIfViewportOnly()后照样开始了Tick?
    以此衍生出新的问题
    1. UE是如何控制Actor的Tick的开始的?
    2. 有哪些变量或者函数与之相关?

本文将从这些问题出发,简要地探究一下Actor的Tick机制。

2. Actor和ActorComponent Tick的实质

FTickFuntion结构体

众所周知,我们可以通过修改PrimaryActorTick.bCanEverTick的方式来控制一个Actor是否会被Tick,以此为线索,我们很快就能找到相关的代码,也就是PrimaryActorTick成员所属的结构体:FActorTickFunction

同样的,我们可以在Component中找到类似的结构体FActorComponentTickFunction,这两个结构体都是继承了FTickFunction结构体,区别在于FActorTickFunction保存了指向Actor的指针,FActorComponentTickFunction保存了指向Component的指针,除此以外也没有显著的区别了。

那么我们当前研究的重点就是FTickFunction

以上是这个结构体的主要结构。

关于这个类,网上已经有不少研究了,这里就直接使用知乎网友制作的图片,可以从文末的链接中找到原文。

其中有两个很重要的函数:

ExecuteTick()

该函数是一个虚函数,功能和名字一样,提供了一个统一执行Tick的功能。子类可以对其进行重写,在实际运行时,在注册的地方统一调用所有该类的ExecuteTick函数,实现Tick的执行与Actor实现的解耦。

例如FActorTickFunction的实现中,它会调用AActor::TickActor,再调用Actor的Tick函数。正如注释所言,它是Actrually execute the tick的,那么说明真正调用Tick函数的其实是Actor中的成员变量PrimaryActorTick

要想知道Tick是怎样注册并统一执行的,就得看看另一个主要函数。

RegisterTickFunction(class ULevel* Level)

这个函数很短,主要就是调用了FTickTaskManager::Get().AddTickFunction(Level, this);,而这个函数则根据传入的Level,拿到Level的一个FTickTaskLevel类型的成员变量,并调用该变量的AddTickFunction函数,把FTickFunction保存到FTickTaskLevel的一个保存TickFunction的集合AllEnabledTickFunctions中。

到这里为止,我们就能大致了解Actor的Tick到底是怎样被执行了。

在Actor刚被创建的时候,此时还没有任何一个地方会调用Tick函数,Actor处于静止的状态。

而后在游戏进程的某个阶段中,会将PrimaryActorTick成员变量的FTickFunction::ExecuteTick注册到Level中的一个变量里的集合中。例如,游戏运行的过程中,创建Actor会自动调用其BeginPlay函数,在这个函数中就有注册Tick的操作。

通过这些操作,Level可以获取所有Actor的Tick函数,在World的Tick中,就可以通过遍历Level的方式获取所有已注册的Actor的Tick,并将其一起执行。

点到即止,关于FTickTaskLevel的运行机制这里就不深究了,接下来我们看看FTickFuntion有哪些重要参数需要我们注意:

  • bCanEverTick:是否允许注册Tick。当这个值为False时,就不会将ExcuteTick函数注册,因此Tick函数将不再被执行。官方的注释还提到,这个值只应该在初始化的时候修改。
  • bStartWithTickEnabled:是一个EditDefaultOnly的变量,在蓝图的名字叫做“启用Tick并开始"(UE5.1)。如果其值为false,不管有没有注册,那么Tick函数都不会被执行。这个值可以在运行的时候动态调整。
  • TickInterval:设置Tick的间隔时间
  • TickGroup:一个枚举变量,它指定该Tick在一次引擎Tick的什么时机执行

顺带一提,Actor类有一个变量也值得注意:

  • bAllowTickBeforeBeginPlay: ”允许开始播放前Tick“,哦我的上帝,看看这蹩脚的翻译。之前在编辑器中看到这个选项总是一头雾水,现在了解过源码后也知道了其含义:是否允许在调用BeginPlay函数前进行Tick。

网上总说Actor会在BeginPlay函数调用后才会开始Tick,从之前的分析中我们也知道,在Beginplay中会对tick函数进行注册。那么在beginplay之前呢?也有地方调用注册函数吗?

文章的后半段,我会简要地从源码角度探究这个问题,并简要地了解Actor的初始化。

3. Actor的初始化(Tick注册相关)

这是生成(实例)Actor 时的路径。

  1. SpawnActor 被调用。
  2. PostSpawnInitialize
  3. PostActorCreated - 创建后即被生成的 Actor 调用,构建函数类行为在此发生。PostActorCreated 与 PostLoad 互斥。
  4. ExecuteConstruction
    • OnConstruction - Actor 的构建。蓝图 Actor 的组件在此处创建,蓝图变量在此处初始化
  5. PostActorConstruction
    1. PreInitializeComponents - 在 Actor 的组件上调用 InitializeComponent 之前进行调用。
    2. InitializeComponent - Actor 上定义的每个组件的创建辅助函数。
    3. PostInitializeComponents - Actor 的组件初始化后调用。
  6. OnActorSpawned 在 UWorld 上播放。
  7. BeginPlay 被调用。

以上内容来自官方文档,这是三条路径的其中一条。

官方文档给出了三种不同的Actor生成方式,通过断点调试测试,发现大多数时候的Actor生成都会走上面的这条路线,包括SpawnActor生成、从文件浏览器拖入场景、PIE等。

可以看出,BeginPlay才是Actor初始化最后的一环,而前面还有很多初始化的环节。

AAcotr::PostSpawnInitialize

Actor初始化的大部分操作都在这个函数里进行,包括初始化Actor的位置、Actor的所有权、组件的初始化等等。上述路径的2-5步骤都在这个函数里进行。

然后在这个函数中的某一行,我们发现了RegisterAllComponents()函数。很简洁明确的函数名,我们关注的Tick注册就在这个函数里。

RegisterAllComponents()函数很简单,里面只有个AActor::IncrementalRegisterComponents函数。

而终于在这个函数里,我们找到了注册Tick函数的入口

标签:调用,函数,BeginPlay,Actor,注册,UE5,Tick
From: https://www.cnblogs.com/Qiu-Bai/p/17818008.html

相关文章

  • 浅析Spring IoC源码(八)了解BeanFactoryAware
    这一节我们还是先了解一下BeanFactoryAware这个接口,之所以说只是了解一下,还是希望等到分析refresh()的时候有个更好的理解吧照旧先上源代码:官方解释:实现这个接口的bean其实是希望知道自己属于哪一个beanfactory言简意赅,不需要做多解释,先实现一下自己,看看他的基本功能吧,看代码:MyBean......
  • 浅析Spring IoC源码(七)浅谈BeanFactory和FactoryBean
    这一节我们就简单的介绍一下FactoryBean,知道这个接口的作用和意义,方便我们refresh()这个方法的理解照旧,我们依旧先看源码,从源码中查看一下他的作用吧~这次就不一句句翻译了(太多了),还是稍微大概的讲一下意思吧:FactoryBean是一个接口,任何一个Bean可以实现这个接口,那么这个bean将成为一......
  • 上下文中找不到org.springframework.boot.web.servlet.server.ServletWebServerFactor
    1.问题报错如下:Description:Webapplicationcouldnotbestartedastherewasnoorg.springframework.boot.web.servlet.server.ServletWebServerFactorybeandefinedinthecontext.Action:Checkyourapplication'sdependenciesforasupportedservletwebse......
  • 37-Vue脚手架-nextTick(使用nextTick优化TodoList案例)
    this.$nextTick(十分常用的功能)语法:this.$nextTick(回调函数)作用:在下一次DOM更新结束后执行其指定的回调什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行 案例:使用 $nextTick优化TodoList案例,在UserItem中添加一个编辑按......
  • Azure Data Factory(十)Data Flow 组件详解
    一,引言随着大数据技术的不断发展,数据处理和分析变得越来越重要。为了满足企业对数据处理的需求,微软推出了AzureDataFactory(ADF),它是一个云端的数据集成服务,用于创建、安排和管理数据工作流。在本文中,我们将重点介绍AzureDataFactory的数据流(DataFlow),以及它如何帮助......
  • Vue中nextTick的使用及原理
    在Vue.js中,nextTick方法可以让我们在DOM更新后执行一些操作。通常情况下,在数据发生变化后,Vue.js会异步地更新DOM,这样可以减少不必要的DOM操作,提高性能。但是,有时候我们需要在DOM更新后对页面进行一些后续操作,比如修改元素的样式、设置定时器等,这时候就需要用到nextTick方法。一、ne......
  • In R, how to split/subset a data frame by factors in one column?
    按照某列的值拆分data.frame Mydataislikethis(forexample):IDRateState124AL235MN346FL434AL578MN699FLIwanttosplitthedatabystateandIwanttoget3datasetslikebelow:dataset1IDRateState124AL......
  • dotnet 探究 SemanticKernel 的 planner 的原理
    在使用SemanticKernel时,我着迷于SemanticKernel强大的plan能力,通过plan功能可以让AI自动调度拼装多个模块实现复杂的功能。我特别好奇SemanticKernel里的planner的原理,好奇底层具体是如何实现的。好在SemanticKernel是完全开源的,通过阅读源代码,我理解了SemanticK......
  • UE4蓝图对Actor的引用
    通过关卡蓝图调用在关卡中放置一个Actor,在关卡蓝图中右键createareferencetoactor,即可注意使用该方法创建时,需要现在关卡中选择上该类Actor当Actor生成时创建其的引用当我们在蓝图中利用SpawnActorfromClass生成Actor节点返回值拖出promotetovariable,提升为变量即可......
  • UE5打包后,无法切换关卡的问题
    首先是普通的会遇到的问题,比如多个Level不在同一目录,或者不在默认的Maps目录打包不成功这时候要设置,ProjectSettings->Packaging上面保证没问题之后,打包发现依然有问题,不管切换路径为相对或者绝对路径都不行把蓝图中的OpenLevel,变为按引用传递可以了,详细请看如下:https://www......