UI
Lyra 的 层级栈管理
为了维护一个UI系统的层级,我们通常会用 AddToViewport 的 Z-Order。例如间隔100 定义个层级 Low Mid Hight 把游戏UI低一点,菜单就高一点。
而Lyra定义了这样的一个界面来管理层级

UPrimaryGameLayout 是每个本地玩家的 UI 根节点,维护了一个TMAP
TAG=>UCommonActivatableWidgetContainerBase(子类实现队列或者栈)
├─ UI.Layer.Game ← HUD、战斗 UI(最底层)=> GameLayer_Stack
├─ UI.Layer.GameMenu ← 游戏内菜单(背包、地图等)=> GameMenu_Stack
├─ UI.Layer.Menu ← 主菜单、暂停菜单、设置 => Menu_Stack
├─ UI.Layer.Modal ← 模态对话框、确认弹窗 => Modal_Stack
栈里面的元素是 UCommonActivatableWidget 对象。
通过往容器中push pop 对象 ActivatableWidget来实现 UI的栈
push和pop的接口可以参考 UCommonUIExtensions 这个 BP Func Lib
===
配置了这个界面用的是哪个的,我们叫它 Lyra 的 UI Policy。
ps: 【B_LyraUIPolicy_C】的蓝图,本质是一个 GameInstance SubSystem
在GameInstance的 Local Player 的 Add 和 Remove的时候,维护了【Root Layout】整体的可见性
===
基于扩展点的控件管理

