关于 roblox-input
面向 Roblox 开发者的输入处理与摄像机技能,适用于构建跨设备游戏控制。内容讲解推荐的 Input Action System,也介绍底层及旧式方案、摄像机实现和跨平台检查。
主要功能
- 01跨设备 Input Action System
- 02原始输入与旧式绑定
- 03输入上下文与动作状态
- 04适配设备的操作提示
- 05自定义摄像机更新模式
- 06跨平台测试清单
适用场景
- 01构建键盘、手柄和触屏控制
- 02为菜单或载具切换控制方案
- 03显示当前设备对应的操作提示
- 04实现自定义摄像机或屏幕到世界射线
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ el4cteo/ roblox-skills --skill roblox-input安装后在 Claude Code 中输入/roblox-input确认已加载。
详细介绍
Roblox 输入与相机
选择一种方案
| 方案 | 适用场景 |
|---|---|
Input Action System(InputContext → InputAction → InputBinding) | 新的玩法控制。以实例形式配置的跨设备绑定、可启用/禁用的上下文、内置屏幕按钮绑定、提示显示。推荐。 |
UserInputService 事件 | 原始输入、无文本快捷键、鼠标位置/增量、触摸手势、设备检测。 |
ContextActionService:BindAction | 旧式/简单的带移动按钮绑定;已有代码。 |
所有输入处理都在客户端进行。通过 remote 向服务发送意图;由服务进行验证。
Input Action System
结构(通常位于 ReplicatedStorage.Inputs,或由客户端脚本创建):
Inputs (Folder)
PlayContext (InputContext, Priority 1000) -- gameplay controls
Sprint (InputAction, Type = Bool)
Keyboard (InputBinding, KeyCode = LeftShift)
Gamepad (InputBinding, KeyCode = ButtonL3)
Touch (InputBinding, UIButton = <on-screen TextButton>)
MenuContext (InputContext, Priority 2000, Sink = true, Enabled = false)
InputAction.Type:Bool(按下/松开)、Direction1D(扳机、缩放)、Direction2D(移动、相机)、Direction3D(飞行)、ViewportPosition(指针位置)。- 事件:
Pressed、Released(仅Bool)、适用于所有类型的StateChanged(value)。action:GetState()读取当前值。 InputBinding字段:KeyCode;复合方向Up/Down/Left/Right/Forward/Backward;Scale(例如MouseDelta/TouchDelta用0.01);模拟扳机的PressedThreshold/ReleasedThreshold; 组合键的PrimaryModifier/SecondaryModifier(Ctrl+S);用于通过 GUI 按钮驱动该动作的UIButton; 用于提示的DisplayName/DisplayImage。InputContext.Enabled用于切换整套控制方案(游戏 ↔ 菜单 ↔ 载具);Priority+Sink可让更高优先级的上下文消耗按键,从而避免更低优先级的上下文也被触发。- 设置
Workspace.PlayerScriptsUseInputActionSystem = Enabled,可让 Roblox 默认的玩家/相机 脚本也使用该系统(服务端权威所必需)。
--!strict
-- Client: build a Sprint action in code and react to it.
local Players = game:GetService("Players")
local player = Players.LocalPlayer
local context = Instance.new("InputContext")
context.Name = "PlayContext"
context.Priority = 1000
local sprint = Instance.new("InputAction")
sprint.Name = "Sprint"
sprint.Type = Enum.InputActionType.Bool
sprint.Parent = context
local keyboard = Instance.new("InputBinding")
keyboard.KeyCode = Enum.KeyCode.LeftShift
keyboard.Parent = sprint
local gamepad = Instance.new("InputBinding")
gamepad.KeyCode = Enum.KeyCode.ButtonL3
gamepad.Parent = sprint
context.Parent = player:WaitForChild("PlayerScripts")
local function setSprinting(on: boolean)
local character = player.Character
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
if humanoid then
humanoid.WalkSpeed = if on then 24 else 16 -- server should validate/own real speed limits
end
end
sprint.Pressed:Connect(function()
setSprinting(true)
end)
sprint.Released:Connect(function()
setSprinting(false)
end)
显示正确的按键/按钮:InputActionLabel(beta)会自动渲染当前设备的绑定,
或者读取 action.PreferredBinding(及其 DisplayName/KeyCode)以及 UserInputService:GetStringForKeyCode
/ GetImageForKeyCode,并在 GetPropertyChangedSignal("PreferredBinding") 时更新。
UserInputService 要点
--!strict
local UserInputService = game:GetService("UserInputService")
UserInputService.InputBegan:Connect(function(input: InputObject, gameProcessedEvent: boolean)
if gameProcessedEvent then
return -- typing in a TextBox, clicking UI, or a core-script action consumed it
end
if input.KeyCode == Enum.KeyCode.E then
print("interact")
elseif input.UserInputType == Enum.UserInputType.MouseButton1 then
print("click at", input.Position)
end
end)
local function onPreferredInputChanged()
local preferred = UserInputService.PreferredInput -- KeyboardAndMouse | Touch | Gamepad
print("show prompts for", preferred)
end
UserInputService:GetPropertyChangedSignal("PreferredInput"):Connect(onPreferredInputChanged)
onPreferredInputChanged()
- 务必检查
gameProcessedEvent,否则玩家在聊天中输入时也会触发技能。 PreferredInput反映玩家实际使用的输入方式(手机 + 蓝牙手柄 →Gamepad); 不要仅依据TouchEnabled/KeyboardEnabled进行分支判断。- 鼠标:FPS 使用
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter(如果其他脚本可能重置它,在第一人称下每帧设置),GetMouseLocation()(包含顶栏内边距)、MouseDeltaSensitivity、MouseIconEnabled。 - 手柄:
GetConnectedGamepads、GamepadConnected/Disconnected、InputChanged中的摇杆input.Position(应用约 0.15 的死区)。触觉反馈:HapticService/HapticEffect。 - 触摸:
TouchTap、TouchLongPress、TouchPinch、TouchSwipe;仅在你替换默认控制时才隐藏它们(GuiService.TouchControlsEnabled)。 - 无法覆盖的保留按键:Esc/Start(菜单)、F9(控制台)、F11、F12、PrintScreen;聊天
/、 Tab(玩家列表)、背包和工具数字键,除非你禁用这些核心功能。
ContextActionService(兼容旧版)
ContextActionService:BindAction(name, handler, createTouchButton, ...inputs),其中 handler 为
(actionName, inputState, inputObject) -> Enum.ContextActionResult?;返回 Pass 可让其他
绑定看到该输入。使用 BindActionAtPriority 控制顺序,并在上下文
结束时(例如工具被卸下)使用 UnbindAction。新工作请优先使用 Input Action System。
自定义摄像机
--!strict
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local camera = workspace.CurrentCamera
local OFFSET = Vector3.new(0, 30, 20)
camera.CameraType = Enum.CameraType.Scriptable
RunService:BindToRenderStep("TopDownCamera", Enum.RenderPriority.Camera.Value + 1, function()
local character = Players.LocalPlayer.Character
local root = character and character:FindFirstChild("HumanoidRootPart") :: BasePart?
if root then
local target = root.Position
camera.CFrame = CFrame.lookAt(target + OFFSET, target)
end
end)
-- Restore: RunService:UnbindFromRenderStep("TopDownCamera"); camera.CameraType = Enum.CameraType.Custom
- 在
BindToRenderStep中更新摄像机(在渲染前运行,按顺序执行),不要使用Heartbeat/task.wait循环。 - 使用
camera.CFrame:Lerp(goal, 1 - math.exp(-speed * deltaTime))平滑过渡(与帧率无关)。 - 第一人称:设置
player.CameraMode = Enum.CameraMode.LockFirstPerson;使用player.CameraMinZoomDistance/CameraMaxZoomDistance设置缩放限制。抖动:偏移 CFrame,不要移动部件。 - 屏幕坐标 → 世界坐标:使用
camera:ViewportPointToRay(x, y)/ScreenPointToRay+workspace:Raycast。
跨平台检查清单
- 每个操作都要支持键盘、游戏手柄和触控绑定(触控通过
UIButton或屏幕按钮实现)。 - 当
PreferredInput发生变化时,更新提示。控制台 UI 可通过方向键导航,并可按 B 关闭。 - 不要使用仅支持悬停的交互(触控不支持悬停)。对于破坏性操作,要求按住确认。
- 在 Studio 的 Device Simulator(包含触控提示)和 Controller Emulator 中进行测试。
相关技能
roblox-ui(按钮、游戏手柄选择)、roblox-characters-animation(移动、能力)、
roblox-networking(发送意图)、roblox-physics(载具)。