客户端状态机
用 client/*.yml 定义玩家模型的动画状态机,供服务端控制器驱动
概述
- 客户端状态机决定「玩家模型此刻播哪个动画,该玩家处于这个状态的时候,客户端有哪些行为 」。
- 它是一套动画状态机:定义「什么情况播什么动画」,它每一游戏刻自动判断玩家当前行为,切到对应动画——站着播待机、走路播行走、跑步播跑步。
- 而攻击、连招这类动作,则由服务端控制器在判定输入后驱动它切换。
配置文件
客户端状态机的配置放在插件的 client/ 文件夹里,每个 .yml 文件就是一套客户端状态机,文件名即它的 ID:
服务端控制器通过 setting.client_controller_id 指向某套客户端状态机,两者就绑在了一起:
改完 client/ 里的配置,和服务端控制器一样用 /chronos reload 重载即可。
基本结构
一套客户端状态机由若干子控制器组成,每个子控制器是一台独立运行的状态机。最简单的写法只有一个子控制器 main:
main是子控制器名,随意命名;一套状态机可以有多个子控制器(见 多子控制器混合)。initial_state是玩家挂上这套状态机时的默认状态。states下声明所有状态,每个状态至少绑定一个animation(动画名,来自模型里的动画)。
子控制器每一游戏刻从上到下检查各状态的触发条件,取第一个成立的。idle 的条件恒成立,所以放最后当兜底。
状态的判断顺序 = 你在 states 里写的顺序(从上到下)。把优先级高的状态写在前面——比如先判 run 再判 walk,否则跑步时会被走路盖掉。
状态触发方式
状态名分两类,决定它「谁来触发」:
- 保留名(如
idlewalkrunsword…):状态机会自动根据玩家行为判定触发。完整列表见文末 状态保留名。 - 自定义名(如
attack1charge_release…):不会被自动触发,只能由服务端控制器驱动切换。连招、技能等动作都用自定义名。
这正是两个状态机的接缝:服务端控制器某个 state 的 controller 指向这里的子控制器名、state_name 指向这里的状态名。服务端一进入该 state,就通知客户端把对应子控制器切到那个动画。
状态属性
每个状态支持以下配置项,全部可选(除 animation 外都有默认值):
| 配置键 | 类型 | 默认 | 说明 |
|---|---|---|---|
animation | string | "" | 主动画名(来自模型) |
speed | double | 1.0 | 动画播放速度 |
weight | double | 1.0 | 动画权重(多子控制器混合时用,取值0.5-2,如果想该动画所驱动的骨骼 完全不被其它动画影响 则设置为-1) |
inTick | int | 5 | 进入该状态时的边界过渡时长 |
outTick | int | -1 | 离开该状态时的边界过渡时长),设为-1 则用下一个状态的 inTick |
in | map | {} | 进入过渡动画:key = 来源状态名,value = 过渡动画名 |
to | map | {} | 离开过渡动画:key = 目标状态名,value = 过渡动画名 |
exclusive | bool | false | 独占:抑制其它控制器的行为 |
moveLimit | double | 1.0 | 移动速度倍率(多状态叠加时相乘) |
clientLock | object | {} | 客户端输入锁定(下文详解) |
onStart | string | "" | 进入状态时执行的 Aria 脚本(仅针对使用该控制器的本地玩家,进入该状态时,该玩家客户端会执行此脚本) |
onEnd | string | "" | 离开状态时执行的 Aria 脚本(同上,离开状态时候运行) |
clientLock、onStart、onEnd 都是客户端当前玩家自身生效的,比如可以操作玩家进入这个状态时候临时操作下相机位置之类的。
动画播放
一个状态的动画完整性可以拆成三段:进入动画(in)→ 主动画(play)→ 离开动画(out),三段无缝衔接,段与段之间没有过渡。
主动画(animation)是否循环,取决于动画本身在建模软件里的循环设定:
- 勾了「循环」的动画(待机、行走、跑步这类)会一直循环播放。
- 没勾循环的动画(一次性动作,如攻击、收招)播完后停在最后一帧,(就算设置为“播放一次”也会按照停在最后一帧处理)。
- 关于“停止在最后一帧”,这是设计使然,像是播放一次的动作,比如交互等,我们默认您是由Chronos完全接管,如需按照“原版”处理,请特化一个生存模式(无任何控制器接管)。
- 也就是说,无Chronos这类动作控制引擎接管,播放一次性动画是不会停止的。
过渡时长(inTick / outTick)
状态之间的切换不是硬切换,而是有一段平滑混合。过渡时长只作用在「状态与状态之间的边界」——上一个状态收尾 → 下一个状态开头:
inTick:进入该状态时的混合时长。inTick: 0= 瞬间切入,没有混合(攻击这类要求立即响应的动作常设 0)。outTick:离开该状态时的混合时长。-1(默认)= 交给下一个状态的inTick决定。
过渡动画(in / to)
有时两个状态之间需要一段专门的过渡动画,而不只是混合。用 to 指定「从当前状态切到某状态时」先播的过渡动画,设置该过渡模式时,平滑过渡强制为0:
in 则相反——指定「从某状态进入当前状态时」先播的过渡动画。key 用 "*" 可匹配任意来源/目标:
独占(exclusive)
exclusive: true 仅该控制器接管动画,其余控制器动画操作直接完全抑制
移动限制(moveLimit)
moveLimit 限制该状态下玩家的移动速度,取值 0~1:0.5 = 减速一半,0 = 完全不能动。多个子控制器的 moveLimit 会相乘。攻击时常配 0.5 防止玩家边打边乱跑。
客户端输入锁定(clientLock)
在某些状态下屏蔽玩家的部分输入:
input 可选值:
LOCK— 攻击这类动作期间,点击左右键不把身体转向相机指向(近战吸附时把相机与身体解耦,配合朝向吸附脚本用)。DIRECTION— 锁定移动方向输入(第三人称自由相机下不因转视角、方向键而改变前进方向)。
action 可选值:
JUMP— 禁止跳跃SNEAK— 禁止下蹲MOVE— 禁止所有方向的移动输入
多子控制器混合
一套状态机里可以有多个子控制器并行运行,各管一部分骨骼。
main负责全身移动动画。sub负责上半身。empty是默认状态,指向一个「实际存在但不含任何骨骼动画」的空动作(需要在bbmodel真实存在)——玩家不攻击时,sub就停在empty,不干预main的骨骼。attack1/attack2是自定义名,由服务端连招驱动。- 关于
main和sub的命名,事实上是自定义的,实际执行根据声明顺序,也就是第一个声明的实际上默认为主状态机。
副控制器的默认状态(如 empty)必须对应一个真实存在、但不含骨骼动画的空动作;否则它会用一套空姿态覆盖掉主控制器的动画。
服务端控制器可以用 state 的 controller 字段分别驱动 main 和 sub:移动交给保留名自动判定,攻击段用 controller: "sub" 只切上半身。
速查注释对照配置
状态保留名(附录)
以下状态名会被客户端状态机自动判定触发。用了这些名字的状态,无需服务端驱动,玩家做对应行为时自动播放。
基础姿态
| 状态名 | 触发条件 |
|---|---|
idle | 恒成立(作默认兜底状态) |
walk | 移动中,速度 > 0.1 |
run | 移动中且正在冲刺 |
sneak | 下蹲 |
sneak_walk | 下蹲且移动(速度 > 0.05) |
jump | 跳跃中 |
sleep | 睡觉 |
die | 死亡 |
特殊移动
| 状态名 | 触发条件 |
|---|---|
swim | 游泳 |
swim_stand | 在水中(非游泳姿态) |
fly | 创造/旁观飞行 |
fall_fly | 鞘翅滑翔 |
climb | 匍匐 |
climb_walk | 匍匐移动 |
ladder_up / ladder_down / ladder_idle | 爬梯上 / 下 / 静止 |
骑乘
| 状态名 | 触发条件 |
|---|---|
ride | 骑乘任意实体 |
ride_pig | 骑猪 |
ride_boat | 骑船 |
战斗与物品使用
以下状态区分主手(_right)和副手(_left):
| 状态名 | 触发条件 |
|---|---|
sword | 主手挥剑 |
punch | 主手空手攻击 |
break_right / break_left | 挥动手臂 |
bow_right / bow_left | 拉弓 |
crossbow_right / crossbow_left | 弩蓄力 / 持弩 |
trident_throw_right / trident_throw_left | 投掷三叉戟 |
shield_block_right / shield_block_left | 盾牌格挡 |
eat_right / eat_left | 进食 / 饮用 |
spyglass_right / spyglass_left | 望远镜 |
toot_horn_right / toot_horn_left | 山羊角 |
brush_right / brush_left | 刷子 |
use_right / use_left | 通用使用物品 |
状态名不在上表内的,就是自定义名——不会被自动触发,只能由服务端控制器驱动切换(连招、技能就靠它)。