这是 ULyraHUDLayout 再父类是 ULyraActivatableWidget
这里维护了控制器断连的逻辑,ESC出一个界面的逻辑,重连的时候主机平台手柄按A继续那个界面,和具体业务逻辑有关,但是如果我们关注架构的话,这几个功能可以先忽视。
UI上摆放了很多个
UUIExtensionPointWidget 父类是 UDynamicEntryBoxBase
这是是一个扩展点(目前只是Lyra插件模块的内容,以后说不定就进引擎)
扩展点会把自己注册到 UUIExtensionSubsystem (world sub system)
另一端是 GameFeatureAction_AddWidget
他可以配置一个 layout (就是上面的这个界面)
下面是每一个扩展点对应的是哪一个UMG
这样就完成了把这样一个界面和每一个扩展点推送到某个位置的逻辑搞清楚了。
有什么优势?
避免控件硬编码在布局蓝图里面,基于GameFeature的热插拔,解耦。
从资源加载的角度也是基于 stream able manager 的异步加载。
用slate画准星UI
对于高频更新的UI例如准星,在UUserWidtet的 RebuildWidget 中返回slate对象。
slate对象在OnPaint 中提交绘制命令。
具体链接笔记 https://www.cwlgame.cn/2026/03/02/oklyrayongslatehuazhunxingdezongjie/
动画系统
原地转身
bEnableRootYawOffset 关闭,这样就不会算RootYawOffset。Rotation Root Bone 和 原地转身不会触发了。
检查当前值:
bUseControllerRotationYaw 使用控制器的旋转Yaw = true
- 如果是false,look输入->影响contoller rot -> X 不会影响角色的Yaw旋转。
- 用true来旋转yaw的问题,想象一下脚不动,角色在转,滑步
bOrientRotationToMovement 将旋转朝向运动 = false
- 字面意思,如果这个开,上面那个也开,一起作用都在控制角色旋转,角色抖动。
- 如果开了后,就是RPG那种,角色能向屏幕走来
Lyra使用的就是,使用控制器旋转Yaw开,将旋转朝向运动关。
这个滑步怎么办
- 计算这一帧的 Yaw的变化量 YawDeltaSinceLastUpdate 和 变化率 YawDeltaSpeed (YawDeltaSinceLastUpdate / DeltaSeconds)
- 在旋转的时候,在tick中,我们反向作用一个 -YawDeltaSpeed,角色就在原地不转了。这个反作用了多少,我们叫它RootYawOffset
- RootYawOffset 作用在动画节点RotateRootBone的Yaw参数来实现,把角色转回去。当值过大的时候触发 原地旋转
- 在Idle状态中,如果RootYawOffset过大,状态机变化播放转身动画,但是是原地踏步。ProcessTurnYaw函数会识别到
多Mesh和动画层的设计
为了解决
- 一套动画蓝图用在多个模型
- 一个动画蓝图对应多套动画
主角的 skelemesh 只有骨骼,没有mesh。动画蓝图正常用。
主角Character动态加载一个子Actor,子Actor的skele mesh 有具体的模型,这个子actor的动画蓝图用【copy pose from mesh】来复制父类的动画蓝图
Base动画蓝图来写具体的状态过度。如IDLE,Cycle,Jump等。但是具体的状态不写系统,用动画层接口
另一个Item动画蓝图实现同样的接口。通过link anim layer 来关联。
Item动画蓝图中的动画不用具体的动画,而是用动画资源的变量。在Item的子类中配置具体使用的资产。
例如手枪和步枪在开火动画就是同一个逻辑下的不同
上述是动画蓝图相关细节,到落地上。
需要设计数据结构,例如武器配置和切换决定链接不同的动画层来拿到不同的。
回转运动
向左运动,突然向右的时候,有一个猛的一下刹车然后切另一个方向运动的过程。
切换条件是 速度和加速度方向相反。
注意状态不可重入,所以有pivotA <=>pivotB两个状态
相互切换的条件
- 进入速度加速度相反(回转的进入条件)
- 且 pivotA 进入时记录的加速度方向和当前反向,就进入另一个回转了
- IsMovingPerpendicularToInitialPivot,需要前切后,左切右这种切换才切回转,垂直不行
LocomotionSM
最早我们会设计 IDLE, CYCLE 两个状态。
如果角色非要加上Start和Stop的过度。
这是一个动画,就可能会有Start的时候突然停下,Stop动画播放的时候又开始跑了,可能会出现滑步不一致的问题。
- 运动匹配
正常动画节点可以绑定一个update函数,Set Sequence with inertial blending,基于时间推进动画应节点一个输出动画的那一帧
如果用距离匹配,需要提供每帧移动的距离,动画曲线中有这个每帧运动距离应该是那一帧的动画。(Advance Time By Distance Match 出现在 Update Start Anim)
Update Stop Anim 的情况又不一样,他需要根据,预测的速度值什么时候停下来,来推进动画输出哪一帧(TODO check一下这里有条线没连对不对,原项目也没有)
-
脚步锁定ALS的方案
-
朝向扭曲,步幅扭曲
https://www.cwlgame.cn/2026/02/24/lyralog6donghuaxitong1/
Orientation Warping 朝向扭曲,目的是只有四个方向的动画通过混合出 45 度的效果。
可以通过姿势 + 修改 LocomotionAngle 45 和 -45 观察角色 45 度移动的效果。
Stride Warping 通过 Stride Warping Start Alpha (前面提过和播放实际相关的一个计算量)
来影响步幅。测试发现 Alpha 0 的时候是源不服,越大步幅越小。
另一个是基于运动方向选不同方向的动画,老版本要手写,现在有接口
// 0前,右90,左-90,顺时针正,这样一个坐标系
LocalVelocityDirectionAngle = UKismetAnimationLibrary::CalculateDirection(WorldVelocity2D, WorldRotation);
// 比如角色右转,为了转回去,root yaw offset是一个负数。但是角色朝向是向右的,因为瞄准偏移,这个with offset就是角色这种情况下朝向这样的定义
LocalVelocityDirectionAngleWithOffset = LocalVelocityDirectionAngle - RootYawOffset;
- 跳跃
由Jump状态进入,
【Start -> Start Loop】 由动画驱动,
基于初中物理,下落公式,算出什么时候到Apex 顶点了 【JumpApex】动画播完就 Fall Loop
根据 Ground Land 距离 切换 【FallLand】
LocomotionSM之后到输出还有什么
- Left Hand Pose Override State
拿一个握枪动画,基于mask只混合左手。让握枪姿势更加正确。动画曲线和bool开关可以屏蔽这一行为。
- 上下半身混合

