前言

这篇文章主要讲的是Unreal LevelSequence RunTime的部分。即在游戏中运行Level Sequence的源码解析。(而且抛去Replicated 的Sequence,一般Sequence不会在DS上播,因为比较浪费性能,在DS上播的很少这么使用,所以本篇自动忽略。)

即,本篇主要讲的是单纯的只在客户端运行时的LevelSequence的步骤。

作用

  • 我是如何分析LevelSequence 源码过程
  • 本篇文章主要讲述LevelSequeence中绑定的Actor是如何在运行游戏时候被运行。
  • 可以解决LevelSequence运行时的相关bug。比如楼主接触LevelSequence遇到的一个bug,就是Editor Play运行正常,但是在Shipping(正式发布)版运行,某个被绑定在Sequence中的Actor跟没绑定一样。不起作用...

问题分析

我比较喜欢直接讲述实际的案例,我们就拿一个例子来说吧,就是Sequence中我们可以很简单的控制Actor的隐藏,那么在游戏中运行时,是如何被隐藏的,隐藏步骤是啥样的,这个怎么找纳?下面就来说说具体步骤。

1.Editor Sequence中先将Actor 隐藏

  • 从以下,我们知道是通过ActorHiddenInGame实现的

2.堆栈寻找

  • 从上述步骤我们知道隐藏一个Actor,Sequence也是通过ActorHiddenInGame来实现的,于是就知道了
UFUNCTION(BlueprintCallable, Category="Rendering", meta=( DisplayName = "Set Actor Hidden In Game", Keywords = "Visible Hidden Show Hide" ))
virtual void SetActorHiddenInGame(bool bNewHidden);

我们是否可以直接在这个方法里直接断点一下,寻找到Sequence在Runtime将Actor隐藏的堆栈。这个办法分析源码必备之技巧。尤其对于这种一开始摸不着头脑,可以反向推理。

3.核心知识

我们知道Sequence的类型是:ALevelSequenceActor*
我们知道Sequence在Runtime怎么播放,是通过下述代码:
    ALevelSequenceActor::InitializePlayer()
ALevelSequenceActor->SequencePlayer->Play();
ALevelSequenceActor->SequencePlayer->Update(DeltaSeconds);

显而易见,需要知道这个SequencePlayer,它的类型是:ULevelSequencePlayer*

那么需要了解这两个类之间的关系即可。很显然,ULevelSequencePlayer是控制ALevelSequenceActor管理播放的,比如快进,快退,都是通过ULevelSequencePlayer.

1.FMovieSceneEvaluationRange
时间驱动结构,我们知道动画的运动肯定是基于Tick,那么是如何将DeltaSeconds,传递给SequencePlayer,并且还要支持回放,返回,加速等。所以Sequence这里将时间封装了一层。因为考虑到如此多的功能,所以封装成下述时间,需要了解。
    inline FFrameTime ConvertFrameTime(FFrameTime SourceTime, FFrameRate SourceRate, FFrameRate DestinationRate)
{
if (SourceRate == DestinationRate)
{
return SourceTime;
}
//We want NewTime =SourceTime * (DestinationRate/SourceRate);
//And want to limit conversions and keep int precision as much as possible
int64 NewNumerator = static_cast<int64>(DestinationRate.Numerator) * SourceRate.Denominator;
int64 NewDenominator = static_cast<int64>(DestinationRate.Denominator) * SourceRate.Numerator;
double NewNumerator_d = double(NewNumerator);
double NewDenominator_d = double(NewDenominator);
//Now the IntegerPart may have a Float Part, and then the FloatPart may have an IntegerPart,
//So we add the extra Float from the IntegerPart to the FloatPart and then add back any extra Integer to IntegerPart
int64 IntegerPart = ( (int64)(SourceTime.GetFrame().Value) * NewNumerator ) / NewDenominator;
const double IntegerFloatPart = ((double(SourceTime.GetFrame().Value) * NewNumerator) / NewDenominator) - double(IntegerPart);
const double FloatPart = ((SourceTime.GetSubFrame() * NewNumerator_d) / NewDenominator_d) + IntegerFloatPart;
const double FloatPartFloored = FMath::FloorToDouble(FloatPart);
const int64 FloatAsInt = int64(FloatPartFloored);
IntegerPart += FloatAsInt;
double SubFrame = FloatPart - FloatPartFloored;
if (SubFrame > 0)
{
SubFrame = FMath::Min(SubFrame, 0.999999940);
} //@TODO: FLOATPRECISION: FFrameTime needs a general once over for precision (RE: cast to ctor)
return FFrameTime( (int32)IntegerPart, (float)SubFrame);
}

