FEATURED · 精选文章

Roblox TSB自制英雄“刀锋”常规状态完整实现教程

发布时间 / 2026/9/6 4:48:14
来源 / 创域科博编辑部
栏目 / 资讯中心
Roblox TSB自制英雄“刀锋”常规状态完整实现教程 很多玩家在接触 Roblox 的 TSBThe Strongest Battlegrounds类玩法时都会先尝试做一个属于自己的自制英雄。比起直接拿现成角色去对战自己设计英雄外观、技能和状态逻辑才是这类沙盒游戏最有意思的地方。本文以“暴风机动队·刀锋”这个自建英雄为例完整拆解在 Roblox Studio 中实现一个英雄“常规状态”的全过程从角色模型准备、状态机设计、Lua 脚本编写到攻击判定、动画播放和联机测试一条链路讲完。新手可以按步骤照做有 Lua 和 Roblox Studio 基础的开发者也可以直接跳到核心代码部分做扩展。1. 背景与核心概念1.1 什么是 TSB 自制英雄TSB 是 Roblox 平台上一类偏对战玩法的游戏模式缩写玩家可以扮演不同英雄通过普攻、技能和位移来击败对手。这种玩法最吸引人的一点是“自由度”你可以在 Roblox Studio 中创建自己的英雄模型给它命名、设计阵营、配置攻击逻辑。很多玩家喜欢围绕一个虚构阵营打造系列角色比如本文提到的“暴风机动队”就是一套自制的精英作战小队设定而“刀锋”是这个阵营中的近战型英雄。自制英雄并不是只改一个外观那么简单还要让角色在游戏里有完整的行为。角色什么时候可以移动、什么时候只能攻击、攻击范围多大、受击后如何表现这些统称为“角色状态”。只有把状态管理好了英雄才会手感顺畅不会出现攻击时还能乱跑这种违和感。1.2 什么是角色的“常规状态”在动作对战类游戏里角色行为通常被拆成多种状态状态说明Idle 待机角色站立不动时的表现Move 移动普通行走、跑步、转身Jump 跳跃起跳与下落过程Attack 攻击普攻或技能攻击动作Hurt 受击被命中后的硬直表现Dead 死亡血量归零后的收尾“常规状态”是指角色在没有触发特殊技能、没有被击飞、没有进入连招保护时的一套基础行为集合。简单说就是英雄平时表现出的待机、移动、跳跃和普通攻击。如果你连常规状态都没做好后面的技能和特殊机制都很难稳定展开所以它是最优先要打的地基。1.3 刀锋英雄的设计目标“暴风机动队·刀锋”是一个近战型角色普通状态下的核心体验是轻快、直接、有打击感。我们把它拆成几个明确目标角色移动速度略高于默认英雄体现机动队特色。普通攻击使用鼠标左键触发默认造成中等伤害。攻击期间有一个短硬直不能立刻接下一次攻击。受击后播放简短的受击表现但不做太长的击飞处理。允许跳跃但跳跃不能打断攻击动画。把这些目标转成代码就是我们要实现的“常规状态逻辑”。2. 环境准备与版本说明2.1 Roblox Studio 安装要求开发 Roblox 自制英雄最核心的工具是 Roblox Studio。它支持 Windows 和 macOS直接在 Roblox 官网下载安装即可。本文示例以 Roblox Studio 当前稳定版本为准如果你使用的是旧版本部分 UI 位置可能略有差异但脚本 API 基本一致。建议在开始前准备Roblox 账号一个。Roblox Studio 最新版。一个用于测试的 Roblox 游戏项目类型不限。基础的 Lua 语法了解至少能看懂变量、函数和事件。如果对 Roblox API 还不熟不用怕本文代码会逐段解释。2.2 游戏项目设置在 Roblox Studio 中新建项目建议选择 Baseplate基础平台模板。这个模板自带一个地面和一个基础灯光环境适合快速测试角色动作。打开项目后我们需要确认两个关键目录StarterPlayer初始玩家设置StaterCharacterScripts角色脚本存放位置注意Roblox 的英文目录名是 StarterPlayerScripts 和 StarterCharacterScripts二者不要混淆。StarterCharacterScripts 中的脚本会随着角色生成并复制到角色身上适合放跟角色状态、攻击逻辑相关的代码。2.3 基础项目结构为了让代码不混乱我们按下面的结构组织游戏项目 ├── ReplicatedStorage │ ├── BladeAttackEventRemoteEvent │ └── StateManagerModuleScript └── StarterCharacterScripts ├── InputHandler.client.luaLocalScript └── CombatHandler.server.luaScriptRemoteEvent 放在 ReplicatedStorage 中供客户端和服务端通信。StateManager 也放在 ReplicatedStorage 中方便两边复用。StarterCharacterScripts 下的脚本跟随角色生成职责单一便于后续扩展技能。3. 状态机设计原理3.1 为什么角色需要状态机如果没有状态机角色的行为就容易互相冲突。比如玩家按住攻击键时角色同时在走、在跳、在播放攻击动画最终表现就是动画穿插、伤害判定混乱。状态机的核心思想是“同一时刻只允许一种状态存在”切换状态时必须满足条件。对于 TSB 自制英雄来说状态机不一定要做成复杂的图表可以用一个当前状态变量 一些切换条件组合完成。关键是让代码知道当前是什么状态。什么条件允许切换到下一个状态。切换时执行哪些逻辑。3.2 刀锋常规状态的划分我们把刀锋的常规状态细化为状态进入条件退出条件Idle无输入且在地面有移动输入或跳跃Move有方向输入且在地面输入停止或跳跃Jump按下跳跃键且非攻击状态落地或进入攻击状态Attack按下攻击键且当前非攻击状态攻击动画播完Hurt被伤害命中且未死亡受击动画播完在代码实现中我们没必要严格区分 Idle 和 Move因为 Roblox 自带的 Humanoid 组件已经处理了大部分移动表现。我们可以用 isAttacking 这样的布尔标志来管理攻击状态用 Health 变化事件处理受击状态。这是一种轻量级实现比大而全的状态机更符合单个自制英雄的体量。3.3 常规状态与技能状态的关系后续为刀锋添加技能时技能状态通常属于“非常规状态”。比如释放一个突进技能技能执行期间角色不能普攻也不能移动这就需要在状态机上增加一个 Skill 状态并在技能结束后切回 Idle。本文先聚焦常规状态等常规状态跑通后我们再考虑如何把技能状态平滑地接入。这也是很多 TSB 自制英雄开发的推荐顺序先让角色走跑跳打都自然再谈花哨技能。4. 创建刀锋英雄角色4.1 创建或导入 R15 角色Roblox 中的角色模型主要分 R6 和 R15 两种R15 关节更多动作表现更自然适合做 TSB 英雄。我们可以从 Roblox 的人物目录中选择一个基础 R15 角色也可以插入系统自带的“R15 标准角色”模型。步骤如下打开 Roblox Studio。在 工具箱Toolbox 中搜索 R15。选择一个 R15 角色模型插入到 Workspace。将这个模型作为玩家的角色模型使用。如果只是测试可以直接使用默认的 R15 角色。我们需要把角色模型切成独立模型或者使用 StarterPlayer 中的默认角色再通过脚本调整名字和外观即可。4.2 添加外观与标识给角色添加标识的方式有很多常见做法是修改角色的 Humanoid 显示名称。在角色头顶加一个 BillboardGui 显示“暴风机动队·刀锋”。给角色添加专属配饰比如肩部特效或腰部光带。这些都是纯展示层不影响核心逻辑。我们要注意的是无论加了什么装饰都要确保这些装饰的碰撞组不会干扰攻击射线检测。4.3 设置英雄基本属性刀锋是机动队近战英雄基础属性可以比默认角色略强。但要注意不是所有数值都是越大越好攻击伤害过高会让对战失去平衡。我们先给出一套用于本地测试的初始属性属性默认值刀锋测试值WalkSpeed1618Health100100攻击伤害无15攻击冷却无0.5 秒在 StarterCharacterScripts 中写一个初始化脚本角色生成时自动应用属性-- 文件路径StarterCharacterScripts/ClassInit.server.lua local character script.Parent local humanoid character:WaitForChild(Humanoid) -- 移动速度 humanoid.WalkSpeed 18 -- 跳跃相关较新版本使用 JumpHeight旧版本使用 JumpPower local success, result pcall(function() return humanoid.JumpHeight end) if success then humanoid.JumpHeight 12 else humanoid.JumpPower 50 end这里用了 pcall 做 API 兼容判断避免因为 Roblox 版本更新导致脚本报错。如果你使用的是老版本直接设置 JumpPower 即可。5. 常规状态的完整实现5.1 全局状态管理器状态管理器的作用是给所有脚本一个统一的“当前状态”出口。我们把它做成 ModuleScript方便后续技能脚本和 UI 脚本读取。文件路径ReplicatedStorage/StateManager.lua-- 刀锋英雄状态管理器 local StateManager {} StateManager.State { Idle Idle, Move Move, Jump Jump, Fall Fall, Attack Attack, Hurt Hurt, Dead Dead, } local currentState StateManager.State.Idle function StateManager:Get() return currentState end function StateManager:Set(newState) if currentState newState then return end currentState newState print([刀锋] 状态切换 -, currentState) end function StateManager:Reset() currentState StateManager.State.Idle end return StateManager这个模块并不直接控制角色动画它只是状态的“裁判”。真正的动画播放、移动限制、攻击逻辑分散在对应脚本中但所有脚本都通过它来读取当前状态避免因为多个脚本同时改状态导致冲突。5.2 待机与移动状态Roblox 的 Humanoid 自身已经有待机、跑步、跳跃等动画调度所以我们不需要从零实现移动。只需要在代码里把 Humanoid 的 StateChanged 事件接到状态管理器上让外部脚本知道角色当前是在运动还是静止。文件路径StarterCharacterScripts/MovementStateWatcher.server.lualocal StateManager require(game.ReplicatedStorage:WaitForChild(StateManager)) local character script.Parent local humanoid character:WaitForChild(Humanoid) local lastState nil humanoid.StateChanged:Connect(function(oldState, newState) -- newState 是 Enum.HumanoidStateType 类型 local mappedState StateManager.State.Idle if newState Enum.HumanoidStateType.Running or newState Enum.HumanoidStateType.RunningNoPhysics then mappedState StateManager.State.Move elseif newState Enum.HumanoidStateType.Jumping then mappedState StateManager.State.Jump elseif newState Enum.HumanoidStateType.Falling then mappedState StateManager.State.Fall end -- 如果当前不是攻击、受击、死亡才允许同步为移动类状态 local current StateManager:Get() if current ~ StateManager.State.Attack and current ~ StateManager.State.Hurt and current ~ StateManager.State.Dead then StateManager:Set(mappedState) end lastState newState end)这段代码的关键在于攻击和受击期间移动相关状态不会被覆盖。这样能保证角色攻击时不会立刻进入跑步状态动画表现不会乱。5.3 基础攻击状态攻击是整个常规状态的重点。我们采用“客户端触发服务端验证”的架构玩家点击鼠标左键客户端通过 RemoteEvent 通知服务器服务器判断状态并执行伤害判定。这样做的好处是防止玩家通过修改本地数据无限刷伤害。首先在 ReplicatedStorage 下创建 RemoteEvent-- 在命令行或脚本中执行 local ReplicatedStorage game:GetService(ReplicatedStorage) local attackRemote Instance.new(RemoteEvent) attackRemote.Name BladeAttackEvent attackRemote.Parent ReplicatedStorage然后处理客户端输入。文件路径StarterCharacterScripts/InputHandler.client.lualocal UserInputService game:GetService(UserInputService) local ReplicatedStorage game:GetService(ReplicatedStorage) local attackRemote ReplicatedStorage:WaitForChild(BladeAttackEvent) local function onInputBegan(input, gameProcessed) if gameProcessed then return end -- 鼠标左键触发普通攻击 if input.UserInputType Enum.UserInputType.MouseButton1 then attackRemote:FireServer() end end UserInputService.InputBegan:Connect(onInputBegan)这个脚本只做一件事把“玩家想攻击”这个意图发送给服务器。真正的攻击判断必须由服务器完成避免作弊。接下来是服务器端的攻击逻辑。文件路径StarterCharacterScripts/CombatHandler.server.lualocal ReplicatedStorage game:GetService(ReplicatedStorage) local StateManager require(ReplicatedStorage:WaitForChild(StateManager)) local attackRemote ReplicatedStorage:WaitForChild(BladeAttackEvent) local character script.Parent local humanoid character:WaitForChild(Humanoid) local humanoidRootPart character:WaitForChild(HumanoidRootPart) local isAttacking false local attackCooldown 0.5 local attackDamage 15 local attackRange 5 local function playAttackAnimation() local animator humanoid:FindFirstChildOfClass(Animator) if not animator then return end -- 这里需要替换成你上传的动画 ID local animation Instance.new(Animation) animation.AnimationId rbxassetid://你的动画ID local track animator:LoadAnimation(animation) track:Play() track.Ended:Connect(function() track:Destroy() end) end local function performAttack() if isAttacking then return end -- 攻击中不再响应普通状态覆盖 isAttacking true StateManager:Set(StateManager.State.Attack) -- 播放攻击动画 playAttackAnimation() -- 延迟 0.2 秒后造成伤害贴近动画节奏 task.wait(0.2) -- 射线检测判定敌人 local origin humanoidRootPart.Position local direction humanoidRootPart.CFrame.LookVector * attackRange local raycastParams RaycastParams.new() raycastParams.FilterDescendantsInstances {character} raycastParams.FilterType Enum.RaycastFilterType.Exclude local result workspace:Raycast(origin, direction, raycastParams) if result then local targetHumanoid result.Instance:FindFirstChildOfClass(Humanoid) if targetHumanoid then targetHumanoid:TakeDamage(attackDamage) end end task.wait(attackCooldown - 0.2) -- 攻击结束恢复状态 StateManager:Set(StateManager.State.Idle) isAttacking false end attackRemote.OnServerEvent:Connect(function(player) if player.Character ~ character then return end performAttack() end)代码中需要注意几个细节isAttacking 用来防止攻击还没结束就再次触发。StateManager 将状态修改为 Attack禁止移动状态覆盖。task.wait 模拟攻击前摇和后摇。射线检测使用 LookVector 作为攻击方向范围是 5 个 BindUnit 长度。伤害通过 Humanoid:TakeDamage 产生Roblox 会自动计算死亡逻辑。这个脚本是常规状态的核心。你可以根据动画时长调整前摇和后摇数值。5.4 受击状态实现TSB 英雄被命中后通常会产生短硬直避免被打断后毫无反馈。我们在服务器端监听 Humanoid.HealthChanged判断血量减少时进入受击状态。文件路径StarterCharacterScripts/HurtHandler.server.lualocal ReplicatedStorage game:GetService(ReplicatedStorage) local StateManager require(ReplicatedStorage:WaitForChild(StateManager)) local character script.Parent local humanoid character:WaitForChild(Humanoid) local lastHealth humanoid.Health humanoid.HealthChanged:Connect(function(newHealth) if newHealth lastHealth then lastHealth newHealth return end local currentState StateManager:Get() if currentState StateManager.State.Dead then lastHealth newHealth return end -- 进入受击状态快速恢复 StateManager:Set(StateManager.State.Hurt) -- 可以在这里播放受击动画需要替换为实际动画 ID local animator humanoid:FindFirstChildOfClass(Animator) if animator then local hurtAnimation Instance.new(Animation) hurtAnimation.AnimationId rbxassetid://你的受击动画ID local track animator:LoadAnimation(hurtAnimation) track:Play() end task.wait(0.15) StateManager:Set(StateManager.State.Idle) lastHealth newHealth end)受击状态的时间不要设置太长否则连招中角色会一直处于硬直体验比较差。0.15 秒足够表现出“被命中了”的感觉。5.5 状态绑定到角色以上脚本都放在 StarterCharacterScripts 中角色生成时会自动执行所以不需要额外写绑定逻辑。只要保证模块名、事件名和脚本路径一致角色诞生后状态机就会自动运转。如果你在单机测试时发现脚本没有生效可以先检查输出窗口是否打印了“[刀锋] 状态切换”日志。如果没有任何输出大概率是模块路径或脚本类型设置错了。6. 完善英雄手感6.1 动画加载与播放动画是手感的重要部分。代码中使用的 AnimationId 必须替换成实际存在的动画资源。获取动画的方式有两种打开 Roblox 开发创作者中心上传或购买动画资源。在 Roblox Studio 工具箱中搜索动画复制动画 ID。每个动画 ID 都是形如 rbxassetid://123456789 的格式。你可以在 Roblox Studio 中把动画拖到角色身上再查看预设动画的 ID。千万不能随便填一个数字否则动画不会播放。6.2 攻击节奏与冷却设计攻击手感好不好主要看前摇、后摇和冷却。前摇太长角色显得笨重后摇太长连招不流畅冷却太短连续点击会导致攻击频率过高。在测试阶段你可以把每个阶段的时间都单独抽出来调试参数含义测试建议前摇(0.2)按下攻击到伤害产生的时间0.15-0.3 之间微调后摇(0.3)伤害产生到攻击完全结束的时间0.1-0.4 之间微调总冷却(0.5)两次攻击的最小间隔0.4-0.7 之间微调需要提醒的是不要把所有时间都写死在一个 task.wait 里。更好的做法是把它们定义为常量或配置表放在 ModuleScript 中统一管理方便后续技能开发。6.3 音效和特效普通攻击可以添加一个挥刀音效命中时增加打击特效。Roblox 中可以使用 Sound 服务和 ParticleEmitter 实现。这部分属于表现层不影响核心逻辑但能大大提高对战时的爽快感。建议优先级攻击命中时的血花/闪光特效。角色挥拳时的速度线或残影。受击时的屏幕震动仅客户端表现。如果前期不熟悉特效制作可以把这部分放到常规状态稳定后再做。先确保逻辑正确再打磨视觉。7. 运行测试与联机验证7.1 在 Studio 中单机测试写完代码后点击 Play 按钮进入测试模式。此时你会控制刀锋英雄。测试重点包括是否可以正常移动。点击鼠标左键是否播放攻击动画。攻击是否对假人造成伤害。攻击期间是否还能移动。被假人攻击时是否进入受击状态。建议在 Workspace 中放一个目标假人攻击正前方测试伤害是否生效。假人可以是基础的 R15 角色模型也可以使用 Roblox 自带的 Dummy 模型。7.2 多玩家联机测试TSB 是对战玩法必须验证客户端和服务端的数据同步。最简单的方式是使用 Roblox Studio 中的“测试”功能开两个客户端一个担任主控一个作为对手。联机测试重点观察对方能否看到你的攻击动画。攻击伤害是否在服务器统一计算。双方同时攻击时状态是否会互相干扰。网络延迟下射线判定是否仍然准确。一般来说只要攻击逻辑全部放在服务器端就不会出现双端伤害不一致的问题。客户端脚本只负责发事件和表现动画。7.3 常见性能问题自制英雄脚本如果写得不注意容易造成性能开销。比如使用每帧检测攻击距离的 while 循环就非常消耗服务器资源。本文使用射线一次性判定性能开销很小。如果后续加技能特效记得及时销毁特效实例避免场景堆积大量无用对象。可以使用 Debris 服务自动删除特效local Debris game:GetService(Debris) local effect Instance.new(Part) effect.Size Vector3.new(1, 1, 1) effect.Anchored true effect.Parent workspace Debris:AddItem(effect, 2) -- 2秒后自动删除8. 常见问题与排查思路问题现象常见原因解决思路鼠标左键没反应RemoteEvent 名称不一致检查 InputHandler 和 CombatHandler 中事件名称是否相同攻击没有伤害Raycast 方向或 FilterType 配置错误打印射线结果确认碰撞分组是否排除自身攻击后角色卡住不能动isAttacking 没有恢复检查攻击协程是否被 error 中断加 pcall 保护动画不播放动画 ID 无效在 Studio 中测试动画确保 ID 可正常加载角色还在跑就能攻击状态机没有在攻击时锁定移动在攻击开始前设置 StateManager.State.Attack并检查移动监听逻辑多人联机伤害不一致伤害逻辑写在客户端把所有伤害判定移到服务器脚本客户端只触发事件受击状态不能恢复HealthChanged 里等待时间过长缩短受击硬直时间或添加超时保护如果遇到疑难 Bug先在输出窗口看日志。比如 StateManager 会在每次状态切换时打印日志如果日志停在 Attack 没有恢复说明 attack 协程中出现异常。可以给 performAttack 包一层 pcall避免一个错误导致整个角色状态卡死。9. 最佳实践与工程建议9.1 用配置表管理英雄参数把英雄的移动速度、攻击伤害、攻击冷却、攻击距离等数值统一放到一个配置 ModuleScript 中而不是散落在各脚本里。这样后续调整平衡性只需要改一处。一个简单的配置表示例local BladeConfig { DisplayName 暴风机动队·刀锋, WalkSpeed 18, AttackDamage 15, AttackCooldown 0.5, AttackRange 5, HurtTime 0.15, } return BladeConfig战斗脚本通过 require 读取配置避免魔法数字。9.2 攻击逻辑必须在服务器端玩家可以修改本地脚本如果攻击判定放在客户端就会被外挂利用。正确做法是客户端只发送攻击意图服务器负责所有伤害判定和数值变更。这样既能保证公平性也能防止脚本冲突。9.3 远程事件要加频率限制TSB 对战中玩家会高频点击攻击键如果不加限制服务器会收到大量攻击请求。可以在 CombatHandler 中做一个简单的冷却窗口同一个玩家的攻击请求在冷却时间内直接丢弃不进入攻击逻辑。local lastAttackTime 0 local cooldown 0.2 attackRemote.OnServerEvent:Connect(function(player) local nowTime os.clock() if nowTime - lastAttackTime cooldown then return end lastAttackTime nowTime if player.Character ~ character then return end performAttack() end)9.4 状态超时保护状态机最怕的问题就是“卡状态”。比如攻击动画被中断后isAttacking 一直没有重置角色就永远无法攻击。建议在攻击开始时记录时间攻击逻辑结束后无论成功失败都重置状态或者使用 task.defer 做超时兜底。9.5 为技能状态预留接口虽然本文只实现了常规状态但你的英雄后续大概率要加技能。在 StateManager 中加入一个 Skill 状态并且在移动监听中把 Skill 和 Attack 一样处理成“不可覆盖状态”。这样以后接入技能就方便很多。10. 从常规状态到完整英雄刀锋的常规状态跑通之后你就已经完成了 TSB 自制英雄最核心的地基。你可以继续给这个角色添加一技能、二技能比如向前突进的“刀锋冲锋”或者强化下一次普攻的“蓄力斩”。这些技能无非就是在状态机中新增一个 Skill 状态然后在攻击逻辑里判断当前状态播放对应动画并执行不同的伤害判定。如果你还打算把“暴风机动队”做成一个系列英雄那更应该注重状态机和配置表的设计。每增加一个新英雄只需要复制一份角色模板修改配置表和动画 ID再补充专属技能即可。这样角色越多维护成本反而越可控。最后再分享一个实战经验在 Roblox 中做自定义角色最忌讳一上来就写大量复杂逻辑。先把“走路、跑步、跳跃、普攻、受击”这五个常规状态做到手感自然再谈其他。手感这种东西代码层面很难一次调对一定要多进测试模式跑一跑。攻击前摇、后摇、移动速度这些参数数值变化一点点角色手感就会差很多。用数据驱动设计每次只改一个数值直到找到最舒服的手感这才是 TSB 自制英雄的正确打开方式。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