LyraALS学习

01 Animation BluePrints

几种播放动画的方法:

  1. Sequence Player -> Output Pose

    1765354469150

  2. 变量传入Sequence Player -> Output Pose

    1765354564173

    1765354575160

02 Blueprint communication Intermediate

蓝图之间的三种通信方式

Casting(投射)

Interfaces(接口)

Property Access(属性访问)

Blueprint casting

初始化时,Pawn -> Cast to 目标Character -> 拿到移动组件的引用

1765442336806

Update每帧从移动组件里读数据

1765442469726

Blueprint Interfaces

新建一个动画蓝图接口:

1765444437383

1765444550152

在角色动画蓝图中,继承接口:

1765444694691

1765444707546

一个单纯的动画事件、一个有传参的方法

1765444567536

1765444848280

角色蓝图:

1765444933530

Property Access

角色动画蓝图中:

先新建一个线程安全的Update,

1765445448796

然后新建一个函数,

1765446585236

返回节点要勾选Pure(全程只读取数据、不修改任何内容)

1765446607600

回到线程安全的Update,调用新建函数

1765446857523

03 Idle Animations Pro

Input System

1765972150220

1765972201519

1765972276299

先实现数字键123对应打印字符123:

1.数字键2和3加上输入Modifiers修饰器(修改原始输入数据)——Scalar(标量:相当于给输入值乘一个系数)

1765973598925

2.角色蓝图中,初始事件中调用AddMappingContext传入InputMapping,增强输入系统事件打印InputActions的值(按下瞬间触发)

1765974451741

EnhancedInputAction(增强输入动作)的事件参数:

  1. Triggered:按住按键时调用的事件。
  2. Started:按下按键的瞬间调用的事件。
  3. Ongoing输入动作处于持续激活状态中的事件(类似 Triggered的持续反馈)。
  4. Canceled:输入动作被 中断 / 取消时 (比如按了一半突然松开按键)调用的事件。
  5. Completed:输入动作 完成触发后 (比如按键按下后完全松开)调用的事件。
  6. Action Value:输入动作对应的 具体数值 (比如摇杆的 X/Y 轴值、扳机键的按压程度),是最常用的参数之一。
  7. Elapsed Seconds:输入动作从 “开始” 到当前的 已持续时间 (单位:秒)。
  8. Triggered Seconds:输入动作处于 “触发状态” 的 累计时间 (可能包含多次触发的总和)。
  9. Input Action触发的 输入动作本身的引用 (可以通过它获取动作的配置信息)。

在Game中就实现了:

1765973996543

其他Modifiers:

  • None :不使用任何修饰器,直接传递原始输入值。
  • Dead Zone(死区) :忽略手柄摇杆 / 扳机键的微小输入(比如摇杆漂移),低于阈值的输入会被视为 0。
  • FOV Scaling(视场缩放) :根据当前相机的 FOV(视场角)调整输入值的大小。
  • Negate(否定) :反转输入值(比如将 “1” 变成 “-1”),常用于反转轴方向(如相机 Y 轴)。
  • Response Curve - Exponential(响应曲线 - 指数) :给输入值套指数曲线,让小幅度输入更细腻、大幅度输入更灵敏(适合瞄准类操作)。
  • Response Curve - User Defined(响应曲线 - 自定义) :用用户自己绘制的曲线来调整输入响应。
  • Scalar(标量) :给输入值乘以一个系数,用于调整灵敏度(比如把移动速度放大 2 倍)。
  • Scale By Delta Time(按 DeltaTime 缩放) :输入值乘以每帧的时间间隔,让输入效果不受帧率影响。
  • Smooth(平滑) :对多帧输入做平滑处理,减少输入抖动。
  • Swizzle Input Axis Values(交换输入轴值) :交换输入的坐标轴顺序(比如把 X 轴输入映射到 Y 轴)。
  • To World Space(转世界空间) :将本地空间的输入(比如角色自身坐标系)转换为世界

如何传递参数到动画蓝图中

1.创建一个枚举类型

1765974356655

2.角色蓝图新建该枚举类型的变量

1765975528417

1765975545646

3.用switch on int根据输入的123转换为三个枚举变量,打印

1765975636143

Truncate(截断节点):

Action Value可能出现的浮点数转为整数 (比如输入值是 “1.2” 会被截断成 “1”)

效果:

1765975719338

4.枚举类型EquippedGun —>动画蓝图

AnimationBluePrintsInterface中新建一个方法,传入参数为该枚举类型:

1765977854332

动画事件蓝图中定义该方法,

1765977902093

角色蓝图中通过Mesh引一个动画实例传入该方法,

1765977977721

这样EnhancedInputSystem事件中输入的参数就传入了动画蓝图,与前面角色蓝图中直接传递参数的效果一致

1765983828001

解决相机显示问题:

角色组件中,

1765978905491

动画状态机

和unity Animator很像,但因为有事件系统,其实更像是animancer的可视化

动画蓝图中新建一个状态机,

1765980534744

构建跳转关系,

1765980572329

每个State绑定对应的动画资产,

1765980685669

勾选上循环动画,

1765980698984

跳转条件,

1765980767670

把跳转条件promote为shared transition,

1765980962567

效果:

1765981540642

Blend Poses 姿势混合

Blend Poses by Int

1765982012375

Blend Poses by Enum(最常用)

1765982146749

Dynamic sequence with blend inertialization

1765983523577

1765983530790

带 Inertial Blending 的 Set Sequence 方法:需要有Inertialization才能正常运作

Inertialization(惯性混合节点):

让 “旧待机动画的动量” 在 0.2 秒内衰减,平滑过渡到新的待机动画。

04 Linked Animations Pro

AnimationLayer

1766062591725

1766062621449

IdleLayer中,

1766066283063

AnimationLayer Interfaces

用接口的方式添加AnimationLayer

1.新建AnimationLayer Interfaces——ALI_Lyra

1766062806260

1766062912085

2.在ABP_Base的ClassSettings中添加接口

1766063006799

IdleLayer和上面一样设置好之后加到动画蓝图中即可

1766063116053

1766066615781

1.新建一个AnimationBlueprints——ABP_Layers

继承接口ALI_Lyra

1766063951154

在IdleLayer中,新建一个变量IdleAnim(这个会在后面的子ABP中更改值),传入SequencePlayer

1766065775554

在角色事件蓝图中 Link Anim Class Layers,Class设置为ABP_UnarmedLayers

1766064024128

这样就连接到了ABP_Layers

2.创建ABP_Layers的子ABP

1766064697561

设置好各自的默认值

1766064710349

1766064742905

1766064732357

角色事件蓝图中,Link Anim Class Layers到各个子ABP_Layer

1768574435222

ABP_Layers中,

1766065863852

1766065898221

ABP_Layers只用来管理各种Layer,不做更多的事,惯性节点在ABP_Base中添加

1766066000457

总结

ABP_Base 和ABP_Layers继承自 动画层接口ALI_Lyra(规定了各种Layer)

IdleLayer在ABP_Layers中的Idle On Update实现

ABP_Layers的Idle On Update中的变量IdleAnim在每个子ABP_Layer中更改默认值

IdleLayer在ABP_Base中传入(考虑惯性节点)

05 Organizing our work Beginner

整理蓝图

06 Character movement Beginner

角色移动

1.视角的移动

新建IA_Look

1766069062801

1766069084837

在IMC_ALS中加进来

1766069144497

角色时间蓝图中

1766069906852

SpringArm中勾上使用 Pawn 控制旋转

1766069668067

解决一下竖直方向视角移动反向的问题

1766070042709

2.人物的移动

同样的做法

1766070703455

把轴向调正确

1767930110515

角色事件蓝图中

1766072879935

1766072890716

为什么左右移动只连xz,前后移动只连z?

07 Cycle Animation Part01 Intermediate

1.动画蓝图中新建一个LocomotionSM,将会包含Idle、Move等等

1766146397815

1766146407355

1766146416184

2.在线程安全UpdateAnimation函数中,新建一个函数GetVelocityData并调用

1766148545232

1766149347410

注意GetVelocityData函数要勾上线程安全

1766149293246

3.LocomotionSM状态机