FMovieSceneEvaluationRange

  • 上一帧的时间点
  • 下一帧的时间点
  • 当前的EPlayDirection:Forwards, Backwards
  • 当前速率
2.FMovieSceneContext
FMovieSceneContext(FMovieSceneEvaluationRange InRange)
: FMovieSceneEvaluationRange(InRange)
, Status(EMovieScenePlayerStatus::Stopped)
...

对上述FMovieSceneEvaluationRange,再次的封装,传到

FMovieSceneRootEvaluationTemplateInstance::Evaluate(FMovieSceneContext Context, IMovieScenePlayer& Player)
就是将IMovieScenePlayer数据 和 记录的时间结构体FMovieSceneContext,传给MovieSceneTootEvaluationTemplateInstance中。
3.FMovieSceneRootEvaluationTemplateInstance

FMovieSceneEvaluationTrack 这个数据Info是重点,就是对应到Sequence每一条轨道。。

4.FMovieSceneEvaluationGroup



上述堆栈就是找出当前所需要运行的Track List.

5.FMovieSceneExecutionTokens



这就是对实际的需要Track List进行运行。

比如,我一开始遇到的bug:在Editor运行某轨道我想隐藏某Actor是正常的,但是在Shipping正式包,运行了,某轨道运行没反应,还是没有被隐藏。于是就断点查:





最终是通过在Visibility中的Execute 发现foundboundObject一直找不到,才发现原来是Shipping会将场景中一些static打成一个包,所以通过路径查找obj一直找不到,static的被优化了。所以解决这个问题直接将static改成moveable即可。大部分项目应该都会有此优化。

3.总结

这种Sequence的源码分析,可以采用逆向分析,反向打断点找出堆栈,去除次要逻辑,某些特别难的逻辑,可以抛去,略过。

LavelSequence的源码,主要是FMovieSceneRootEvaluationTemplateInstance::EvaluateGroup 根据当前的时间点,查找出哪些轨道,然后根据每条轨道,做出具体的分别不同的事件。每条轨道的规则很容易理解。其实就只剩下根据时间点查找出轨道List,这段代码其实实在看不懂,其实也不需要太过于纠结了。

这段很难得代码就是下述:


void FMovieSceneRootEvaluationTemplateInstance::EvaluateGroup(const FMovieSceneEvaluationPtrCache& EvaluationPtrCache, const FMovieSceneEvaluationGroup& Group, const FMovieSceneContext& RootContext, IMovieScenePlayer& Player)
{
FPersistentEvaluationData PersistentDataProxy(Player);
FMovieSceneEvaluationOperand Operand;
FMovieSceneContext Context = RootContext;
FMovieSceneContext SubContext = Context;
for (const FMovieSceneEvaluationGroupLUTIndex& Index : Group.LUTIndices)
{
int32 TrackIndex = Index.LUTOffset; // - Do the above in a lockless manner
for (; TrackIndex < Index.LUTOffset + Index.NumInitPtrs + Index.NumEvalPtrs; ++TrackIndex)
{
//略 *** Track->Evaluate(
SegmentPtr.SegmentID,
Operand,
SubContext,
PersistentDataProxy,
ExecutionTokens);
}
} ExecutionTokens.Apply(Context, Player);
}
}

上述代码有删改(是我看不懂的,个人感觉也没必要非要纠结,知道大概意思即可),只要知道 ExecutionTokens,和后面得 ExecutionTokens.Apply(Context, Player) 即可。