UpperBodyAdditive 上面的分支是,播放蒙太奇的时候,float是1,然后慢慢减少权重到0。
除了这个就是一个普通的上下半身分离的蒙太奇的写法。
后面那个 FullBodyAddtivePreAim 至今没看到用的地方?
- 瞄准偏移
AimYaw = - Root Yaw Offset
AimPitch 就是 Character->GetBaseAimRotation().Pitch 标准化一下,
- FullBodyAdditive
又混了一个状态机,基于OnGround状态,在落地的时候,混入一个落地动作
- FullBody_SkeletalControls
主要是一些骨骼的微调
https://www.cwlgame.cn/2026/02/24/lyralog8donghuaxitong3/
其他文档: https://dev.epicgames.com/documentation/zh-cn/unreal-engine/animation-retargeting-in-unreal-engine
有些东西忘记了,https://www.bilibili.com/video/BV12f4y1r71N 第88,89节,复习了一下还是这个解释最合适
问题:右手握枪直接attack到骨骼。另一边左手如果静止没问题,但是由于各种运动叠加,呼吸叠加,会晃动。
动画的平移数据从骨骼取,IK从动画来取数据,(可以在Mesh骨骼点出 Translation Retargeting 来看,IK设置写死了不让改)
右手握枪,创建一个基于右手的虚拟骨骼,挪到左手的位置。这样这个左手虚拟骨骼相对于,右手
Hand IK Retarget
Transform (Modify) Bone
如果开了Control Rig把根骨骼挪下来一点
Foot Placement 和 Leg IK 不会,做的是把脚放下来的功能
- Control Rig
就是那个脚步贴地的操作,具体没仔细看。
ALS的时候,用 Transform (Modify) Bone 把脚放下去,原理也是射线检测,把脚放下去。UCwlBaseAnimIns::SetFootOffsets
对比ALS
ALSv4:
基础层:八向运动,冲刺混合,走跑过度,走停过度和原地转身,蹲伏和站立过度。
空中地面过度,扩展了飞行和攀爬
叠加层:基于曲线决定哪些动作要叠加到基础层动画上,
hand IK, Foot IK => 输出
摄像机系统
最简单的是弹簧臂和摄像机,这样可能有什么不好呢,硬挑的话过渡是基于弹簧臂长度,旋转的“线性“插值,过度控制有限,并且原生没有状态管理,需要自己处理。
ALSv4的摄像机,提出了给摄像机加个mesh但是没有动画,用动画状态机做状态管理,但是只维护了曲线值数值,例如是否蹲伏状态返回不同值,影响摄像机的offset。由于这是一个动画系统示例,这里面缺少了技能镜头的处理。
Lyra的摄像机
概述
运行时的摄像机,可以在两个地方 CameraManager::UpdateViewTarget 和 CameraComponent::GetCameraView 抛出一个Loc, Rot, FOV给引擎来控制摄像机。
对于配置来说Lyra的比较重要的配置是基于Pitch俯仰角的曲线配置。例如(仰视=靠近+降低,俯视=拉远+升高)
其他配置还有FOV,混合的时间配置,防穿透配置。
不同的模式(Camera Mode)有不同的配置,代表了不同摄像机的表现
摄像机系统设计上的几个问题:
- 多状态如何过度
- 如果不是弹簧臂和摄像机而是Loc, Rot 就要自己处理穿透问题(摄像机在角色里面,摄像机和角色之间有障碍物)
多状态如何维护和过度
维护一个摄像机栈,命名上的,但是维护操作并不符合栈的定义。我们可以理解为TArray,里面的元素简单认为一个状态和混合都
一开始 【第三人称 1】
玩家开镜技能,在array[0] 塞入一个ADS状态
【ADS 0】【第三人称 1】
每次更新会不断的加百分比,根据混合时间,下一次可能就是
【ADS 0.3】【第三人称 1】
每一帧会返回最后一个 array[-1] 的摄像机数据,并倒着遍历,计算贡献。
例如当前状态,会按第三人称的摄像机信息,并考虑0.3混合的开镜权重。
如果在 【ADS 0.3】【第三人称 1】状态,玩家退出了开镜状态,他会把【第三人称 1】删除并提到开头。并修改权重
【第三人称 ?】【ADS 0.3】
他会算贡献,
- 遍历到 ADS(0.3):Contribution *= (1 - 0.3) = 0.7
- 遍历到 ThirdPerson:找到!Contribution *= 1.0 = 0.7
最后一个权重会被强制改成1,见代码push流程中的 CameraModeStack.Last()->SetBlendWeight(1.0f);
【第三人称 0.7】【ADS 1】
总结一下,
【ADS 0.3】【第三人称 1】 开镜混入一半停止开镜,栈变为 【第三人称 0.7】【ADS 1】
变为开镜完成状态,向第三人称状态混入。
顺带一提,这段逻辑只在权威跑,改变摄像机属性。
第三人称,偏移蹲伏穿透防护
首先摄像机位置是一个基于pitch的曲线读取,然后加上蹲伏偏移的逻辑。
例如(仰视=靠近+降低,俯视=拉远+升高)
穿透防护:
【SafeLocation/角色】 【墙壁】 【DesiredLocation/摄像机】
·────────────────────────────────X────────────────────────·
← DistBlockedPct (0~1) →
SafeLocation:安全位置(强制最近点,通常在角色胶囊体表面内侧)DesiredLocation:偏移曲线计算出的理想位置DistBlockedPct:阻挡百分比。1.0 = 无阻挡(摄像机在理想位置),0.0 = 完全被挤到安全点
其实都不用解释,看张图就懂了,黄线碰到东西了,篮球就会变进,这是我F8跳出来后截图的。