1766151728171

1766151908541

记得勾选Loop

1766151949708

跳转条件

1766151713826

1766151992200

动画资产要启用Root Motion和Force Root Lock

1766152162532

效果

1766153452856

4.Aim

Aim input

1766153585653

创建E_Gate枚举类型,包含走路(瞄准时用到)和慢跑(不瞄准时用到)两个状态

1766153891230

1766153874093

在角色事件蓝图中新建E_Gate枚举变量CurrentGate

1766154095724

创建S_GateSettings结构体

六个参数:

最大走路速度

最大加速度

制动减速度

制动制动摩擦力因素

制动摩擦力

使用单独的制动摩擦力

1766154722205

角色事件蓝图中新建一个Key为E_Gate枚举类型,Value为S_GateSettings结构体类型的变量

1766155795300

1766155827491

新建函数UpdateGate(E_Gate Gate)

1766156045886

1766156963791

瞄准Started时更新当前Gate为Walking,Completed时为Jogging

1766157046751

效果:

1766157396915

这里只是根据状态不同更新Character Movement的速度

在动画蓝图中根据当前的状态切换对应动画

在动画蓝图接口BPI_AnimationBlueprintsInterface中新建一个函数RecieveCurrentGate(E_Gate Gate)

1766157612683

动画事件蓝图中定义这个函数是用来设置CurrentGate的

1766157936084

传参:角色事件蓝图中的UpdateGate()中把Set的CurrentGate值通过Anim Instance传入动画蓝图中的事件函数RecieveCurrentGate()

1766158273899

下面只需要在ABP_Base的LocomotionSM中设置好动画资产即可

在ABP_Base中的Cycle中姿势混合(with E_gate),传入参数为CurrentGate

1766159134918

把这个姿势混合逻辑放到ABP_Layers中

ALI_Lyra中加一个CycleLayer

1766323669009

把Cycle(State)的逻辑放到ABP_Layers的CycleLayer中

1766324157023

这里的CurrentGate需要在ABP_Layers新建一个函数GetABPBase(Pure),来获取ABP_Base中的函数和变量

1766325041708

在ABP_Layers的CycleLayer中就可以获取到CurrentGate这个参数了

1766325445539

和IdleOnUpdate一样,逻辑封装到CycleOnUpdate中,然后CycleAnim变量在每个子ABP_Layer中设置对应动画资产

1766328494884

1766328535006

注意:这些Anim变量都不用设置默认值

我们应该在角色事件蓝图的初始化中设置Anim Class为ABP_UnarmedLayer:

1766329903943

各子ABP_Layer的资产设置:

以ABP_Pistol为例,

1766328565701

如何批量设置动画资产的RootMotion:

1766327868432

1766327907003

1766327841572

效果:

1766330258161

08 Debug Options Intermediate

调试

DebugPrint

1766574047115

1766574061741

1766574073286

1766574095327

效果:

1766574130117

DebugDrawVector

1.需要在角色脚下画箭头就需要先获取到角色的世界坐标

1766574944027

1766574920215

勾上线程安全后连到:

1766574972902

2.Debug Draw Vector

线的起始和最终位置只需要xy两个维度

1766577063572

1766577075299

1766576832978

效果:

1766577189184

建立一个Debug系统管理上面的Debug方法

新建一个结构体S_DebugOptions

1766578898082

1766578909183

ABP_Base中新建该结构体变量

1766578952083

以及函数Debug,把之前事件蓝图中的Debug方法移进来,用DebugOptions结构体变量做if判断执行对应的Debug方法

1766578854938

1766578982545

回到事件蓝图中连上Debug即可

1766579363646

Debug慢动作模式

角色事件蓝图中用Set Global Time Dilation:时间膨胀系数越小越慢

1766579786090

09 Cycle animation Part02 Pro

先获取RotationData

1766831632555

1766831528399

更新当前朝向(2d,只有xy)

1766831880028

1766833413385

在Debug中显示当前朝向

1766833480038

前后左右分别是:

1766835258081

下面就可以根据这个朝向设置不同的转向动画资产

新建一个移动方向的枚举类型

1766834639310

1766834629098

然后在ABP_Base中创建该枚举变量

1766834729497

计算移动方向

后:

1766835023827

前:

1766835233241

更新朝向的时候计算移动方向

1766835445233

Debug显示出来

1766835337542

左右方向的计算同理:

1766919206452

模块化该函数

1766920060674

保持UpdateOrientationData函数的单一功能

1766920309993

把计算移动方向放到外面来

1766920278873

效果:

1766920701797

方向死区Direction DeadZone

转向滞后:

如果只是小幅度变化(在死区范围内),动画不改变

增加Input参数——CurrentDirection , DeadZone

1766924799128

各方向的死区计算

1766922153566

前后方向的死区就是往直角坐标系的水平方向靠,左右方向的死区就是往竖直方向靠

前:[前min - 死区, 前MAX + 死区] 比如原来的前:[-50,50] 死区20的前:[-70,70]

1766924355095

后:[后min + 死区, 后MAX - 死区] 比如原来的后:[-∞,-130]∪[130,∞] 死区20的后:[-∞,-110]∪[110,∞]

1766924364934

左:[后min - 死区, 前min + 死区] 比如原来的左:[130,-50] 死区20的前:[-150,-30]

1766924372576

右:[前MAX - 死区, 后MAX + 死区] 比如原来的右:[50,130] 死区20的右:[30,150]

1766924382323

用Sequence分离 原逻辑和死区逻辑

1766924579610

这里我传错了一个参数

1766925037338

应该是CurrentDirection

1766925024087

整理一下

1766924690946

Select Animations with structures

根据得出的移动方向,配置动画资产

1.新建一个结构体变量S_DirectionalAnimations

1766925890721

1766925922158

2.在ABP_Layers中创建该结构体变量传入Cycle On Update

1766926451928

1766926407831

在各个子ABP_Layers中配置资产

以Pistol为例:

1766926699408

效果:

1766927450000

修复滑步现象——Stride Warping

姿势适配:动态调整角色的动画步幅来匹配胶囊体移动速度

通过前面做的慢放功能可以看出移动的时候存在滑步,因此引入姿势适配Stride Warping

1766927616967

在CycleLayer中加一个Stride Warping节点

1766928507999

Stride Warping节点的配置

1766928602708

效果:

1766929394911

修复45°左右的动画融合问题——Orientation Warping

朝向适配

在Stride Warping节点中加一个Orientation Warping节点

1766928970528

Orientation Warping的配置:

1766929053757

效果:

1766929794213

慢放:

1766929538085

现在各种角度的移动动画能正常融合了,但因为缺少起步和停步的动画,起步和停步的时候还是会滑步,下面加入起步和停步的逻辑

10 Lean animations Intermediate

初探起步倾斜动画

导入资产

导入步枪的倾斜姿势:

1767011615522

三个姿势都设置为局部空间叠加(Local Space),基准姿势是Center

1767011602593

创建混合空间

创建一个混合空间(BlendSpace)——BS_Lean

1767012000192

x轴为倾斜角度,y轴对应不同的权重大小

1767013553757

混合树中设置好资产:

1767014849396

回到Axis Settings,把平滑过渡时间Smooth设置为0.5

1767014914331

按住ctrl在混合树中预览混合效果:

1767015108579

在动画蓝图中应用

ABP_Layers中的CycleLayer,在warping之前应用叠加(Apply Additive)

1767015874024

BS_Lean混合空间的Gate权重设置好,还需要角色自身的偏航角作为倾斜角度。

因此回到ABP_Base的GetRotationData方法,原先得到的WorldRotation是世界旋转角,Break出一个偏航角Yaw,前一步新建一个变量为LastFrameActorYaw(上一帧的偏航角)。

1767017104866

现在得到了上一帧的偏航角和当前的偏航角

相减得到DeltaYaw

1767017262541

再除以DeltaTime,适当放缩后收敛到±90°区间内,得到基于DeltaTime的倾斜角LeanAngle

1767017307162

其中DeltaTime是GetRotationData的输入变量

1767017405722

然后传入ABP_Layers的CycleLayer的BS_Lean

1767017467589

修复后退时的倾斜角度会反向的问题:

1767018185112

根据移动方向的不同,乘以±1

1767017972731

效果:

1767018377517

下面开始起步和停步动画

11 Stop Animations Pro

导入资产

导入动画

新建动画压缩曲线CurveCompression

1767108663261

1767108682613

批量设置所有Stop动画为该曲线,后面“距离匹配”中会用到

1767108774777

停下动画的触发时机应该由加速度来决定,因此下面先获取角色的加速度

获取角色的加速度

新建函数GetAccelerationData

1767109821051

1767109828748

1767109844641

放到安全线程中

1767109873306

debug出这个加速度2D:

1767110169978

效果:

1767110295989

ABP_Base状态机中新建Stop状态以及跳转条件

1767163323542

1767163340672

1767164221411

ALI_Lyra接口下加一个Stop Layer

1767164460623

为什么用Sequence Evaluator?

如果直接用Play Sequence播放Stop动画,会发现由于开启Root Motion导致Stop动画的前几帧会有一段位移,而Sequence Evaluator可以指定动画资产在特定帧开始播放,也就是Explicit Time这个参数:

1767174435338

之后就可以用距离匹配只匹配一些细微的位移变化

什么是距离匹配?

在S_DebugOptions中添加ShowDistanceMatching

1767169834957

添加一个插件

1767172121806

回到ABP_Base的Debug,加一个分支情况

1767172819500

1767172835797

效果:

1767172868676

回到ABP_Layers的StopLayer添加Sequence Evaluator

播放停止动画只需要在切到Stop状态才会调用,因此On Become Relevant

On Become Relevant:当该对象 / 节点 从 “无关状态” 变为 “相关状态” 时触发

1767174051702

1767174015799

Sequence的逻辑和之前Cycle一样:

1767174683938

在子ABP_Layer中配置动画资产即可

以PistoLayer为例:

1767174919502

设置Sequence Evaluator的Explicit Time参数

1767175555321

让Stop动画逐帧更新播放

1767175530861

1767175642653

在逐帧更新Stop动画的函数UpdateStopAnim()中应用距离匹配

1767176547627

停下的距离StopDistance > 0,就应用距离匹配,否则就是已经停下了只需要按固定速度播放

1767177021415

注意到距离匹配还需要距离曲线

1767177000941

因此需要在动画资产中用AnimationModifier新建一个距离曲线修改器

1767177078908

我们可以进行批量操作:

1767177301707

回到ABP_Layers的UpdateStopAnim,距离曲线名字设置为Distance

1767178518115

效果:

1767178724215

按角度停下Stop At Angle

直接从CycleLayer中复制过来这个节点即可

1767186368883

12 Start Animations Pro

起步动画

导入Start动画,开启根运动,设置曲线压缩为CC_UniformIndexable

记得最后在CC_UniformIndexable中点击应用压缩(这会应用到所有引用它的设置)

1767196369677

在ABP_Base新建Start状态及其跳转条件

1767253732551

Idle -> Start: 正在加速

1767251860561

Start -> Cycle:角色速度到达最大速度

1767251900911

Start->Stop: 不在加速(和Cycle->Stop一样)

Stop->Start:在加速(和Idle->Start一样)

Start -> Cycle的新加一个条件:前后帧的速度移动方向改变

需要计算的参数

获取上一帧的速度移动方向,并判断前后帧速度移动方向是否改变

1767251787656

1767252711907

新加跳转条件

Start -> Cycle的新加一个条件:前后帧的速度移动方向改变

也就是如果Start动画期间速度没有到达最大值,改变移动方向也会跳转到Cycle

1767252765672

Start -> Cycle的新加一个条件:前后帧的Gate改变

需要计算的参数

把原来RecieveCurrentGate设置的变量换成IncomingGate

1767252990018

新建一个安全线程的函数GetCharacterState

1767253301679

获取上一帧的Gate,当前的Gate,前后帧的Gate是否改变

1767253202514

1767253368003

新加跳转条件

Start -> Cycle的新加一个条件:前后帧的Gate改变

1767253442270

同一帧应当只允许一次状态切换

因为上面跳转条件中有的参数是基于前后帧某个值是否改变

1767256857151

用StartLayer来管理动画资产

操作和StopLayer中一样

ABP_Lyra接口中新建StartLayer

1767256982191

1767257033768

1767258231617

1767257937227

在各子ABP_Layer中配置Start的动画资产

以ABP_PistoLayer为例:

1767258011512

让Start动画逐帧更新播放(从这里开始会和StopLayer的有些不同)

1767258346277

1767259658158

这里会出现所有方向的起步动画都更新的是前进的起步动画:

1767260137092

在UpdateStartAnim()中确保要更新的动画是正确的起步动画

比较SequenceEvaluator中的值和设定的值,一样就Advance Time播放,不一样就设置为一样的

1767264370301

效果:

1767260256698

在逐帧更新Start动画的函数UpdateStartAnim()中应用距离匹配

Advance Time(普通推进时间)

Advance Time by Distance Matching(距离匹配推进时间)

1767261722614

获得上一帧的角色位置和DeltaLocation

回到ABP_Base的GetLocationData方法

1767262056129

DeltaLocation

1767262119005

回到ABP_Layers的UpdateStartAnims()

1767262272395

为Start动画批量添加距离匹配修改器(和Stop动画的操作一样)

1767262395273

回到ABP_Layers的UpdateStartAnims()

1767268425217

Blend Logic

动画过渡时候的混合逻辑

Start->Cycle过渡的混合逻辑全部改为惯性(Inertialization),混合时间改为标准的0.25

1767262921241

这会改善两个动画切换时因为迈出脚不一样导致的停顿,但是还是会有不流畅的感觉,最好还是要保证动画资产的连贯性

Warping的设置

直接从CycleLayer复制过来

倾斜

1767268690514

方向适配、步幅适配

1767268718705

步幅适配加上插值混合,解决滑步问题

1767266155936

如果出现Warping不起作用,可以看是不是忘记取消勾选Teleport to Explicit Time:

由于Stop和Start用的都是Sequence Evaluator,和Sequence Player不一样,需要取消勾选Teleport to Explicit Time

并且由于都是短动画不需要循环,取消勾选 Loop

1767268026850

效果:

1767269375592

13 Pivot animations Pro

移动时急转

导入动画资产,设置为根运动、应用曲线压缩

PivotState

Alias

相当于把那些要跳转到同一个State的State集合到一个Alias,然后只需要让这个Alias跳转到目标State即可

1767275422009

1767275364471

PivotAlias -> Pivot

如果是180度急转:移动速度与加速度反向,也就是二者的单位向量的点积为-1,也就是cos180°

其他角度的急转都是钝角,也就是点积<0即可

1767276002425

Pivot->Stop

直接应用stop的共享条件

Pivot->Cycle

跳转条件1

进入急转状态时,需要保存当前的加速度

在PivotState的输出动画姿势上加一个回调函数SetupPivotState,这个函数在当前节点激活时调用

1767339826348

获取 急转状态时候的加速度

1767339958010

Pivot->Cycle的跳转条件1:角色速度 与 急转后的加速度 向量不平行

1767340042798

跳转条件2

新建一个蓝图类AnimNotify

1767340784768

1767340818186

Pivot->Cycle的跳转条件2:当前的Pivot动画播放完了

1767340983648

因此要在每个Pivot动画资产的末尾加上这个动画通知ANS_Exit_Pivot

1767341315601

PivotLayer

ALI_lyra新建一个PivotLayer

1767344772352

ABP_Base的PivotState连上PivotLayer

1767344851568

PivotStateMachine

回到ABP_Layer的PivotLayer

由于急转存在一个方向的急转与另一个方向的急转之间也会存在跳转关系,比如:

玩家会在转向没结束的时候转到另一个方向,我们需要一个状态机来处理这种到处乱转的情况

1767345301743

1767345309878

跳转条件

A与B的来回切换条件应该是相同的:

  1. 速度与加速度反向,也就是单位点积<0
  2. 速度 与 急转后的加速度 向量不平行

两条规则同时满足

1767346714211

AState

1767347828632

SetupPivotAnims()

1767347897228

