角色控制器库 (CCL) 是一个模块化框架,通过属性和 Luau 脚本构建角色移动和行为。该架构用灵活、可扩展的角色机制系统替代了僵化的 Humanoid 状态机。
启用 CCL
CCL 是通过 Studio 的 头像设置 窗口 可选 的。要启用它:
从 头像 选项卡中,打开 头像设置。

在窗口左侧选择 移动 选项卡,在 能力 部分,选择 角色控制器库。

所有标准能力,如 奔跑、跳跃 和 攀爬 默认启用。要在运行时禁用其中任何一个,请取消选中相关框。
能力
能力评估角色可以做什么,例如跑步、跳跃、攀爬和游泳的能力。CCL 能力动态确定角色可以做什么以及如何响应玩家输入,而不是依赖于固定的引擎定义角色状态,如 Enum.HumanoidStateType 中的状态。
从结构上讲,能力是一个自包含的 Luau 表,主要指定以下内容:
| 表字段 | 目的 |
|---|---|
| Name | 能力的唯一标识;条件 和 冲突 通过名称引用它。注册两个具有相同 Name 的能力会抛出错误。对于一些相似的能力,给每个能力一个不同的 Name,并在 Labels 中共享一个公共 标签。ModuleScript 名称决定唯一配置键。 |
| Labels, TimedLabels | 共享 64 位掩码中的命名位,作为能力之间的协调总线;见 标签。 |
| StartsWhen, RunsWhile | 定义何时启动能力和何时保持其运行的条件;见 条件。 |
| Blocks, Stops, Suspends, ExclusiveGroup | 如何处理不能同时处于活动状态的能力之间的 冲突。 |
| Input | 触发能力的输入。Input.Mode 控制输入如何启动和停止能力;RunsWhile 仅包含世界条件,而不包含输入本身;见 输入。 |
| Config, State | 每个能力注册的默认配置值和复制状态。回调从 abilityCtx.Config 读取配置,并通过 abilityCtx.State 读取或写入复制状态。 |
| OnSetup, OnStart, OnStop, OnUpdate, OnTeardown | 生命周期回调,能力的实际行为在此处编写;见 回调。 |
标签
标签是共享 64 位掩码中的命名位,作为能力之间的协调总线。基本上:
- 活动能力 广播 其 Labels 和 TimedLabels 到世界掩码。
- 能力 冲突 定义在激活时被阻止、停止或暂停的 其他 能力。
在以下设置中,当 Running 处于活动状态时,"CanFallDown" 标签被广播到世界掩码。具有其 条件 的 FallingDown 能力 StartsWhen = All( "CanFallDown", "Stunned" ) 自动成为候选,但 "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, Any, Not = Rule.All, Rule.Any, 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, Any, Not = Rule.All, Rule.Any, 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, Any, Not = Rule.All, Rule.Any, 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, Any, Not = Rule.All, Rule.Any, 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 }),因此它可以中断并超越 Sprinting,即使它的优先级(150)较低。
local AvatarAbilities = require("@rbx/AvatarAbilities")
local Identifiers = AvatarAbilities.Identifiers
local Ability = Identifiers.Ability
local Rule = AvatarAbilities.Rule
local Sensor = Identifiers.Sensor
local All, Any, Not = Rule.All, Rule.Any, 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, Any, Not = Rule.All, Rule.Any, Rule.Not
local Dash: AvatarAbilities.AbilityDefinition = {
Name = "Dash",
Input = { InputName = "Dash", Mode = "Press", ActionSlot = 5 }
}InputName 是一个逻辑名称,而不是一个键。CCL 生成一个输入传感器,并始终将其与 StartsWhen 进行 AND 操作。
Mode 定义此输入如何启动和停止能力。RunsWhile 从不包含输入本身;它仅包含世界条件,例如在地面上或在水中,因此 Mode 是输入驱动的启动和停止的唯一来源:
模式 启动 从输入停止 Press 按下时。 从不。能力通过冲突、其自身的 RunsWhile 或调用 Stop() 自行结束。 Hold 按下时。 释放时。 Hold 与 Repeats 按下时。 释放结束重新触发,而不是已经在飞行中的周期。 Toggle 切换开启时。 切换关闭时。 Press 适合于离散动作,如冲刺、攻击和投掷。Hold 和 Toggle 适合于持续动作,如冲刺、瞄准、阻挡、蹲下和悬浮。Hold 与 Repeats 适合于在按住时自我停止并重新触发的动作,例如重复跳跃。
Repeats(布尔值,仅适用于 Hold)使能力在每个周期自我终止,并在输入保持按住时重新触发,而不是在整个保持期间持续运行。
OutlivesInputWhile(仅适用于 Hold/Toggle)是一个 条件,在该条件下能力在释放或切换关闭后继续运行,直到条件清除。使用它当能力在特定世界条件下应持续超过其输入时。
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 仅包含世界条件。编译器根据 Mode 在幕后将输入的效果组合到能力的实际延续中,因此将输入本身放入 StartsWhen 或 RunsWhile 会抛出错误(不要在此处引用 Rule.Input)。Hold 和 Toggle 能力在释放或切换关闭时默认已经停止,因此您不需要在 RunsWhile 中重复这一点:
local AvatarAbilities = require("@rbx/AvatarAbilities")
local Rule = AvatarAbilities.Rule
local Sensor = AvatarAbilities.Identifiers.Sensor
local All, Any, Not = Rule.All, Rule.Any, Rule.Not
local Glide: AvatarAbilities.AbilityDefinition = {
Name = "Glide",
Input = { InputName = "Glide", Mode = "Hold", ActionSlot = 6 },
StartsWhen = Not(Sensor.Ground),
RunsWhile = Not(Sensor.Ground),
}要使 Hold 或 Toggle 能力在世界条件下超过其输入,请设置 OutlivesInputWhile,而不是将输入添加到 RunsWhile。以下 Crouching 能力在玩家释放蹲下输入后保持蹲下,只要上方仍有物体:
local AvatarAbilities = require("@rbx/AvatarAbilities")
local Identifiers = AvatarAbilities.Identifiers
local Sensor = Identifiers.Sensor
local Crouching: AvatarAbilities.AbilityDefinition = {
Name = "Crouching",
Input = { InputName = "Crouch", Mode = "Hold", OutlivesInputWhile = Sensor.Ceiling, ActionSlot = 3 },
RunsWhile = Sensor.Ground,
}传感器
传感器是关于世界的命名值,引擎为您读取。您通常会读取传感器而不是写入它们。为了方便,预注册了几个传感器:
| 传感器 | 描述 |
|---|---|
| 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 是错误的。