比如还有个问题,有人好奇根据时间点Sequence对应的值都不同,这个在哪判断纳,


void FMovieSceneFloatPropertySectionTemplate::Evaluate(const FMovieSceneEvaluationOperand& Operand, const FMovieSceneContext& Context, const FPersistentEvaluationData& PersistentData, FMovieSceneExecutionTokens& ExecutionTokens) const
{
float Result = 0.f; // Only evaluate if the curve has any data
if (FloatFunction.Evaluate(Context.GetTime(), Result))
{
// Actuator type ID for this property
FMovieSceneBlendingActuatorID ActuatorTypeID = EnsureActuator<float>(ExecutionTokens.GetBlendingAccumulator()); // Add the blendable to the accumulator
const float Weight = EvaluateEasing(Context.GetTime());
ExecutionTokens.BlendToken(ActuatorTypeID, TBlendableToken<float>(Result, BlendType, Weight));
}
}

这就很简单了,显然,在各自的Template中判断。

LevelSequence源码分析的更多相关文章

  1. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

  2. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  3. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  4. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

  5. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  6. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

  7. java使用websocket,并且获取HttpSession,源码分析

    转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...

  8. ABP源码分析二:ABP中配置的注册和初始化

    一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...

  9. ABP源码分析三:ABP Module

    Abp是一种基于模块化设计的思想构建的.开发人员可以将自定义的功能以模块(module)的形式集成到ABP中.具体的功能都可以设计成一个单独的Module.Abp底层框架提供便捷的方法集成每个Modu ...

随机推荐

  1. 爬取豆瓣喜剧类热门TOP60的电影

    学习任务:爬取豆瓣喜剧类热门TOP60的电影并保存在douban.txt文件中. 代码示例: import requests url="https://movie.douban.com/j/ ...

  2. 12.MYSQL基础-常见函数

    4. 常见函数 一.字符函数 概念 类似于Java的方法,将一组逻辑语句封装在方法中,对外暴露方法名 优点 隐藏了实现细节 提高代码的重用性 调用 select 函数名(实参列表) [ from 表] ...

  3. 用OpenMV自动识别颜色序列

    目录 用OpenMV自动识别颜色序列 用OpenMV自动识别颜色序列 新年假期过去啦~ 主控:OpenMV3 M7摄像头(STM32F765) IDE:OPENMV官方IDE 我将Capstone期间 ...

  4. JAVA - 线程同步和线程调度的相关方法

    JAVA - 线程同步和线程调度的相关方法 wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁:wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等 ...

  5. 2022年Web前端开发流程和学习路线(详尽版)

    前言 前端侧重于人机交互和用户体验,后端侧重于业务逻辑和大规模数据处理.理论上,面向用户的产品里,所有问题(包括产品.设计.后端.甚至看不见的问题)的表现形式,都会暴露在前端,而只有部分问题(数据问题 ...

  6. SAP HTLM Control

    HTML 事件 效果 代码 *&---------------------------------------------------------------------* *& Re ...

  7. SAP Using Text Modules in Adobe Forms

    In this demo we will create an adobe form which displays text in two different languages (English or ...

  8. RPA 微信财务报销机器人 竹间智能

    1.首先通过微信对话机器人收集报销信息及内容 2.上传发票并进行OCR识别 3.收集相关的出差信息,支持对话中修改内容 4.完成信息收集后,后台RPA机器人执行报销操作,并发送确认邮件 5.收到邮件后 ...

  9. 一次 Keepalived 高可用的事故,让我重学了一遍它!

    原文首发: 你好,我是悟空. 前言 上次我们遇到了一个 MySQL 故障的事故,这次我又遇到了另外一个奇葩的问题: Keepalived 高可用组件的虚拟 IP 持续漂移,导致 MySQL 主从不断切 ...

  10. Python基础教程:模块重载的五种方法

    环境准备 新建一个 foo 文件夹,其下包含一个 bar.py 文件 $ tree foo foo └── bar.py 0 directories, 1 file bar.py 的内容非常简单,只写 ...