这里的Sequence选取动画资产和之前大致一样,但是需要根据加速度的角度来决定

因此,我们需要先获取加速度的角度

获取加速度的角度,加速度的移动方向(E_LocomotionDirection类型变量)

直接参考速度的这两个变量写

1767349062325

1767349074875

整理一下UpdateOrientationData1767349193299

回到ABP_Layers的SetupPivotAnims()

这里的Sequence获取方式直接复制之前的,把Index换成加速度的移动方向

1767349376424

1767351353479

右键把这个选择动画资产封装为一个函数

1767351384223

勾选为Pure函数以及线程安全

1767351561455

可以在其他Layer中也这样封装选择资产的函数,我就直接操作了,不在这里赘述,后面如果看到调用"SelectXXXAnims"就是这个选择对应资产函数

同样的,回到各个子ABP_Layer中配置动画资产即可

以ABP_PistoLayer为例

注意:

Pivot动画要注意动画的实际表现是什么样的,Lyra的动画资产中,pisto walk pivot Bwd实际上是衔接上一个Bwd和下一个Fwd

而我们上面写的动画资产选取是以当前的加速度移动方向,也就是已经发生实际的转向逻辑,所以应该把Bwd的动画资产放在Fwd的插槽,其他的动画资产同理

1767350186202

UpdatePivotAnims()

1767425058868

1767425071119

1767425138426

注意速度与加速度反向时的距离匹配用的DistanceToTarget是PivotLocation预测节点

距离匹配用的曲线和之前一样操作,对Pivot动画添加DistanceCurve修改器

BState

直接复制AState的SequenceEvaluator

1767425427185

方向适配

1767428743582

然后需要设置一下方向适配节点的参数:

1767428806411

当根运动速度Root Motion Speed低于阈值时,Warping缩放值被强制设为1.0(即不进行缩放),当根运动速度高于阈值时,才根据实际速度比例计算Warping缩放,很多动画的根运动速度可能低于10.0(比如慢走、小幅度移动),这种时候就需要降低这个阈值,更有甚者,对于停下/急转这种速度会降到0的动画,可以把阈值设置为0

当角色移动速度与动画速度不匹配时,本应进行的Warping被禁用,导致角色动画与实际移动不协调,还有一种情况就是:在动画开始或停止时,根运动速度会在阈值附近频繁变化,导致Warping效果频繁开关,产生动画抖动,即使启用了插值,这种频繁的开关也会影响动画流畅度

记得复制到BState

效果:

1767430134905

14 Turn in place Pro

原地转身

导入动画,开启根运动,应用压缩曲线

RotateRootBone

根骨骼旋转节点

UE中的三个旋转角

UE使用的是左手坐标系:

  • Pitch:绕Y轴旋转
  • Roll:绕X轴旋转
  • Yaw:绕Z轴旋转

因此加入新的节点RotateRootBone,原地转身应该改变的是Yaw,新建变量RootYawOffset

1767438979118

UpdateRootYawOffset

获得RootYawOffset

相机水平转动,默认情况时角色朝向保持不变,当水平转角超过90度,朝向同步变化

原先的逻辑是角色朝向随相机同步,我们只需要每帧让传入RotateRootBone节点的参数RootYawOffset减去相机转角就行

前后帧相机转角——DeltaYaw(这个值我们之前就已经在GetRotationData中算过了)

1767440291951

让角色朝向保持不变

传入RotateRootBone节点的参数RootYawOffset减去这个DeltaActorYaw即可

1767440332139

这会导致运动时候角色的朝向也不变,那么转向就不能正常运作了,我们需要让这个逻辑只在Idle时起作用

只有在Idle时角色朝向才会保持不变

用枚举类型区分Idle和其他状态

1767440579065

1767440708050

1767441019972

Debug显示RootYawOffset

S_DebugOptions结构体加一个参数

1767441814566

1767442733249

1767442720192

区分RootYawOffsetMode

回到ABP_Base的IdleState,OutputPose绑定一个OnUpdate回调函数1767445167279

Update Idle State直接赋值Mode为Accumulate即可,这样就会在Idle状态期间RootYawOffsetMode保持为Accumulate

1767445181599

回到UpdateRootYawOffset,让mode的默认值为BlendOut

1767445608465

封装一个函数专门用于设置RootYawOffset的值

1767446150861

并且,如果当前的Mode是BlendOut,就重置RootYawOffset

1767446084494

这样就做到了Idle的时候摄像头移动不改变角色朝向,只有角色不在idle状态时才会改变朝向

效果:

1767446467361

存在缺陷:角色朝向与相机朝向存在差异时,移动的时候会立刻重置RootYawOffset导致角色朝向突变,需要加一个插值过渡一下

插值过渡(RootYawOffset,0)

Update Root Yaw Offset函数需要先新建一个Input参数DeltaTime,在安全线程中传入

1767446769566

1767446716113

效果:

1767447133616

Float Spring Interp(浮点弹簧插值)节点

  • Current :弹簧插值的当前浮点数值(起始值)。
  • Target :弹簧插值最终要趋近的目标浮点数值。
  • Spring State :需变量存储的弹簧状态容器,记录跨帧的速度、加速度等动态数据。
  • Stiffness :弹簧的硬度,数值越大,趋近目标的速度越快。
  • Critical Damping Factor :控制回弹幅度,1.0 是无回弹的临界阻尼,<1 回弹、>1 缓慢趋近。
  • Delta Time :当前帧的时间增量,保证不同帧率下运动节奏一致。
  • Mass :弹簧末端物体的质量,数值越大,惯性越强、响应越慢。
  • Target Velocity Amount :目标速度的权重,数值越大,弹簧对目标速度变化的响应越灵敏。

存在缺陷,RootYawOffset过渡到0期间方向适配OrientationWarping会失效,需要修复

1767495279129

这个带偏移的angle传入ABP_Layers的各个Layer的方向适配

1767495388538

1767495478515

1767495625093

1767495839562

1767495953027

效果:

1767496377089

起步会有滑步,以及移动时转动视角同时按下相反方向不会进到PivotState(待修复)

曲线附加动画实现原地转身

MotionExtractorModifier

动作提取修改器:

从动画序列中,提取出角色的位移、旋转、速度等运动数据,用于驱动角色的实际移动

暂时关闭RootMotion,可以看到转身动画是根骨骼的z轴在旋转,从0到-90(nearly)

1767498360756

并且需要将曲线最终的值设置为0,可以把曲线都上下翻转一下,然后手动在各个动画资产中对曲线进行上下限的重定义

因此,添加MotionExtractorModifier如下设置:

1767498501842

进入每个资产手动调整曲线上下限,目的是让最终的值为0,以90度和180度的转身动画资产为例:

90度

1767507670556

注意上下限要严格精确到-90(或-180)和0

1767507759767

1767507797834

180度

1767507889452

加一条IsTurning曲线,并标注两个关键帧

第二个关键帧是root_rotation_z完全不变的第一个点,值为0

第一个关键帧是第二个关键帧的前一个点,值为1

这样,1和0对应true和false,true代表正在转身,false代表已经转身结束

1767509687128

1767509779094

最终呈现先1后0的突变函数

1767510410230

如果想更自然的过渡,可以选中IsTurning曲线,右键Auto

1767510550674

得到更平滑的曲线

1767510561668

TurnInplace States

回到ABP_Layers,在IdleLayer新建一个IdleSM,用来管理IdleLayer下的逻辑

1767511016174

1767511028079

原来Idle的SequencePlayer放进Idle State中

1767511061744

Idle->TurnInPlaceEntry

跳转条件:RootYawOffset超过一个阈值

1767511560049

从RootYawOffset的变化可以看出,如果RootYawOffset>50左转,<-50右转是较为合适的值

因此转身的阈值可以设置为50

1767511752510

左/右转由RootYawOffset的正负决定,>0左转,<0右转

当进入TurnInPlaceEntry State时,执行该判断

1767513404294

1767513375681

TurnPlaceEntry State的SequenceEvaluator

回到TurnPlaceEntry State,用SequenceEvaluator播放动画

1767515423224

SetupTurnInPlaceEntryAnims()

1767515361044

