从 0 到 1 实战教学
从头开始,完整做出一套可玩的近战动作机制
开始之前
前面几章讲的是「每个字段是什么意思」,这一章讲的是「这些字段凑在一起,怎么做出打起来舒服的战斗」。它是经验性的实战流程,不是硬性规则——但每一步背后都有明确的机制原因,照着走能少踩很多坑。
心法:先做出最小可玩的闭环,再迭代手感。 别一上来就配十段连招加分支。先让「点左键出一刀、有伤害、能连第二刀」跑通,感受节奏,再往上加窗口、派生、蓄力。手感是调出来的,不是想出来的。
本章用一把「横刀」从零搭起:基础姿态 → 四段连击 → 近战吸附 → 强化模式(强化普攻 + 蓄力斩)。跟着做一遍,你就能举一反三。
本章的示例模型来自社区成员 阿肆。
P1 · 客户端模型准备
先准备一个带动作的 .bbmodel 模型,放进资源目录。

资源目录的规划很重要。 ArcartX 的所有 ID 都是根据资源路径自动生成的。如果你打算对资源加密,切记全部用英文命名。
到这里,客户端侧的准备就完成了。
P2 · 服务端目录准备
装好插件后打开插件目录,你会看到这些文件:

三个 .yml 文件在配置目录结构已经讲过,这里只关注两个文件夹:
client/— 放客户端状态机(定义每个状态下客户端播什么动画、有什么行为)controllers/— 放服务端控制器(定义状态逻辑、输入、连招)
先在 client/ 建一个 yml(记住文件名),再在 controllers/ 建一个 yml:

P3 · 定义客户端状态机
我们的示例模型只需要一条轨道,所以只定义一个 main 子控制器。先写保留名状态(run/idle/walk 这类自带条件判断、会自动触发的状态)。
保留名状态是从上往下取第一个成立的,所以顺序 = 优先级。下面是一份比较通用的推荐顺序:
顺序不能乱写。idle 是兜底状态(条件恒为 true),必须放在最后——否则它会把后面所有状态都挡掉。
按模型的动画列表逐个填。模型没有的姿态(比如本例没有爬梯动画)直接删掉对应状态即可:
接着按模型内容逐个精调。这个模型给跳跃、跑步、走路都做了结束动画,用 to 挂上去——to 是「离开当前状态去往某状态时,先播的过渡动画」,key 填目标状态名:
to 的 key 是目标状态名——上面只对 idle 配了过渡,意味着「只有落回/停下到 idle 时才播结束动画」。如果玩家跳跃后接着跑、或跑步中直接转走路,就不会播这段收尾,衔接更自然。
至此保留名状态就定义完了。实际使用中动作往往还要反复微调,这部分请自行测试打磨。
P4 · 服务端控制器地基
客户端的基础姿态搭好后,先把服务端控制器的 setting 地基打好,连招和技能这些特殊状态后面再加。
把刚才客户端状态机的文件名(不带 .yml)填到 client_controller_id,设好模型 ID 和缩放,配好输入缓冲:
这里走的是 model 路径(直接换模型)。如果你用的是 ArcartX 内置玩家渲染器、想走动画包路径,则改用 animation 字段、并把 model 留空(详见 服务端控制器 · model 与 animation)。这套动作没有需要初始化的触发器变量,所以先不写 action。
地基打好后,就可以用指令或物品 tag 把这个控制器设给玩家了。进客户端重载,先测一遍基础姿态动画是否符合预期——不对就继续精调,对了再往下加连招。
P5 · 定义特殊状态(连招段)
特殊状态就是连招、技能、运动技这些客户端不会自动触发的状态,完全由 Chronos 接管。做法是两边都定义:客户端状态机管它的动画表现,服务端控制器管它的逻辑。
先看模型有哪些普攻动画(每段攻击都配了一个「后摇」):

