角色控制器库 (CCL) 是一个模块化框架,通过属性和 Luau 脚本构建角色移动和行为。该架构用灵活、可扩展的角色机制系统替代了僵化的 Humanoid 状态机。
能力
能力评估角色可以做什么,例如跑步、跳跃、攀爬和游泳的能力。CCL 能力动态确定角色可以做什么以及如何响应玩家输入,而不是依赖于固定的引擎定义角色状态集,如 Enum.HumanoidStateType 中的状态。
从结构上讲,能力是一个自包含的 Luau 表,主要指定以下内容:
| 表字段 | 目的 |
|---|---|
| Name | 分配给 标签 位的名称,以便 条件 和 冲突 可以引用该能力。多个能力定义可以使用相同的名称。ModuleScript 名称决定唯一的配置键。 |
| Labels, TimedLabels | 共享 64 位掩码中的命名位,作为能力之间的协调总线;见 标签。 |
| StartsWhen, RunsWhile | 定义何时启动能力和何时保持其运行的条件;见 条件。 |
| Blocks, Stops, Suspends, ExclusiveGroup | 如何处理不能同时处于活动状态的能力之间的 冲突。 |
| Input | 触发能力的输入。CCL 将其注入到 StartsWhen 中,并在省略 RunsWhile 时,将其用作默认的继续条件;见 输入。 |
| Config, State | 每个能力注册的默认配置值和复制状态。回调从 abilityCtx.Config 读取配置,并通过 abilityCtx.State 读取或写入复制状态。 |
| OnSetup, OnStart, OnStop, OnUpdate, OnTeardown | 生命周期回调,能力的实际行为在此处编写;见 回调。 |
标签
标签 是共享 64 位掩码中的命名位,作为能力之间的协调总线。基本上:
- 活动能力 广播 其 Labels 和 TimedLabels 到世界掩码。
- 能力 冲突 定义在激活时被阻止、停止或暂停的 其他 能力。
在以下设置中,当 Running 处于活动状态时,"CanFallDown" 标签被广播到世界掩码。具有 条件 StartsWhen = All( "CanFallDown", "Stunned" ) 的 FallingDown 能力自动成为候选者,但 "Stunned" 也必须在 FallingDown 发生之前广播到世界掩码。
local AvatarAbilities = require("@rbx/AvatarAbilities")
local Identifiers = AvatarAbilities.Identifiers
local Ability = Identifiers.Ability
local Rule = AvatarAbilities.Rule
local Sensor = Identifiers.Sensor
local All, Not = Rule.All, Rule.Not
local Running: AvatarAbilities.AbilityDefinition = {
Name = Ability.Running,
Labels = { "CanFallDown" }, -- 能力处于活动状态时广播的标签
StartsWhen = Sensor.Ground,
RunsWhile = Sensor.Ground,
}local AvatarAbilities = require("@rbx/AvatarAbilities")
local Identifiers = AvatarAbilities.Identifiers
local Ability = Identifiers.Ability
local Rule = AvatarAbilities.Rule
local Sensor = Identifiers.Sensor
local All, Not = Rule.All, Rule.Not
local FallingDown: AvatarAbilities.AbilityDefinition = {
Name = Ability.FallingDown,
StartsWhen = All( "CanFallDown", "Stunned" ), -- 启动能力所需的标签
Blocks = { Ability.Running }
}标签也可以使用 TimedLabels 字典以 定时 方式广播或消耗。
| 键 | 描述 |
|---|---|
| TimedLabels.OnStart | 包含标签(键)和相关持续时间的字典。当能力启动时,标签被广播,并在其持续时间结束时自动过期。例如,OnStart = { Dashing = 1 } 在能力启动时广播 Dashing 标签,持续 1 秒。 |
| TimedLabels.OnStop | 包含标签(键)和相关持续时间的字典。当能力停止时,标签被广播,并在其持续时间结束时自动过期。例如,OnStop = { DashCooldown = 2 } 在能力停止时广播 DashCooldown 标签,持续 2 秒。 |
| TimedLabels.Consumes | 在能力激活时要移除(消耗)的标签列表。例如,如果一款格斗游戏允许玩家在阻挡对手攻击后进行反击,CounterAttack 能力可能包含 StartsWhen = "AfterBlock" 和 TimedLabels = { Consumes = { "AfterBlock" } },以防止 CounterAttack 能力的双重触发。 |
local AvatarAbilities = require("@rbx/AvatarAbilities")
local Identifiers = AvatarAbilities.Identifiers
local Rule = AvatarAbilities.Rule
local Sensor = Identifiers.Sensor
local All, Not = Rule.All, Rule.Not
local Dash: AvatarAbilities.AbilityDefinition = {
Name = "Dash",
StartsWhen = All( Sensor.Ground, Not("DashCooldown") ),
RunsWhile = "Dashing",
TimedLabels = {
OnStart = { Dashing = 1 },
OnStop = { DashCooldown = 2 },
},
}条件
条件 是一个或多个 标签、传感器 或输入引用,用于 StartsWhen 和 RunsWhile。条件在运行时编译为位掩码操作,评估是整数数学 — 没有表遍历和字符串比较。
| 目标 | 语法 | 示例 |
|---|---|---|
| 一个必需的条件。 | StartsWhen = Sensor.Ground | |
| AND 逻辑,当 所有 标签存在于世界掩码中且 所有 传感器处于活动状态时。 | All() | StartsWhen = All( "CanFallDown", "Stunned" ) |
| OR 逻辑,当 任何 标签存在于世界掩码中或 任何 传感器处于活动状态时。 | Any() | StartsWhen = Any( "WallClimbing", "Climbing" ) |
| 否定,使得标签 不能 存在于世界掩码中且传感器 不能 处于活动状态。 | Not() | RunsWhile = Not("Stunned") |
条件评估可以组合以实现更复杂的逻辑,例如 All() 链接加上 Not(),以指示传感器必须处于活动状态,而标签必须不存在:
local AvatarAbilities = require("@rbx/AvatarAbilities")
local Identifiers = AvatarAbilities.Identifiers
local Rule = AvatarAbilities.Rule
local Sensor = Identifiers.Sensor
local All, Not = Rule.All, Rule.Not
local Dive: AvatarAbilities.AbilityDefinition = {
Name = "Dive",
StartsWhen = All( Sensor.WaterSurface, Not("Recovering") ),
}冲突
某些能力在另一种能力处于活动状态时无法激活;例如,角色在游泳时无法跳跃,在下落时无法奔跑。引擎在能力定义内部声明性地解决这些冲突:
| 冲突键 | 目的 |
|---|---|
| Blocks | 当拥有的能力处于活动状态时,列出的其他能力无法启动。例如,ScopeAim 能力可能包含 Blocks = { Ability.Running, Ability.Jumping },以防止角色在仔细瞄准武器的瞄准镜时奔跑或跳跃。 |
| Stops | 当拥有的能力启动时,列出的其他能力 强制停止,并必须重新触发。例如,Hover 能力可能包含 Stops = { Ability.Running },以便在角色开始悬停时立即停止奔跑动作。 |
| Suspends | 当拥有的能力启动时,列出的其他能力 暂停,然后在拥有的能力停止时 自动恢复。例如,自定义冲刺能力可能包含 Suspends = { Ability.Running },以便在冲刺开始时暂停奔跑 🄐,在冲刺中被阻止 🄑,并在冲刺停止时恢复 🄒。 |
另一个独特的冲突键是 ExclusiveGroup,它将多个能力放入一个组,每个组都有一个 Priority 值。每组中只能有一个能力处于活动状态,优先级高的胜出。然而,如果挑战者声明 Stops 目标为持有者的名称/标签,则无论优先级如何,它都将胜出。
在以下设置中,三个能力(Sprinting、Crouching、Stagger)被添加到一个 Locomotion 独占组中。Sprinting 的优先级最高(200),因此它胜过 Crouching(100),这两个能力不会同时运行。然而,Stagger 强制停止冲刺 (Stops = { Ability.Sprinting }),因此即使其优先级(150)较低,它也可以中断并超越 Sprinting。
local AvatarAbilities = require("@rbx/AvatarAbilities")
local Identifiers = AvatarAbilities.Identifiers
local Ability = Identifiers.Ability
local Rule = AvatarAbilities.Rule
local Sensor = Identifiers.Sensor
local All, Not = Rule.All, Rule.Not
local Sprinting: AvatarAbilities.AbilityDefinition = {
Name = Ability.Sprinting,
ExclusiveGroup = { Name = "Locomotion", Priority = 200 },
}
local Crouching: AvatarAbilities.AbilityDefinition = {
Name = Ability.Crouching,
ExclusiveGroup = { Name = "Locomotion", Priority = 100 },
}
-- 低优先级的能力可以通过停止高优先级的能力来覆盖它
local Stagger: AvatarAbilities.AbilityDefinition = {
Name = "Stagger",
ExclusiveGroup = { Name = "Locomotion", Priority = 150 },
Stops = { Ability.Sprinting },
}输入
能力的 Input 定义指定用于尝试激活能力的输入。它接受配置输入行为、动作槽和可选触摸按钮图标的键值对。
local AvatarAbilities = require("@rbx/AvatarAbilities")
local Identifiers = AvatarAbilities.Identifiers
local Rule = AvatarAbilities.Rule
local Sensor = Identifiers.Sensor
local All, Not = Rule.All, Rule.Not
local Dash: AvatarAbilities.AbilityDefinition = {
Name = "Dash",
Input = { InputName = "Dash", Mode = "Press", ActionSlot = 5 }
}InputName 是一个逻辑名称,而不是一个键。CCL 生成一个输入传感器,并始终将其添加到 StartsWhen 中。不要自己将 Rule.Input 添加到 StartsWhen。
Mode 定义此输入将如何被解释:
模式 行为 用例 Press 当输入被按下时尝试激活能力。自动注入到 StartsWhen 条件中。 离散动作,如冲刺、攻击和投掷。 Hold 当输入被按住时,能力运行;释放时停止,当生成的输入传感器是 RunsWhile 条件时。 持续动作,如冲刺、瞄准和阻挡。 Toggle 每次按下切换能力的开或关,当生成的输入传感器是 RunsWhile 条件时。 切换姿态或动作,如蹲下或悬浮。 Repeat 类似于 Hold,但每个周期重新触发。 自我停止并可以在按住时重新触发的动作。 ActionSlot 定义一个 动作槽,与 输入动作系统 中的一系列 InputActions 和 InputBindings 相关联。
几个动作槽输入绑定是由Roblox预定义的,未来的 输入动作管理器 将允许您根据需要重新配置动作槽的默认输入绑定。将 ActionSlot 设置为 0 将选择下一个可用的空槽。在移动设备上,槽 1-7 会填充到屏幕上的按钮(见下图)。
槽 键盘和鼠标 游戏手柄 触摸 默认分配 1 Space ButtonA ① 跳跃 2 LeftShift ButtonL1 ② 冲刺 3 LeftControl ButtonB ③ 蹲下 4 R ButtonX ④ 5 MouseLeftButton ButtonR2 ⑤ 6 Q ButtonY ⑥ 7 X ButtonR1 ⑦ 8 C ButtonL2 9 F DPadLeft 10 G DPadRight 11 V DPadDown 
CustomIcon、CustomIconActive 和 CustomIconInvalid 分别指定能力处于闲置、活动或不可用状态时的触摸按钮的 Roblox 资产 ID。
当您省略 RunsWhile 时,CCL 使用生成的输入传感器作为继续条件。当您定义 RunsWhile 时,它替换该默认值。对于必须在输入变为非活动时停止的 Hold 或 Toggle 能力,请直接在自定义条件中包含 Rule.Input 哨兵。Rule.Input 是一个值,而不是一个函数:
local AvatarAbilities = require("@rbx/AvatarAbilities")
local Rule = AvatarAbilities.Rule
local Sensor = AvatarAbilities.Identifiers.Sensor
local All, Input, Not = Rule.All, Rule.Input, Rule.Not
local Glide: AvatarAbilities.AbilityDefinition = {
Name = "Glide",
Input = { InputName = "Glide", Mode = "Hold", ActionSlot = 6 },
StartsWhen = Not(Sensor.Ground),
RunsWhile = All(Not(Sensor.Ground), Input),
}传感器
传感器 是关于世界的命名值,引擎为您读取。您通常会读取传感器而不是写入它们。为了方便,预注册了几个传感器:
| 传感器 | 描述 |
|---|---|
| Sensor.Ground | 站在一个表面上 |
| Sensor.IsMoving | 正在应用移动输入 |
| Sensor.MoveInput | 移动向量本身 |
| Sensor.Ceiling | 某物在正上方 |
| Sensor.Climb | 可攀爬的表面在范围内 |
| Sensor.Water / Sensor.WaterSurface | 在水中 / 在水面上 |
| Sensor.Sit | 坐着 |
| Sensor.Tipped | 翻倒了 |
| Sensor.Tool | 持有一个 Tool |
| Sensor.LookDirectionInput | 命令的视线方向 |
| Sensor.RotateToLookDirectionInput | 角色是否应该旋转到命令的视线方向 |
回调
能力 回调函数 让您编写特定行为:
尽管您在服务器上注册自定义能力,但它们的回调在预测的客户端仿真和权威的服务器仿真中都运行。保持回调行为的确定性,以便两个仿真产生相同的结果。
| 回调 | 运行时机 | 用例 |
|---|---|---|
| OnSetup(managerCtx, abilityCtx) | 注册能力时运行一次。 | 缓存引用,初始化状态等。 |
| OnStart(managerCtx, abilityCtx, hadLabel) | 每次激活能力时运行。 | 应用效果,例如冲击。hadLabel() 函数报告在激活开始时是否存在指定标签,在冲突解决之前。 |
| OnUpdate(managerCtx, abilityCtx) | 每个活动帧。 | 持续工作,例如计时器或每帧力。 |
| OnStop(managerCtx, abilityCtx) | 每次停用,无论是自愿还是强制。 | 撤销 OnStart() 所做的事情。 |
| OnTeardown(managerCtx, abilityCtx) | 在能力移除时运行。 | 断开连接,销毁实例等。 |
每个回调函数的第一个参数 managerCtx 是一个 ManagerContext 对象,具有共享的角色和管理器属性,包括:
- managerCtx.AbilityManager — 管理器的简化视图,以便能力可以在其自己的回调中添加、移除和查询能力。
- managerCtx.BodyParts — 角色的身体部件,具有每个肢体打开和关闭碰撞的助手。
- managerCtx.RootCFrame — 根部件的 CFrame,在帧开始时快照一次,以便回调不必各自去获取它。
- managerCtx.RootLookVector — 角色根部件面朝的方向。
- managerCtx.RootUpVectorY — 根部件的上向量的 Y 分量。
- managerCtx.TaskSynchronize() — 在启用并行回调支持时,在访问 DataModel 之前同步回调。目前,OnUpdate 不在并行上下文中运行,因此此函数没有效果。未来更新计划提供完整的并行 Luau 支持。
第二个参数 abilityCtx 是一个 AbilityContext 对象,具有引擎管理的当前能力注册的表:
- abilityCtx.Config — 此能力注册的只读配置值。
- abilityCtx.State — 可变状态,通过 DataModel 复制。 服务器权限 在回滚和重新仿真期间恢复这些值。
- abilityCtx.Local — 可变的临时状态,不会复制或参与回滚。
将自定义回调数据存储在 abilityCtx.State 或 abilityCtx.Local 中。直接写入 abilityCtx 的自定义字段是错误的。