选择资产函数SelectTurnInPlaceAnims()

1767515706548

1767515672771

然后在各子ABP_Layer中配置TurnInPlace动画资产

以ABP_PistolLayer为例

1767516003546

UpdateTurnInPlaceEntryAnims()

1767517193828

1767517208929

这里新加了一个随DeltaTime自增的变量TurnInPlaceTime,传入ExplicitTime之后动画播完需要重置为0,因此需要回到SetupTurnInPlaceEntryAnims设置它为0

1767517329203

TurnInPlaceEntry->TurnInPlaceRecovery

跳转条件:动画曲线IsTurning的值为0

前面在转身动画资产中的曲线IsTurning由1跳变为0,0的时候就是转身完成,也就是进入TurnInPlaceRecovery

1767517764501

注意要回到IdleSM把每帧最大跳转数改为1

1767517926548

并且这个跳转不需要过渡,因为播放的是同一个动画的不同阶段

1767520304499

下面设置TurnInPlaceEntry的动画播放器

TurnInPlaceRecovery State

1767535203061

前面UpdateTurnInPlaceEntryAnims()中设置的变量TurnInPlaceTime反映在这个曲线中就是会一直自增,在跳变到0的时候会积累到一个值,这个值我们在TurnInPlaceRecovery State中将作为动画播放的长度?

1767518078784

动画播放的时间解决了,选择的动画也要在TurnInPlaceRecovery State中调用

因此UpdateTurnInPlaceEntryAnims()中把最终选择的转身动画存进一个单独的变量FinalTurnInPlaceAnim中

1767518993570

回到TurnInPlaceRecovery State

1767520162228

让角色的转向同步转身动画曲线

这个转向同步逻辑可以写在ABP_Base的Idle State的UpdateIdleState()中,

新建函数ProcessTurnYawCurve()

1767521440952

逻辑是:

如果此时曲线接近0,说明已经转身结束,那就重置这两个帧的值

反之,正在转身过程中,我们就保存每一帧曲线关键帧的值,然后算出前后帧的曲线值差,最后RootYawOffset -=前后帧的曲线值差从而更新角色实际转向

每一帧曲线关键帧的值 = root_rotation_Z曲线值

1767522859344

1767532980222

1767599170954

1767533043101

SafeDivide:在执行除法前先判断除数,若除数为 0,会直接返回0

TurnInPlaceRecovery -> Idle

TurnInPlaceRecovery播放完自动跳转到Idle即可

下面这种方法会存在bug,我们不用1767533308054

可以手动获取当前动画剩余时间==0的时候跳转即可

1767533509682

TurnInPlaceRecovery -> TurnInPlaceEntry

直接套用idle->TurnInPlaceEntry的跳转条件即可,并且这里和TurnInPlaceEntry-> TurnInPlaceRecovery一样由于播放的是同一个动画,不需要混合

1767533767768

最终效果:

1767535035021

转身的时候还存在缺陷,180度转身时,有的时候会播放两段90度的转身

貌似是因为相机视角的原因,后面做相机部分的时候再考虑修不修?(已经在Couch时的TurnInPlace章节解决了)

15 Crouch gate Intermediate

蹲伏

导入动画资产,RootMotion

Gate

在E_Gate中新加一个Gate——Crouching

1767596523726

角色蓝图BP_LyraCharacter中GateSettings

1767690967341

后面我们只需要进到每一个Layer中为对应的SelectAnims函数增加对应的Anims变量即可

Input

创建输入IA_Crouch,值类型为bool

在IMC_ALS中加进来

1767599597656

回到角色事件蓝图,添加input事件

由于蹲伏的时候还需要改变胶囊碰撞体的高度,因此需要向Character发出通知“Crouch / UnCrouch”

1767601304605

去characterMovement组件上打开Can Crouch

1767601718551

蹲下后的碰撞体高度值修改为合适的值

1767601930573

1767602064552

Idle蹲伏状态区分

蹲伏动画一般会分为Crouch和UnCrouch,我们需要根据蹲伏状态判断来决定播放哪个动画,通过判断前后帧的IsCrouching是否相等来决定播放Crouch还是UnCrouch

比如:

上一帧没蹲,这一帧蹲了,那就播放蹲下

如果上一帧蹲,这一帧没蹲,那就播放起身

回到ABP_Base的GetCharacterState()编写逻辑

1767604026296

之前的Idle Anim只包含Idle站立的动画,需要做个区分:Idle Stand和Idle Crouch(蹲伏又分为Crouch 和 UnCrouch)

1767604479296

因此,

IsCrouching用来区分Idle Stand和Idle Crouch,以及CrouchEntry和CrouchExit

CrouchStateChanged用来判断是否从Idle过渡到Crouch

Idle State

IsCrouching用来区分Idle Stand和Idle Crouch

1767604957033

1767604979517

在各个子ABP_Layer中设置CrouchIdle动画资产

1767605063629

效果:

1767605362263

StanceTransition State

IsCrouching用来区分CrouchEntry 和 CrouchExit

把原来的Idle State用状态机再次拆分

1767605842277

1767686832145

1767686858424

1767614527400

新建两个动画序列变量CrouchEntryAnim 和 CrouchExitAnim

1767614473033

SetupStanceTransitionAnim()

1767687910925

1767687971549

在子ABP_Layer中配置动画资产

以ABP_PistolLayer为例:

1767688080932

1767688080932

Idle-> StanceTransition

CrouchStateChanged用来判断是否从Idle过渡到Crouch

1767686893077

StanceTransition -> Idle

跳转条件1:和Idle-> StanceTransition一致

跳转条件2:StanceTransition动画播完(注意这里要区分规则的优先级,自动完成播放恢复Idle优先级要比状态改变要低)

1767690742218

1767688564864

StartLayer加入Crouch

动画资产处理:

Crouch Start设置曲线压缩和距离曲线修改器

设置身体侧倾的Crouching权重

1767689922810

SelectStartAnims()

1767690396626

1767690464896

CycleLayer加入Crouch

动画资产处理:

只需要确保Crouch Walk 开启了RootMotion

1767691804003

1767692484155

1767692464785

发现蹲伏的速度和我们实际设定好的速度不一致,是因为CharacterMovement在进入蹲伏函数Crouch()时,会有自己的最大移动速度,这个最大移动速度和我们所选择的不一致,因此我们需要将其调整回来

1767693213744

或者因为我们只需要Crouch()/UnCrouch的设置胶囊碰撞体高度这一个功能,所以其实我们可以不用这个CanCrouch,自己设置Crouch时的胶囊体高度也可以

StopLayer加入Crouch

动画资产处理:

Crouch Stop设置曲线压缩和距离曲线修改器

注意,如果动画出现跳帧的情况,勾上距离曲线修改器的Stop at End,这样就不会在末尾帧打上曲线

1767704155589

1767700791547

1767700413630

1767700531502

PivotLayer加入Crouch

动画资产处理:

Crouch Pivot设置曲线压缩和距离曲线修改器

1767704448045

1767704464828

1767704603142

注意:Pivot急转动画资产前后左右命名是相反的

1767705575306

回顾之前Pivot的对动画资产的处理,还需要在动画末尾加上回调通知notify,通知动画已经播完

1767706182533

Crouch时的TurnInPlace

蹲伏状态的原地转身

把原来选择原地转身动画的函数封装一下

1767711912869

根据CurrentGate选择要传入SelectTurnAnims()的转身动画资产

1767711992307

其中,把转身动画序列用结构体S_TurnInPlaceAnimations封装起来

1767710822845

1767710791196

在SelectTurnAnims()中按原先逻辑break出对应动画序列传入即可

1767712381652

1767712573429

然后我们需要和前面站立时候一样对动画资产进行处理——MotionExtractorModifier动作提取修改器、补偿曲线

这部分不展示了,可以回去看14 Turn In Place Pro章节

修复之前转身时就已经发现的Bug——180度转身时,有的时候会播放两段90度的转身

1767714792833

最终Crouch效果:

1767715140654

16 Jump Animations Pro

跳跃

导入动画资产,Root Motion,压缩曲线

输入事件

新建IA_Jump,bool类型,两个Trigger(按下和松开,后面会用来区分短按和长按的起跳加速度)