客户端侧:四段攻击
按「二游」的手感,停下来才播后摇,有后续操作就不播——正好把后摇挂到 to: idle 上(只有回到待机才播后摇)。单轨控制器下,再加个 moveLimit: 0.2 让攻击时的滑步更自然:
服务端侧:一段攻击的逻辑
客户端定义了四个攻击状态后,在服务端控制器里给它们配逻辑。这里只写第一段,2/3/4 段照此类推(仅 state_name 和动画判定不同):
逐字段说明这些数值从哪来:
| 字段 | 怎么定 |
|---|---|
controller | 客户端子控制器 ID(这里是 main)。 |
state_name | 该子控制器下、刚才定义的状态名(attack1)。 |
duration | 动画时长,按动画实际秒数填。 |
buffer_at | 预输入的落地时机——一般设在挥砍收手那一刻(本例 450ms),让下一段接得跟手。 |
speed | 整段状态的速度,会缩放所有时间线。可用 Aria 表达式实现「攻速」。 |
windows.cancel | 可被打断的时机:在此窗口内输入别的连招链(如闪避),会中断当前链、转入那条。 |
windows.derive | 接收下一段输入的时机:要打击感就设 0 ~ 动画结束;要严格就在收手前关掉。 |
execute | 在指定时间点跑脚本,是状态的「实际内容」。上例在 350ms 对面前扇形做一次范围伤害。 |
execute 的时间点是「相对状态生命周期」的。如果状态在 250ms 被取消,那么 250ms 之后的所有执行点都不会触发——这也是为什么伤害判定点要放在挥砍真正生效的那一刻,而不是一进状态就结算。
照此把 2/3/4 段也定义好,进入下一步。
P6 · 连招链
四段攻击的状态都齐了,用 combo 把它们串成一条链。这套普攻是最简单的单轨 4A——每段都是「点左键 → 派生下一段」:
写完进客户端、设控制器、重载,点鼠标左键就能打出四连击了。至此,一套最小可玩的连招闭环就跑通了。
P7 · 进阶技巧
一套好的动作机制不止连段。下面在跑通的基础上,加几个动作游戏里常见的进阶操作。
一、自由镜头 + 近战吸附
把客户端状态机的所有状态设成「攻击时不跟随相机」,再配合 Chronos 的吸附语句,就能实现动作游戏那种近战吸附——玩家不用分心把准心怼到怪头上,专注操作即可。
所有状态锁定相机跟随。 给 main 里每个状态都加上 clientLock,在 input 里放一个 LOCK,这样玩家左右键交互时身体不会转向镜头:
攻击段额外锁定方向。 给攻击状态再加 DIRECTION,攻击时就不能用方向键改变朝向了:
服务端加吸附语句。 在攻击状态的 execute 里、出手之前调一次 turnToNearby(自动转向最近的目标):
这样,近战吸附就完整落地了。
二、多攻击模式(强化状态)
这套模型正好有个「放技能后开启第二形态」的动作。我们来做:释放技能 → 进入 8 秒强化状态 → 强化期间解锁两条新路径(强化普攻 + 蓄力斩)。
用一个变量当「强化标记」。 在服务端控制器的 setting.action.init 里初始化一个「强化时长」变量,记录强化状态的到期时间戳:
做「启动技能」状态。 释放后把「强化时长」设成「当前时间 + 8 秒」,于是接下来 8 秒都算强化态:
客户端状态机侧也要定义 skill-start 等这些新状态(管它们的动画表现),做法和普攻状态完全一样。本步骤起,客户端侧的定义都省略,只讲服务端逻辑。
强化普攻:多一个进入条件。 和普通普攻几乎一样,只是加一条「必须处于强化态」的条件。这里只贴一段:
蓄力斩:两段状态(蓄力 + 释放)。 第一段长按蓄力(charge: true)、第二段松手结算,用 self.getChargeTime() 读蓄了多久:
第一段必须开 charge: true,第二段才能用 self.getChargeTime() 读到蓄力时长(单位毫秒)。buffer_at 大于 0 会抬高蓄力最小读数——想要精确的松手时长,把蓄力段的 buffer_at 设成 -1。蓄力强度怎么用(分档、乘伤害)由脚本自行决定;也可以用「不同蓄力等级进入不同状态」做多级蓄力。
把新链接上连招树。 蓄力斩是「长按左键 → 松开左键」的单线派生:
强化普攻链也照普通四连的写法加上(近战1段-强化 → 近战2段-强化 → …)。
处理优先级:强化链必须排在普通链前面。 连招根节点是按声明顺序判优先级的,所以强化态的状态要写在普通状态之前。下面每个根节点的子结构用 # ... 略去,只看顺序:
别忘了给普通 近战1段 加一条反向条件。 因为强化两条链都带 var.强化时长 > datetime.now() 的进入条件,如果不给普通段加反条件,在取消窗口里可能会误进普通 近战1段 而不是强化段。给普通第一段补上:
这条反条件只需加在第一段即可。
P8 · 结语
以上就是一套基础战斗机制的完整实战流程。在此之上还能做更多——闪避、格挡、处决、多级蓄力……但它们的思路都和本章一致:客户端管表现、服务端管逻辑,用状态 + 窗口 + 连招 + 脚本拼出你想要的手感。 跟着上面走一遍,相信你已经能融会贯通了。