Lyra的组件总结
项目的组件分布在四类 Actor 上:GameState、PlayerState、Controller、Character/Pawn
大部分组件通过体验的 GameFeatureAction 加上的,很隐晦,这里做一个总结。
GameState
UEqZeroExperienceManagerComponent
- 构造函数硬编码
- Experience体验加载的核心逻辑。GameMode::InitGame开始,读体验配置,然后交给这个组件,处理加载流程,以及GameFeatureAction的激活流程,以及流程的回调函数的激活
UEqZeroAbilitySystemComponent
- 构造函数硬编码
- 全局的GAS ASC,用于全局的GE, Cue的操作,用于玩法的阶段管理。但是目前我还没复刻到那个地方
UEqZeroBotCreationComponent
- GameFeatureAction挂载
- 体验加载完成后spawn特定数量的机器人
UEqZeroPlayerSpawningManagerComponent
- GameFeatureAction挂载
- 玩家的出生点管理,缓存,负责处理自定义的出生点选择逻辑,例如随机一个,还是子类覆写按队伍。
UEqZeroFrontendStateComponent
- GameFeatureAction挂载
- 登录界面用的,处理登录状态,用户初始化,手柄按A建继续,加入Session,主菜单的逻辑。
- 注意这个场景是单机场景,不分客户端服务器。但是这里是一些 EOS,的登录逻辑,我没看,这些流程跳过以后,就是直接显示登录菜单了。
PlayerState
UEqZeroAbilitySystemComponent
- 硬编码 (构造函数)
- 角色用的ASC,继承引擎的ASC之后。还干了啥?
- Avator的切换,技能阻塞逻辑,技能输入(TODO 细化记录一下手枪的按下设计和长按设计是如何设计的)
UEqZeroHealthSet
- 硬编码 (构造函数)
- Health/MaxHealth 属性集
UEqZeroCombatSet
- 硬编码 (构造函数)
- 战斗相关属性集(BaseDamage/BaseHeal 等)
Controller (PlayerController)
UEqZeroInventoryManagerComponent
- GameFeatureAction
- 物品组件
UEqZeroQuickBarComponent
- GameFeatureAction
- 快捷装备栏
UEqZeroControllerComponent_CharacterParts
- GameFeatureAction
- 外观管理,动画系统是空mesh,基于这个挂具体的模型
UEqZeroWeaponStateComponent
- GameFeatureAction
- 武器命中标记追踪
UEqNumberPopComponent
- GameFeatureAction
- 跳字管理(还没写)
Character/Pawn
UEqZeroPawnExtensionComponent
- 硬编码 (构造函数)
- 中枢组件,分管ASC和PawnData
UEqZeroHealthComponent
- 硬编码 (构造函数)
- 维护生命值相关的,死亡逻辑。
UEqZeroCameraComponent
- 硬编码 (构造函数)
- 继承引擎的摄像机组件
UEqZeroCharacterMovementComponent
- 硬编码
UEqZeroHeroComponent
- 蓝图拖到角色上的
- 输入绑定,摄像机,GAS初始化
UEqZeroEquipmentManagerComponent
- GameFeatureAction
- 装备组件
UEqZeroPawnComponent_CharacterParts
- GameFeatureAction
- 外观组件,注意这个在controller和pawn各一个组件 TODO
总结
上班的时候,只需要考虑逻辑写场景组件,还是玩家组件。基于继承的 MainPlayer,Follower,NPC 几个类设计。
到了UE这里有 Character,PlayerController,PlayerState,GameState,逻辑架构如何设计?
决策思考
- 生命周期
| Actor | 生命周期 | 含义 |
|---|---|---|
| GameState | 整局游戏/整个关卡 | 全局唯一,关卡切换才销毁 |
| PlayerState | 玩家在线期间 | Pawn死亡/重生不影响,断线重连可恢复 |
| PlayerController | 玩家连接期间 | Pawn死亡/重生不影响,但无缝旅行会重建 |
| Character/Pawn | 一条命 | 死亡就销毁,重生是新实例,Character还可能表示其他玩家和怪物啥的 |
基于此:
- 物品栏(Inventory)、快捷栏(QuickBar)放 Controller → 死了重生背包还在
- 装备管理(EquipmentManager)放 Pawn → 死了装备状态重置,重生重新装配
- Health属性集放 PlayerState(跨Pawn存活),HealthComponent放 Pawn(驱动死亡逻辑,死了就没了)
- 网络复制可见性
| Actor | 谁能看到 |
|---|---|
| GameState | 所有客户端 |
| PlayerState | 所有客户端 |
| PlayerController | 仅拥有者客户端 + 服务器 |
| Pawn | 所有客户端(在相关性范围内) |
其他玩家需要看到的数据(血量、外观、战斗属性)→ PlayerState 或 Pawn
只有自己需要的数据(输入、UI状态、物品栏详情)→ Controller
Lyra里 ControllerComponent_CharacterParts(外观管理)在 Controller,而 PawnComponent_CharacterParts(外观渲染)在 Pawn,就是这个原因:决策逻辑私有,表现结果公开
- 语义上的
GameState = 比赛/玩法规则(全局事务)
PlayerState = 玩家身份和持久属性(你是谁,你有什么能力)
PlayerController = 玩家意志和私有操作(你想做什么)
Pawn = 物理存在和当前状态(你的身体在做什么)
对应到lyra
┌─ GameState ─────────────────────────────────────────┐
│ ExperienceManager → 全局玩法加载(规则层) │
│ AbilitySystemComp → 全局GE/Cue(规则层) │
│ BotCreation → 全局AI管理(规则层) │
│ PlayerSpawning → 全局出生点(规则层) │
│ FrontendState → 全局登录流程(规则层) │
└─────────────────────────────────────────────────────┘
┌─ PlayerState ───────────────────────────────────────┐
│ AbilitySystemComp → 角色能力(跨死亡持久) │
│ HealthSet → 血量属性(需要全客户端可见) │
│ CombatSet → 战斗属性(需要全客户端可见) │
└─────────────────────────────────────────────────────┘
┌─ Controller ────────────────────────────────────────┐
│ InventoryManager → 背包(跨死亡 + 私有) │
│ QuickBar → 快捷栏(跨死亡 + 私有) │
│ CharacterParts(Ctrl) → 外观决策端(私有) │
│ WeaponState → 命中追踪(私有) │
│ NumberPop → 跳字(私有UI) │
└─────────────────────────────────────────────────────┘
┌─ Pawn ──────────────────────────────────────────────┐
│ PawnExtension → 中枢(ASC桥接 + PawnData) │
│ HealthComponent → 死亡逻辑(一条命的事) │
│ Camera → 摄像机(物理存在) │
│ Movement → 移动(物理存在) │
│ Hero → 输入/相机初始化(当前身体) │
│ EquipmentManager → 当前装备(死了重置) │
│ CharacterParts(Pawn) → 外观渲染端(全客户端可见) │
└─────────────────────────────────────────────────────┘
精简决策
Q1: 这是全局/比赛级别的逻辑吗?
→ Yes → GameState
Q2: 玩家死了,这个状态要保留吗?
→ Yes → Q3
→ No → Pawn
Q3: 其他玩家需要看到这个数据吗?
→ Yes → PlayerState
→ No → Controller
和脚本语言世界的对比:
场景组件(Scene) ≈ GameState
玩家组件(Player) ≈ PlayerState + Controller + Pawn(UE把这一层拆成了三个)
UE 把"玩家"拆成三层的核心原因是 网络同步的精细控制。脚本语言框架通常由服务器统一管理状态,客户端只是薄壳;而 UE 的 Actor 模型是每个 Actor 独立控制自己的复制策略,所以需要按可见性和生命周期拆分,避免不必要的带宽开销和信息泄露(比如不应该让敌人看到你的背包内容)。
物品装备武器系统
物品系统的设计
我们设计系统会分为,配置,组件(考虑物品只需要自己知道,挂载controller上的)
ItemDefinition,虚幻配置很喜欢取Define这个名。作用就是配置表
Define里面成员是TArray 物品片段。片段的子类实现扩展出这个物品的不同特征。
例如:
装备片段,多了一个字段是装备定义。那么这个物品就是一个装备。
SetStats片段,多了一个tag=>int的字段,结果这个物品就能描述武器的 剩余弹药,总弹药的数据
Controller上挂载物品组件,组件上有一个FastArray 里面是 运行时物品实例。
也可以理解为这个组件是一个背包,里面有很多物品。基于这个物品可以去读配置表。物品的运行时数据很笼统的准备了一个tag=>int的成员。
装备,装备栏
在Lyra中,装备是Equip,装备栏叫QuickBar
EquipDefine 装备定义(配置表)
EquipComp 装备组件(挂载character)上,里面有一个FastArray 里面是 装备运行时实例 EquipInstance
===
QuickBar 组件挂载Controller上,维护了一个大小 N=3的 物品数组,当前装备的指针。
(UE这里命名Instance,但是通常我们理解这里就是物品对象,所以我就直接说物品了)
===
(加物品,装备物品 都发生在Server,然后一些变量OnRep或者fast array )
在角色初始化的时候,默认给一把【枪的物品】,加到库存(或者叫背包)里面。然后QuickBar装备栏加物品。
然后激活这个“枪”SetActiveSlotIndex,Server函数,(装备装备实在太拗口了,后面直接讲枪)
【QuickBar】在触发装备 枪的时候,会在【EquipComp】添加装备,然后返回一个实例自己拿着
这里【EquipComp】添加的装备是加到fast array里面,复制到客户端的时候,就有枪了。
好像很抽象。。。
概括一下
角色有一个背包(里面是物品),里面有的物品是装备。
可以把装备加到装备栏(QuickBar)上面,然后激活。
激活的时候就会加进装备系统。装备系统的FastArray的 Item同步的时候,客户端会创建Actor,角色就有这把枪了。
武器
装备也可以指铠甲这种只提供数值的东西。在Lyra中只有那几把枪
UEqZeroEquipmentInstance => UEqZeroWeaponInstance => UEqZeroRangedWeaponInstance
装备=>武器=>远程武器
UEqZeroWeaponInstance:
主要维护了 装备的时候过度动画和链接那个动画层(开火时间,和手柄适配设置,先加载没看到)
UEqZeroRangedWeaponInstance:
这里主要维护子弹射击方向的逻辑,维护了武器热度和倍数的概念。
形象的解释就是,连续开抢热度上升,不开枪就恢复了。
倍数,例如开镜的时候,倍数小一点,跑的时候倍数大一点。
基于热度获取一个开火散射半径,在乘上倍数,开镜的时候倍数小,扩散范围就小了。
这里主要是开火技能调用的。具体还给结合开火技能写
装扮系统
一个Random Actor 然后挂到Character下面。
Lyra的Character Mesh只有骨骼,mesh是透明的,这个Random的Actor的mesh才是主要跑动画的那个。
这个Actor会提供一些Tag,让装备的时候能读到,例如性别标识。这样装备的时候就可以参考,来链接不同的动画层。
逻辑找 /Game/Characters/Cosmetics/B_PickRandomCharacter.B_PickRandomCharacter
这里 调用Controller 组件服务器 加外观,不复制,也啥也没做。
然后直接调用角色组件的 外观 UEqZeroPawnComponent_CharacterParts 组件,加外观。
这里是加给FastArray了,到客户端的时候就会加Mesh
技能系统
技能输入
增强输入维护了一个技能 TAG 到 输入 回调函数 关系。维护了Trigger 和 Completed 对于Press和Release两张回调
ASC维护了三个数组【按下Press,按住Held,松开Release】
Press: 【按下】+= Handle,【按住】+= handle
Release: 【松开】+= Handle, 【按住】-= Handle
在ProcessInput的时候,可以理解为tick,是一个搬到的post input事件。
- 激活所有【按住容器】中 (未激活 && 激活类型是按住) 的技能。
- 所有【按下容器】未激活的技能激活,已激活的发输入事件,该时间会被输入ability task检测到。
- 所有【松开容器】中的技能,如果还是激活中,传递输入事件, wait input release的ability task会监听到。
- !!! 【输入】【松开】容器清空(差一个【按住容器】,在上面release -= handle的地方)
所以手枪是按下触发,步枪是按住触发是如何实现的呢?
和这堆破东西没关系
IA_Weapon_Fire 配置了 Trigger 是 Press,导致按下,马上触发press 和 release。按住不会触发输入回调。所以只能打一枪
IA_Weapon_FireAuto 就是一个普通的IA,所以trigger会一直触发回调。
那抽象这一层的意义是什么?
- 解耦,了吗?GAS确实不需要知道输入的细节了。只需要知道某个技能处于按下,松开状态就好了。
- 没激活的技能去激活,激活的事件去通知GA,GA->InputPressed => wait input Press/Release
- 我觉得主要还给谁 InputPressed 这个变量
===
其他:
https://www.cwlgame.cn/2026/02/26/lyralog21lryagaszongjie1/
AI系统
https://www.cwlgame.cn/2026/02/26/lyralog25jiqiren3guinazongjie/
这份写的还行
以前ALSv4 版本写的行为树