1767766016698

1767766551094

添加到IMC_ALS中

1767766218379

调用CharacterMovement的函数Jump和StopJumpping

1767766366781

设置CharacterMovement.Jump相关参数

1767766710951

构建起跳阶段的状态机

Jump Start ->Jump Start Loop->JumpApex

JumpApex

分析动画资产:Jump Start ->Jump Start Loop->JumpApex->JumpFall_Loop->JumpFall_Land->JumpRecoveryAdditive

有主动跳跃和被动跳跃两种情况:

1.主动跳跃:完整的起跳落地:起跳-起跳循环-下落

2.被动跳跃:当从边沿到空中且无起跳输入时,只有落地

因此,Jump的起跳阶段状态机有两种入口——JumpStart和JumpApex

1767773892499

Conduit(管道)

可用于创建1对多、多对1或多对多的跃迁

最常用的是用它来分散状态机的入口点,相当于一个单向的中继站

跳转条件

JumpAlias->JumpSelector以及JumpSelector本身的规则先都设置为真(后面会修改)

1767772267972

1767772278661

跳转条件参数的计算

IsOnAir

1767773630799

IsJumping和IsFalling

1767776275967

1767776300344

测试一下两种Jump入口是否都能正常进入

1767775124608

跳转正常

JumpStart->JumpLoop

跳转条件:动画播完自动跳转

JumpLoop->JumpApex

跳转条件:角色在重力加速度的情况下到达最高点所需要的时间<一个阈值

z轴速度/重力加速度,最后取反就是到达最高所需时间

1767776767343

1767948644348

动画分层管理

ALI_Lyra中新建JumpStartLayer、JumpStartLoopLayer、JumpApexLayer

1767779017974

ABP_Base中对应State连上对应Layer

然后在ABP_Layers相应的Layer中用SequencePlayer播放

JumpStartLayer

1767778190466

JumpStartLoopLayer

1767779065663

JumpApexLayer

1767779080787

1767779172895

构建落地阶段的状态机

JumpApex->JumpFallLoop->JumpFallRecovery

1767800536005

JumpApex->JumpFallLoop

1767780363148

JumpFallLoopLayer

1767854366770

JumpFallLandLayer

JumpFallLoop->JumpFallLand:需要实时计算地面的距离匹配,所以JumpFallLandLayer用SequenceEvaluator播放

1767854446101

1767854514212

JumpFallLoop->JumpFallLand

采用向下的球体追踪,本质上就是实时向下发射(球型范围的)射线,从射线碰撞(若能碰撞到)返回的信息与角色的当前的位置相减得到距离

既然是向下发射检测射线,起点肯定是脚部,那么就先要获取角色脚部的z轴位置

获取角色脚部的z轴位置

角色位置 - (0,0,胶囊体高度的一半)

1767855823999

射线检测脚部距离地面高度

1767856196733

1767856173084

击中地面的时候返把击中距离Distance传给动画蓝图

1767856493103

怎么在角色蓝图和动画蓝图通信?

可以回顾02 Blueprint communication Intermediate/Blueprint Interfaces

用接口BPI_AnimationBluprintsInterface传输

1767856751489

1767857036976

这个接口的函数在BP可以直接调用,在ABP中需要继承才能使用

角色蓝图

1767857247593

ABP_Base

1767857475655

JumpFallLoop->JumpFallLand

跳转条件:GroundDistance<一个阈值

通过debug可以看出150是个较为合适的值

1767858383573

1767858394913

SetupJumpFallLandAnim()

在OnBecomeRelevant中设置从0播放动画

1767859554021

UpDateJumpFallLandAnim()

前面提到由于落地需要实时距离匹配,所以用SequenceEvaluator

因此,在OnUpdate中实现这个距离匹配,并推进动画播放

添加z轴的距离曲线修改器

1767860269924

确保曲线的最后的值为0

1767860581815

1767860604092

Advance Time by Distance MatchingDistance Match to Target的区别:

1767861194007

因此对于停下或者落地这种有目标位置的,应当使用 Distance Match to Target

落地时候的DistanceToTarget就是0

1767861556704

效果:

1767860984389

还需要最后一个收尾

EndOnAir

1767862801246

跳转规则:

1767862776188

1767862458046

1767862422246

1767862514446

为了防止同时触发,->CycleAlias的优先级设置为1,->IdleAlias的优先级设置为2

1767862618927

效果:

1767863185723

最后需要在落地后加一个缓冲附加动画

JumpInterupts

Jump提前落地机制

除了JumpFallLand可以回到Idle和Cycle,还会存在一些情况

比如跳到一个高的平台,不需要JumpFallLand也应该立刻回到Idle和Cycle

1767864194035

这些JumpInterupts只需要保证不在空中就跳转回Idle和Cycle

1767864239758

效果:

1767864491190

JumpFallLandRecoveryAdditiveLayer

落地缓冲附加动画

JumpFallLandRecoveryAdditiveSM

ALI_Lyra

1767865163621

1767865279316

1767865355043

1767872774476

1767872793352

1767872808258

JumpFallLandRecovery->Default

1767872921714

1767872863031

1767873636826

动画资产设置为Additive

1767863931946

1767873662643

获得下落的时间

1767948709535

JumpFallLandRecovery State需要一个权重参数LandRecoveryAlpha

1767875985176

LandRecoveryAlpha这个参数在状态机到达JumpFallLandRecovery状态实时更新,也就是LandRecoveryStart(),通过debug发现Idle起跳时FallingTime通常是0.4,所以先把FallingTime输入限制在0-~0.4,映射到0.1-1.0输出到LandRecoveryAlpha,如果是移动状态下就再乘以0.5放缩一下,避免alpha过大导致落地脚部畸形

1767876797030

考虑到如果起跳过一次后直接从高处落下,这个FallingTime需要在JumpApex的时候也要重置为0

1767876616464

1767876631079

效果:

1767877767860

存在缺陷:蹲伏状态无法进入Jump状态机,并且空中Crouch后落地会出现滑步,后面有空可以看LyraStarter工程的实现

17 Sync Groups Intermediate

Sync Groups同步组

为什么用同步组?

Sync Groups是ue动画系统中用于 同步多个动画播放 的重要机制。它允许不同的动画节点按照相同的时间进度进行播放,确保动画之间的协调性。当起步动画结束的时候,我们希望将其混合到Cycle动画中,本质是 确保多个动画在相同的时间点播放 ,通过领导者选择和位置同步机制,实现动画的协调一致。这解决了动画不同步导致的视觉不协调问题,让角色动作更加自然流畅。

如果没有Sync Groups,我们只能在混合的时候将摄像头放大,用来掩盖脚部的穿帮

常见应用场景:

场景1:角色移动动画同步

  • 需求 :行走、奔跑、跳跃等动画需要同步播放
  • 配置 :所有移动动画使用相同的Sync Group名称
  • 效果 :动画切换时保持时间连续性

场景2:武器攻击动画同步

  • 需求 :左右手武器攻击动画需要精确同步
  • 配置 :左右手动画使用相同的Sync Group,角色类型为CanBeLeader
  • 效果 :双手攻击动作完美同步

场景3:表情动画同步

  • 需求 :面部表情动画需要与身体动画同步
  • 配置 :表情动画作为跟随者,跟随身体动画的Sync Group
  • 效果 :表情与身体动作协调一致

SyncMarkerAnimModifier

同步标记动画修改器

ue会自动帮我们标记好动画的左右脚

一定要先关闭ForceRootLock再应用同步标记动画修改器,否则标记将不准确,应用完修改器再启用ForceRootLock

正确的流程:

  1. 关闭ForceRootLock
  2. 应用同步标记动画修改器
  3. 启用ForceRootLock

接下来,为所有的Crouch、Cycle、Start、Stop、Pivot动画做这些操作

1767926574827

关闭ForceRootLock,保存

1767879597220

应用同步标记动画修改器,保存

1767879647684

启用ForceRootLock,保存

1767879674568

为每个Layer设置同步组

这一部分更详细的解释:UE5 骨骼动画 停步方案探讨(已完结) - DarkFlameMaster的文章 - 知乎

因为CycleLayer占据了最大的时间,所以AlwaysFollower

1767880307934

1767925783895

Stop的同步组必须单独设置:从Start单向过渡到Cycle,Start作为领路者,是Cycle去调整自己的“StartPosition”来配合当前角色动画播放到的位置。而当Cycle单向过渡到Stop时,按理来说是要Stop动画去调整自己的“StartPosition”来配合相位的,但倘若Stop作为领路者,从第一帧开始自然播放,本来的Cycle动画就得因此发生跳变了——自然,混合的结果也会出现跳变。

1767931258637

1767925867516

1767925896779

Blend Options

混合参数

1767928054416

1767928112375

开启后可以更加自然,常见情况是停步和急转时开启

18 Aim offset Intermediate

瞄准偏移

MeshSpace和LocalSpace

按照官方文档的指导:

对于采用BlendSpace实现混合时,通常会选择LocalSpace

而对于采用AimOffset实现时,通常需要选择MeshSpace

1767932212390

  • Mesh Space是 “基于网格初始姿势的全局叠加”;
  • Local Space是 “基于骨骼当前姿势的局部叠加”,被叠加的动画会受到叠加前的骨骼位置的影响

瞄准附加动画不需要根据当前姿势进行叠加,应当直接附加到初始姿势上,因此选择MeshSpace

可以参考这篇博客:浅谈MeshSpace和LocalSpace - 贺志武的文章 - 知乎

动画资产处理

1767940519337

选_CC作为基础pose

1767940597727

AimOffset混合空间

1767942660300

动画资产命名规则

C:Center

B:Back

U:Up

D:Down

因此LBC就是LeftBackCenter

1767941940702

AimOffset(Layer)

ALI_Lyra

1767946264505

获取AimOffset的Yaw和Pinch

1767947476601

1767947459653

1767946609131

1767947567653

配置资产

1767945773295

修复问题——蹲下时瞄准会站起来

1767950841256

1767950926560

效果:

1767951070484

19 Weapons Pro

武器

武器模型资产

ORM贴图:R-AmbientOcclusion阴影,G-Rough粗糙度,B-Metallic金属度

1767952153019

在角色SketalMesh中添加Soket,添加预览Mesh用于调整位置:

1768118486962

1768120056268

武器添加到角色class

新建结构体S_Sockets

1768120280773

1768120390442

注意命名规范

1768120514571

设置结构体的参数默认值

1768120571343

回到角色class,把武器添加到角色Mesh的子级

1768121333018

在角色蓝图中编写持枪的逻辑(这部分可以用C++重构)

1768574529439

1768123038675

用Attach Component To Component

1768123026271

IK解决持枪时手部摆放问题

1768130300194

HandIkRetargeting

1768130313785

Two Bone IK

1768401381057

修复移动时手部摆放问题

1768130946340

1768130953627

新建一个CopyBone节点,让hand_l复制前面设置的虚拟骨骼VB

1768401292935

把相机弹簧臂设置为越肩视角,效果如下:

1768132005311

这一章节如果要重定向为Meta human,可以参考:

【虚幻5 Metahuman 教程 - 如何完美重定向 Lyra 动画?

效果:

1768139057025

20 Foots Intermediate

脚部放置

FootPlacement

1768142468766

1768142503349

LegIK

1768142594019

效果:

1768142709653

跳起时将FootPlacement节点权重设置为0

1768143964838

番外篇:参考Lyra改进当前的动画表现

设置RootYawOffset时,需要考虑偏转角过大时,原地转身动画与瞄准动画的来回抽搐

1768406418709

实测效果:移动时转身还是会有问题,这个后面有空再看Lyra中是怎么写的

1768406693733

21 Weapons animations Intermediate

武器动画——开火、换弹

开火

导入动画

只开启武器动画的根运动,不需要开启人物动画的根运动,因为人物动画是一个附加动画

人物动画设置为附加动画

1768402493763

Input

1768317172764

1768317224399

1768398955778

1768398966860

动画蒙太奇

创建动画蒙太奇

1768320790123

1768320798207

1768397162482

渐入0,渐出0.3,曲线设置为cubic

把蒙太奇设置为ALS.FullBodyAdditive

1768397530328

回到ABP_Base,设置想要蒙太奇的slot

1768397292455

在角色事件蓝图中播放蒙太奇

1768398983109

效果:

1768400109629

换弹

导入动画

只勾选武器的根动画

Input

1768401474407

1768402771614

动画蒙太奇

创建动画蒙太奇

换弹动画的蒙太奇slot设置为ALS.UpperBody

1768402834020

渐入渐出参数

1768402930392

在角色事件蓝图中播放蒙太奇

1768403889255

Blend Mask

混合蒙版

1768407178002

在这个ALS_UpperBody的BlendMask中只需要把手部相关的全设置为1(除了手部的虚拟骨骼),剩下的可以参考下面的图

1768407786133

1768407775756

1768407833161

回到ABP_Base把BlendSlot“UpperBody”混合到整个LocomotionSM状态机

LocomotionSM创建一个副本,然后用LayeredBlendPerBone来把BlendSlot“UpperBody”混合到整个LocomotionSM状态机上

1768408050089

LayeredBlendPerBone节点设置为BlendMask,添加ALS_UpperBody进来,并启用Mesh旋转

1768408175565

我们还需要让播放换弹动画时禁用两个TwoBoneIk,否则换弹时的左手会很奇怪

可以添加动画曲线DisableReloadHandIK来实现,动画开始后0.03和结束前0.03s为0,其余为1

1768486414738

也就是换弹动画开始后0.03和结束前0.03手部ik的权重都为0,最大权重值1减去DisableReloadHandIK动画曲线的值 再传入手部的每个节点的权重alpha

1768484961848

1768486633013

1768487241722

效果:

1768409489779

22 Updates Section Intermediate

一些优化工作

修复:瞄准时的逻辑

Crouch时瞄准保持Crouch状态

1768481579164

瞄准时设置放缩相机

1768482610966

1768482600240

效果:

1768487677845

手持不同武器对应不同的Movement参数

空手、手枪、长枪时的速度加速度摩擦力这些都应当不同

Data Table

数据表

基于S_GateSettings创建一个数据表,这样就可以根据不同的武器来设置Movement参数

1768487748850

1768488662412

参考值:

1768488341903

手枪和空手的身体权重差异小,各项值较为接近

长枪重惯性大,速度更小,制动时的减速度更大

回到角色事件蓝图,先把原来配置Gate各项Movement参数的逻辑折叠为函数

1768492473137

为什么不用Macro(宏)?对于复杂逻辑不太适配

1768489337432

删去原来GateSettings的配置来源

1768495269462

1768495274899

这种{枚举,结构体}的字典用来管理大量数据不够优雅

1768489566056

因此换成我们前面的新加的数据表DT_WeaponGates

1768492289946

整体:

1768492243194

运行后发现更换Weapon的时候Gate没有变

在SwitchWeapon的事件中更新Gate

1768491296803

运行后发现更换Weapon的时候切换到Gate为Crouch状态时,数据表设置的每个武器的Crouch时的属性并没有实际作用,这是因为在Character Movement里面的Couch能力会覆盖我们的设置

1768491638154

因此我们需要改造SetGateSettings这个函数,加一个分支用来赋值Crouch最大速度即可,因为Character Movement里面的Couch能力只覆盖了这个最大速度

1768492607955

1768493275039

👆同时我整理了下这个函数的节点排版,清爽很多

效果:

1768495120619

修复:移动时切换武器,有时会出现脚部没同步的情况

在ABP_Base的各个Link Layer,默认的Blend In/Out值是-1,也就是这些链接层默认不进行渐入渐出

1768493934279

1768493943001

因此,我们需要设置一下他们各自的渐入渐出值,一个比较合适的值是0.15

1768493959606

渐入渐出的Profile还可以设置是否启用FastFoot,暂时效果还行,可以不用启用FastFoot

效果:

1768494755851

23 Audio Intermediate

音效

导入音频资产

开火声音

播放声音

播放声音的位置应该是枪管

1768577494985

1768578361188

1768578391081

让Fire声音更加随机化

创建一个MetaSoundSource

1768578902564

1768578890319

创建这个MetaSoundSource的预设

1768579104403

1768579407339

找到InputSource,可以Override其预设模板,替换为各自的资源

1768579440950

1768579316533

每个武器分配完InputSource Sounds后,回到前面设置声音资产的地方

1768579604581

子弹轨迹线

1768580421128

起点是我们视线的中心点,也就是相机的位置

是吗?

终点是是从起点出发向正前方很远的点

终点可以做很多文章,这会影响子弹的下坠等等效果

如果击中目标,则返回受击点相关数据,未击中,则返回子弹轨迹线的终点

1768580799785

1768582772686

1768582784370

效果:

1768581371435

在受击点播放子弹撞击声音

1768581776375

1768582982914

这里还可以根据ImpactPoint的信息来判断击中点具体是什么东西,分配不同的击中声音,后面再加

受击点的声音随距离衰减

创建一个SoundAttenuation(声音衰减)

1768583082262

1768583254561

打开受击MS的Source选项,选择声音衰减

1768583287857

1768583421765

勾上这个debug,运行游戏后,按下~键,输入

1768583623362

即可看到声音衰减的调试信息:

1768583641320

不同类型的声音衰减的调试画面不同,可以多试试

根据受击物体的类型播放不同的受击点声音

在项目设置中添加一个表面类型——Glass

1768730403385

新建物理材质,设置表面类型为Glass

1768731084163

1768732128026

新建材质,设置该材质的物理材质

1768731114567

1768732216990

在场景中附上材质

1768731347332

回到角色蓝图,封装一下播放受击点声音的逻辑——PlayImpactSoundWithDebris

1768733041543

1768733094268

换弹声音

一个换弹蒙太奇动画有多个阶段(ClipIn,ClipOut,Slide),因此需要在不同帧播放不同的声音资产

只需要在换弹蒙太奇中添加Notify即可

1768735110810

TODO:手枪换弹时切换武器会出现播放蒙太奇错误

根据地面材质类型播放对应的落地声音

新建一个AnimNotify类(注意:不是AnimNotifyState)

1768735994168

在JumpRecoveryAdditive动画中添加AN_Land

1768737397022

回到AN_Land,添加ReceiveNotify函数

1768737606031

从Pelvis处向下发射检测射线

1768740177879

1768737581085

这会在目标动画的AN_Land触发时发射检测射线

1768737718005

根据落地面不同的表面材质播放不同MetaSound

1768738723087

1768738776822

1768738913249

1768738918622

记得把三种Wepon的落地动画资产中都添加AN_Land通知

1768739030871

走路和慢跑时的脚步声音

回顾17 Sync Groups Intermediate时做的:SyncMarkerAnimModifier在动画曲线中标记了动画的左右脚

1768741918802

先创建AnimNotify——AN_FootOnGround(内部逻辑和AN_Land一样,只是替换了BoneName和MetaSound资产)

1768739820721

1768740361745

新建MetaSound:MS_FootOnGround,与MS_PlayRandomSound的唯一区别是在Out Mono加一个的音量调节,并设置好默认值和区间

1768820722963

1768820680721

1768823919206

然后创建四个MSP

因为动画曲线中标记了动画的左右脚,因此需要两个动画通知来管理,这两个通知类是AN_FootOnGround动画通知的子类

1768740592512

1768740515286

为了在子类中能Override变量BoneName,需要公开出来

而声音资产也对左右脚声音做了区分,因此也需要公开

1768741283616

在子类中Override

1768741734361

1768741740193

Locomotion用的动画通知设置好了,下面需要把通知加到所有的动画资产中

这里我开发了一个用于批量处理动画资产的编辑器工具。它可以根据动画中已有的 Anim Notify (含Sync Marker同步标记)的名字,在同一帧自动添加指定的 AnimNotify 类

github:https://github.com/EanoJiang/AnimNotifyBatchTool

1768792786847

1768792830222

对于一些Sync Marker修改器无法标记的动画资产,比如原地转身,则需要手动添加通知

并且由于Sync Marker只在脚部完全落地时才标记左右脚,因此还需要对自动添加的通知所在帧进行略微向前调整(这一部分重复度太高,后面再做)

改变脚步音量大小

在基类AN_FootOnGround把原来的PlaySoundAtLocation节点换成SpawnSoundAtLocation,这会返回一个AudioComponent,然后用SetFloatParameter设置其参数,因为前面在MS中加入了Volume音量调节输入,因此这里的InName填Volume,下面的InFloat稍后根据当前速度处理

1768821815821

我们已经在动画蓝图ABP_Base中获取过当前角色的速度,那么如何在其他蓝图中访问这个参数?

我们需要在BPI接口中添加方法,并添加Output

1768822331276

1768822216674

然后在ABP_Base中实现这个方法

1768822379688

回到AN_FootOnGround,用AnimIntance调用动画蓝图中的方法,并根据DT_WeaponGates中的速度范围,钳制0-500映射为0-2.0,存在为Volume传入SetFloatParameter节点的InFloat

1768823847823

1768823239659

1768823362543

24 Visual effects Intermediate

特效

枪焰特效

在枪模型Skeleton的Root下,新建一个Socket——Muzzle,摆放到枪口位置

1768924868938

在枪的开火动画资产中,添加Play Niagara Particle Effect动画通知

1768927610918

1768927676817

设置SocketName和特效资产

1768927661979

记得勾上特效的USER.Trigger

1768927578671

效果:

1768928454705

弹壳弹出特效

方法相同

1768928625892

1768928658493

1768928762798

1768928785621

为其他武器做同样的操作

枪线轨迹特效

1769004053585

1769004092363

1769003049451

整理代码

1769004840130

1769004863968

受击材质破碎特效

1769006150225

1769006597415

1769006614310

1769006064254

要Set的值有

1769005123387

效果:

1769006324046

25 Weapons UI Intermediate

武器切换UI

弹夹和子弹图标

创建材质,把贴图Promote为参数

1769008617390

创建材质实例,更换Icon贴图

1769008691420

1769008865767

准星UI部件

创建WidgetBlueprint

1769008996605

1769008949771

1769009020434

1769010901065

效果:

1769010944107

Debug——显示子弹数量

从ABP_Base复制Debug函数到角色蓝图中

1769091145342

1769091034784

1769091055142

1769091177048

这几个变量需要在Fire和Reload事件中更新

开火后更新子弹数量

如果子弹数量PistolBulletAmount > 0,自减1,并返回有子弹,否则返回无子弹

1769097742284

1769097751318

效果:

1769091817330

换弹后更新子弹数量

1769098388648

1769098203153

1769097948938

效果:

1769098649224

开枪和换弹时的UIBackGround

UI布局

1769099401376

UI面板大小

1769099391523

Border,半透明黑色背景,白色边框

1769099744885

子弹Icon

1769100217186

1769100360362

1769100382460

1769101008588

部件蓝图

创建User Widget

1769101148787

1769101167060

1769173120922

回到WBP_PistolUI,把子弹和弹夹所在的容器设置为变量

1769173348929

1769173356233

打开蓝图模式

1769174187633

1769174031010

Widget和角色蓝图通信

Interface

1769174363133

1769174348456

1769174355972

回到WBP_PistolUI

1769174420477

1769175514258

回到角色蓝图,新建Widget,并设置为屏幕空间,设置WidgetClass

1769174689791

1769174707979

在枪的骨骼上新建Socket——Widget

1769174947077

1769174962743

1769175100151

在角色蓝图的Fire中调用BPI_Widget接口的UpdateBulletAmount,在Reload中调用UpdateClipAmount

1769177159263

1769177180888

1769177063583

并在BeginPlay的时候初始化这两个接口函数

1769177127991

设置一下武器弹药UI什么时候显示

设置标志位

1769178231721

1769178798237

1769178815094

拿枪瞄准时显示,换弹时不显示

1769178217108

1769178850619

效果:

1769179133586

为其他武器做同样的事情

效果:

1769181952139

修复bug——换弹期间不能开火

1769183153201

26 Health bar UI Intermediate

posted @ 2025-12-17 23:05  EanoJiang  阅读(39)  评论(0)    收藏  举报